summaryrefslogtreecommitdiffstats
path: root/private/newsam
diff options
context:
space:
mode:
Diffstat (limited to 'private/newsam')
-rw-r--r--private/newsam/client/bind.c248
-rw-r--r--private/newsam/client/makefile6
-rw-r--r--private/newsam/client/sam_rev.rc10
-rw-r--r--private/newsam/client/samclip.h96
-rw-r--r--private/newsam/client/samlib.def70
-rw-r--r--private/newsam/client/sources65
-rw-r--r--private/newsam/client/tchgpwd.c210
-rw-r--r--private/newsam/client/tconnect.c98
-rw-r--r--private/newsam/client/tdisplay.c638
-rw-r--r--private/newsam/client/temp.c571
-rw-r--r--private/newsam/client/tmachine.c902
-rw-r--r--private/newsam/client/tmultipl.c574
-rw-r--r--private/newsam/client/toempass.c231
-rw-r--r--private/newsam/client/tsamobj.c12350
-rw-r--r--private/newsam/client/tshut.c119
-rw-r--r--private/newsam/client/wrappers.c8849
-rw-r--r--private/newsam/dirs28
-rw-r--r--private/newsam/makefil078
-rw-r--r--private/newsam/passfilt/makefile6
-rw-r--r--private/newsam/passfilt/passfilt.c354
-rw-r--r--private/newsam/passfilt/passfilt.def7
-rw-r--r--private/newsam/passfilt/passfilt.rc10
-rw-r--r--private/newsam/passfilt/passtest.c85
-rw-r--r--private/newsam/passfilt/sources54
-rw-r--r--private/newsam/samcli.acf96
-rw-r--r--private/newsam/samimp.idl56
-rw-r--r--private/newsam/samrpc.idl1446
-rw-r--r--private/newsam/samsrv.acf64
-rw-r--r--private/newsam/server/alias.c3975
-rw-r--r--private/newsam/server/almember.c3610
-rw-r--r--private/newsam/server/attr.c4273
-rw-r--r--private/newsam/server/bldsam3.c5260
-rw-r--r--private/newsam/server/close.c179
-rw-r--r--private/newsam/server/context.c1634
-rw-r--r--private/newsam/server/display.c5840
-rw-r--r--private/newsam/server/domain.c8012
-rw-r--r--private/newsam/server/gentab2.c3561
-rw-r--r--private/newsam/server/global.c304
-rw-r--r--private/newsam/server/group.c3158
-rw-r--r--private/newsam/server/makefile6
-rw-r--r--private/newsam/server/makefile.inc2
-rw-r--r--private/newsam/server/notify.c581
-rw-r--r--private/newsam/server/oldstub.c659
-rw-r--r--private/newsam/server/regnames.txt260
-rw-r--r--private/newsam/server/rundown.c150
-rw-r--r--private/newsam/server/sam_rev.rc10
-rw-r--r--private/newsam/server/samifree.c405
-rw-r--r--private/newsam/server/sampmsgs.mc259
-rw-r--r--private/newsam/server/samsrv.def126
-rw-r--r--private/newsam/server/samsrv.prf699
-rw-r--r--private/newsam/server/samsrvp.h3318
-rw-r--r--private/newsam/server/samss.c1701
-rw-r--r--private/newsam/server/secdescr.c3069
-rw-r--r--private/newsam/server/security.c1063
-rw-r--r--private/newsam/server/server.c920
-rw-r--r--private/newsam/server/sources89
-rw-r--r--private/newsam/server/string.c252
-rw-r--r--private/newsam/server/upgrade.c1580
-rw-r--r--private/newsam/server/upgrade.txt13
-rw-r--r--private/newsam/server/user.c11229
-rw-r--r--private/newsam/server/utest/makefile29
-rw-r--r--private/newsam/server/utest/samtest.cxx2033
-rw-r--r--private/newsam/server/utest/sources46
-rw-r--r--private/newsam/server/utility.c6935
64 files changed, 102561 insertions, 0 deletions
diff --git a/private/newsam/client/bind.c b/private/newsam/client/bind.c
new file mode 100644
index 000000000..deb81215b
--- /dev/null
+++ b/private/newsam/client/bind.c
@@ -0,0 +1,248 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ wrappers.c
+
+Abstract:
+
+ This file contains all SAM rpc binding routines.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "samclip.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+RPC_BINDING_HANDLE
+PSAMPR_SERVER_NAME_bind (
+ PSAMPR_SERVER_NAME ServerName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine calls a common bind routine that is shared by all services.
+ This routine is called from SamConnect server stub to connect to the
+ server.
+
+Arguments:
+
+ ServerName - A pointer to a string containing the name of the server
+ to bind with.
+
+Return Value:
+
+ The binding handle is returned to the stub routine. If the
+ binding is unsuccessful, a NULL will be returned.
+
+--*/
+{
+ RPC_BINDING_HANDLE BindingHandle;
+
+ RpcpBindRpc (
+ ServerName,
+ L"samr",
+ 0,
+ &BindingHandle
+ );
+
+
+ return( BindingHandle);
+}
+
+
+
+void
+PSAMPR_SERVER_NAME_unbind (
+ PSAMPR_SERVER_NAME ServerName,
+ RPC_BINDING_HANDLE BindingHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine calls a common unbind routine that is shared by
+ all services.
+
+ This routine is called from the SamConnect client stub to
+ unbind from the SAM client.
+
+
+Arguments:
+
+ ServerName - This is the name of the server from which to unbind.
+
+ BindingHandle - This is the binding handle that is to be closed.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ UNREFERENCED_PARAMETER(ServerName); // This parameter is not used
+
+
+ RpcpUnbindRpc ( BindingHandle );
+ return;
+}
+
+RPC_BINDING_HANDLE
+SampSecureBind(
+ LPWSTR ServerName,
+ ULONG AuthnLevel
+ )
+
+/*++
+
+Routine Description:
+
+ This routine calls a common bind routine that is shared by all services.
+ This routine is called from SamConnect server stub to connect to the
+ server.
+
+Arguments:
+
+ ServerName - A pointer to a string containing the name of the server
+ to bind with.
+
+ AuthnLevel - Authentication level to bind with.
+
+Return Value:
+
+ The binding handle is returned to the stub routine. If the
+ binding is unsuccessful, a NULL will be returned.
+
+--*/
+{
+ RPC_BINDING_HANDLE BindingHandle = NULL;
+ RPC_STATUS RpcStatus;
+
+#if 1
+ RpcpBindRpc ( ServerName,
+ L"samr",
+ 0,
+ &BindingHandle
+ );
+#else
+ LPWSTR StringBinding;
+ RpcStatus = RpcStringBindingComposeW(
+ 0,
+ L"ncacn_spx",
+ ServerName+2,
+ NULL, // dynamic endpoint
+ NULL, // no options
+ &StringBinding
+ );
+ if (RpcStatus != 0)
+ {
+ return(NULL);
+ }
+ RpcStatus = RpcBindingFromStringBindingW(
+ StringBinding,
+ &BindingHandle
+ );
+ RpcStringFreeW(&StringBinding);
+
+#endif
+
+
+ if ( (BindingHandle != NULL) &&
+ (AuthnLevel != RPC_C_AUTHN_LEVEL_NONE) ) {
+
+ RpcStatus = RpcBindingSetAuthInfoW(
+ BindingHandle,
+ NULL, // server principal name
+ AuthnLevel,
+ RPC_C_AUTHN_WINNT,
+ NULL,
+ RPC_C_AUTHZ_DCE
+ );
+ if (RpcStatus != 0) {
+ RpcBindingFree(&BindingHandle);
+ }
+
+ }
+
+
+
+ return( BindingHandle);
+}
+
+
+
+void
+SampSecureUnbind (
+ RPC_BINDING_HANDLE BindingHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine calls a common unbind routine that is shared by
+ all services.
+
+ This routine is called from the SamConnect client stub to
+ unbind from the SAM client.
+
+
+Arguments:
+
+ BindingHandle - This is the binding handle that is to be closed.
+
+Return Value:
+
+ none.
+
+--*/
+{
+
+
+ RpcpUnbindRpc ( BindingHandle );
+ return;
+}
+
diff --git a/private/newsam/client/makefile b/private/newsam/client/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/newsam/client/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/newsam/client/sam_rev.rc b/private/newsam/client/sam_rev.rc
new file mode 100644
index 000000000..ee07fbbeb
--- /dev/null
+++ b/private/newsam/client/sam_rev.rc
@@ -0,0 +1,10 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DLL
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "SAM Library DLL"
+#define VER_INTERNALNAME_STR "SAMLib.DLL"
+#define VER_ORIGINALFILENAME_STR "SAMLib.DLL"
+
+#include "common.ver"
diff --git a/private/newsam/client/samclip.h b/private/newsam/client/samclip.h
new file mode 100644
index 000000000..ca8319833
--- /dev/null
+++ b/private/newsam/client/samclip.h
@@ -0,0 +1,96 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ samclip.h
+
+Abstract:
+
+ This file contains definitions needed by SAM client stubs.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+#ifndef _NTSAMP_CLIENT_
+#define _NTSAMP_CLIENT_
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <nt.h>
+#include <ntrtl.h> // DbgPrint prototype
+#include <rpc.h> // DataTypes and runtime APIs
+#include <nturtl.h> // needed for winbase.h
+#include <windows.h> // LocalAlloc
+//#include <winbase.h> // LocalAlloc
+
+#include <string.h> // strlen
+#include <stdio.h> // sprintf
+//#include <tstring.h> // Unicode string macros
+
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <samrpc_c.h> // midl generated client SAM RPC definitions
+#include <lmcons.h> // To get LM password length
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <ntlsa.h> // for LsaOpenPolicy...
+#include <rc4.h> // rc4, rc4_key
+#include <rpcndr.h> // RpcSsDestroyContext
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Defines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// data types //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+void
+SampSecureUnbind (
+ RPC_BINDING_HANDLE BindingHandle
+ );
+
+RPC_BINDING_HANDLE
+SampSecureBind(
+ LPWSTR ServerName,
+ ULONG AuthnLevel
+ );
+
+#endif // _NTSAMP_CLIENT_
diff --git a/private/newsam/client/samlib.def b/private/newsam/client/samlib.def
new file mode 100644
index 000000000..572b141b4
--- /dev/null
+++ b/private/newsam/client/samlib.def
@@ -0,0 +1,70 @@
+LIBRARY SAMLIB
+
+DESCRIPTION 'Security Accounts Manager User-Mode Client Library'
+
+EXPORTS
+
+;
+; Dave Hart (DaveHart) 28-Mar-92
+;
+; Exported Public Sam APIs
+;
+ SamFreeMemory
+ SamSetSecurityObject
+ SamQuerySecurityObject
+ SamCloseHandle
+ SamConnect
+ SamShutdownSamServer
+ SamLookupDomainInSamServer
+ SamEnumerateDomainsInSamServer
+ SamOpenDomain
+ SamQueryInformationDomain
+ SamSetInformationDomain
+ SamCreateGroupInDomain
+ SamEnumerateGroupsInDomain
+ SamCreateUserInDomain
+ SamCreateUser2InDomain
+ SamEnumerateUsersInDomain
+ SamCreateAliasInDomain
+ SamEnumerateAliasesInDomain
+ SamRemoveMemberFromForeignDomain
+ SamGetAliasMembership
+ SamLookupNamesInDomain
+ SamLookupIdsInDomain
+ SamOpenGroup
+ SamQueryInformationGroup
+ SamSetInformationGroup
+ SamAddMemberToGroup
+ SamDeleteGroup
+ SamRemoveMemberFromGroup
+ SamGetMembersInGroup
+ SamSetMemberAttributesOfGroup
+ SamOpenAlias
+ SamQueryInformationAlias
+ SamSetInformationAlias
+ SamDeleteAlias
+ SamAddMemberToAlias
+ SamAddMultipleMembersToAlias
+ SamRemoveMemberFromAlias
+ SamRemoveMultipleMembersFromAlias
+ SamGetMembersInAlias
+ SamOpenUser
+ SamDeleteUser
+ SamQueryInformationUser
+ SamSetInformationUser
+ SamChangePasswordUser
+ SamChangePasswordUser2
+ SamGetGroupsForUser
+ SamQueryDisplayInformation
+ SamGetDisplayEnumerationIndex
+
+ SamTestPrivateFunctionsDomain
+ SamTestPrivateFunctionsUser
+
+ SamiChangePasswordUser
+ SamiLmChangePasswordUser
+ SamiChangePasswordUser2
+ SamiOemChangePasswordUser2
+ SamiEncryptPasswords
+
+
diff --git a/private/newsam/client/sources b/private/newsam/client/sources
new file mode 100644
index 000000000..8d8844f98
--- /dev/null
+++ b/private/newsam/client/sources
@@ -0,0 +1,65 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+
+Revision History:
+
+!ENDIF
+
+
+MAJORCOMP = SAM
+MINORCOMP = client
+
+TARGETNAME= samlib
+TARGETPATH=$(BASEDIR)\public\sdk\lib
+TARGETTYPE=DYNLINK
+
+TARGETLIBS= \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcutil.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcrt4.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcndr.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\kernel32.lib \
+ ..\..\lsa\crypt\engine\obj\*\rc4c.obj
+
+
+INCLUDES=.;..;..\inc;..\..\inc;..\..\lsa\crypt\engine
+
+
+
+SOURCES=\
+ bind.c \
+ sam_rev.rc \
+ samrpc_c.c \
+ wrappers.c
+
+
+UMTYPE=console
+UMTEST=tmp*tconnect*tshut*tsamobj*tchgpwd*tdisplay
+C_DEFINES=-DRPC_NO_WINDOWS_H
+UMLIBS= \
+ $(BASEDIR)\public\sdk\lib\*\samlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcutil.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcrt4.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcndr.lib \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\kernel32.lib
diff --git a/private/newsam/client/tchgpwd.c b/private/newsam/client/tchgpwd.c
new file mode 100644
index 000000000..5177e05dc
--- /dev/null
+++ b/private/newsam/client/tchgpwd.c
@@ -0,0 +1,210 @@
+// Test changing a SAM password
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <nt.h>
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <ntlsa.h>
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <seopaque.h>
+#include <string.h>
+
+
+VOID
+main (argc, argv)
+int argc;
+char **argv;
+
+{
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ SECURITY_QUALITY_OF_SERVICE SecurityQos;
+ PSID DomainSid = NULL;
+ PULONG UserId = NULL;
+ PSID_NAME_USE NameUse = NULL;
+ SAM_HANDLE SamHandle = NULL;
+ SAM_HANDLE DomainHandle = NULL;
+ SAM_HANDLE UserHandle = NULL;
+ WCHAR UserNameBuffer[80];
+ WCHAR OldPasswordBuffer[80];
+ WCHAR NewPasswordBuffer[80];
+ UNICODE_STRING UserName;
+ UNICODE_STRING Domain;
+ UNICODE_STRING OldPassword;
+ UNICODE_STRING NewPassword;
+ ANSI_STRING AnsiString;
+
+ UserName.Buffer = UserNameBuffer;
+ UserName.MaximumLength = sizeof(UserNameBuffer);
+
+ OldPassword.Buffer = OldPasswordBuffer;
+ OldPassword.MaximumLength = sizeof(OldPasswordBuffer);
+
+ NewPassword.Buffer = NewPasswordBuffer;
+ NewPassword.MaximumLength = sizeof(NewPasswordBuffer);
+
+ RtlInitUnicodeString(&Domain, L"Account");
+
+
+ RtlInitAnsiString(&AnsiString, argv[1]);
+ RtlAnsiStringToUnicodeString(&UserName, &AnsiString, FALSE);
+
+ if (*(argv[2]) == '-') {
+ *(argv[2]) = 0;
+ }
+ RtlInitAnsiString(&AnsiString, argv[2]);
+ RtlAnsiStringToUnicodeString(&OldPassword, &AnsiString, FALSE);
+
+ if (*(argv[3]) == '-') {
+ *(argv[3]) = 0;
+ }
+ RtlInitAnsiString(&AnsiString, argv[3]);
+ RtlAnsiStringToUnicodeString(&NewPassword, &AnsiString, FALSE);
+
+ //
+ // Setup ObjectAttributes for SamConnect call.
+ //
+
+ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
+ ObjectAttributes.SecurityQualityOfService = &SecurityQos;
+
+ SecurityQos.Length = sizeof(SecurityQos);
+ SecurityQos.ImpersonationLevel = SecurityIdentification;
+ SecurityQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
+ SecurityQos.EffectiveOnly = FALSE;
+
+ Status = SamConnect(
+ NULL,
+ &SamHandle,
+ GENERIC_EXECUTE,
+ &ObjectAttributes
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: SamConnect failed, status %8.8x\n", Status);
+ goto Cleanup;
+ }
+
+
+ Status = SamLookupDomainInSamServer(
+ SamHandle,
+ &Domain,
+ &DomainSid
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: Cannot find account domain, status %8.8x\n", Status);
+ Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto Cleanup;
+ }
+
+ Status = SamOpenDomain(
+ SamHandle,
+ GENERIC_EXECUTE,
+ DomainSid,
+ &DomainHandle
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: Cannot open account domain, status %8.8x\n", Status);
+ Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto Cleanup;
+ }
+
+ Status = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &UserName,
+ &UserId,
+ &NameUse
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: Cannot lookup user %wZ, status %8.8x\n", &UserName, Status);
+ goto Cleanup;
+ }
+
+ Status = SamOpenUser(
+ DomainHandle,
+ USER_CHANGE_PASSWORD,
+ *UserId,
+ &UserHandle
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: Cannot open user %wZ, status %8.8x\n",
+ &UserName, Status);
+ goto Cleanup;
+ }
+
+ Status = SamChangePasswordUser(
+ UserHandle,
+ &OldPassword,
+ &NewPassword
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("MspChangePasswordSam: Failed to change user password, status %8.8x\n", Status);
+ }
+
+
+Cleanup:
+
+ //
+ // Free DomainSid if used.
+ //
+
+ if (DomainSid) {
+ SamFreeMemory(DomainSid);
+ }
+
+ //
+ // Free UserId if used.
+ //
+
+ if (UserId) {
+ SamFreeMemory(UserId);
+ }
+
+ //
+ // Free NameUse if used.
+ //
+
+ if (NameUse) {
+ SamFreeMemory(NameUse);
+ }
+
+ //
+ // Close UserHandle if open.
+ //
+
+ if (UserHandle) {
+ SamCloseHandle(UserHandle);
+ }
+
+ //
+ // Close DomainHandle if open.
+ //
+
+ if (DomainHandle) {
+ SamCloseHandle(DomainHandle);
+ }
+
+ //
+ // Close SamHandle if open.
+ //
+
+ if (SamHandle) {
+ SamCloseHandle(SamHandle);
+ }
+
+}
+
diff --git a/private/newsam/client/tconnect.c b/private/newsam/client/tconnect.c
new file mode 100644
index 000000000..03d621d24
--- /dev/null
+++ b/private/newsam/client/tconnect.c
@@ -0,0 +1,98 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tconnect.c
+
+Abstract:
+
+ This is the file for a simple connection test to SAM.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <nt.h>
+#include <ntsam.h>
+#include <ntrtl.h> // DbgPrint()
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+VOID
+main (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This is the main entry routine for this test.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAM_HANDLE ServerHandle;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
+
+
+ NtStatus = SamConnect(
+ NULL, // ServerName (Local machine)
+ &ServerHandle,
+ SAM_SERVER_ALL_ACCESS,
+ &ObjectAttributes
+ );
+
+ DbgPrint("SAM TEST (Connect): Status of SamConnect() is: 0x%lx\n", NtStatus);
+
+
+ return;
+}
diff --git a/private/newsam/client/tdisplay.c b/private/newsam/client/tdisplay.c
new file mode 100644
index 000000000..7ac570969
--- /dev/null
+++ b/private/newsam/client/tdisplay.c
@@ -0,0 +1,638 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ tdisplay.c
+
+Abstract:
+
+ This file is a temporary test for the Display query apis.
+
+Author:
+
+ Jim Kelly (JimK) 14-Feb-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#include <nt.h>
+#include <ntsam.h>
+#include <ntrtl.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Global Variables //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+SAM_HANDLE SamHandle;
+SAM_HANDLE DomainHandle;
+PSID DomainSid;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+VOID _CRTAPI1
+main( VOID )
+
+{
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ SECURITY_QUALITY_OF_SERVICE SecurityQos;
+ UNICODE_STRING Domain;
+
+ UNICODE_STRING TestString;
+ ULONG TestIndex;
+
+
+ ULONG TotalAvailable, TotalReturned, ReturnedEntryCount, i;
+ PDOMAIN_DISPLAY_USER SortedUsers;
+ PDOMAIN_DISPLAY_MACHINE SortedMachines;
+ PDOMAIN_DISPLAY_GROUP SortedGroups;
+ PDOMAIN_DISPLAY_OEM_USER SortedOemUsers;
+ PDOMAIN_DISPLAY_OEM_GROUP SortedOemGroups;
+
+
+
+ SamHandle = NULL;
+ DomainHandle = NULL;
+ DomainSid = NULL;
+
+ DbgPrint("\n\n\nSAM TEST: Testing SamQueryDisplayInformation() api\n");
+
+ //
+ // Setup ObjectAttributes for SamConnect call.
+ //
+
+ InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
+ ObjectAttributes.SecurityQualityOfService = &SecurityQos;
+
+ SecurityQos.Length = sizeof(SecurityQos);
+ SecurityQos.ImpersonationLevel = SecurityIdentification;
+ SecurityQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
+ SecurityQos.EffectiveOnly = FALSE;
+
+ Status = SamConnect(
+ NULL,
+ &SamHandle,
+ GENERIC_EXECUTE,
+ &ObjectAttributes
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("SamConnect failed, status %8.8x\n", Status);
+ goto Cleanup;
+ }
+
+ RtlInitUnicodeString(&Domain, L"JIMK_DOM2");
+
+ Status = SamLookupDomainInSamServer(
+ SamHandle,
+ &Domain,
+ &DomainSid
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("Cannot find account domain, status %8.8x\n", Status);
+ Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto Cleanup;
+ }
+
+ Status = SamOpenDomain(
+ SamHandle,
+ GENERIC_EXECUTE,
+ DomainSid,
+ &DomainHandle
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DbgPrint("Cannot open account domain, status %8.8x\n", Status);
+ Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto Cleanup;
+ }
+
+
+ //
+ // normal users ...
+ //
+
+ DbgPrint("Query users - zero index...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayUser,
+ 0, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedUsers)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedUsers);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedUsers[i].Index);
+ DbgPrint(" Rid: %d\n", SortedUsers[i].Rid);
+ DbgPrint(" Logon Name: *%Z*\n", &SortedUsers[i].LogonName);
+ DbgPrint(" Full Name: *%Z*\n", &SortedUsers[i].FullName);
+ DbgPrint("Admin Comment: *%Z*\n\n\n", &SortedUsers[i].AdminComment);
+ }
+
+ Status = SamFreeMemory( SortedUsers );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+ DbgPrint("Query users - Nonzero index (index = 2)...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayUser,
+ 2, // Index
+ 10, // Entries
+ 100, // PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedUsers)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedUsers);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedUsers[i].Index);
+ DbgPrint(" Rid: %d\n", SortedUsers[i].Rid);
+ DbgPrint(" Logon Name: *%Z*\n", &SortedUsers[i].LogonName);
+ DbgPrint(" Full Name: *%Z*\n", &SortedUsers[i].FullName);
+ DbgPrint("Admin Comment: *%Z*\n\n\n", &SortedUsers[i].AdminComment);
+ }
+
+ Status = SamFreeMemory( SortedUsers );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+
+ DbgPrint("Get enumeration index...\n");
+
+ RtlInitUnicodeString(&TestString, L"BString");
+
+ Status = SamGetDisplayEnumerationIndex (
+ DomainHandle,
+ DomainDisplayUser,
+ &TestString,
+ &TestIndex
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Enumeration index for %wZ is %d\n", &TestString, TestIndex);
+ }
+
+
+
+ //
+ // Machine accounts ...
+ //
+
+ DbgPrint("\n\nQuery Machines - zero index...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayMachine,
+ 0, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedMachines)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedMachines);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedMachines[i].Index);
+ DbgPrint(" Rid: %d\n", SortedMachines[i].Rid);
+ DbgPrint(" Machine: *%Z*\n", &SortedMachines[i].Machine);
+ DbgPrint(" Comment: *%Z*\n\n\n", &SortedMachines[i].Comment);
+ }
+
+ Status = SamFreeMemory( SortedMachines );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+ DbgPrint("Query Machines - Nonzero index (index = 1)...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayMachine,
+ 1, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedMachines)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedMachines);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedMachines[i].Index);
+ DbgPrint(" Rid: %d\n", SortedMachines[i].Rid);
+ DbgPrint(" Machine: *%Z*\n", &SortedMachines[i].Machine);
+ DbgPrint(" Comment: *%Z*\n\n\n", &SortedMachines[i].Comment);
+ }
+
+ Status = SamFreeMemory( SortedMachines );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+
+ DbgPrint("Get enumeration index...\n");
+
+ RtlInitUnicodeString(&TestString, L"BString");
+
+ Status = SamGetDisplayEnumerationIndex (
+ DomainHandle,
+ DomainDisplayMachine,
+ &TestString,
+ &TestIndex
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Enumeration index for %wZ is %d\n", &TestString, TestIndex);
+ }
+
+
+
+
+ //
+ // normal Groups ...
+ //
+
+ DbgPrint("Query Groups - zero index...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayGroup,
+ 0, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedGroups)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedGroups);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedGroups[i].Index);
+ DbgPrint(" Rid: %d\n", SortedGroups[i].Rid);
+ DbgPrint(" Name: *%Z*\n", &SortedGroups[i].Group);
+ DbgPrint("Admin Comment: *%Z*\n\n\n", &SortedGroups[i].Comment);
+ }
+
+ Status = SamFreeMemory( SortedGroups );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+ DbgPrint("Query Groups - Nonzero index (index = 2)...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayGroup,
+ 2, // Index
+ 10, // Entries
+ 100, // PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedGroups)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedGroups);
+ DbgPrint(" TotalAvailable: 0x%lx\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedGroups[i].Index);
+ DbgPrint(" Rid: %d\n", SortedGroups[i].Rid);
+ DbgPrint(" Name: *%Z*\n", &SortedGroups[i].Group);
+ DbgPrint("Admin Comment: *%Z*\n\n\n", &SortedGroups[i].Comment);
+ }
+
+ Status = SamFreeMemory( SortedGroups );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+
+ DbgPrint("Get enumeration index...\n");
+
+ RtlInitUnicodeString(&TestString, L"BString");
+
+ Status = SamGetDisplayEnumerationIndex (
+ DomainHandle,
+ DomainDisplayGroup,
+ &TestString,
+ &TestIndex
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Enumeration index for %wZ is %d\n", &TestString, TestIndex);
+ }
+
+
+ //
+ // OEM user ...
+ //
+
+ DbgPrint("Query OEM users - zero index...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayOemUser,
+ 0, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedOemUsers)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedOemUsers);
+ DbgPrint(" TotalAvailable: 0x%lx (should be garbage)\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedOemUsers[i].Index);
+ DbgPrint(" User: *%Z*\n", &SortedOemUsers[i].User);
+ }
+
+ Status = SamFreeMemory( SortedOemUsers );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+ DbgPrint("Query OEM users - Nonzero index (index = 2)...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayOemUser,
+ 2, // Index
+ 10, // Entries
+ 100, // PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedOemUsers)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedOemUsers);
+ DbgPrint(" TotalAvailable: 0x%lx (should be garbage)\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedOemUsers[i].Index);
+ DbgPrint(" User: *%Z*\n", &SortedOemUsers[i].User);
+ }
+
+ Status = SamFreeMemory( SortedOemUsers );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+
+
+
+ //
+ // OEM groups ...
+ //
+
+ DbgPrint("Query OEM groups - zero index...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayOemGroup,
+ 0, //Index
+ 10, // Entries
+ 1000, //PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedOemGroups)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedOemGroups);
+ DbgPrint(" TotalAvailable: 0x%lx (should be garbage)\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedOemGroups[i].Index);
+ DbgPrint(" Group: *%Z*\n", &SortedOemGroups[i].Group);
+ }
+
+ Status = SamFreeMemory( SortedOemGroups );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+ DbgPrint("Query OEM Groups - Nonzero index (index = 2)...\n");
+ Status = SamQueryDisplayInformation (
+ DomainHandle,
+ DomainDisplayOemGroup,
+ 2, // Index
+ 10, // Entries
+ 100, // PreferredMaximumLength,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedEntryCount,
+ &((PVOID)SortedGroups)
+ );
+
+
+ DbgPrint("Completion Status: 0x%lx\n", Status);
+ if (NT_SUCCESS(Status)) {
+ DbgPrint(" Buffer Address: 0x%lx\n", SortedGroups);
+ DbgPrint(" TotalAvailable: 0x%lx (should be garbage)\n", TotalAvailable);
+ DbgPrint(" TotalReturned: 0x%lx\n", TotalReturned);
+ DbgPrint(" Entries Returned: %d\n", ReturnedEntryCount);
+
+ DbgPrint("\n\n");
+
+ for (i=0;i<ReturnedEntryCount ; i++) {
+
+ DbgPrint("Array entry: [%d]\n", i);
+ DbgPrint(" Index: %d\n",SortedOemGroups[i].Index);
+ DbgPrint(" Group: *%Z*\n", &SortedOemGroups[i].Group);
+ }
+
+ Status = SamFreeMemory( SortedOemGroups );
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("\n\n\n ******** SamFreeMemory() failed. *********\n");
+ DbgPrint("\n\n\n ******** Status: 0x%lx *********\n", Status);
+ }
+ }
+
+
+
+
+
+
+ DbgPrint("\n\n Th Tha That's all folks\n");
+
+
+Cleanup:
+
+ //
+ // Close DomainHandle if open.
+ //
+
+ if (DomainHandle) {
+ SamCloseHandle(DomainHandle);
+ }
+
+ //
+ // Close SamHandle if open.
+ //
+
+ if (SamHandle) {
+ SamCloseHandle(SamHandle);
+ }
+
+}
+
diff --git a/private/newsam/client/temp.c b/private/newsam/client/temp.c
new file mode 100644
index 000000000..0b7405346
--- /dev/null
+++ b/private/newsam/client/temp.c
@@ -0,0 +1,571 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ temp.c
+
+Abstract:
+
+ This file contains temporary SAM rpc wrapper routines.
+
+Author:
+
+ Jim Kelly (JimK) 14-Feb-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "samclip.h"
+
+
+typedef struct _SAMP_TEMP_USER_STRINGS {
+ ULONG Rid;
+ WCHAR LogonName[14];
+ WCHAR FullName[24];
+ WCHAR AdminComment[24];
+} SAMP_TEMP_USER_STRINGS, *PSAMP_TEMP_USER_STRINGS;
+
+
+#define SAMP_TEMP_USER_COUNT (40)
+#define SAMP_TEMP_USER1 (25)
+#define SAMP_TEMP_USER2 (15)
+
+
+typedef struct _SAMP_TEMP_MACHINE_STRINGS {
+ ULONG Rid;
+ WCHAR Machine[14];
+ WCHAR Comment[24];
+} SAMP_TEMP_MACHINE_STRINGS, *PSAMP_TEMP_MACHINE_STRINGS;
+
+
+#define SAMP_TEMP_MACHINE_COUNT (40)
+#define SAMP_TEMP_MACHINE1 (16)
+#define SAMP_TEMP_MACHINE2 (24)
+
+
+SAMP_TEMP_USER_STRINGS DummyUsers[SAMP_TEMP_USER_COUNT] = {
+
+ {1031, L"Abba" , L"Abb Abb" , L"Admin Comment Field"},
+ {1021, L"Acea" , L"Ace Abb" , L"Value Admin Comment"},
+ {1526, L"beverlyE" , L"Beverly Eng" , L"Field Value Admin"},
+ {1743, L"BorisB" , L"Boris Borsch" , L"Comment Field Value"},
+ {1734, L"BruceK" , L"Bruce Kane" , L"Comment Field Value"},
+ {1289, L"BullS" , L"Bull Shiite" , L"Comment Field Value"},
+ {1830, L"CallieW" , L"Callie Wilson" , L"Comment Field Value"},
+ {1628, L"CarrieT" , L"Carrie Tibbits" , L"Comment Field Value"},
+ {1943, L"ChrisR" , L"Christopher Robin" , L"40 acre woods"},
+ {1538, L"CorneliaG" , L"Cornelia Gutierrez" , L"Comment Field Value"},
+ {1563, L"CoryA" , L"Cory Ander" , L"Comment Field Value"},
+ {1758, L"DanielJ" , L"Daniel John" , L"Comment Field Value"},
+ {1249, L"Dory" , L"Dory" , L"Comment Field Value"},
+ {1957, L"EltonJ" , L"Elton John" , L"Comment Field Value"},
+ {1555, L"HarrisonF" , L"Harrison Ford" , L"Comment Field Value"},
+ {1795, L"HarryB" , L"Harry Belafonte" , L"Comment Field Value"},
+ {1458, L"IngridB" , L"Ingrid Bergman" , L"Comment Field Value"},
+ {1672, L"Ingris" , L"Ingris" , L"Comment Field Value"},
+ {1571, L"JenniferB" , L"Jennifer Black" , L"Comment Field Value"},
+ {1986, L"JoyceG" , L"Joyce Gerace" , L"Comment Field Value"},
+ {1267, L"KristinM" , L"Kristin McKay" , L"Comment Field Value"},
+ {1321, L"LeahD" , L"Leah Dootson" , L"The Lovely Miss D"},
+ {2021, L"LisaP" , L"Lisa Perazzoli" , L"Wild On Skis"},
+ {1212, L"MeganB" , L"Megan Bombeck" , L"M1"},
+ {2758, L"MelisaB" , L"Melisa Bombeck" , L"M3"},
+ {2789, L"MichaelB" , L"Michael Bombeck" , L"M2"},
+ {2682, L"PanelopiP" , L"Panelopi Pitstop" , L"Comment Field Value"},
+ {2438, L"Prudence" , L"Prudence Peackock" , L"Comment Field Value"},
+ {2648, L"QwertyU" , L"Qwerty Uiop" , L"Comment Field Value"},
+ {2681, L"ReaddyE" , L"Readdy Eddy" , L""},
+ {2456, L"SovietA" , L"Soviet Union - NOT" , L"Soviet Union Aint"},
+ {1753, L"TAAAA" , L"TTT AAAA" , L"Comment Field Value"},
+ {1357, L"TBBB" , L"Ingris" , L"Comment Field Value"},
+ {1951, L"TCCCCC" , L"Jennifer Black" , L"Comment Field Value"},
+ {1159, L"TCAAAAAA" , L"Joyce Gerace" , L"Comment Field Value"},
+ {1654, L"Ulga" , L"Ulga Bulga" , L"Comment Field Value"},
+ {1456, L"UnixY" , L"Unix Yuck" , L"Unix - why ask why?"},
+ {1852, L"Vera" , L"Vera Pensicola" , L""},
+ {1258, L"WinP" , L"Winnie The Pooh" , L"Comment Field Value"},
+ {2821, L"Zoro" , L"Zoro" , L"The sign of the Z"}
+};
+
+
+
+
+
+SAMP_TEMP_MACHINE_STRINGS DummyMachines[SAMP_TEMP_MACHINE_COUNT] = {
+
+ {1031, L"WKS$abba" , L"Admin Comment Field"},
+ {1021, L"WKS$Acea" , L"Value Admin Comment"},
+ {1526, L"WKS$beverlyE" , L"Field Value Admin"},
+ {1743, L"WKS$BorisB" , L"Comment Field Value"},
+ {1734, L"WKS$BruceK" , L"Comment Field Value"},
+ {1289, L"WKS$BullS" , L"Comment Field Value"},
+ {1830, L"WKS$CallieW" , L"Comment Field Value"},
+ {1628, L"WKS$CarrieT" , L"Comment Field Value"},
+ {1943, L"WKS$ChrisR" , L"40 acre woods Server"},
+ {1538, L"WKS$CorneliaG" , L"Comment Field Value"},
+ {1563, L"WKS$CoryA" , L"Comment Field Value"},
+ {1758, L"WKS$DanielJ" , L"Comment Field Value"},
+ {1249, L"WKS$Dory" , L"Comment Field Value"},
+ {1957, L"WKS$EltonJ" , L"Comment Field Value"},
+ {1555, L"WKS$HarrisonF" , L"Comment Field Value"},
+ {1795, L"WKS$HarryB" , L"Comment Field Value"},
+ {1458, L"WKS$IngridB" , L"Comment Field Value"},
+ {1672, L"WKS$Ingris" , L"Comment Field Value"},
+ {1571, L"WKS$JenniferB" , L"Comment Field Value"},
+ {1986, L"WKS$JoyceG" , L"Comment Field Value"},
+ {1267, L"WKS$KristinM" , L"Comment Field Value"},
+ {1321, L"WKS$LeahD" , L"The Lovely Miss D's"},
+ {2021, L"WKS$LisaP" , L"Wild On Skis Server"},
+ {1212, L"WKS$MeganB" , L"M1 Machine"},
+ {2758, L"WKS$MelisaB" , L"M3 Machine"},
+ {2789, L"WKS$MichaelB" , L"M2 Machine"},
+ {2682, L"WKS$PanelopiP" , L"Comment Field Value"},
+ {2438, L"WKS$Prudence" , L"Comment Field Value"},
+ {2648, L"WKS$QwertyU" , L"Comment Field Value"},
+ {2681, L"WKS$ReaddyE" , L"Ready Eddy Computer"},
+ {2456, L"WKS$SovietA" , L"Soviet Union Aint"},
+ {1753, L"WKS$TAAAA" , L"Comment Field Value"},
+ {1357, L"WKS$TBBB" , L"Comment Field Value"},
+ {1951, L"WKS$TCCCCC" , L"Comment Field Value"},
+ {1159, L"WKS$TCAAAAAA" , L"Comment Field Value"},
+ {1654, L"WKS$Ulga" , L"Comment Field Value"},
+ {1456, L"WKS$UnixY" , L"Unix - why ask why?"},
+ {1852, L"WKS$Vera" , L"Vera tissue"},
+ {1258, L"WKS$WinP" , L"Comment Field Value"},
+ {2821, L"WKS$Zoro" , L"The sign of the Z"}
+};
+
+
+
+
+VOID
+SampBuildDummyAccounts(
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PULONG ReturnedEntryCount,
+ OUT PVOID *SortedBuffer
+ );
+
+
+
+VOID
+SampBuildDummyAccounts(
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PULONG ReturnedEntryCount,
+ OUT PVOID *SortedBuffer
+ )
+
+{
+ ULONG AccountCount, Account1, Account2;
+ ULONG i, j, BeginIndex, EndIndex;
+ ULONG ReturnStructSize, ArrayLength, StringLengths;
+ PCHAR NextByte;
+ UNICODE_STRING Us;
+
+
+
+ ASSERT (SAMP_TEMP_USER1 != 0);
+ ASSERT (SAMP_TEMP_USER2 != 0);
+ ASSERT (SAMP_TEMP_MACHINE1 != 0);
+ ASSERT (SAMP_TEMP_MACHINE2 != 0);
+
+ if (DisplayInformation == DomainDisplayUser) {
+
+ ReturnStructSize = sizeof(DOMAIN_DISPLAY_USER);
+ Account1 = SAMP_TEMP_USER1;
+ Account2 = SAMP_TEMP_USER2;
+ AccountCount = SAMP_TEMP_USER_COUNT;
+
+ } else {
+
+ ReturnStructSize = sizeof(DOMAIN_DISPLAY_MACHINE);
+ Account1 = SAMP_TEMP_MACHINE1;
+ Account2 = SAMP_TEMP_MACHINE2;
+ AccountCount = SAMP_TEMP_MACHINE_COUNT;
+
+ }
+
+
+
+ //
+ // Build up a number of dummy accounts in a single buffer.
+ //
+
+
+ if (Index < Account1) {
+
+ //
+ // Give the first group of accounts
+ //
+
+ ArrayLength = ReturnStructSize * Account1;
+ BeginIndex = 0;
+ EndIndex = Account1;
+
+
+ } else {
+
+ //
+ // Give the second group of accounts
+ //
+
+ ArrayLength = ReturnStructSize * Account2;
+ BeginIndex = Account1;
+ EndIndex = AccountCount;
+
+ }
+
+
+
+ //
+ // Figure out how large a buffer is needed.
+ //
+
+ StringLengths = 0;
+ for (i=BeginIndex; i<EndIndex; i++) {
+
+ if (DisplayInformation == DomainDisplayUser) {
+
+ RtlInitUnicodeString( &Us, DummyUsers[i].LogonName);
+ StringLengths += Us.Length;
+ RtlInitUnicodeString( &Us, DummyUsers[i].FullName);
+ StringLengths += Us.Length;
+ RtlInitUnicodeString( &Us, DummyUsers[i].AdminComment);
+ StringLengths += Us.Length;
+
+ } else {
+
+ RtlInitUnicodeString( &Us, DummyMachines[i].Machine);
+ StringLengths += Us.Length;
+ RtlInitUnicodeString( &Us, DummyMachines[i].Comment);
+ StringLengths += Us.Length;
+
+ }
+
+ }
+ (*SortedBuffer) = MIDL_user_allocate( ArrayLength + StringLengths );
+ ASSERT(SortedBuffer != NULL);
+
+
+ //
+ // First free byte in the return buffer
+ //
+
+ NextByte = (PCHAR)((ULONG)(*SortedBuffer) + (ULONG)ArrayLength);
+
+
+ //
+ // Now copy the structures
+
+ if (DisplayInformation == DomainDisplayUser) {
+
+ PDOMAIN_DISPLAY_USER r;
+ r = (PDOMAIN_DISPLAY_USER)(*SortedBuffer);
+
+ j=0;
+ for (i=BeginIndex; i<EndIndex; i++) {
+
+ r[j].AccountControl = USER_NORMAL_ACCOUNT;
+ r[j].Index = i;
+ r[j].Rid = DummyUsers[i].Rid;
+
+
+ //
+ // copy the logon name
+ //
+
+ RtlInitUnicodeString( &Us, DummyUsers[i].LogonName);
+ r[j].LogonName.MaximumLength = Us.Length;
+ r[j].LogonName.Length = Us.Length;
+ r[j].LogonName.Buffer = (PWSTR)NextByte;
+ RtlMoveMemory(NextByte, Us.Buffer, r[j].LogonName.Length);
+ NextByte += r[j].LogonName.Length;
+
+ //
+ // copy the full name
+ //
+
+ RtlInitUnicodeString( &Us, DummyUsers[i].FullName);
+ r[j].FullName.MaximumLength = Us.Length;
+ r[j].FullName.Length = Us.Length;
+ r[j].FullName.Buffer = (PWSTR)NextByte;
+ RtlMoveMemory(NextByte, Us.Buffer, r[j].FullName.Length);
+ NextByte += r[j].FullName.Length;
+
+ //
+ // copy the admin comment
+ //
+
+ RtlInitUnicodeString( &Us, DummyUsers[i].AdminComment);
+ r[j].AdminComment.MaximumLength = Us.Length;
+ r[j].AdminComment.Length = Us.Length;
+ r[j].AdminComment.Buffer = (PWSTR)NextByte;
+ RtlMoveMemory(NextByte, Us.Buffer, r[j].AdminComment.Length);
+ NextByte += r[j].AdminComment.Length;
+
+ j++;
+
+ }
+
+ } else {
+
+ PDOMAIN_DISPLAY_MACHINE r;
+ r = (PDOMAIN_DISPLAY_MACHINE)(*SortedBuffer);
+
+ j=0;
+ for (i=BeginIndex; i<EndIndex; i++) {
+
+
+ r[j].AccountControl = USER_WORKSTATION_TRUST_ACCOUNT;
+ r[j].Index = i;
+ r[j].Rid = DummyMachines[i].Rid;
+
+
+ //
+ // copy the logon name
+ //
+
+ RtlInitUnicodeString( &Us, DummyMachines[i].Machine);
+ r[j].Machine.MaximumLength = Us.Length;
+ r[j].Machine.Length = Us.Length;
+ r[j].Machine.Buffer = (PWSTR)NextByte;
+ RtlMoveMemory(NextByte, Us.Buffer, r[j].Machine.Length);
+ NextByte += r[j].Machine.Length;
+
+
+ //
+ // copy the admin comment
+ //
+
+ RtlInitUnicodeString( &Us, DummyMachines[i].Comment);
+ r[j].Comment.MaximumLength = Us.Length;
+ r[j].Comment.Length = Us.Length;
+ r[j].Comment.Buffer = (PWSTR)NextByte;
+ RtlMoveMemory(NextByte, Us.Buffer, r[j].Comment.Length);
+ NextByte += r[j].Comment.Length;
+
+ j++;
+
+ }
+
+
+ }
+
+ (*TotalAvailable) = 6*1024; // A lie, but just a little lie.
+ (*TotalReturned) = ArrayLength + StringLengths;
+ (*ReturnedEntryCount) = EndIndex - BeginIndex;
+
+
+ return;
+
+
+}
+
+
+
+NTSTATUS
+SamQueryDisplayInformation (
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ IN ULONG PreferredMaximumLength,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PULONG ReturnedEntryCount,
+ OUT PVOID *SortedBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine provides fast return of information commonly
+ needed to be displayed in user interfaces.
+
+ NT User Interface has a requirement for quick enumeration of SAM
+ accounts for display in list boxes. (Replication has similar but
+ broader requirements.)
+
+ The netui listboxes all contain similar information. That is:
+
+ o AccountControl, the bits that identify the account type,
+ eg, HOME, REMOTE, SERVER, WORKSTATION, etc.
+
+ o Logon name (machine name for computers)
+
+ o Full name (not used for computers)
+
+ o Comment (admin comment for users)
+
+ SAM maintains this data locally in two sorted indexed cached
+ lists identified by infolevels.
+
+ o DomainDisplayUser: HOME and REMOTE user accounts only
+
+ o DomainDisplayMachine: SERVER and WORKSTATION accounts only
+
+ Note that trust accounts, groups, and aliases are not in either of
+ these lists.
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which information is to be enumerated.
+
+ Index - The index of the first entry to be retrieved.
+
+ PreferedMaximumLength - A recommended upper limit to the number of
+ bytes to be returned. The returned information is allocated by
+ this routine.
+
+ TotalAvailable - Total number of bytes availabe in the specified info
+ class.
+
+ TotalReturned - Number of bytes actually returned for this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+ ReturnedEntryCount - Number of entries returned by this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+
+ SortedBuffer - Receives a pointer to a buffer containing a sorted
+ list of the requested information. This buffer is allocated
+ by this routine and contains the following structure:
+
+
+ DomainDisplayMachine --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_USER. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_USER structures.
+
+ DomainDisplayMachine --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_MACHINE. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_MACHINE structures.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+ STATUS_INVALID_INFO_CLASS - The requested class of information
+ is not legitimate for this service.
+
+
+
+
+
+--*/
+{
+
+
+
+// if ((DisplayInformation != DomainDisplayUser) &&
+// (DisplayInformation != DomainDisplayMachine) ) {
+// return( STATUS_INVALID_INFO_CLASS );
+//
+// }
+
+
+
+ SampBuildDummyAccounts( DisplayInformation,
+ Index,
+ TotalAvailable,
+ TotalReturned,
+ ReturnedEntryCount,
+ SortedBuffer);
+
+ return(STATUS_SUCCESS);
+
+ DBG_UNREFERENCED_PARAMETER(DomainHandle);
+ DBG_UNREFERENCED_PARAMETER(PreferredMaximumLength);
+
+}
+
+
+NTSTATUS
+SamGetDisplayEnumerationIndex (
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN PUNICODE_STRING Prefix,
+ OUT PULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the index of the entry which alphabetically
+ immediatly preceeds a specified prefix. If no such entry exists,
+ then zero is returned as the index.
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which sorted information class is
+ to be searched.
+
+ Prefix - The prefix to compare.
+
+ Index - Receives the index of the entry of the information class
+ with a LogonName (or MachineName) which immediatly preceeds the
+ provided prefix string. If there are no elements which preceed
+ the prefix, then zero is returned.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+
+--*/
+{
+
+ (*Index) = 0;
+
+ return(STATUS_SUCCESS);
+
+
+ DBG_UNREFERENCED_PARAMETER(DomainHandle);
+ DBG_UNREFERENCED_PARAMETER(DisplayInformation);
+ DBG_UNREFERENCED_PARAMETER(Prefix);
+
+}
+
+
diff --git a/private/newsam/client/tmachine.c b/private/newsam/client/tmachine.c
new file mode 100644
index 000000000..32b91a1d2
--- /dev/null
+++ b/private/newsam/client/tmachine.c
@@ -0,0 +1,902 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tmachine.c
+
+Abstract:
+
+ This module tests the machine account creation facilities
+ of SAM.
+
+Author:
+
+ Jim Kelly (JimK) 7-Feb-1994
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <nt.h>
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <ntlsa.h>
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <seopaque.h>
+#include <string.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Macros //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#ifndef SHIFT
+#define SHIFT(c,v) {c--; v++;}
+#endif //SHIFT
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+TSampGetLsaDomainInfo(
+ IN PUNICODE_STRING ServerName,
+ OUT PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves ACCOUNT domain information from the LSA
+ policy database.
+
+
+Arguments:
+
+ ServerName - name of machine to get account domain information
+ from.
+
+ PolicyAccountDomainInfo - Receives a pointer to a
+ POLICY_ACCOUNT_DOMAIN_INFO structure containing the account
+ domain info.
+
+
+Return Value:
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsaOpenPolicy()
+ LsaQueryInformationPolicy()
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ LSA_HANDLE
+ PolicyHandle;
+
+ OBJECT_ATTRIBUTES
+ PolicyObjectAttributes;
+
+ //
+ // Open the policy database
+ //
+
+ InitializeObjectAttributes( &PolicyObjectAttributes,
+ NULL, // Name
+ 0, // Attributes
+ NULL, // Root
+ NULL ); // Security Descriptor
+
+ NtStatus = LsaOpenPolicy( ServerName,
+ &PolicyObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &PolicyHandle );
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Query the account domain information
+ //
+
+ NtStatus = LsaQueryInformationPolicy( PolicyHandle,
+ PolicyAccountDomainInformation,
+ (PVOID *)PolicyAccountDomainInfo );
+
+
+ IgnoreStatus = LsaClose( PolicyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+TSampConnectToServer(
+ IN PUNICODE_STRING ServerName,
+ IN ACCESS_MASK DomainAccess,
+ OUT PHANDLE ServerHandle,
+ OUT PHANDLE DomainHandle,
+ OUT PSID *DomainSid
+ )
+
+/*++
+
+Routine Description:
+
+ Open a handle to the SAM server on the specified server
+ and then open the account domain on that same server.
+
+Arguments:
+
+ ServerName - Name of server to connect to.
+
+ DomainAccess - accesses needed to the account domain.
+
+ ServerHandle - Receives a handle to the SAM server on the specified
+ system.
+
+ DomainHandle - Receives a handle to the account domain.
+
+ DomainSid - Receives a pointer to the SID of the account domain.
+
+
+Return Value:
+
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ OBJECT_ATTRIBUTES
+ ObjectAttributes;
+
+ PPOLICY_ACCOUNT_DOMAIN_INFO
+ AccountDomainInfo;
+
+ //
+ // get account domain info
+ //
+
+ NtStatus = TSampGetLsaDomainInfo( ServerName,
+ &AccountDomainInfo);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Failed to get lsa domain info...\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+ printf("SAM TEST: Target domain is %wZ\n", &AccountDomainInfo->DomainName);
+
+ (*DomainSid) = AccountDomainInfo->DomainSid;
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
+
+
+ NtStatus = SamConnect(
+ ServerName,
+ ServerHandle,
+ SAM_SERVER_READ | SAM_SERVER_EXECUTE,
+ &ObjectAttributes
+ );
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Failed to connect...\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ NtStatus = SamOpenDomain(
+ (*ServerHandle),
+ DomainAccess,
+ *DomainSid,
+ DomainHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed account domain open\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+ return(STATUS_SUCCESS);
+
+
+}
+
+
+BOOLEAN
+TSampEnableMachinePrivilege( VOID )
+
+/*++
+
+Routine Description:
+
+ This function enabled the SeMachineAccountPrivilege privilege.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE if privilege successfully enabled.
+ FALSE if not successfully enabled.
+
+--*/
+{
+
+ NTSTATUS Status;
+ HANDLE Token;
+ LUID SecurityPrivilege;
+ PTOKEN_PRIVILEGES NewState;
+ ULONG ReturnLength;
+
+
+ //
+ // Open our own token
+ //
+
+ Status = NtOpenProcessToken(
+ NtCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES,
+ &Token
+ );
+ if (!NT_SUCCESS(Status)) {
+ printf("SAM TEST: Can't open process token to enable Privilege.\n"
+ " Completion status of NtOpenProcessToken() is: 0x%lx\n", Status);
+ return(FALSE);
+ }
+
+
+ //
+ // Initialize the adjustment structure
+ //
+
+ SecurityPrivilege =
+ RtlConvertLongToLargeInteger(SE_MACHINE_ACCOUNT_PRIVILEGE);
+
+ ASSERT( (sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)) < 100);
+ NewState = RtlAllocateHeap( RtlProcessHeap(), 0, 100 );
+
+ NewState->PrivilegeCount = 1;
+ NewState->Privileges[0].Luid = SecurityPrivilege;
+ NewState->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+
+ //
+ // Set the state of the privilege to ENABLED.
+ //
+
+ Status = NtAdjustPrivilegesToken(
+ Token, // TokenHandle
+ FALSE, // DisableAllPrivileges
+ NewState, // NewState
+ 0, // BufferLength
+ NULL, // PreviousState (OPTIONAL)
+ &ReturnLength // ReturnLength
+ );
+ // don't use NT_SUCCESS here because STATUS_NOT_ALL_ASSIGNED is a success status
+ if (Status != STATUS_SUCCESS) {
+ return(FALSE);
+ }
+
+
+ //
+ // Clean up some stuff before returning
+ //
+
+ RtlFreeHeap( RtlProcessHeap(), 0, NewState );
+ Status = NtClose( Token );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ return TRUE;
+
+}
+
+
+NTSTATUS
+TSampCreateMachine(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to create a machine account.
+
+ One of two cases may be tested:
+
+ 1) DomainHandle is open for DOMAIN_CREATE_USER,
+ or
+ 2) DomainHandle is open for DOMAIN_LOOKUP and
+ the SeMachineAccountPrivilege privilege is
+ enabled.
+
+ It is the caller's responsibility to establish the
+ correct case criteria before calling.
+
+Arguments:
+
+ DomainHandle - handle to domain to create account in.
+
+ AccountName - Name of the account to create.
+
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ SAM_HANDLE
+ UserHandle;
+
+ ACCESS_MASK
+ GrantedAccess;
+
+ ULONG
+ Rid;
+
+ NtStatus = SamCreateUser2InDomain( DomainHandle,
+ AccountName,
+ USER_WORKSTATION_TRUST_ACCOUNT,
+ MAXIMUM_ALLOWED,
+ &UserHandle,
+ &GrantedAccess,
+ &Rid);
+
+ if (NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SamCloseHandle( UserHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ printf("SAM TEST: Machine account created.\n"
+ " GrantedAccess: 0x%lx\n"
+ " Rid: %d (0x%lx)\n",
+ GrantedAccess, Rid, Rid);
+ } else {
+ printf("SAM TEST: Machine account creation failed.\n"
+ " Status: 0x%lx\n", NtStatus);
+ }
+
+
+ return(NtStatus);
+
+
+}
+
+
+NTSTATUS
+TSampSetPasswordMachine(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName,
+ IN PUNICODE_STRING Password
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to set the password of a machine account.
+
+
+Arguments:
+
+ DomainHandle - handle to domain account is in.
+
+ AccountName - Name of the account to set password.
+
+ Password - New password.
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+ SAM_HANDLE
+ UserHandle;
+
+ PULONG
+ RelativeIds;
+
+ PSID_NAME_USE
+ Use;
+
+ USER_SET_PASSWORD_INFORMATION
+ PasswordInfo;
+
+
+ PasswordInfo.Password = (*Password);
+ PasswordInfo.PasswordExpired = FALSE;
+
+ NtStatus = SamLookupNamesInDomain( DomainHandle,
+ 1,
+ AccountName,
+ &RelativeIds,
+ &Use);
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't find account to set password.\n"
+ " Lookup status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ NtStatus = SamOpenUser( DomainHandle,
+ USER_FORCE_PASSWORD_CHANGE,
+ RelativeIds[0],
+ &UserHandle);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't open user account for FORCE_PASSWORD_CHANGE.\n"
+ " Lookup status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+ NtStatus = SamSetInformationUser( UserHandle,
+ UserSetPasswordInformation,
+ &PasswordInfo
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't set password on user account.\n"
+ " Set Info status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ return(STATUS_SUCCESS);
+
+
+}
+
+
+NTSTATUS
+TSampDeleteMachine(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to delete a machine account.
+
+
+Arguments:
+
+ DomainHandle - handle to domain to delete account from.
+
+ AccountName - Name of the account to delete.
+
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+ SAM_HANDLE
+ UserHandle;
+
+ PULONG
+ RelativeIds;
+
+ PSID_NAME_USE
+ Use;
+
+ NtStatus = SamLookupNamesInDomain( DomainHandle,
+ 1,
+ AccountName,
+ &RelativeIds,
+ &Use);
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't find account to delete.\n"
+ " Lookup status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ NtStatus = SamOpenUser( DomainHandle,
+ DELETE,
+ RelativeIds[0],
+ &UserHandle);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't open user account for delete.\n"
+ " Open status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+ NtStatus = SamDeleteUser( UserHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Couldn't delete user account.\n"
+ " DeleteUser status: 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ return(STATUS_SUCCESS);
+
+
+}
+
+
+VOID
+TSampPrintYesOrNo(
+ IN BOOLEAN b
+ )
+{
+ if (b) {
+ printf("Yes\n");
+ } else {
+ printf("No\n");
+ }
+}
+
+
+
+VOID
+TSampUsage( VOID )
+{
+
+ printf("\n\n Command format:\n");
+ printf(" tmachine [/c] [/p] [/d] <account-name> <machine> [<password>]\n");
+ printf("\n");
+ printf(" Switches\n");
+ printf(" /c - create account\n");
+ printf(" /p - set password on account\n");
+ printf(" /d - delete account\n");
+ printf("\n");
+ printf(" if multiple switches are specified, they are attempted in\n");
+ printf(" the order listed above. An error in any attempt will prevent\n");
+ printf(" any further attempts.\n");
+ printf("\n");
+ return;
+}
+
+
+VOID
+main (c,v)
+int c;
+char **v;
+
+/*++
+
+Routine Description:
+
+ This is the main entry routine for this test.
+
+Arguments:
+
+ Argv[1] - account name to create or delete
+
+ Argv[2] - domain controller machine name
+
+ Argv[3] - 'D' to delete account, otherwise account is created.
+
+
+
+Return Value:
+
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ UNICODE_STRING
+ AccountName,
+ ControllerName,
+ Password;
+
+ WCHAR
+ AccountNameBuffer[80],
+ ControllerNameBuffer[80],
+ PasswordBuffer[80];
+
+ ANSI_STRING
+ AnsiString;
+
+ SAM_HANDLE
+ ServerHandle,
+ ServerHandle2,
+ DomainHandle,
+ DomainHandle2;
+
+ PSID
+ DomainSid;
+
+ BOOLEAN
+ Create = FALSE,
+ SetPassword = FALSE,
+ Delete = FALSE;
+
+ ULONG
+ ArgNum = 0;
+
+ PCHAR
+ p;
+
+ CHAR
+ ch;
+
+ AccountName.Length = 0;
+ ControllerName.Length = 0;
+ Password.Length = 0;
+
+
+
+ //
+ // Command format:
+ //
+ // tmachine [/c] [/p] [/d] <account-name> <machine> [<password>]
+ //
+ // Switches
+ // /c - create account
+ // /p - set password on account
+ // /d - delete account
+ //
+ // if multiple switches are specified, they are attempted in
+ // the order listed above. An error in any attempt will prevent
+ // any further attempts.
+ //
+
+ SHIFT (c,v);
+ while ((c > 0) && ((ch = *v[0]))) {
+ p = *v;
+ if (ch == '/') {
+ while (*++p != '\0') {
+ if ((*p == 'c') || (*p == 'C')) {
+ Create = TRUE;
+// printf("Create\n");
+ } else if ((*p == 'p') || (*p == 'P')) {
+ SetPassword = TRUE;
+// printf("SetPassword\n");
+ } else if ((*p == 'd') || (*p == 'D')) {
+ Delete = TRUE;
+// printf("Delete\n");
+ } else {
+ TSampUsage();
+ return;
+ }
+ }
+ } else {
+
+ switch (ArgNum) {
+ case 0:
+
+ //
+ // collecting account name
+ //
+
+ AccountName.Buffer = AccountNameBuffer;
+ AccountName.MaximumLength = sizeof(AccountNameBuffer);
+ RtlInitAnsiString(&AnsiString, (*v));
+ RtlAnsiStringToUnicodeString(&AccountName, &AnsiString, FALSE);
+
+// printf("account: %wZ\n", &AccountName);
+ break;
+
+ case 1:
+
+ //
+ // collecting machine name
+ //
+
+ ControllerName.Buffer = ControllerNameBuffer;
+ ControllerName.MaximumLength = sizeof(ControllerNameBuffer);
+ RtlInitAnsiString(&AnsiString, (*v));
+ RtlAnsiStringToUnicodeString(&ControllerName, &AnsiString, FALSE);
+
+// printf("machine: %wZ\n", &ControllerName);
+ break;
+
+
+ case 2:
+
+ //
+ // collecting password name
+ //
+
+ Password.Buffer = PasswordBuffer;
+ Password.MaximumLength = sizeof(PasswordBuffer);
+ RtlInitAnsiString(&AnsiString, (*v));
+ RtlAnsiStringToUnicodeString(&Password, &AnsiString, FALSE);
+
+// printf("password: %wZ\n", &Password);
+ break;
+
+ default:
+
+ //
+ // collecting garbage.
+ //
+
+ break;
+ }
+
+ ArgNum++;
+ }
+ SHIFT(c,v);
+ }
+
+
+
+ printf("parameters:\n");
+ printf(" Create Account: "); TSampPrintYesOrNo( Create );
+ printf(" Set Password : "); TSampPrintYesOrNo( SetPassword );
+ printf(" Delete Account: "); TSampPrintYesOrNo( Delete );
+ printf(" Account : *%wZ*\n", &AccountName);
+ printf(" Machine : *%wZ*\n", &ControllerName);
+ printf(" Password : *%wZ*\n", &Password);
+
+
+ //
+ // Make sure we don't have conflicting parameters
+ //
+ // Rules:
+ //
+ // 1) account name is always required.
+ // 2) password and machine are required if /P was specified.
+ // 3) machine is optional if /P not specified.
+ //
+ //
+
+ if ( (AccountName.Length == 0) ||
+ ( SetPassword && (ControllerName.Length == 0) ) ||
+ ( SetPassword && (Password.Length == 0) ) ) {
+ TSampUsage();
+ return;
+ }
+
+
+ //
+ // Open the server and the account domain
+ //
+
+ NtStatus = TSampConnectToServer(&ControllerName,
+ DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
+ &ServerHandle,
+ &DomainHandle,
+ &DomainSid);
+
+
+ if (Create) {
+ //
+ // try to create the machine account with privilege.
+ //
+
+
+ printf("SAM TEST: Creating machine account with privilege.\n");
+ TSampEnableMachinePrivilege();
+ NtStatus = TSampCreateMachine( DomainHandle, &AccountName );
+ if (NT_SUCCESS(NtStatus)) {
+ printf(" Status: successful\n");
+ } else {
+
+ if (NtStatus == STATUS_ACCESS_DENIED) {
+ //
+ // We didn't have the privilege, and didn't have
+ // the domain open so that it would work without
+ // the privilege.
+ //
+
+ printf(" Couldn't create account with privilege (0x%lx)\n"
+ " Attempting normal creation (without privilege)\n"
+ , NtStatus);
+ NtStatus = TSampConnectToServer(&ControllerName,
+ DOMAIN_LOOKUP |
+ DOMAIN_READ_PASSWORD_PARAMETERS |
+ DOMAIN_CREATE_USER,
+ &ServerHandle2,
+ &DomainHandle2,
+ &DomainSid);
+ if (!NT_SUCCESS(NtStatus)) {
+ printf(" Can't open domain for CREATE_USER access (0x%lx)\n", NtStatus);
+ } else {
+ NtStatus = TSampCreateMachine( DomainHandle2,
+ &AccountName );
+ if (NT_SUCCESS(NtStatus)) {
+ printf(" Status: successful\n");
+ } else {
+ printf(" Failed: 0x%lx\n", NtStatus);
+ }
+
+ IgnoreStatus = SamCloseHandle( DomainHandle2 );
+
+ }
+ }
+ if (!NT_SUCCESS(NtStatus)) {
+ printf(" Status: 0x%lx", NtStatus);
+ return;
+ }
+ }
+ }
+
+
+ if (SetPassword) {
+
+ //
+ // Try to set the password on the account
+ //
+
+ printf("SAM TEST: Setting password of account ...\n");
+ NtStatus = TSampSetPasswordMachine( DomainHandle, &AccountName, &Password );
+ if (NT_SUCCESS(NtStatus)) {
+ printf(" Status: successful\n");
+ } else {
+ printf(" Status: 0x%lx", NtStatus);
+ return;
+ }
+ }
+
+
+ if (Delete) {
+
+ printf("SAM TEST: Deleting account ...\n");
+ NtStatus = TSampDeleteMachine( DomainHandle, &AccountName );
+ if (NT_SUCCESS(NtStatus)) {
+ printf(" Status: successful\n");
+ } else {
+ printf(" Status: 0x%lx", NtStatus);
+ return;
+ }
+ }
+
+
+ return;
+}
+
diff --git a/private/newsam/client/tmultipl.c b/private/newsam/client/tmultipl.c
new file mode 100644
index 000000000..25f93d97c
--- /dev/null
+++ b/private/newsam/client/tmultipl.c
@@ -0,0 +1,574 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tmultipl.c
+
+Abstract:
+
+ This module tests the addition and removal of multiple
+ alias members.
+
+Author:
+
+ Jim Kelly (JimK) 11-Oct-1994
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <nt.h>
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <ntlsa.h>
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <seopaque.h>
+#include <string.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Macros and defines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#define TSAMP_MEMBER_COUNT 35
+
+#ifndef SHIFT
+#define SHIFT(c,v) {c--; v++;}
+#endif //SHIFT
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+VOID
+TSampUsage( VOID )
+{
+
+ printf("\n\n Test multiple member operations on alias\n");
+ printf("\n\n Command format:\n");
+ printf(" tmultipl [/a] [/r]\n");
+ printf("\n");
+ printf(" Switches\n");
+ printf(" /a - causes members to be added to an alias\n");
+ printf(" /r - causes members to be removed from alias\n");
+ printf("\n");
+ printf(" If multiple switches are specified, first adding will be attempted\n");
+ printf(" and then removal.\n");
+ printf(" Defaults to Account Operator alias.\n");
+ printf("\n");
+ return;
+}
+
+
+VOID
+TSampParseCommandLine(
+ IN int c,
+ IN char **v,
+ OUT PBOOLEAN Add,
+ OUT PBOOLEAN Remove
+ )
+
+{
+ PCHAR
+ p;
+
+ CHAR
+ ch;
+
+
+ //
+ // Command format:
+ //
+ // tmultipl [/a] [/r]
+ //
+ // Switches
+ // /a - causes members to be added to an alias\n");
+ // /r - causes members to be removed from alias\n");
+ //
+ // if multiple switches are specified, first adding will be
+ // attempted and then removal.
+ //
+
+ (*Add) = FALSE;
+ (*Remove) = FALSE;
+
+ SHIFT (c,v);
+ while ((c > 0) && ((ch = *v[0]))) {
+ p = *v;
+ if (ch == '/') {
+ while (*++p != '\0') {
+ if ((*p == 'a') || (*p == 'A')) {
+ (*Add) = TRUE;
+ printf("Add\n");
+ } else if ((*p == 'r') || (*p == 'R')) {
+ (*Remove) = TRUE;
+ printf("Remove\n");
+ } else {
+ TSampUsage();
+ return;
+ }
+ }
+ }
+ SHIFT(c,v);
+ }
+}
+
+NTSTATUS
+TSampGetLsaDomainInfo(
+ IN PUNICODE_STRING ServerName,
+ OUT PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves ACCOUNT domain information from the LSA
+ policy database.
+
+
+Arguments:
+
+ ServerName - name of machine to get account domain information
+ from.
+
+ PolicyAccountDomainInfo - Receives a pointer to a
+ POLICY_ACCOUNT_DOMAIN_INFO structure containing the account
+ domain info.
+
+
+Return Value:
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsaOpenPolicy()
+ LsaQueryInformationPolicy()
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ LSA_HANDLE
+ PolicyHandle;
+
+ OBJECT_ATTRIBUTES
+ PolicyObjectAttributes;
+
+ //
+ // Open the policy database
+ //
+
+ InitializeObjectAttributes( &PolicyObjectAttributes,
+ NULL, // Name
+ 0, // Attributes
+ NULL, // Root
+ NULL ); // Security Descriptor
+
+ NtStatus = LsaOpenPolicy( ServerName,
+ &PolicyObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &PolicyHandle );
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Query the account domain information
+ //
+
+ NtStatus = LsaQueryInformationPolicy( PolicyHandle,
+ PolicyAccountDomainInformation,
+ (PVOID *)PolicyAccountDomainInfo );
+
+
+ IgnoreStatus = LsaClose( PolicyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+TSampConnectToServer(
+ IN PUNICODE_STRING ServerName,
+ IN ACCESS_MASK DomainAccess,
+ OUT PHANDLE ServerHandle,
+ OUT PHANDLE AccountDomain OPTIONAL,
+ OUT PSID *AccountDomainSid OPTIONAL,
+ OUT PHANDLE BuiltinDomain OPTIONAL,
+ OUT PSID *BuiltinDomainSid OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ Open a handle to the SAM server on the specified server
+ and then open the account and builtin domains on that same
+ server.
+
+Arguments:
+
+ ServerName - Name of server to connect to.
+
+ DomainAccess - accesses needed to the account domain.
+
+ ServerHandle - Receives a handle to the SAM server on the specified
+ system.
+
+ AccountDomain - Receives a handle to the account domain.
+
+ AccountDomainSid - Receives a pointer to the SID of the account domain.
+ Must be present if AccountDomain is present.
+
+ BuiltinDomain - Receives a handle to the Builtin domain.
+
+ BuiltinDomainSid - Receives a pointer to the SID of the Builtin domain.
+ Must be present if BuiltinDomain is present.
+
+
+Return Value:
+
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ OBJECT_ATTRIBUTES
+ ObjectAttributes;
+
+ PPOLICY_ACCOUNT_DOMAIN_INFO
+ AccountDomainInfo;
+
+ SID_IDENTIFIER_AUTHORITY
+ BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+ //
+ // Connect to the server
+ //
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
+
+
+ NtStatus = SamConnect(
+ ServerName,
+ ServerHandle,
+ SAM_SERVER_READ | SAM_SERVER_EXECUTE,
+ &ObjectAttributes
+ );
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Failed to connect...\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+
+ //
+ // Get account domain handle and sid
+ //
+
+ if (ARGUMENT_PRESENT(AccountDomain)) {
+ //
+ // get account domain info
+ //
+
+ NtStatus = TSampGetLsaDomainInfo( ServerName,
+ &AccountDomainInfo);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Failed to get lsa domain info...\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+ (*AccountDomainSid) = AccountDomainInfo->DomainSid;
+
+
+ NtStatus = SamOpenDomain(
+ (*ServerHandle),
+ DomainAccess,
+ *AccountDomainSid,
+ AccountDomain
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed account domain open\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+ } //end_if
+
+
+ //
+ // Get builtin domain handle and sid
+ //
+
+ if (ARGUMENT_PRESENT(BuiltinDomain)) {
+
+ NtStatus = RtlAllocateAndInitializeSid(
+ &BuiltinAuthority,
+ 1, //SubAuthorities
+ SECURITY_BUILTIN_DOMAIN_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ BuiltinDomainSid
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Failed to allocate and init builtin domain sid...\n"
+ " status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+
+ NtStatus = SamOpenDomain(
+ (*ServerHandle),
+ DomainAccess,
+ *BuiltinDomainSid,
+ BuiltinDomain
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed builtin domain open\n"
+ " Completion status is 0x%lx\n", NtStatus);
+ return(NtStatus);
+ }
+ } //end_if
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+
+VOID
+TSampInitializeSids(
+ OUT PSID *MemberSids,
+ IN ULONG MemberCount
+ )
+{
+
+ //
+ // Return and array of sids.
+ //
+
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ i;
+
+ SID_IDENTIFIER_AUTHORITY
+ BuiltinAuthority = SECURITY_NT_AUTHORITY,
+ UnusedSidAuthority = {0, 0, 0, 0, 0, 6}; //Authority that isn't used
+
+ //
+ // Fill MemberSids with MemberCount SIDs
+ //
+
+ for (i=0; i<MemberCount; i++) {
+
+ NtStatus = RtlAllocateAndInitializeSid(
+ &UnusedSidAuthority,
+ 3, //SubAuthorityCount
+ 72549230,
+ i,
+ i*17,
+ 0, 0, 0, 0, 0,
+ &MemberSids[i]
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Tsamp: Couldn't allocate or initialize sid %d, status: 0x%lx\n", NtStatus);
+ return;
+ }
+ } // end_for
+
+
+ return;
+
+}
+
+
+NTSTATUS
+TSampTestAddMembers(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID *MemberSids,
+ IN ULONG MemberCount
+ )
+{
+ NTSTATUS
+ NtStatus;
+
+ NtStatus = SamAddMultipleMembersToAlias(
+ AliasHandle,
+ MemberSids,
+ MemberCount
+ );
+ printf("TSamp: Added %d members to alias. Status: 0x%lx\n",
+ MemberCount, NtStatus);
+ return(NtStatus);
+}
+
+
+NTSTATUS
+TSampTestRemoveMembers(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID *MemberSids,
+ IN ULONG MemberCount
+ )
+{
+ NTSTATUS
+ NtStatus;
+
+ NtStatus = SamRemoveMultipleMembersFromAlias(
+ AliasHandle,
+ MemberSids,
+ MemberCount
+ );
+ printf("TSamp: Removed %d members from alias. Status: 0x%lx\n",
+ MemberCount, NtStatus);
+ return(NtStatus);
+}
+
+
+//VOID
+__cdecl
+main(c,v)
+int c;
+char **v;
+
+/*++
+
+Routine Description:
+
+ This is the main entry routine for this test.
+
+Arguments:
+
+
+
+
+Return Value:
+
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ BOOLEAN
+ Add,
+ Remove;
+
+ UNICODE_STRING
+ ControllerName;
+
+ WCHAR
+ ControllerNameBuffer[80];
+
+ SAM_HANDLE
+ ServerHandle,
+ AccountDomainHandle,
+ BuiltinHandle,
+ AliasHandle;
+
+ ULONG
+ MemberCount = TSAMP_MEMBER_COUNT;
+
+ PSID
+ MemberSids[TSAMP_MEMBER_COUNT],
+ AccountDomainSid,
+ BuiltinSid;
+
+
+ ControllerName.Length = 0;
+ ControllerName.Buffer = ControllerNameBuffer;
+ ControllerName.MaximumLength = sizeof(ControllerNameBuffer);
+
+
+ TSampParseCommandLine( c, v, &Add, &Remove );
+
+ if (!Add && !Remove) {
+ TSampUsage();
+ return;
+ }
+
+ //
+ // Open the server and its domains
+ //
+
+ NtStatus = TSampConnectToServer(&ControllerName,
+ DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
+ &ServerHandle,
+ &AccountDomainHandle,
+ &AccountDomainSid,
+ &BuiltinHandle,
+ &BuiltinSid);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Initialize a bunch of SIDs to add to the alias.
+ //
+
+ TSampInitializeSids( MemberSids, MemberCount );
+
+ //
+ // Open the alias we are going to play with
+ //
+
+ NtStatus = SamOpenAlias( BuiltinHandle,
+ (ALIAS_ADD_MEMBER | ALIAS_REMOVE_MEMBER),
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS,
+ &AliasHandle);
+
+ if (Add) {
+ NtStatus = TSampTestAddMembers( AliasHandle, MemberSids, MemberCount );
+ }
+
+ if (Remove) {
+ NtStatus = TSampTestRemoveMembers( AliasHandle, MemberSids, MemberCount );
+ }
+
+
+
+
+ return(0);
+}
+
diff --git a/private/newsam/client/toempass.c b/private/newsam/client/toempass.c
new file mode 100644
index 000000000..a3461b348
--- /dev/null
+++ b/private/newsam/client/toempass.c
@@ -0,0 +1,231 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ toempass.c
+
+Abstract:
+
+ This file contains test code for the oem password change routine.
+
+Author:
+
+ Mike Swift (MikeSw) 4-January-1995
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "samclip.h"
+
+
+NTSTATUS
+SampEncryptLmPasswords(
+ IN LPSTR OldPassword,
+ IN LPSTR NewPassword,
+ OUT PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
+ OUT PENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewLm
+)
+/*++
+
+Routine Description:
+
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ LM_OWF_PASSWORD OldLmOwfPassword;
+ LM_OWF_PASSWORD NewLmOwfPassword;
+ PSAMPR_USER_PASSWORD NewLm = (PSAMPR_USER_PASSWORD) NewEncryptedWithOldLm;
+ struct RC4_KEYSTRUCT Rc4Key;
+ NTSTATUS NtStatus;
+ CHAR LocalNewPassword[SAM_MAX_PASSWORD_LENGTH];
+ CHAR LocalOldPassword[SAM_MAX_PASSWORD_LENGTH];
+
+ if ((lstrlenA(OldPassword) > SAM_MAX_PASSWORD_LENGTH - 1) ||
+ (lstrlenA(NewPassword) > SAM_MAX_PASSWORD_LENGTH - 1) )
+ {
+ return(STATUS_PASSWORD_RESTRICTION);
+ }
+
+ //
+ // Upcase the passwords
+ //
+ lstrcpyA(LocalOldPassword,OldPassword);
+ lstrcpyA(LocalNewPassword,NewPassword);
+
+ strupr(LocalOldPassword);
+ strupr(LocalNewPassword);
+
+
+
+ //
+ // Calculate the LM OWF passwords
+ //
+
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ LocalOldPassword,
+ &OldLmOwfPassword
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlCalculateLmOwfPassword(
+ LocalNewPassword,
+ &NewLmOwfPassword
+ );
+ }
+
+
+
+ //
+ // Calculate the encrypted old passwords
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ &OldLmOwfPassword,
+ &NewLmOwfPassword,
+ OldLmOwfEncryptedWithNewLm
+ );
+ }
+
+
+ //
+ // Calculate the encrypted new passwords
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT(sizeof(SAMPR_ENCRYPTED_USER_PASSWORD) == sizeof(SAMPR_USER_PASSWORD));
+
+
+ //
+ // Compute the encrypted new password with LM key.
+ //
+
+
+ rc4_key(
+ &Rc4Key,
+ LM_OWF_PASSWORD_LENGTH,
+ (PUCHAR) &OldLmOwfPassword
+ );
+
+ RtlCopyMemory(
+ ((PUCHAR) NewLm->Buffer) +
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ strlen(NewPassword),
+ NewPassword,
+ strlen(NewPassword)
+ );
+
+ NewLm->Length = strlen(NewPassword);
+ rc4(&Rc4Key,
+ sizeof(SAMPR_USER_PASSWORD),
+ (PUCHAR) NewEncryptedWithOldLm
+ );
+
+
+ }
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SamOemChangePassword(
+ LPWSTR ServerName,
+ LPSTR UserName,
+ LPSTR OldPassword,
+ LPSTR NewPassword
+ )
+{
+ handle_t BindingHandle = NULL;
+ NTSTATUS Status;
+ SAMPR_ENCRYPTED_USER_PASSWORD NewLmEncryptedWithOldLm;
+ ENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewLm;
+ STRING UserString;
+ UNICODE_STRING ServerUnicodeString;
+ STRING ServerString;
+
+ RtlInitUnicodeString(
+ &ServerUnicodeString,
+ ServerName
+ );
+
+ Status = RtlUnicodeStringToOemString(
+ &ServerString,
+ &ServerUnicodeString,
+ TRUE
+ );
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+
+ RtlInitString(
+ &UserString,
+ UserName
+ );
+
+ Status = SampEncryptLmPasswords(
+ OldPassword,
+ NewPassword,
+ &NewLmEncryptedWithOldLm,
+ &OldLmOwfEncryptedWithNewLm
+ );
+
+ if (!NT_SUCCESS(Status)) {
+ RtlFreeOemString(&ServerString);
+ return(Status);
+ }
+ BindingHandle = SampSecureBind(
+ ServerName,
+ RPC_C_AUTHN_LEVEL_PKT_PRIVACY
+ );
+ if (BindingHandle == NULL) {
+ RtlFreeOemString(&ServerString);
+ return(RPC_NT_INVALID_BINDING);
+ }
+
+ RpcTryExcept{
+
+ Status = SamrOemChangePasswordUser2(
+ BindingHandle,
+ (PRPC_STRING) &ServerString,
+ (PRPC_STRING) &UserString,
+ &NewLmEncryptedWithOldLm,
+ &OldLmOwfEncryptedWithNewLm
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ RtlFreeOemString(&ServerString);
+ return(Status);
+
+
+}
diff --git a/private/newsam/client/tsamobj.c b/private/newsam/client/tsamobj.c
new file mode 100644
index 000000000..4448f16c1
--- /dev/null
+++ b/private/newsam/client/tsamobj.c
@@ -0,0 +1,12350 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tsamobj.c
+
+Abstract:
+
+ This is the primary SAM object test.
+ It contains a suite of tests for each type of SAM object.
+
+Author:
+
+ Jim Kelly (JimK) 12-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <nt.h>
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <ntlsa.h>
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <seopaque.h>
+#include <string.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#define TMPP_USER_NAME_ADMIN "Administrator"
+#define TMPP_USER_NAME_GUEST "Guest"
+#define TMPP_GROUP_NAME_ADMINS "Domain Admins"
+#define TMPP_GROUP_NAME_USERS "Domain Users"
+#define TMPP_GROUP_NAME_NONE "None"
+#define TMPP_ALIAS_NAME_ADMINS "Administrators"
+#define TMPP_ALIAS_NAME_SYSTEM_OPS "System Operators"
+#define TMPP_ALIAS_NAME_POWER_USERS "Power Users"
+#define TMPP_ALIAS_NAME_USERS "Users"
+#define TMPP_ALIAS_NAME_GUESTS "Guests"
+#define TMPP_ALIAS_NAME_ACCOUNT_OPS "Account Operators"
+#define TMPP_ALIAS_NAME_PRINT_OPS "Print Operators"
+#define TMPP_ALIAS_NAME_BACKUP_OPS "Backup Operators"
+
+
+
+#define GROUP_NAME1 "GROUP1"
+#define ALIAS_NAME1 "ALIAS1"
+#define ALIAS_NAME2 "ALIAS2"
+#define USER_NAME1 "USER1"
+#define USER_NAME2 "USER2"
+#define USER_NAME3 "USER3"
+
+// Keep these names not longer than 8 char's until long registry names supported
+#define DUMMY_NAME1 "DName1"
+#define DUMMY_NAME2 "2emaNuD"
+
+#define DUMMY_STRING1 "This is test string 1"
+#define DUMMY_STRING2 "Test String2 - test string 2 - tEST sTRING 2"
+
+#define ALL_NAMES_COUNT (3)
+#define SOME_NAMES_COUNT (7)
+#define NO_NAMES_COUNT (2)
+
+#define LOOKUP_KNOWN_NAME0 TMPP_USER_NAME_ADMIN
+#define LOOKUP_KNOWN_NAME1_A TMPP_GROUP_NAME_NONE
+#define LOOKUP_KNOWN_NAME2_A TMPP_GROUP_NAME_NONE
+#define LOOKUP_KNOWN_NAME1_P TMPP_GROUP_NAME_USERS
+#define LOOKUP_KNOWN_NAME2_P TMPP_GROUP_NAME_USERS
+
+#define LOOKUP_KNOWN_NAME0_RID DOMAIN_USER_RID_ADMIN
+#define LOOKUP_KNOWN_NAME1_RID DOMAIN_GROUP_RID_USERS
+#define LOOKUP_KNOWN_NAME2_RID DOMAIN_GROUP_RID_USERS
+
+#define LOOKUP_UNKNOWN_NAME0 "JoeJoe"
+#define LOOKUP_UNKNOWN_NAME1 "Tanya"
+#define LOOKUP_UNKNOWN_NAME2 "Fred"
+#define LOOKUP_UNKNOWN_NAME3 "Anyone"
+
+#define LOOKUP_KNOWN_NAME0_USE (SidTypeUser)
+#define LOOKUP_KNOWN_NAME1_USE (SidTypeGroup)
+#define LOOKUP_KNOWN_NAME2_USE (SidTypeGroup)
+
+
+//
+// This byte is expected to be different in the DummyLogonHours and
+// NoRestrictionLogonHours.
+//
+
+#define LOGON_HOURS_DIFFERENT_OFFSET (5)
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Global variables //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+LARGE_INTEGER LargeInteger1,
+ LargeInteger2;
+
+UNICODE_STRING DummyName1,
+ DummyName2,
+ DummyString1,
+ DummyString2;
+
+STRING DummyAnsiString1,
+ DummyAnsiString2;
+
+LOGON_HOURS NoLogonRestriction,
+ DummyLogonHours;
+
+CHAR NoLogonRestrictionBitMask[21],
+ DummyLogonHoursBitMask[21];
+
+
+UNICODE_STRING AllNames[ALL_NAMES_COUNT],
+ SomeNames[SOME_NAMES_COUNT],
+ NoNames[NO_NAMES_COUNT];
+
+
+SID_NAME_USE AllUses[ALL_NAMES_COUNT],
+ SomeUses[SOME_NAMES_COUNT],
+ NoUses[NO_NAMES_COUNT];
+
+ULONG AllRids[ALL_NAMES_COUNT],
+ SomeRids[SOME_NAMES_COUNT],
+ NoRids[NO_NAMES_COUNT];
+
+
+PSID BuiltinDomainSid,
+ AccountDomainSid,
+ PrimaryDomainSid,
+ WorldSid,
+ AdminsAliasSid,
+ AccountAliasSid;
+
+
+UNICODE_STRING BuiltinDomainName,
+ AccountDomainName,
+ PrimaryDomainName;
+
+BOOLEAN AccountDomainIsNotPrimaryDomain;
+
+
+//
+// These are NOT mutually exclusive
+//
+
+BOOLEAN BuiltinDomainTest, // Test the builting domain
+ SecurityOperatorTest, // Test auditing accessibility
+ AccountOpAliasTest, // Test account operator functions
+ AdminsAliasTest; // Test domain admin functions
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private macros //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+//
+// VOID
+// TST_SUCCESS_ASSERT( IN NTSTATUS S );
+//
+
+#define TST_SUCCESS_ASSERT( S ) \
+{ \
+ if ( !NT_SUCCESS((S)) ) { \
+ printf("\n** SUCCESS STATUS ASSERTION FAILURE **\n"); \
+ printf(" Status is: 0x%lx\n", (S) ); \
+ ASSERT(NT_SUCCESS((S))); \
+ } \
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+BOOLEAN
+TInitialize( VOID );
+
+BOOLEAN
+EnableSecurityPrivilege( VOID );
+
+VOID
+DetermineTestsToRun( VOID );
+
+VOID
+SeeIfSidIsSpecial(
+ IN PSID Sid
+ );
+
+BOOLEAN
+ServerTestSuite(
+ PHANDLE ServerHandle,
+ PHANDLE DomainHandle,
+ PHANDLE BuiltinDomainHandle,
+ PSID *DomainSid
+ );
+
+BOOLEAN
+SecurityTestSuite(
+ HANDLE ServerHandle,
+ HANDLE DomainHandle,
+ ULONG Pass
+ );
+
+BOOLEAN
+CheckReturnedSD(
+ IN SECURITY_INFORMATION SI,
+ IN PSECURITY_DESCRIPTOR SD,
+ IN BOOLEAN PrintTestSuccess
+ );
+
+
+BOOLEAN
+DomainTestSuite(
+ HANDLE DomainHandle
+ );
+
+BOOLEAN
+GroupTestSuite(
+ HANDLE DomainHandle,
+ ULONG Pass
+ );
+
+BOOLEAN
+AliasTestSuite(
+ HANDLE DomainHandle,
+ HANDLE BuiltinDomainHandle,
+ PSID DomainSid,
+ ULONG Pass
+ );
+
+BOOLEAN
+UserTestSuite(
+ HANDLE DomainHandle,
+ ULONG Pass
+ );
+
+
+NTSTATUS
+SampSetDomainPolicy( VOID );
+
+
+NTSTATUS
+SampGetLsaDomainInfo(
+ PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo,
+ PPOLICY_PRIMARY_DOMAIN_INFO *PolicyPrimaryDomainInfo
+ );
+
+
+//
+// The following are in WRAPPERS.C, but are prototyped here since this
+// test is the only thing that should ever call them.
+//
+
+NTSTATUS
+SamTestPrivateFunctionsDomain(
+ IN HANDLE DomainHandle
+ );
+
+NTSTATUS
+SamTestPrivateFunctionsUser(
+ IN HANDLE UserHandle
+ );
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+VOID
+_CRTAPI1 main (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This is the main entry routine for this test.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAM_HANDLE ServerHandle, DomainHandle, BuiltinDomainHandle;
+ PSID DomainSid;
+ BOOLEAN TestSucceeded = FALSE;
+
+
+ printf("\n\n\n\n");
+ printf(" Test: TSAMOBJ\n\n");
+ printf(" Test Date: \n");
+ printf(" Test Time: \n");
+
+
+
+
+ //
+ // Initialize and determine which flavor test(s) to run
+ //
+
+ if (!TInitialize()) {
+ return;
+ }
+
+ if (ServerTestSuite( &ServerHandle, &DomainHandle, &BuiltinDomainHandle, &DomainSid )) {
+
+ //
+ // Do security manipulation tests on domain object
+ //
+
+ if (SecurityTestSuite( ServerHandle, DomainHandle, 1)) {
+
+ if (AdminsAliasTest) {
+
+ //
+ // Do individual tests for domain, group, and user objects.
+ //
+
+ if (DomainTestSuite( DomainHandle )) {
+
+ if (AdminsAliasTest) {
+
+ if (GroupTestSuite( DomainHandle, 1)) {
+
+ if (AliasTestSuite( DomainHandle, BuiltinDomainHandle, DomainSid, 1)) {
+
+ if (UserTestSuite( DomainHandle, 1 )) {
+
+ if (SecurityTestSuite( ServerHandle, DomainHandle, 2)) {
+
+ if (GroupTestSuite( DomainHandle, 2)) {
+
+ if (AliasTestSuite( DomainHandle, BuiltinDomainHandle, DomainSid, 2)) {
+
+ if (UserTestSuite( DomainHandle, 2)) {
+
+ TestSucceeded = TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ TestSucceeded = TRUE;
+ }
+ }
+
+ SamFreeMemory(DomainSid);
+
+ NtStatus = SamCloseHandle( DomainHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Status of SamCloseHandle(Domain) is: 0x%lx\n", NtStatus);
+ DbgBreakPoint();
+ return;
+ }
+
+ NtStatus = SamCloseHandle( ServerHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("SAM TEST: Status of SamCloseHandle(Server) is: 0x%lx\n", NtStatus);
+ DbgBreakPoint();
+ return;
+ }
+
+ }
+
+ printf("\n");
+ printf("\n");
+ printf(" SAM Test: ");
+ if (TestSucceeded) {
+ printf("Succeeded\n");
+ } else {
+ printf("** Failed **\n");
+ }
+
+
+ return;
+}
+
+
+BOOLEAN
+TInitialize (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize test variables, et cetera.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ STRING Name;
+ ULONG i;
+
+ SID_IDENTIFIER_AUTHORITY WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY;
+ SID_IDENTIFIER_AUTHORITY DomainSidAuthority = {0,0,0,0,0,0};
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+
+ //
+ // Get the domain SIDs from the policy database...
+ //
+
+ NtStatus = SampSetDomainPolicy();
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // A random large integer value..
+ //
+
+ LargeInteger1.LowPart = 1234;
+ LargeInteger1.HighPart = 0;
+
+ LargeInteger2.LowPart = 4321;
+ LargeInteger2.HighPart = 0;
+
+
+ RtlInitString( &Name, DUMMY_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &DummyName1, &Name, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ RtlInitString( &Name, DUMMY_NAME2 );
+ NtStatus = RtlAnsiStringToUnicodeString( &DummyName2, &Name, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ RtlInitString( &DummyAnsiString1, DUMMY_STRING1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &DummyString1, &DummyAnsiString1, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ RtlInitString( &DummyAnsiString2, DUMMY_STRING2 );
+ NtStatus = RtlAnsiStringToUnicodeString( &DummyString2, &DummyAnsiString2, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ DummyLogonHours.UnitsPerWeek = SAM_HOURS_PER_WEEK;
+ DummyLogonHours.LogonHours = &DummyLogonHoursBitMask[0];
+ DummyLogonHoursBitMask[LOGON_HOURS_DIFFERENT_OFFSET] = 103; // Any non-zero value
+
+ NoLogonRestriction.UnitsPerWeek = SAM_HOURS_PER_WEEK;
+ NoLogonRestriction.LogonHours = &NoLogonRestrictionBitMask[0];
+ for ( i=0; i<(ULONG)((NoLogonRestriction.UnitsPerWeek+7)/8); i++) {
+ NoLogonRestrictionBitMask[0] = 0;
+ }
+
+
+
+ //
+ // Initialize some SIDs
+ //
+
+
+ WorldSid = RtlAllocateHeap( RtlProcessHeap(), 0, RtlLengthRequiredSid(1) );
+ ASSERT(WorldSid != NULL);
+ RtlInitializeSid( WorldSid, &WorldSidAuthority, 1 );
+ *(RtlSubAuthoritySid( WorldSid, 0 )) = SECURITY_WORLD_RID;
+
+ AdminsAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(AdminsAliasSid != NULL);
+ RtlInitializeSid( AdminsAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AdminsAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AdminsAliasSid, 1 )) = DOMAIN_ALIAS_RID_ADMINS;
+
+ AccountAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(AccountAliasSid != NULL);
+ RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS;
+
+
+
+
+ //
+ // Initialize some stuff for SID and NAME lookup operations
+ //
+
+ RtlInitString( &Name, LOOKUP_KNOWN_NAME0 );
+
+ AllUses[0] = LOOKUP_KNOWN_NAME0_USE; AllRids[0] = LOOKUP_KNOWN_NAME0_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &AllNames[0], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+ SomeUses[0] = LOOKUP_KNOWN_NAME0_USE; SomeRids[0] = LOOKUP_KNOWN_NAME0_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[0], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+ if (AccountDomainIsNotPrimaryDomain == TRUE) {
+ RtlInitString( &Name, LOOKUP_KNOWN_NAME1_A );
+ } else {
+ RtlInitString( &Name, LOOKUP_KNOWN_NAME1_P );
+ }
+ AllUses[1] = LOOKUP_KNOWN_NAME1_USE; AllRids[1] = LOOKUP_KNOWN_NAME1_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &AllNames[1], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+ SomeUses[1] = LOOKUP_KNOWN_NAME1_USE; SomeRids[1] = LOOKUP_KNOWN_NAME1_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[1], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+ RtlInitString( &Name, LOOKUP_UNKNOWN_NAME0 );
+
+ SomeUses[2] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[2], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+ NoUses[0] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &NoNames[0], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+ RtlInitString( &Name, LOOKUP_UNKNOWN_NAME1 );
+
+ SomeUses[3] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[3], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+ NoUses[1] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &NoNames[1], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+
+ RtlInitString( &Name, LOOKUP_UNKNOWN_NAME2 );
+
+ SomeUses[4] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[4], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+ if (AccountDomainIsNotPrimaryDomain == TRUE) {
+ RtlInitString( &Name, LOOKUP_KNOWN_NAME2_A );
+ } else {
+ RtlInitString( &Name, LOOKUP_KNOWN_NAME2_P );
+ }
+ AllUses[2] = LOOKUP_KNOWN_NAME2_USE; AllRids[2] = LOOKUP_KNOWN_NAME2_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &AllNames[2], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+ SomeUses[5] = LOOKUP_KNOWN_NAME2_USE; SomeRids[5] = LOOKUP_KNOWN_NAME2_RID;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[5], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+
+ RtlInitString( &Name, LOOKUP_UNKNOWN_NAME3 );
+
+ SomeUses[6] = SidTypeUnknown;
+ NtStatus = RtlAnsiStringToUnicodeString( &SomeNames[6], &Name, TRUE ); TST_SUCCESS_ASSERT(NtStatus);
+
+
+ DetermineTestsToRun();
+
+ return(TRUE);
+}
+
+
+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 these domains in:
+
+ BuiltinDomainSid
+ BuiltinDomainName
+ AccountDomainSid
+ AccountDomainName
+ PrimaryDomainSid
+ PrimaryDomainName
+
+ It also sets the boolean:
+
+ AccountDomainIsNotPrimaryDomain
+
+ to TRUE if the account domain is found to be different from the
+ Primary Domain.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo;
+ PPOLICY_PRIMARY_DOMAIN_INFO PolicyPrimaryDomainInfo;
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+ //
+ // Builtin domain - well-known name and SID
+ //
+
+ RtlInitUnicodeString( &BuiltinDomainName, L"Builtin");
+
+ BuiltinDomainSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+ ASSERT( BuiltinDomainSid != NULL );
+ RtlInitializeSid( BuiltinDomainSid, &BuiltinAuthority, 1 );
+ *(RtlSubAuthoritySid( BuiltinDomainSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+
+ //
+ // Account domain
+ //
+
+ NtStatus = SampGetLsaDomainInfo(
+ &PolicyAccountDomainInfo,
+ &PolicyPrimaryDomainInfo
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ return(NtStatus);
+ }
+
+ AccountDomainSid = PolicyAccountDomainInfo->DomainSid;
+ AccountDomainName = PolicyAccountDomainInfo->DomainName;
+
+ PrimaryDomainSid = PolicyPrimaryDomainInfo->Sid;
+ PrimaryDomainName = PolicyPrimaryDomainInfo->Name;
+
+ //
+ // Determine whether the account domain is a primary domain.
+ //
+
+ AccountDomainIsNotPrimaryDomain =
+ !RtlEqualUnicodeString( &PrimaryDomainName, &AccountDomainName, TRUE);
+
+ return(NtStatus);;
+}
+
+
+
+NTSTATUS
+SampGetLsaDomainInfo(
+ PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo,
+ PPOLICY_PRIMARY_DOMAIN_INFO *PolicyPrimaryDomainInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves ACCOUNT domain information from the LSA
+ policy database.
+
+
+Arguments:
+
+ PolicyAccountDomainInfo - Receives a pointer to a
+ POLICY_ACCOUNT_DOMAIN_INFO structure containing the account
+ domain info.
+
+ PolicyPrimaryDomainInfo - Receives a pointer to a
+ POLICY_PRIMARY_DOMAIN_INFO structure containing the Primary
+ domain info.
+
+
+Return Value:
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsaOpenPolicy()
+ LsaQueryInformationPolicy()
+--*/
+
+{
+ NTSTATUS Status, IgnoreStatus;
+
+ LSA_HANDLE PolicyHandle;
+ OBJECT_ATTRIBUTES PolicyObjectAttributes;
+
+ //
+ // Open the policy database
+ //
+
+ InitializeObjectAttributes( &PolicyObjectAttributes,
+ NULL, // Name
+ 0, // Attributes
+ NULL, // Root
+ NULL ); // Security Descriptor
+
+ Status = LsaOpenPolicy( NULL,
+ &PolicyObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &PolicyHandle );
+ if ( NT_SUCCESS(Status) ) {
+
+ //
+ // Query the account domain information
+ //
+
+ Status = LsaQueryInformationPolicy( PolicyHandle,
+ PolicyAccountDomainInformation,
+ (PVOID *)PolicyAccountDomainInfo );
+#if DBG
+ if ( NT_SUCCESS(Status) ) {
+ ASSERT( (*PolicyAccountDomainInfo) != NULL );
+ ASSERT( (*PolicyAccountDomainInfo)->DomainSid != NULL );
+ ASSERT( (*PolicyAccountDomainInfo)->DomainName.Buffer != NULL );
+ }
+#endif \\DBG
+
+ //
+ // Query the Primary domain information
+ //
+
+ Status = LsaQueryInformationPolicy( PolicyHandle,
+ PolicyPrimaryDomainInformation,
+ (PVOID *)PolicyPrimaryDomainInfo );
+#if DBG
+ if ( NT_SUCCESS(Status) ) {
+ ASSERT( (*PolicyPrimaryDomainInfo) != NULL );
+ ASSERT( (*PolicyPrimaryDomainInfo)->Sid != NULL );
+ ASSERT( (*PolicyPrimaryDomainInfo)->Name.Buffer != NULL );
+ }
+#endif \\DBG
+
+ IgnoreStatus = LsaClose( PolicyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(Status);
+}
+
+
+
+
+PSID
+CreateUserSid(
+ PSID DomainSid,
+ ULONG Rid
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates a domain account sid given a domain sid and
+ the relative id of the account within the domain.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Pointer to Sid, or NULL on failure.
+ The returned Sid must be freed with DeleteUserSid
+
+--*/
+{
+
+ NTSTATUS IgnoreStatus;
+ PSID AccountSid;
+ UCHAR AccountSubAuthorityCount = *RtlSubAuthorityCountSid(DomainSid) + (UCHAR)1;
+ ULONG AccountSidLength = RtlLengthRequiredSid(AccountSubAuthorityCount);
+ PULONG RidLocation;
+
+ // Temp sanity check
+ ASSERT(AccountSidLength == RtlLengthSid(DomainSid) + sizeof(ULONG));
+
+ //
+ // Allocate space for the account sid
+ //
+
+ AccountSid = MIDL_user_allocate(AccountSidLength);
+
+ if (AccountSid != NULL) {
+
+ //
+ // Copy the domain sid into the first part of the account sid
+ //
+
+ IgnoreStatus = RtlCopySid(AccountSidLength, AccountSid, DomainSid);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Increment the account sid sub-authority count
+ //
+
+ *RtlSubAuthorityCountSid(AccountSid) = AccountSubAuthorityCount;
+
+ //
+ // Add the rid as the final sub-authority
+ //
+
+ RidLocation = RtlSubAuthoritySid(AccountSid, AccountSubAuthorityCount - 1);
+ *RidLocation = Rid;
+ }
+
+ return(AccountSid);
+}
+
+
+
+VOID
+DeleteUserSid(
+ PSID UserSid
+ )
+
+/*++
+
+Routine Description:
+
+ Frees a sid returned by CreateUserSid.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ MIDL_user_free(UserSid);
+}
+
+
+
+BOOLEAN
+EnableSecurityPrivilege(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This function enabled the SeSecurityPrivilege privilege.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE if privilege successfully enabled.
+ FALSE if not successfully enabled.
+
+--*/
+{
+
+ NTSTATUS Status;
+ HANDLE Token;
+ LUID SecurityPrivilege;
+ PTOKEN_PRIVILEGES NewState;
+ ULONG ReturnLength;
+
+
+ //
+ // Open our own token
+ //
+
+ Status = NtOpenProcessToken(
+ NtCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES,
+ &Token
+ );
+ if (!NT_SUCCESS(Status)) {
+ printf(" \n\n\n");
+ printf("Tsamobj: Can't open process token to enable Security Privilege.\n");
+ printf(" Completion status of NtOpenProcessToken() is: 0x%lx\n", Status);
+ printf("\n");
+ return(FALSE);
+ }
+
+
+ //
+ // Initialize the adjustment structure
+ //
+
+ SecurityPrivilege =
+ RtlConvertLongToLargeInteger(SE_SECURITY_PRIVILEGE);
+
+ ASSERT( (sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)) < 100);
+ NewState = RtlAllocateHeap( RtlProcessHeap(), 0, 100 );
+
+ NewState->PrivilegeCount = 1;
+ NewState->Privileges[0].Luid = SecurityPrivilege;
+ NewState->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+
+ //
+ // Set the state of the privilege to ENABLED.
+ //
+
+ Status = NtAdjustPrivilegesToken(
+ Token, // TokenHandle
+ FALSE, // DisableAllPrivileges
+ NewState, // NewState
+ 0, // BufferLength
+ NULL, // PreviousState (OPTIONAL)
+ &ReturnLength // ReturnLength
+ );
+ // don't use NT_SUCCESS here because STATUS_NOT_ALL_ASSIGNED is a success status
+ if (Status != STATUS_SUCCESS) {
+ return(FALSE);
+ }
+
+
+ //
+ // Clean up some stuff before returning
+ //
+
+ RtlFreeHeap( RtlProcessHeap(), 0, NewState );
+ Status = NtClose( Token );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ return TRUE;
+
+}
+
+
+
+VOID
+printfSid(
+ PSID Sid
+ )
+
+/*++
+
+Routine Description:
+
+ Prints a sid
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ UCHAR Buffer[128];
+ UCHAR String[128];
+ UCHAR i;
+ ULONG Tmp;
+ PISID iSid = (PISID)Sid; // pointer to opaque structure
+ PSID NextSid = (PSID)Buffer;
+
+ ASSERT(sizeof(Buffer) >= RtlLengthRequiredSid(1));
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_WORLD_SID_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_WORLD_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("World");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_LOCAL_SID_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_LOCAL_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Local");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_CREATOR_SID_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_CREATOR_OWNER_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Creator");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_DIALUP_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Dialup");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_NETWORK_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Network");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_BATCH_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Batch");
+ return;
+ }
+ }
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_INTERACTIVE_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Interactive");
+ return;
+ }
+ }
+
+
+ {
+ SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
+ RtlInitializeSid(NextSid, &SidAuthority, 1 );
+ *(RtlSubAuthoritySid(NextSid, 0)) = SECURITY_LOCAL_SYSTEM_RID;
+ if (RtlEqualSid(Sid, NextSid)) {
+ printf("Local System");
+ return;
+ }
+ }
+
+
+
+ sprintf(Buffer, "S-%u-", (USHORT)iSid->Revision );
+ strcpy(String, Buffer);
+
+ if ( (iSid->IdentifierAuthority.Value[0] != 0) ||
+ (iSid->IdentifierAuthority.Value[1] != 0) ){
+ sprintf(Buffer, "0x%02hx%02hx%02hx%02hx%02hx%02hx",
+ (USHORT)iSid->IdentifierAuthority.Value[0],
+ (USHORT)iSid->IdentifierAuthority.Value[1],
+ (USHORT)iSid->IdentifierAuthority.Value[2],
+ (USHORT)iSid->IdentifierAuthority.Value[3],
+ (USHORT)iSid->IdentifierAuthority.Value[4],
+ (USHORT)iSid->IdentifierAuthority.Value[5] );
+ strcat(String, Buffer);
+ } else {
+ Tmp = (ULONG)iSid->IdentifierAuthority.Value[5] +
+ (ULONG)(iSid->IdentifierAuthority.Value[4] << 8) +
+ (ULONG)(iSid->IdentifierAuthority.Value[3] << 16) +
+ (ULONG)(iSid->IdentifierAuthority.Value[2] << 24);
+ sprintf(Buffer, "%lu", Tmp);
+ strcat(String, Buffer);
+ }
+
+
+ for (i=0;i<iSid->SubAuthorityCount ;i++ ) {
+ sprintf(Buffer, "-%lu", iSid->SubAuthority[i]);
+ strcat(String, Buffer);
+ }
+
+ printf(Buffer);
+
+ return;
+}
+
+
+VOID
+DetermineTestsToRun(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This function determines which tests are to be run.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+
+--*/
+{
+
+ NTSTATUS Status;
+ HANDLE Token;
+
+ PTOKEN_USER User;
+ PTOKEN_GROUPS Groups;
+
+ ULONG ReturnLength,
+ i;
+
+
+
+ //
+ // See if we can play with auditing information
+ //
+
+ SecurityOperatorTest = EnableSecurityPrivilege();
+
+
+ //
+ // Open our own token
+ //
+
+ Status = NtOpenProcessToken(
+ NtCurrentProcess(),
+ TOKEN_QUERY,
+ &Token
+ );
+ if (!NT_SUCCESS(Status)) {
+ printf(" \n\n\n");
+ printf("Tsamobj: Can't open process token to query owner.\n");
+ printf(" Completion status of NtOpenProcessToken() is: 0x%lx\n", Status);
+ printf("\n");
+ return;
+ }
+
+
+ //
+ // Query the user id
+ //
+
+ User = RtlAllocateHeap( RtlProcessHeap(), 0, 1000 ); // should be plenty big
+ Status = NtQueryInformationToken( Token, TokenUser, User, 1000, &ReturnLength );
+ ASSERT(NT_SUCCESS(Status));
+
+ //
+ // See if the ID is one of the special IDs (e.g., local admin,
+ // domain account operator, or domain admin)
+ //
+
+ SeeIfSidIsSpecial( User->User.Sid );
+
+
+
+ //
+ // Query the group ids
+ //
+
+ Groups = RtlAllocateHeap( RtlProcessHeap(), 0, 1000 ); // should be plenty big
+ Status = NtQueryInformationToken( Token, TokenGroups, Groups, 1000, &ReturnLength );
+ ASSERT(NT_SUCCESS(Status));
+
+ //
+ // See if any of these IDs are special IDs
+ //
+
+ for (i=0; i<Groups->GroupCount; i++) {
+ SeeIfSidIsSpecial( Groups->Groups[i].Sid );
+ }
+
+
+
+
+
+ //
+ // Clean up some stuff before returning
+ //
+
+ RtlFreeHeap( RtlProcessHeap(), 0, User );
+ RtlFreeHeap( RtlProcessHeap(), 0, Groups );
+ Status = NtClose( Token );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ printf("\n\n\n\nPerforming:\n\n");
+
+ printf(" Administrator Alias Test. . . . . ");
+ if (AdminsAliasTest) {
+ printf("Yes\n\n");
+ } else {
+ printf("No\n\n");
+ }
+
+ printf(" Account Operator Alias Test . . ");
+ if (AccountOpAliasTest) {
+ printf("Yes\n\n");
+ } else {
+ printf("No\n\n");
+ }
+
+ printf(" Security Operator Test . . . . . ");
+ if (SecurityOperatorTest) {
+ printf("Yes\n\n");
+ } else {
+ printf("No\n\n");
+ }
+
+ printf("\n\n\n");
+
+
+
+ return;
+
+}
+
+
+VOID
+SeeIfSidIsSpecial(
+ IN PSID Sid
+ )
+
+/*++
+
+Routine Description:
+
+ This function determines whether the passed SID is one of the special
+ SIDs, such as ADMINISTRATORS alias, or DomainAccountOperator, and
+ sets test flags accordingly.
+
+
+Arguments:
+
+ Sid - Pointer to the SID to check.
+
+Return Value:
+
+ None.
+
+
+--*/
+{
+
+
+
+
+ if (RtlEqualSid( Sid, AdminsAliasSid )){
+ AdminsAliasTest = TRUE;
+ }
+
+ if (RtlEqualSid( Sid, AccountAliasSid )){
+ AccountOpAliasTest = TRUE;
+ }
+
+ return;
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Server Object Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+ServerTestSuite(
+ PHANDLE ServerHandle,
+ PHANDLE DomainHandle,
+ PHANDLE BuiltinDomainHandle,
+ PSID *DomainSid
+ )
+
+{
+ NTSTATUS NtStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ BOOLEAN TestStatus = TRUE;
+ ULONG CountReturned;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ PSAM_RID_ENUMERATION EnumerationBuffer;
+ PSID BuiltinDomainSid;
+ ACCESS_MASK ServerAccessMask, DomainAccessMask;
+
+
+
+
+
+ printf("\n");
+ printf("\n");
+ printf(" Server Object Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Connect To Server //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Connect / Disconnect. . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Connect . . . . . . . . . . . . . . . . . . . . . . ");
+
+
+ ServerAccessMask = SAM_SERVER_READ | SAM_SERVER_EXECUTE;
+ if (AdminsAliasTest) {
+ ServerAccessMask |= SAM_SERVER_ALL_ACCESS;
+ }
+ if (SecurityOperatorTest) {
+ ServerAccessMask |= ACCESS_SYSTEM_SECURITY;
+ }
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
+
+
+ NtStatus = SamConnect(
+ NULL, // ServerName (Local machine)
+ ServerHandle,
+ ServerAccessMask,
+ &ObjectAttributes
+ );
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf(" Disconnect . . . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamCloseHandle( (*ServerHandle) );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+
+
+ printf(" Re-Connect . . . . . . . . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamConnect(
+ NULL, // ServerName (Local machine)
+ ServerHandle,
+ ServerAccessMask,
+ &ObjectAttributes
+ );
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Lookup/Enumerate Domains Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ printf("\n");
+ printf(" Domain Lookup/Enumerate/Open . . . . . . . . . . . . Suite\n");
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf(" Lookup Account Domain . . . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamLookupDomainInSamServer(
+ (*ServerHandle),
+ &AccountDomainName,
+ DomainSid
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ if ( TRUE != RtlEqualSid((*DomainSid), AccountDomainSid)) {
+ printf("Failed\n");
+ printf(" The SID retrieved from the policy database did not\n");
+ printf(" match the SID retrieved from SAM for the account\n");
+ printf(" domain.\n");
+ printf(" Sid from Policy Database is: ");
+ printfSid( AccountDomainSid ); printf("\n");
+ printf(" Sid from SAM is: ");
+ printfSid( (*DomainSid) ); printf("\n");
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ }
+
+
+
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf(" Enumerate Domain . . . . . . . . . . . . . . . . . ");
+
+
+ EnumerationContext = 0;
+ EnumerationBuffer = NULL;
+ NtStatus = SamEnumerateDomainsInSamServer(
+ (*ServerHandle),
+ &EnumerationContext,
+ (PVOID *)&EnumerationBuffer,
+ 1024, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+
+ if (CountReturned == 0) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" CountReturned is: 0x%lx\n", CountReturned);
+ printf(" EnumerationContext is: 0x%lx\n", EnumerationContext);
+ printf(" EnumerationBuffer Address is: 0x%lx\n", (ULONG)EnumerationBuffer);
+ TestStatus = FALSE;
+
+ } else {
+ printf("Succeeded\n");
+ }
+
+ SamFreeMemory( EnumerationBuffer );
+ }
+
+ }
+
+
+
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf(" Open Account Domain . . . . . . . . . . . . . . . . ");
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ DomainAccessMask = DOMAIN_READ | DOMAIN_EXECUTE;
+ if (AccountOpAliasTest) {
+ DomainAccessMask |= DOMAIN_READ | DOMAIN_WRITE | DOMAIN_EXECUTE;
+ }
+ if (AdminsAliasTest) {
+ DomainAccessMask |= DOMAIN_ALL_ACCESS;
+ }
+ if (SecurityOperatorTest) {
+ DomainAccessMask |= ACCESS_SYSTEM_SECURITY;
+ }
+ NtStatus = SamOpenDomain(
+ (*ServerHandle),
+ DomainAccessMask,
+ *DomainSid,
+ DomainHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf(" Open Builtin Domain . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupDomainInSamServer(
+ (*ServerHandle),
+ &BuiltinDomainName,
+ &BuiltinDomainSid
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ DomainAccessMask = DOMAIN_READ | DOMAIN_EXECUTE;
+ if (AccountOpAliasTest) {
+ DomainAccessMask |= DOMAIN_READ | DOMAIN_WRITE | DOMAIN_EXECUTE;
+ }
+ if (AdminsAliasTest) {
+ DomainAccessMask |= (DOMAIN_EXECUTE | DOMAIN_READ |
+ DOMAIN_READ_OTHER_PARAMETERS |
+ DOMAIN_ADMINISTER_SERVER |
+ DOMAIN_CREATE_ALIAS);
+ }
+// if (SecurityOperatorTest) {
+// DomainAccessMask |= ACCESS_SYSTEM_SECURITY;
+// }
+ NtStatus = SamOpenDomain(
+ (*ServerHandle),
+ DomainAccessMask,
+ BuiltinDomainSid,
+ BuiltinDomainHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ }
+
+ return(TestStatus);
+
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Security Manipulation Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+SecurityTestSuite(
+ HANDLE ServerHandle,
+ HANDLE DomainHandle,
+ ULONG Pass
+ )
+{
+
+ BOOLEAN TestStatus = TRUE;
+ NTSTATUS NtStatus;
+
+ PSECURITY_DESCRIPTOR OriginalServerSD,
+ OriginalDomainSD,
+ OriginalUserSD,
+ OriginalGroupSD,
+ SD1;
+
+ SECURITY_INFORMATION SI1;
+ PVOID TmpPointer1;
+
+ SECURITY_DESCRIPTOR SD1_Body;
+
+ HANDLE GroupHandle,
+ UserHandle;
+
+
+
+
+ printf("\n");
+ printf("\n");
+ printf("\n");
+
+ if (Pass == 1) {
+
+ printf(" Security Manipulation (Pass #1) Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Query Security . . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ //
+ // Get Server's original SD
+ //
+
+
+ SI1 = 0;
+ if (AdminsAliasTest) {
+ SI1 |= OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION;
+ }
+ if (SecurityOperatorTest) {
+ SI1 |= SACL_SECURITY_INFORMATION;
+ }
+ if (SI1 != 0) {
+ printf(" Query Server Security Descriptor . . . . . . . . . . ");
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ ServerHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+
+ //
+ // Normally we would do a "SamFreeMemory( SD1 )" here.
+ // However, we want to save this SD for future reference
+ // and use.
+ //
+
+ OriginalServerSD = SD1;
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+
+ //
+ // Get domain's original SD
+ //
+
+
+ SI1 = 0;
+ if (AdminsAliasTest) {
+ SI1 |= OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION;
+ }
+ if (SecurityOperatorTest) {
+ SI1 |= SACL_SECURITY_INFORMATION;
+ }
+ if (SI1 != 0) {
+ printf(" Query Domain Security Descriptor . . . . . . . . . . ");
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+
+ //
+ // Normally we would do a "SamFreeMemory( SD1 )" here.
+ // However, we want to save this SD for future reference
+ // and use.
+ //
+
+ OriginalDomainSD = SD1;
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+
+
+ //
+ // Make sure the wrapper doesn't choke on a non-null pointer being passed
+ // (assuming we have allocated memory).
+ //
+
+
+ SI1 = 0;
+ if (AdminsAliasTest) {
+ SI1 |= OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION;
+ }
+ if (SecurityOperatorTest) {
+ SI1 |= SACL_SECURITY_INFORMATION;
+ }
+ if (SI1 != 0) {
+ printf(" Query Passing Non-null return buffer . . . . . . . . ");
+ SD1 = RtlAllocateHeap( RtlProcessHeap(), 0, 1000 ); ASSERT(SD1 != NULL);
+ TmpPointer1 = SD1;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (SD1 != TmpPointer1) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Passed buffer address used on return.\n");
+ printf(" RPC should have allocated another buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ RtlFreeHeap( RtlProcessHeap(), 0, TmpPointer1 );
+
+ }
+
+
+
+
+
+
+ //
+ // Make sure we can query nothing
+ //
+
+ printf(" Query Nothing . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = 0;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // Query owner
+ //
+
+
+ if (AdminsAliasTest) {
+ printf(" Query Owner (Server Object) . . . . . . . . . . . . . ");
+ SI1 = OWNER_SECURITY_INFORMATION;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ ServerHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+ if (AdminsAliasTest) {
+ printf(" Query Owner (Domain Object) . . . . . . . . . . . . . ");
+ SI1 = OWNER_SECURITY_INFORMATION;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+
+ if (AdminsAliasTest) {
+
+ //
+ // Query Group
+ //
+
+ printf(" Query Group . . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // Query Dacl
+ //
+
+ printf(" Query DACL . . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // Query Sacl
+ //
+
+ printf(" Query SACL . . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = SACL_SECURITY_INFORMATION;
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ DomainHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+ if (TestStatus) {
+ SamFreeMemory( SD1 );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ } // end_if (AdminsAliasTest)
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set Security . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ //
+ // Make sure we can set nothing
+ //
+
+ printf(" Set Nothing . . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = 0;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1, // <------ This is invalid
+ SD1
+ );
+ if (NtStatus == STATUS_INVALID_PARAMETER) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set something not passed
+ //
+
+ printf(" Set something not passed. . . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NtStatus == STATUS_BAD_DESCRIPTOR_FORMAT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set a non-existant DACL
+ //
+
+ if (AdminsAliasTest) {
+ printf(" Set non-existant DACL (Server object) . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Control = SE_DACL_PRESENT;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ ServerHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+ if (AdminsAliasTest) {
+ printf(" Set non-existant DACL (Domain Object) . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Control = SE_DACL_PRESENT;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+
+ //
+ // set original DACL (From original SD)
+ //
+
+ if (AdminsAliasTest) {
+
+ printf(" Set original DACL (Server Object) . . . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = OriginalServerSD;
+ NtStatus = SamSetSecurityObject(
+ ServerHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+ if (AdminsAliasTest) {
+
+ printf(" Set original DACL (Domain Object) . . . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = OriginalDomainSD;
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+
+ if (AdminsAliasTest) {
+
+ //
+ // set a non-existant SACL
+ //
+
+ printf(" Set non-existant SACL . . . . . . . . . . . . . . . . ");
+
+ SI1 = SACL_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Control = SE_SACL_PRESENT;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set original SACL (From original SD)
+ //
+
+ printf(" Set original SACL . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = SACL_SECURITY_INFORMATION;
+ SD1 = OriginalDomainSD;
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // set a owner to null
+ //
+
+ printf(" Set null Owner . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = OWNER_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Owner = NULL;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NtStatus = STATUS_BAD_DESCRIPTOR_FORMAT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // set owner to invalid value
+ //
+
+ printf(" Set owner to invalid value . . . . . . . . . . . . . ");
+
+ SI1 = OWNER_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Owner = WorldSid;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NtStatus = STATUS_INVALID_OWNER) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ //
+ // set a owner to valid value
+ //
+
+ printf(" Set owner to valid value . . . . . . . . . . . . . . ");
+
+ printf("Untested\n");
+
+
+
+
+
+ //
+ // set group to null
+ //
+
+ printf(" Set null Group . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Group = NULL;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NtStatus = STATUS_BAD_DESCRIPTOR_FORMAT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set Group to valid value
+ //
+
+ printf(" Set Group to valid value . . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Group = WorldSid;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ //
+ // set Group back to original value
+ //
+
+ printf(" Set Group to original value . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = OriginalDomainSD;
+ NtStatus = SamSetSecurityObject(
+ DomainHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ }
+
+
+
+
+ } // end Pass1
+
+
+ if (Pass == 2) {
+
+ ACCESS_MASK AccessMask;
+ PSID_NAME_USE LookedUpUses;
+ PULONG LookedUpRids;
+ UNICODE_STRING AccountNames[10];
+ STRING AccountNameAnsi;
+
+
+ //
+ // This pass depends upon user and group accounts established in pass #1
+ //
+
+
+
+
+
+ if (AdminsAliasTest) {
+
+
+ printf(" Security Manipulation (Pass #2) Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Query Security (User Object). . . . . . . . . . . . . Suite\n");
+
+
+ AccessMask = READ_CONTROL;
+ if (SecurityOperatorTest) {
+ AccessMask |= ACCESS_SYSTEM_SECURITY;
+ }
+
+ //
+ // Open the user created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ AccessMask,
+ LookedUpRids[0],
+ &UserHandle);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed to open user account created in pass #1\n");
+ }
+ TST_SUCCESS_ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+ //
+ // Get user's original SD
+ //
+
+ SI1 |= OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION;
+ if (SecurityOperatorTest) {
+ SI1 |= SACL_SECURITY_INFORMATION;
+ }
+
+ printf(" Query User Security Descriptor . . . . . . . . . . . ");
+ SD1 = NULL;
+ NtStatus = SamQuerySecurityObject(
+ UserHandle,
+ SI1,
+ &SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ TestStatus = CheckReturnedSD( SI1, SD1, TRUE );
+
+ //
+ // Normally we would do a "SamFreeMemory( SD1 )" here.
+ // However, we want to save this SD for future reference
+ // and use.
+ //
+
+ OriginalUserSD = SD1;
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+ NtStatus = SamCloseHandle( UserHandle );
+ TST_SUCCESS_ASSERT( UserHandle );
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set Security (User Object) . . . . . . . . . . . . . Suite\n");
+
+ AccessMask = WRITE_DAC | WRITE_OWNER;
+ if (SecurityOperatorTest) {
+ AccessMask |= ACCESS_SYSTEM_SECURITY;
+ }
+
+ //
+ // Open the user created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ AccessMask,
+ LookedUpRids[0],
+ &UserHandle);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed to open user account created in pass #1\n");
+ }
+ TST_SUCCESS_ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // Make sure we can set nothing
+ //
+
+ printf(" Set Nothing . . . . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = 0;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ UserHandle,
+ SI1, // <------ This is invalid
+ SD1
+ );
+ if (NtStatus == STATUS_INVALID_PARAMETER) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set something not passed
+ //
+
+ printf(" Set something not passed. . . . . . . . . . . . . . . ");
+
+ SI1 = GROUP_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ UserHandle,
+ SI1,
+ SD1
+ );
+ if (NtStatus == STATUS_BAD_DESCRIPTOR_FORMAT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+
+ printf(" Set non-existant DACL . . . . . . . . . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = &SD1_Body;
+ NtStatus = RtlCreateSecurityDescriptor( SD1, SECURITY_DESCRIPTOR_REVISION1 );
+ SD1_Body.Control = SE_DACL_PRESENT;
+ ASSERT( NT_SUCCESS(NtStatus) );
+ NtStatus = SamSetSecurityObject(
+ UserHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+ //
+ // set original DACL (From original SD)
+ //
+
+
+ printf(" Set original DACL . . . . . . . . . . . . . . . . . . ");
+
+ SI1 = DACL_SECURITY_INFORMATION;
+ SD1 = OriginalUserSD;
+ NtStatus = SamSetSecurityObject(
+ UserHandle,
+ SI1,
+ SD1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+ NtStatus = SamCloseHandle( UserHandle );
+ TST_SUCCESS_ASSERT( UserHandle );
+
+
+
+ }
+
+ DBG_UNREFERENCED_LOCAL_VARIABLE( GroupHandle );
+ DBG_UNREFERENCED_LOCAL_VARIABLE( OriginalGroupSD );
+ }
+
+
+
+
+
+ return TestStatus;
+}
+
+
+BOOLEAN
+CheckReturnedSD(
+ IN SECURITY_INFORMATION SI,
+ IN PSECURITY_DESCRIPTOR SD,
+ IN BOOLEAN PrintTestSuccess
+ )
+
+
+{
+ NTSTATUS NtStatus;
+
+ BOOLEAN Failed = FALSE,
+ IgnoreBoolean,
+ AclPresent,
+ TestStatus = TRUE;
+
+ PSID SID;
+ PACL ACL;
+
+
+
+ //
+ // Check a returned security descriptor agains the information requested.
+ //
+
+ if (SD == NULL) {
+ TestStatus = FALSE;
+ if (PrintTestSuccess) {
+ printf("Failed\n");
+ Failed = TRUE;
+ printf(" The SecurityDescriptor return address was not properly\n");
+ printf(" set.\n");
+ }
+ }
+
+
+ if (TestStatus) {
+
+ //
+ // Check owner
+ //
+
+ NtStatus = RtlGetOwnerSecurityDescriptor ( SD, &SID, &IgnoreBoolean);
+ ASSERT(NT_SUCCESS(NtStatus));
+ if (SI & OWNER_SECURITY_INFORMATION) {
+ if (SID == NULL) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" An owner was requested but the owner field of the\n");
+ printf(" security descriptor is not set.\n");
+ TestStatus = FALSE;
+
+ }
+ }
+ } else { // Owner not specified
+ if (SID != NULL) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" An owner was not requested but the owner field of the\n");
+ printf(" security descriptor is set.\n");
+ TestStatus = FALSE;
+ }
+ }
+ }
+
+
+
+
+ //
+ // Check group
+ //
+
+ NtStatus = RtlGetGroupSecurityDescriptor ( SD, &SID, &IgnoreBoolean);
+ ASSERT(NT_SUCCESS(NtStatus));
+ if (SI & GROUP_SECURITY_INFORMATION) {
+ if (SID == NULL) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" A group was requested but the group field of the\n");
+ printf(" security descriptor is not set.\n");
+ TestStatus = FALSE;
+
+ }
+ }
+ } else { // Group not specified
+ if (SID != NULL) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" A group was not requested but the group field of the\n");
+ printf(" security descriptor is set.\n");
+ TestStatus = FALSE;
+ }
+ }
+ }
+
+
+
+
+ //
+ // Check sacl
+ //
+
+ NtStatus = RtlGetSaclSecurityDescriptor ( SD, &AclPresent, &ACL, &IgnoreBoolean);
+ ASSERT(NT_SUCCESS(NtStatus));
+ if (SI & SACL_SECURITY_INFORMATION) {
+ if (!AclPresent) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" An SACL was requested but the SaclPresent flag\n");
+ printf(" of the security descriptor is not set.\n");
+ TestStatus = FALSE;
+
+ }
+ }
+ } else { // sacl not specified
+ if (AclPresent) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" An SACL was not requested but the SaclPresent flag\n");
+ printf(" of the security descriptor is set.\n");
+ TestStatus = FALSE;
+ }
+ }
+ }
+
+
+
+
+
+ //
+ // Check Dacl
+ //
+
+ NtStatus = RtlGetDaclSecurityDescriptor ( SD, &AclPresent, &ACL, &IgnoreBoolean);
+ ASSERT(NT_SUCCESS(NtStatus));
+ if (SI & DACL_SECURITY_INFORMATION) {
+ if (!AclPresent) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" A DACL was requested but the DaclPresent flag\n");
+ printf(" of the security descriptor is not set.\n");
+ TestStatus = FALSE;
+
+ }
+ }
+ } else { // Dacl not specified
+ if (AclPresent) {
+ if (PrintTestSuccess) {
+ if (!Failed) {
+ printf("Failed\n");
+ printf(" Security descriptor address is 0x%lx\n", SD );
+ Failed = TRUE;
+ }
+ printf(" A DACL was not requested but the DaclPresent flag\n");
+ printf(" of the security descriptor is set.\n");
+ TestStatus = FALSE;
+ }
+ }
+ }
+
+
+
+
+
+ }
+
+
+
+
+ if (PrintTestSuccess) {
+ if (TestStatus) {
+ printf("Succeeded\n");
+ }
+ }
+
+
+
+ return(TestStatus);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Domain Object Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+DomainTestSuite(
+ HANDLE DomainHandle
+ )
+{
+
+ BOOLEAN TestStatus = TRUE;
+ NTSTATUS NtStatus, IgnoreStatus;
+ PVOID Buffer, Buffer1, Buffer2;
+ CHAR UnusedBuffer[20];
+ UNICODE_STRING AccountName;
+ STRING AccountNameAnsi;
+ HANDLE GroupHandle = NULL;
+ HANDLE AliasHandle = NULL;
+ HANDLE UserHandle = NULL;
+ HANDLE ValidUserHandle = NULL;
+ ULONG GroupRid, AliasRid, UserRid, SavedGroupRid, SavedAliasRid, AccountCount, i;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ ULONG CountReturned;
+ USHORT NameLength;
+ PUNICODE_STRING LookedUpNames;
+ PSID_NAME_USE LookedUpUses;
+ PULONG LookedUpRids;
+
+
+ printf("\n");
+ printf("\n");
+ printf("\n");
+ printf(" Domain Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Query Information . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ //
+ // Make sure the wrapper doesn't choke on a non-null pointer being passed
+ // (assuming we have allocated memory).
+ //
+
+ printf(" Query Buffer Allocation Test . . . . . . . . . . . . ");
+
+ Buffer = &UnusedBuffer[0];
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainStateInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != &UnusedBuffer[0]) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Passed buffer address used on return.\n");
+ printf(" RPC should have allocated another buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Query all the fixed length info levels
+ // Query - Password, Logoff, ServerRole, DomainState, ModifiedCount, LockoutInfo
+ //
+
+ printf(" Query DomainState . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainStateInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+ printf(" Query ServerRole . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainServerRoleInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+ printf(" Query Password Information . . . . . . . . . . . . . ");
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainPasswordInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+ printf(" Query Logoff Information . . . . . . . . . . . . . . ");
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLogoffInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+ printf(" Query Modified . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainModifiedInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+ printf(" Query Lockout . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLockoutInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ printf("Succeeded\n");
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+
+
+
+ //
+ // Query the name of the domain ...
+ //
+
+ printf(" Query Domain Name . . . . . . . . . . . . . . . . . . ");
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( (((DOMAIN_NAME_INFORMATION *)Buffer)->DomainName.MaximumLength > 0) &&
+ (((DOMAIN_NAME_INFORMATION *)Buffer)->DomainName.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" String body returned and allocated,\n");
+ printf(" but character buffer pointer is NULL.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ //
+ // Query whatever is in the OEM Information field ...
+ //
+
+ printf(" Query OEM Information . . . . . . . . . . . . . . . . ");
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainOemInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( (((DOMAIN_OEM_INFORMATION *)Buffer)->OemInformation.MaximumLength >= 0) &&
+ (((DOMAIN_OEM_INFORMATION *)Buffer)->OemInformation.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" String body returned and allocated,\n");
+ printf(" but character buffer pointer is NULL.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ //
+ // Query whatever is in the Replication Information field ...
+ //
+
+ printf(" Query Replication Information . . . . . . . . . . . . ");
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainReplicationInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( (((DOMAIN_REPLICATION_INFORMATION *)Buffer)->ReplicaSourceNodeName.MaximumLength >= 0) &&
+ (((DOMAIN_REPLICATION_INFORMATION *)Buffer)->ReplicaSourceNodeName.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" String body returned and allocated,\n");
+ printf(" but character buffer pointer is NULL.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ //
+ // Query domain general Information...
+ //
+
+ printf(" Query General Information . . . . . . . . . . . . . . ");
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainGeneralInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ printf("Succeeded\n");
+ printf(" Number of Users is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION *)Buffer)->UserCount );
+ printf(" Number of groups is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION *)Buffer)->GroupCount);
+ printf(" Number of aliases is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION *)Buffer)->AliasCount);
+
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ //
+ // Query domain general Information...
+ //
+
+ printf(" Query General Information 2 . . . . . . . . . . . . . ");
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainGeneralInformation2,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ printf("Succeeded\n");
+ printf(" Number of Users is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION2 *)Buffer)->I1.UserCount );
+ printf(" Number of groups is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION2 *)Buffer)->I1.GroupCount);
+ printf(" Number of aliases is: 0x%lx\n",
+ ((DOMAIN_GENERAL_INFORMATION2 *)Buffer)->I1.AliasCount);
+
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf(" Set Information . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ //
+ // Set all the fixed length info levels
+ // - Password, Logoff, ServerRole, DomainState, ModifiedCount
+ //
+
+/*
+ * CANT TEST SERVER STATE SETTING WITHOUT BREAKING THE REST OF THE TEST.
+ * THE REASON IS, ONCE THE STATE IS CHANGED, NOTHING ELSE CAN BE DONE.
+ *
+ * printf(" Set DomainState . . . . . . . . . . . . . . . . . . . ");
+ *
+ * //
+ * // Get the current value...
+ * //
+ *
+ * NtStatus = SamQueryInformationDomain(
+ * DomainHandle,
+ * DomainStateInformation,
+ * &Buffer1
+ * );
+ * ASSERT( NT_SUCCESS(NtStatus) );
+ *
+ * //
+ * // Change the field to a new value and write it out.
+ * //
+ *
+ * if ( ((DOMAIN_STATE_INFORMATION *)Buffer1)->DomainServerState ==
+ * DomainServerEnabled ) {
+ * ((DOMAIN_STATE_INFORMATION *)Buffer1)->DomainServerState =
+ * DomainServerDisabled;
+ * } else {
+ * ((DOMAIN_STATE_INFORMATION *)Buffer1)->DomainServerState =
+ * DomainServerEnabled;
+ * }
+ *
+ * NtStatus = SamSetInformationDomain(
+ * DomainHandle,
+ * DomainStateInformation,
+ * Buffer1
+ * );
+ * if ( NT_SUCCESS(NtStatus) ) {
+ *
+ * //
+ * // Now check that the change was really made...
+ * //
+ *
+ * NtStatus = SamQueryInformationDomain(
+ * DomainHandle,
+ * DomainStateInformation,
+ * &Buffer2
+ * );
+ * ASSERT(NT_SUCCESS( NtStatus ) );
+ * if (((DOMAIN_STATE_INFORMATION *)Buffer1)->DomainServerState ==
+ * ((DOMAIN_STATE_INFORMATION *)Buffer2)->DomainServerState ) {
+ *
+ * printf("Succeeded\n");
+ *
+ * } else {
+ *
+ * printf("Failed\n");
+ * printf(" Value queried doesn't match value written\n");
+ * printf(" Value Written is 0x%lx\n",
+ * (ULONG)((DOMAIN_STATE_INFORMATION *)Buffer1)->DomainServerState);
+ * printf(" Value Retrieved is 0x%lx\n",
+ * (ULONG)((DOMAIN_STATE_INFORMATION *)Buffer2)->DomainServerState);
+ *
+ * TestStatus = FALSE;
+ *
+ * }
+ *
+ * SamFreeMemory( Buffer1 );
+ * SamFreeMemory( Buffer2 );
+ *
+ * } else {
+ * printf("Failed\n");
+ * printf(" Completion status is 0x%lx\n", NtStatus);
+ * TestStatus = FALSE;
+ * SamFreeMemory( Buffer1 );
+ *
+ * }
+ */
+
+
+
+/*
+ * CANT TEST SERVER ROLE SETTING WITHOUT BREAKING THE REST OF THE TEST.
+ * THE REASON IS, ONCE THE ROLE IS SET TO BACKUP, NOTHING ELSE CAN BE
+ * SET.
+ *
+ * printf(" Set ServerRole . . . . . . . . . . . . . . . . . . . ");
+ *
+ * //
+ * // Get the current value...
+ * //
+ *
+ * NtStatus = SamQueryInformationDomain(
+ * DomainHandle,
+ * DomainServerRoleInformation,
+ * &Buffer1
+ * );
+ * ASSERT( NT_SUCCESS(NtStatus) );
+ *
+ * //
+ * // Change the field to a new value and write it out.
+ * //
+ *
+ * if ( ((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer1)->DomainServerRole ==
+ * DomainServerRolePrimary ) {
+ * ((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer1)->DomainServerRole =
+ * DomainServerRoleBackup;
+ * } else {
+ * ((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer1)->DomainServerRole =
+ * DomainServerRolePrimary;
+ * }
+ *
+ * NtStatus = SamSetInformationDomain(
+ * DomainHandle,
+ * DomainServerRoleInformation,
+ * Buffer1
+ * );
+ * if ( NT_SUCCESS(NtStatus) ) {
+ *
+ * //
+ * // Now check that the change was really made...
+ * //
+ *
+ * NtStatus = SamQueryInformationDomain(
+ * DomainHandle,
+ * DomainServerRoleInformation,
+ * &Buffer2
+ * );
+ * ASSERT(NT_SUCCESS( NtStatus ) );
+ * if (((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer1)->DomainServerRole ==
+ * ((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer2)->DomainServerRole ) {
+ *
+ * printf("Succeeded\n");
+ *
+ * } else {
+ *
+ * printf("Failed\n");
+ * printf(" Value queried doesn't match value written\n");
+ * printf(" Value Written is 0x%lx\n",
+ * (ULONG)((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer1)->DomainServerRole);
+ * printf(" Value Retrieved is 0x%lx\n",
+ * (ULONG)((DOMAIN_SERVER_ROLE_INFORMATION *)Buffer2)->DomainServerRole);
+ *
+ * TestStatus = FALSE;
+ *
+ * }
+ *
+ * SamFreeMemory( Buffer1 );
+ * SamFreeMemory( Buffer2 );
+ *
+ * } else {
+ * printf("Failed\n");
+ * printf(" Completion status is 0x%lx\n", NtStatus);
+ * TestStatus = FALSE;
+ * SamFreeMemory( Buffer1 );
+ *
+ * }
+ */
+
+
+
+ printf(" Set Password Information . . . . . . . . . . . . . . ");
+
+
+ //
+ // Get the current value...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainPasswordInformation,
+ &Buffer1
+ );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ //
+ // Change a field to a new value and write it out.
+ //
+
+ if ( ((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->MinPasswordLength == 0 ) {
+ ((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->MinPasswordLength = 6;
+ } else {
+ ((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->MinPasswordLength = 0;
+ }
+
+ //
+ // Set PasswordProperties to COMPLEX so that tests run after this one
+ // are a little more interesting.
+ //
+
+ ((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->PasswordProperties |= DOMAIN_PASSWORD_COMPLEX;
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainPasswordInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainPasswordInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->MinPasswordLength ==
+ ((DOMAIN_PASSWORD_INFORMATION *)Buffer2)->MinPasswordLength ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is 0x%lx\n",
+ (ULONG)((DOMAIN_PASSWORD_INFORMATION *)Buffer1)->MinPasswordLength);
+ printf(" Value Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_PASSWORD_INFORMATION *)Buffer2)->MinPasswordLength);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+ printf(" Set Logoff Information . . . . . . . . . . . . . . . ");
+
+ //
+ // Get the current value...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLogoffInformation,
+ &Buffer1
+ );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ if ( ((DOMAIN_LOGOFF_INFORMATION *)Buffer1)->ForceLogoff.LowPart == 0 ) {
+ ((DOMAIN_LOGOFF_INFORMATION *)Buffer1)->ForceLogoff.LowPart = 1000;
+ } else {
+ ((DOMAIN_LOGOFF_INFORMATION *)Buffer1)->ForceLogoff.LowPart = 0;
+ }
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainLogoffInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLogoffInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (((DOMAIN_LOGOFF_INFORMATION *)Buffer1)->ForceLogoff.LowPart ==
+ ((DOMAIN_LOGOFF_INFORMATION *)Buffer2)->ForceLogoff.LowPart ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is 0x%lx\n",
+ (ULONG)((DOMAIN_LOGOFF_INFORMATION *)Buffer1)->ForceLogoff.LowPart);
+ printf(" Value Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_LOGOFF_INFORMATION *)Buffer2)->ForceLogoff.LowPart);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+ printf(" Set Modified . . . . . . . . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainModifiedInformation,
+ &LargeInteger1
+ );
+
+ if (NtStatus != STATUS_INVALID_INFO_CLASS) {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+
+ printf(" Set Lockout Information . . . . . . . . . . . . . . . ");
+
+ //
+ // Get the current value...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLockoutInformation,
+ &Buffer1
+ );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ if ( ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutDuration.LowPart == 0 ) {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutDuration.LowPart = 9000000;
+ } else {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutDuration.LowPart = 0;
+ }
+ if ( ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutObservationWindow.LowPart == 0 ) {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutObservationWindow.LowPart = 8000000;
+ } else {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutObservationWindow.LowPart = 0;
+ }
+ if ( ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutThreshold == 0 ) {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutThreshold = 2;
+ } else {
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutThreshold = 0;
+ }
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainLockoutInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLockoutInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if ( (((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutDuration.LowPart ==
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutDuration.LowPart ) &&
+ (((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutObservationWindow.LowPart ==
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutObservationWindow.LowPart ) &&
+ (((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutThreshold ==
+ ((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutThreshold ) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Duration Written is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutDuration.LowPart);
+ printf(" Duration Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutDuration.LowPart);
+ printf(" Window Written is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutObservationWindow.LowPart);
+ printf(" Window Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutObservationWindow.LowPart);
+ printf(" Duration Written is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer1)->LockoutThreshold);
+ printf(" Duration Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_LOCKOUT_INFORMATION *)Buffer2)->LockoutThreshold);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Domain Name . . . . . . . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainNameInformation,
+ &DummyName1
+ );
+
+ if (NtStatus != STATUS_INVALID_INFO_CLASS) {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+
+ printf(" Set OEM Information . . . . . . . . . . . . . . . . . ");
+
+ //
+ // Get the current value...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainOemInformation,
+ &Buffer1
+ );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((DOMAIN_OEM_INFORMATION *)Buffer1)->OemInformation.Length;
+ if ( NameLength == DummyName1.Length ) {
+ ((DOMAIN_OEM_INFORMATION *)Buffer1)->OemInformation = DummyName2;
+ } else {
+ ((DOMAIN_OEM_INFORMATION *)Buffer1)->OemInformation = DummyName1;
+ }
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainOemInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainOemInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (((DOMAIN_OEM_INFORMATION *)Buffer1)->OemInformation.Length ==
+ ((DOMAIN_OEM_INFORMATION *)Buffer2)->OemInformation.Length ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is 0x%lx\n",
+ (ULONG)((DOMAIN_OEM_INFORMATION *)Buffer1)->OemInformation.Length);
+ printf(" Value Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_OEM_INFORMATION *)Buffer2)->OemInformation.Length);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Replication Information . . . . . . . . . . . . . ");
+
+ //
+ // Get the current value...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainReplicationInformation,
+ &Buffer1
+ );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((DOMAIN_REPLICATION_INFORMATION *)Buffer1)->ReplicaSourceNodeName.Length;
+ if ( NameLength == DummyName1.Length ) {
+ ((DOMAIN_REPLICATION_INFORMATION *)Buffer1)->ReplicaSourceNodeName = DummyName2;
+ } else {
+ ((DOMAIN_REPLICATION_INFORMATION *)Buffer1)->ReplicaSourceNodeName = DummyName1;
+ }
+
+ NtStatus = SamSetInformationDomain(
+ DomainHandle,
+ DomainReplicationInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationDomain(
+ DomainHandle,
+ DomainReplicationInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (((DOMAIN_REPLICATION_INFORMATION *)Buffer1)->ReplicaSourceNodeName.Length ==
+ ((DOMAIN_REPLICATION_INFORMATION *)Buffer2)->ReplicaSourceNodeName.Length ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is 0x%lx\n",
+ (ULONG)((DOMAIN_REPLICATION_INFORMATION *)Buffer1)->ReplicaSourceNodeName.Length);
+ printf(" Value Retrieved is 0x%lx\n",
+ (ULONG)((DOMAIN_REPLICATION_INFORMATION *)Buffer2)->ReplicaSourceNodeName.Length);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Create User/Group/Alias Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf(" Create User/Group/Alias . . . . . . . . . . . . . . . . Suite\n");
+
+
+ printf(" Create Group . . . . . . . . . . . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (GroupHandle == NULL) || (GroupRid == 0) ) {
+
+ printf("Failed\n");
+ printf(" Invalid GroupHandle or GroupRid returned.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" GroupHandle value is: 0x%lx\n", (ULONG)GroupHandle);
+ printf(" GroupRid value is: 0x%lx\n", GroupRid);
+ TestStatus = FALSE;
+ } else {
+
+ printf("Succeeded\n");
+ SavedGroupRid = GroupRid;
+ NtStatus = SamCloseHandle( GroupHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf(" SamCloseHandle() completion status is: 0x%lx\n", NtStatus);
+ }
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Create Duplicate Group . . . . . . . . . . . . . . . ");
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+
+ GroupRid = 0;
+ GroupHandle = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_GROUP_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_GROUP_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+ printf(" Create Alias . . . . . . . . . . . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ AliasRid = 0;
+ AliasHandle = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ ALIAS_ALL_ACCESS,
+ &AliasHandle,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (AliasHandle == NULL) || (AliasRid == 0) ) {
+
+ printf("Failed\n");
+ printf(" Invalid AliasHandle or AliasRid returned.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" AliasHandle value is: 0x%lx\n", (ULONG)AliasHandle);
+ printf(" AliasRid value is: 0x%lx\n", AliasRid);
+ TestStatus = FALSE;
+ } else {
+
+ printf("Succeeded\n");
+ SavedAliasRid = AliasRid;
+ NtStatus = SamCloseHandle( AliasHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf(" SamCloseHandle() completion status is: 0x%lx\n", NtStatus);
+ }
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+
+ if (AliasRid == SavedGroupRid) {
+ printf(" Create Group/Alias Comparison. . . . . . . . . . . . . Failed\n");
+
+ printf(" Same RID assigned to new alias and group.\n");
+ TestStatus = FALSE;
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Create another Alias . . . . . . . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME2 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ AliasRid = 0;
+ AliasHandle = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ ALIAS_ALL_ACCESS,
+ &AliasHandle,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (AliasHandle == NULL) || (AliasRid == 0) ) {
+
+ printf("Failed\n");
+ printf(" Invalid AliasHandle or AliasRid returned.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" AliasHandle value is: 0x%lx\n", (ULONG)AliasHandle);
+ printf(" AliasRid value is: 0x%lx\n", AliasRid);
+ TestStatus = FALSE;
+ } else {
+
+ printf("Succeeded\n");
+ SavedAliasRid = AliasRid;
+ NtStatus = SamCloseHandle( AliasHandle );
+ if (!NT_SUCCESS(NtStatus)) {
+ printf(" SamCloseHandle() completion status is: 0x%lx\n", NtStatus);
+ }
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+
+ if (AliasRid == SavedGroupRid) {
+ printf(" Create Group/Alias Comparison. . . . . . . . . . . . . Failed\n");
+
+ printf(" Same RID assigned to new alias and group.\n");
+ TestStatus = FALSE;
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Create Duplicate Alias . . . . . . . . . . . . . . . ");
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ AliasRid = 0;
+ AliasHandle = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ ALIAS_ALL_ACCESS,
+ &AliasHandle,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_ALIAS_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_ALIAS_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+
+
+ printf(" Create User . . . . . . . . . . . . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ UserRid = 0;
+ UserHandle = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (UserHandle == NULL) || (UserRid == 0) ) {
+
+ printf("Failed\n");
+ printf(" Invalid UserHandle or UserRid returned.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" UserHandle value is: 0x%lx\n", (ULONG)UserHandle);
+ printf(" UserRid value is: 0x%lx\n", UserRid);
+ TestStatus = FALSE;
+ } else {
+
+ printf("Succeeded\n");
+ ValidUserHandle = UserHandle;
+
+
+ if (UserRid == SavedGroupRid) {
+ printf(" Create Group/User Comparison. . . . . . . . . . . . . Failed\n");
+
+ printf(" Same RID assigned to new user and group.\n");
+ TestStatus = FALSE;
+ }
+
+ if (UserRid == SavedAliasRid) {
+ printf(" Create Alias/User Comparison. . . . . . . . . . . . . Failed\n");
+
+ printf(" Same RID assigned to new user and alias.\n");
+ TestStatus = FALSE;
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+ printf(" Create Duplicate User . . . . . . . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ UserRid = 0;
+ UserHandle = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_USER_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_USER_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+
+ printf(" Create Group With Same Name As User . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ GroupRid = 0;
+ GroupHandle = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_USER_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_USER_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+
+ printf(" Create Group With Same Name As Alias. . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ GroupRid = 0;
+ GroupHandle = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_ALIAS_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_ALIAS_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+ printf(" Create Alias With Same Name As Group. . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ AliasRid = 0;
+ AliasHandle = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &AliasHandle,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_GROUP_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_GROUP_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+ printf(" Create User With Same Name As Group . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+
+ UserRid = 0;
+ UserHandle = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_GROUP_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_GROUP_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+ printf(" Create User With Same Name As Alias . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+
+ UserRid = 0;
+ UserHandle = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if (NtStatus != STATUS_ALIAS_EXISTS) {
+
+ printf("Failed\n");
+ printf(" Completion status should be STATUS_ALIAS_EXISTS\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Call server to test internal functions //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Test internal functions . . . . . . . . . . . . . . . Suite\n");
+ printf(" Test internal domain functions . . . . . . . . . . ");
+
+ NtStatus = SamTestPrivateFunctionsDomain( DomainHandle );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ printf("Succeeded.\n");
+
+ } else {
+
+ if ( NtStatus == STATUS_NOT_IMPLEMENTED ) {
+
+ printf("Not Implemented\n");
+
+ } else {
+
+ printf("Failed.\n");
+ printf(" Status = %lx\n", NtStatus );
+ TestStatus = FALSE;
+ }
+ }
+
+ printf(" Test internal user functions . . . . . . . . . . . ");
+
+ if (ValidUserHandle == NULL) {
+
+ printf("Test omitted - Valid User handle not available\n");
+ TestStatus = FALSE;
+
+ } else {
+
+ NtStatus = SamTestPrivateFunctionsUser( ValidUserHandle );
+ IgnoreStatus = SamCloseHandle( ValidUserHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ printf("Succeeded.\n");
+
+ } else {
+
+ if ( NtStatus == STATUS_NOT_IMPLEMENTED ) {
+
+ printf("Not Implemented\n");
+
+ } else {
+
+ printf("Failed.\n");
+ printf(" Status = %lx\n", NtStatus );
+ TestStatus = FALSE;
+ }
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Enumerate Users/Groups Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ printf(" Enumerate Users/Groups/Aliases. . . . . . . . . . . . Suite\n");
+
+ printf(" Enumerate Groups - large prefered length . . . . . . ");
+
+
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateGroupsInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+ AccountCount = CountReturned; // Save for future test
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if (NtStatus == STATUS_SUCCESS) {
+
+ if (CountReturned > 1) {
+ printf("Succeeded\n");
+ for (i=0; i<CountReturned; i++) {
+ printf(" Rid/Name(%ld): 0x%lx / %wZ\n",i,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name
+ );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected several entries to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+
+
+ printf(" Enumerate Groups - small prefered length . . . . . . ");
+
+
+ for ( i=0; i<AccountCount; i++) {
+ EnumerationContext = i;
+ NtStatus = SamEnumerateGroupsInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 0, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( ((i >= AccountCount -1) && (NtStatus == STATUS_SUCCESS)) ||
+ ((i <= AccountCount -1) && (NtStatus == STATUS_MORE_ENTRIES)) ) {
+
+ if (CountReturned != 1) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected one entry to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ if (i < AccountCount -1 ) {
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ } else {
+ printf(" Expected STATUS_SUCCESS to be returned.\n");
+ }
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+
+ }
+ }
+
+ if ( i == AccountCount) {
+ printf("Succeeded\n");
+ }
+
+
+
+
+ printf(" Enumerate Aliases - large prefered length . . . . . . ");
+
+
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateAliasesInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+ AccountCount = CountReturned; // Save for future test
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if (NtStatus == STATUS_SUCCESS) {
+
+ if (CountReturned > 1) {
+ printf("Succeeded\n");
+ for (i=0; i<CountReturned; i++) {
+ printf(" Rid/Name(%ld): 0x%lx / %wZ\n",i,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name
+ );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected several entries to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+
+
+ printf(" Enumerate Aliases - small prefered length . . . . . . ");
+
+
+ for ( i=0; i<AccountCount; i++) {
+ EnumerationContext = i;
+ NtStatus = SamEnumerateAliasesInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 0, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( ((i >= AccountCount -1) && (NtStatus == STATUS_SUCCESS)) ||
+ ((i <= AccountCount -1) && (NtStatus == STATUS_MORE_ENTRIES)) ) {
+
+ if (CountReturned != 1) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected one entry to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ if (i < AccountCount -1 ) {
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ } else {
+ printf(" Expected STATUS_SUCCESS to be returned.\n");
+ }
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+
+ }
+ }
+
+ if ( i == AccountCount) {
+ printf("Succeeded\n");
+ }
+
+
+
+
+
+ printf(" Enumerate Users - large prefered length . . . . . . ");
+
+
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateUsersInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ 0,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+ AccountCount = CountReturned; // Save for future test
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if (NtStatus == STATUS_SUCCESS) {
+
+ if (CountReturned > 1) {
+ printf("Succeeded\n");
+ for (i=0; i<CountReturned; i++) {
+ printf(" Rid/Name(%ld): 0x%lx / %wZ\n",i,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name
+ );
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected several entries to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+
+ }
+
+
+
+
+ printf(" Enumerate Users - small prefered length . . . . . . ");
+
+
+ for ( i=0; i<AccountCount; i++) {
+ EnumerationContext = i;
+ NtStatus = SamEnumerateUsersInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ 0,
+ &Buffer,
+ 0, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+ if ( ((i >= AccountCount -1) && (NtStatus == STATUS_SUCCESS)) ||
+ ((i <= AccountCount -1) && (NtStatus == STATUS_MORE_ENTRIES)) ) {
+
+ if (CountReturned != 1) {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected one entry to be returned.\n");
+ printf(" Received 0x%lx entries instead.\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ if (i < AccountCount -1 ) {
+ printf(" Expected STATUS_MORE_ENTRIES to be returned.\n");
+ } else {
+ printf(" Expected STATUS_SUCCESS to be returned.\n");
+ }
+ printf(" Received 0x%lx instead.\n", NtStatus);
+ printf(" Buffer = 0x%lx\n", (ULONG)Buffer);
+ printf(" CountReturned = 0x%lx\n", CountReturned);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ i = AccountCount + 100;
+
+ }
+ }
+
+ if ( i == AccountCount) {
+ printf("Succeeded\n");
+ }
+
+
+
+
+
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Lookup Names/IDs Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ // LATER add alias search to lookup name suite.....
+
+
+ printf("\n");
+ printf(" Lookup Names/IDs . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ printf(" Lookup Names (all existing) . . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ ALL_NAMES_COUNT,
+ &AllNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ ASSERT( LookedUpRids != NULL );
+ ASSERT( LookedUpUses != NULL );
+
+ if (
+ (LookedUpRids[0] == AllRids[0]) && (LookedUpUses[0] == AllUses[0])
+ &&
+ (LookedUpRids[1] == AllRids[1]) && (LookedUpUses[1] == AllUses[1])
+ &&
+ (LookedUpRids[2] == AllRids[2]) && (LookedUpUses[2] == AllUses[2])
+ ) {
+
+
+ printf("Succeeded\n");
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Rids or Uses dont match expected values.\n");
+ printf(" Expected Rids: 0x%lx, 0x%lx, 0x%lx\n",
+ AllRids[0], AllRids[1], AllRids[2]);
+ printf(" Received Rids: 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpRids[0], LookedUpRids[1], LookedUpRids[2]);
+ printf(" Expected Uses: 0x%lx, 0x%lx, 0x%lx\n",
+ AllUses[0], AllUses[1], AllUses[2]);
+ printf(" Received Uses: 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpUses[0], LookedUpUses[1], LookedUpUses[2]);
+ TestStatus = FALSE;
+ }
+
+
+ SamFreeMemory( LookedUpRids );
+ SamFreeMemory( LookedUpUses );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Lookup Names (Some existing) . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ SOME_NAMES_COUNT,
+ &SomeNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+
+
+ if (NtStatus == STATUS_SOME_NOT_MAPPED) {
+ ASSERT( LookedUpRids != NULL );
+ ASSERT( LookedUpUses != NULL );
+
+ if (
+ (LookedUpRids[0] == SomeRids[0]) && (LookedUpUses[0] == SomeUses[0])
+ &&
+ (LookedUpRids[1] == SomeRids[1]) && (LookedUpUses[1] == SomeUses[1])
+ &&
+ (LookedUpRids[2] == SomeRids[2]) && (LookedUpUses[2] == SomeUses[2])
+ &&
+ (LookedUpRids[3] == SomeRids[3]) && (LookedUpUses[3] == SomeUses[3])
+ &&
+ (LookedUpRids[4] == SomeRids[4]) && (LookedUpUses[4] == SomeUses[4])
+ &&
+ (LookedUpRids[5] == SomeRids[5]) && (LookedUpUses[5] == SomeUses[5])
+ &&
+ (LookedUpRids[6] == SomeRids[6]) && (LookedUpUses[6] == SomeUses[6])
+ ) {
+
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Rids or Uses dont match expected values.\n");
+ printf(" Expected Rids: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ SomeRids[0], SomeRids[1], SomeRids[2], SomeRids[3], SomeRids[4], SomeRids[5], SomeRids[6]);
+ printf(" Received Rids: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpRids[0], LookedUpRids[1], LookedUpRids[2], LookedUpRids[3], LookedUpRids[4], LookedUpRids[5], LookedUpRids[6]);
+ printf(" Expected Uses: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ SomeUses[0], SomeUses[1], SomeUses[2], SomeUses[3], SomeUses[4], SomeUses[5], SomeUses[6]);
+ printf(" Received Uses: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpUses[0], LookedUpUses[1], LookedUpUses[2], LookedUpUses[3], LookedUpUses[4], LookedUpUses[5], LookedUpUses[2]);
+ TestStatus = FALSE;
+ }
+
+
+ SamFreeMemory( LookedUpRids );
+ SamFreeMemory( LookedUpUses );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ printf(" Lookup Names (None existing) . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ NO_NAMES_COUNT,
+ &NoNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+
+
+ if (NtStatus == STATUS_NONE_MAPPED) {
+ ASSERT( LookedUpRids == NULL );
+ ASSERT( LookedUpUses == NULL );
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+ printf(" Lookup SIDs (all existing) . . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupIdsInDomain(
+ DomainHandle,
+ ALL_NAMES_COUNT,
+ &AllRids[0],
+ &LookedUpNames,
+ &LookedUpUses
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ ASSERT( LookedUpUses != NULL );
+ ASSERT( LookedUpNames != NULL );
+ ASSERT( LookedUpNames[0].Buffer != NULL );
+ ASSERT( LookedUpNames[1].Buffer != NULL );
+ ASSERT( LookedUpNames[2].Buffer != NULL );
+
+ if (
+ (LookedUpUses[0] == AllUses[0]) &&
+ (LookedUpUses[1] == AllUses[1]) &&
+ (LookedUpUses[2] == AllUses[2]) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[0], (PSTRING)&AllNames[0], TRUE ) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[1], (PSTRING)&AllNames[1], TRUE ) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[2], (PSTRING)&AllNames[2], TRUE )
+ ) {
+
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Names or Uses dont match expected values.\n");
+ printf(" Expected Name[0]: %wZ\n", &AllNames[0] );
+ printf(" Received Name[0]: %wZ\n", &LookedUpNames[0] );
+ printf(" Expected Name[1]: %wZ\n", &AllNames[1] );
+ printf(" Received Name[1]: %wZ\n", &LookedUpNames[1] );
+ printf(" Expected Name[2]: %wZ\n", &AllNames[2] );
+ printf(" Received Name[2]: %wZ\n", &LookedUpNames[2] );
+
+ printf(" Expected Uses: 0x%lx, 0x%lx, 0x%lx\n",
+ AllUses[0], AllUses[1], AllUses[2]);
+ printf(" Received Uses: 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpUses[0], LookedUpUses[1], LookedUpUses[2]);
+ TestStatus = FALSE;
+ }
+
+
+ SamFreeMemory( LookedUpUses );
+ SamFreeMemory( LookedUpNames );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Lookup SIDs (Some existing) . . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupIdsInDomain(
+ DomainHandle,
+ SOME_NAMES_COUNT,
+ &SomeRids[0],
+ &LookedUpNames,
+ &LookedUpUses
+ );
+
+
+ if (NtStatus == STATUS_SOME_NOT_MAPPED) {
+ ASSERT( LookedUpUses != NULL );
+ ASSERT( LookedUpNames != NULL );
+ ASSERT( LookedUpNames[0].Buffer != NULL );
+ ASSERT( LookedUpNames[1].Buffer != NULL );
+ ASSERT( LookedUpNames[2].Buffer == NULL ); // Unknown
+ ASSERT( LookedUpNames[3].Buffer == NULL ); // Unknown
+ ASSERT( LookedUpNames[4].Buffer == NULL ); // Unknown
+ ASSERT( LookedUpNames[5].Buffer != NULL );
+ ASSERT( LookedUpNames[6].Buffer == NULL ); // Unknown
+
+ if (
+ (LookedUpUses[0] == SomeUses[0]) &&
+ (LookedUpUses[1] == SomeUses[1]) &&
+ (LookedUpUses[2] == SomeUses[2]) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[0], (PSTRING)&SomeNames[0], TRUE ) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[1], (PSTRING)&SomeNames[1], TRUE ) &&
+ !RtlCompareString( (PSTRING)&LookedUpNames[5], (PSTRING)&SomeNames[5], TRUE )
+ ) {
+
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Names or Uses dont match expected values.\n");
+ printf(" Expected Name[0]: %wZ\n", &SomeNames[0] );
+ printf(" Received Name[0]: %wZ\n", &LookedUpNames[0] );
+ printf(" Expected Name[1]: %wZ\n", &SomeNames[1] );
+ printf(" Received Name[1]: %wZ\n", &LookedUpNames[1] );
+ printf(" Name[2]: (Unknown)\n");
+ printf(" Name[3]: (Unknown)\n");
+ printf(" Name[4]: (Unknown)\n");
+ printf(" Expected Name[5]: %wZ\n", &SomeNames[5] );
+ printf(" Received Name[5]: %wZ\n", &LookedUpNames[5] );
+ printf(" Name[6]: (Unknown)\n");
+
+ printf(" Expected Uses: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ SomeUses[0], SomeUses[1], SomeUses[2], SomeUses[3], SomeUses[4], SomeUses[5], SomeUses[6]);
+ printf(" Received Uses: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx\n",
+ LookedUpUses[0], LookedUpUses[1], LookedUpUses[2], LookedUpUses[3], LookedUpUses[4], LookedUpUses[5], LookedUpUses[2]);
+ TestStatus = FALSE;
+ }
+
+
+ SamFreeMemory( LookedUpUses );
+ SamFreeMemory( LookedUpNames );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Lookup SIDs (None existing) . . . . . . . . . . . . . ");
+
+ NtStatus = SamLookupIdsInDomain(
+ DomainHandle,
+ NO_NAMES_COUNT,
+ &NoRids[0],
+ &LookedUpNames,
+ &LookedUpUses
+ );
+
+
+ if (NtStatus == STATUS_NONE_MAPPED) {
+ ASSERT( LookedUpUses == NULL );
+ ASSERT( LookedUpNames == NULL );
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+ return TestStatus;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Group Object Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+GroupTestSuite(
+ HANDLE DomainHandle,
+ ULONG Pass
+ )
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ HANDLE GroupHandle1, GroupHandle2, UserHandle1;
+ ULONG CountReturned, NameLength, i, MemberCount;
+ ULONG UserRid, GroupRid;
+ PVOID Buffer, Buffer1, Buffer2;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ PULONG Members, Attributes;
+ PSID_NAME_USE LookedUpUses;
+ PULONG LookedUpRids;
+ UNICODE_STRING AccountNames[10], AccountName;
+ STRING AccountNameAnsi;
+
+ BOOLEAN IndividualTestSucceeded, DeleteUser;
+ BOOLEAN TestStatus = TRUE;
+
+
+ if (Pass == 1) {
+ //
+ // This test suite assumes that lookup and enumeration API funciton
+ // properly.
+ //
+
+ printf("\n");
+ printf("\n");
+ printf(" Group (Pass #1) . . . . . . . . . . . . . . . . . . . Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Open Group Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf(" Open Group . . . . . . . . . . . . . . . . . . . . . Suite\n");
+ printf(" Open Groups . . . . . . . . . . . . . . . . . . . . . ");
+ IndividualTestSucceeded = TRUE;
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateGroupsInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer != NULL);
+ ASSERT(CountReturned > 0);
+
+ for (i=0; i<CountReturned; i++) {
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_ALL_ACCESS,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &GroupHandle1
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GENERIC_READ,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &GroupHandle2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SamCloseHandle( GroupHandle2 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening group second time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening group for first time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ if (!IndividualTestSucceeded) {
+ printf(" ");
+ }
+ }
+
+
+ SamFreeMemory( Buffer );
+ if (IndividualTestSucceeded) {
+ printf("Succeeded\n");
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query Group Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Query Group . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Query Group General Information . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupGeneralInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((GROUP_GENERAL_INFORMATION *)Buffer)->Name.MaximumLength > 0) &&
+ (((GROUP_GENERAL_INFORMATION *)Buffer)->Name.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Member Count is: 0x%lx\n",
+ (((GROUP_GENERAL_INFORMATION *)Buffer)->MemberCount) );
+ printf(" Attributes are: 0x%lx\n",
+ (((GROUP_GENERAL_INFORMATION *)Buffer)->Attributes) );
+ printf(" Group Name is: %wZ\n",
+ &(((GROUP_GENERAL_INFORMATION *)Buffer)->Name) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Group Name not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Group Name Information . . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((GROUP_NAME_INFORMATION *)Buffer)->Name.MaximumLength > 0) &&
+ (((GROUP_NAME_INFORMATION *)Buffer)->Name.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Group Name is: %wZ\n",
+ &(((GROUP_NAME_INFORMATION *)Buffer)->Name) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Group Name not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Group Admin Comment Information . . . . . . . . ");
+
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAdminCommentInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((GROUP_ADM_COMMENT_INFORMATION *)Buffer)->AdminComment.MaximumLength >= 0) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Group Admin Comment is: %wZ\n",
+ &(((GROUP_ADM_COMMENT_INFORMATION *)Buffer)->AdminComment) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Group Admin Comment not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Group Attribute Information . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAttributeInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+
+ printf("Succeeded\n");
+
+ printf(" Attributes are: 0x%lx\n",
+ (((GROUP_ATTRIBUTE_INFORMATION *)Buffer)->Attributes) );
+
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Get Members Of Group Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Get Members . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Get Members of Well-Known Account . . . . . . . . . . ");
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_LIST_MEMBERS,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamGetMembersInGroup(
+ GroupHandle1,
+ &Members,
+ &Attributes,
+ &MemberCount
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Members != NULL || Attributes != NULL) {
+
+ printf("Succeeded\n");
+
+
+ printf(" Member Count: %d Users\n", MemberCount);
+ for ( i=0; i<MemberCount; i++) {
+
+ printf(" User[%d] Rid/Attributes: 0x%lx/0x%lx\n",
+ i, Members[i], Attributes[i]);
+
+
+ }
+
+ SamFreeMemory( Members );
+ SamFreeMemory( Attributes );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Get Members of Empty Group. . . . . . . . . . . . . . ");
+
+ //
+ // This group was created earlier in the test
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeGroup);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+
+
+ GroupHandle1 = NULL;
+
+ NtStatus = SamOpenGroup( DomainHandle, GROUP_LIST_MEMBERS, LookedUpRids[0], &GroupHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+ NtStatus = SamGetMembersInGroup(
+ GroupHandle1,
+ &Members,
+ &Attributes,
+ &MemberCount
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (MemberCount == 0) {
+
+ printf("Succeeded\n");
+
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer addresses set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ printf(" Member Count: %d\n", MemberCount);
+ for ( i=0; i<MemberCount; i++) {
+
+ printf(" User[%d] Rid/Attributes: 0x%lx/0x%lx\n",
+ i, Members[i], Attributes[i]);
+ }
+
+ SamFreeMemory( Members );
+ SamFreeMemory( Attributes );
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Group Suite (pass 1) //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set Group . . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ printf(" Set Attribute . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_WRITE_ACCOUNT | GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAttributeInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+ //
+ // Change the value and write it back
+ //
+
+ ((GROUP_ATTRIBUTE_INFORMATION *)Buffer1)->Attributes ^=
+ SE_GROUP_ENABLED_BY_DEFAULT;
+
+
+ NtStatus = SamSetInformationGroup(
+ GroupHandle1,
+ GroupAttributeInformation,
+ Buffer1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check the written value to make sure it stuck
+ //
+
+ Buffer2 = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAttributeInformation,
+ &Buffer2
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer2 != NULL);
+
+ if (((GROUP_ATTRIBUTE_INFORMATION *)Buffer1)->Attributes ==
+ ((GROUP_ATTRIBUTE_INFORMATION *)Buffer2)->Attributes ) {
+
+ printf("Succeeded\n");
+
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Returned Value Doesn't Match Set Value.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer1 );
+ IgnoreStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ printf(" Set Admin Comment . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenGroup(
+ DomainHandle,
+ GROUP_WRITE_ACCOUNT | GROUP_READ_INFORMATION,
+ DOMAIN_GROUP_RID_USERS,
+ &GroupHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAdminCommentInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((GROUP_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((GROUP_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString2;
+ } else {
+ ((GROUP_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString1;
+ }
+
+ NtStatus = SamSetInformationGroup(
+ GroupHandle1,
+ GroupAdminCommentInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ Buffer2 = NULL;
+ NtStatus = SamQueryInformationGroup(
+ GroupHandle1,
+ GroupAdminCommentInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((GROUP_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment,
+ (PSTRING)&((GROUP_ADM_COMMENT_INFORMATION *)Buffer2)->AdminComment,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((GROUP_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((GROUP_ADM_COMMENT_INFORMATION *)Buffer2)->AdminComment);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+
+
+ } // END PASS #1
+ if (Pass == 2) {
+
+ printf("\n");
+ printf("\n");
+ printf(" Group (Pass #2) . . . . . . . . . . . . . . . . . . . Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Delete Group Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Delete Group . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Delete Normal Group . . . . . . . . . . . . . . . . . ");
+
+ //
+ // This group was created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeGroup);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+
+
+ GroupHandle1 = NULL;
+
+ NtStatus = SamOpenGroup( DomainHandle, DELETE, LookedUpRids[0], &GroupHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Delete Well Known Group . . . . . . . . . . . . . . . ");
+
+ GroupHandle1 = NULL;
+
+ NtStatus = SamOpenGroup( DomainHandle, DELETE, DOMAIN_GROUP_RID_USERS, &GroupHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ if (NtStatus == STATUS_SPECIAL_ACCOUNT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ NtStatus = SamCloseHandle( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+
+
+
+
+ printf(" Delete Primary Group Of A User. . . . . . . . . . . . ");
+
+ //
+ // Make a user (might already exist)
+ // Make a group
+ // Make the group the user's primary group
+ // Attempt to delete the group
+ // Change the user so the group isn't the primary group
+ // delete the group
+ // If we created the user, delete it.
+
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ UserRid = LookedUpRids[0];
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ UserRid,
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Make the user a member of this group
+ //
+
+ NtStatus = SamAddMemberToGroup(
+ GroupHandle1,
+ UserRid,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ //
+ // Now try to delete the group
+ //
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ if (NtStatus == STATUS_MEMBER_IN_GROUP) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Now get rid of the group and possibly the user account
+ //
+
+ NtStatus = SamRemoveMemberFromGroup(GroupHandle1, UserRid);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Add/Remove Member Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Add/Remove Member Suite . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Add Member . . . . . . . . . . . . . . . . . . . . . ");
+
+ //
+ // This test sets things up for the next test
+ //
+
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ UserRid = LookedUpRids[0];
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ UserRid,
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Make the user a member of this group
+ //
+
+ NtStatus = SamAddMemberToGroup(
+ GroupHandle1,
+ UserRid,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInGroup(
+ GroupHandle1,
+ &Members,
+ &Attributes,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == UserRid) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (Attributes[i] == SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Member Added but attributes don't match expected value.\n");
+ printf("Expected value: 0x%lx\n",(SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED));
+ printf("Retrieved value: 0x%lx\n",Attributes[i]);
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for group.\n");
+ TestStatus = FALSE;
+ }
+
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ SamFreeMemory( Attributes );
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Remove Member . . . . . . . . . . . . . . . . . . . . ");
+
+ //
+ // The previous test sets this one up.
+ //
+
+ //
+ // Now try to remove the user from the group
+ //
+
+ NtStatus = SamRemoveMemberFromGroup(GroupHandle1, UserRid);
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInGroup(
+ GroupHandle1,
+ &Members,
+ &Attributes,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == UserRid) {
+ NtStatus = STATUS_MEMBER_IN_GROUP;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for group.\n");
+ TestStatus = FALSE;
+ }
+
+
+ SamFreeMemory( Members );
+ SamFreeMemory( Attributes );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // and clean up the user and group accounts
+ //
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+
+
+
+ printf(" Add Non-Existant Member . . . . . . . . . . . . . . . ");
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Specify a non-existant user be added to this group
+ //
+
+ UserRid = 30732579; // Pretty sure this user doesn't exist.
+ NtStatus = SamAddMemberToGroup(
+ GroupHandle1,
+ UserRid,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED
+ );
+
+ if (NtStatus == STATUS_NO_SUCH_USER) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ printf(" Remove Non-existant Member . . . . . . . . . . . . . ");
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Specify a non-existant user be removed from this group
+ //
+
+ UserRid = 30732579; // Pretty sure this user doesn't exist.
+ NtStatus = SamRemoveMemberFromGroup( GroupHandle1, UserRid );
+
+ if (NtStatus == STATUS_NO_SUCH_USER) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ printf(" Remove Primary Group Of Member . . . . . . . . . . . ");
+
+
+ //
+ // Make a user (might already exist)
+ // Make a group
+ // Make the group the user's primary group
+ // Attempt to remove the group (should fail)
+ // Change the user so the group isn't the primary group
+ // remove the group
+ // delete the group
+ // If we created the user, delete it.
+
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ UserRid = LookedUpRids[0];
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ UserRid,
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Make the user a member of this group
+ //
+
+ NtStatus = SamAddMemberToGroup(
+ GroupHandle1,
+ UserRid,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // Set the user's primary group Id to be this group
+ //
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &GroupRid
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+ //
+ // Now try to remove the user from the group
+ //
+
+ NtStatus = SamRemoveMemberFromGroup(GroupHandle1, UserRid);
+ if (NtStatus == STATUS_MEMBERS_PRIMARY_GROUP) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Set the user's primary group Id back and remove the user
+ // from the group
+ //
+
+ GroupRid = DOMAIN_GROUP_RID_USERS;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &GroupRid
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+ NtStatus = SamRemoveMemberFromGroup(GroupHandle1, UserRid);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+ //
+ // Now get rid of the group and possibly the user account
+ //
+
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Group Suite (pass 2) //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set Group . . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+ printf(" Set Name . . . . . . . . . . . . . . . . . . . . . . ");
+ printf("(Untested)\n");
+
+
+ printf(" Set Name Of Well-Known Account . . . . . . . . . . . ");
+ printf("(Untested)\n");
+
+ }
+
+ return(TestStatus);
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Alias Object Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+AliasTestSuite(
+ HANDLE DomainHandle,
+ HANDLE BuiltinDomainHandle,
+ PSID DomainSid,
+ ULONG Pass
+ )
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ HANDLE AdminAliasHandle, AliasHandle1, AliasHandle2, UserHandle1, UserHandle2, UserHandle3;
+ ULONG CountReturned, i, MemberCount;
+ ULONG UserRid, UserRid2, UserRid3, AliasRid, AliasRid2;
+ PVOID Buffer, Buffer1, Buffer2;
+ ULONG NameLength;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ PULONG Members;
+ PSID *AliasMembers;
+ PSID_NAME_USE LookedUpUses;
+ PULONG LookedUpRids;
+ UNICODE_STRING AccountNames[10], AccountName;
+ STRING AccountNameAnsi;
+ PSID UserSid1, UserSid2, GroupSid;
+
+ BOOLEAN IndividualTestSucceeded, DeleteUser;
+ BOOLEAN TestStatus = TRUE;
+
+
+ if (Pass == 1) {
+ //
+ // This test suite assumes that lookup and enumeration API funciton
+ // properly.
+ //
+
+ printf("\n");
+ printf("\n");
+ printf(" Alias (Pass #1) . . . . . . . . . . . . . . . . . . . Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Open Alias Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf(" Open Alias . . . . . . . . . . . . . . . . . . . . . Suite\n");
+ printf(" Open Aliases . . . . . . . . . . . . . . . . . . . . . ");
+ IndividualTestSucceeded = TRUE;
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateAliasesInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer != NULL);
+ ASSERT(CountReturned > 0);
+
+ for (i=0; i<CountReturned; i++) {
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_ALL_ACCESS,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &AliasHandle1
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ GENERIC_READ,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &AliasHandle2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SamCloseHandle( AliasHandle2 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening alias second time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening alias for first time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ if (!IndividualTestSucceeded) {
+ printf(" ");
+ }
+ }
+
+
+ SamFreeMemory( Buffer );
+ if (IndividualTestSucceeded) {
+ printf("Succeeded\n");
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query Alias Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ //
+ // Get the rid of an alias created earlier in the test
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeAlias);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+ AliasRid = LookedUpRids[0];
+
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+
+ printf("\n");
+ printf(" Query Alias . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Query Alias General Information . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_READ_INFORMATION,
+ AliasRid,
+ &AliasHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationAlias(
+ AliasHandle1,
+ AliasGeneralInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((ALIAS_GENERAL_INFORMATION *)Buffer)->Name.MaximumLength > 0) &&
+ (((ALIAS_GENERAL_INFORMATION *)Buffer)->Name.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Member Count is: 0x%lx\n",
+ (((ALIAS_GENERAL_INFORMATION *)Buffer)->MemberCount) );
+ printf(" Alias Name is: %wZ\n",
+ &(((ALIAS_GENERAL_INFORMATION *)Buffer)->Name) );
+
+ } else {
+ printf("Failed\n");
+ printf(" Alias Name not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Alias Name Information . . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_READ_INFORMATION,
+ AliasRid,
+ &AliasHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationAlias(
+ AliasHandle1,
+ AliasNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((ALIAS_NAME_INFORMATION *)Buffer)->Name.MaximumLength > 0) &&
+ (((ALIAS_NAME_INFORMATION *)Buffer)->Name.Buffer != NULL) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Alias Name is: %wZ\n",
+ &(((ALIAS_NAME_INFORMATION *)Buffer)->Name) );
+ } else {
+ printf("Failed\n");
+ printf(" Alias Name not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Alias Admin Comment Information . . . . . . . . ");
+
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_READ_INFORMATION,
+ AliasRid,
+ &AliasHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationAlias(
+ AliasHandle1,
+ AliasAdminCommentInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((ALIAS_ADM_COMMENT_INFORMATION *)Buffer)->AdminComment.MaximumLength >= 0) ) {
+
+ printf("Succeeded\n");
+
+ printf(" Alias Admin Comment is: %wZ\n",
+ &(((ALIAS_ADM_COMMENT_INFORMATION *)Buffer)->AdminComment) );
+ } else {
+ printf("Failed\n");
+ printf(" Alias Admin Comment not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Get Members Of Alias Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Get Members . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+#ifdef LATER // ALIAS_LATER - well-know aliases ?
+
+
+ davidc/chads - this needs to access the builtin domain.
+
+ printf(" Get Members of Well-Known Account . . . . . . . . . . ");
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_LIST_MEMBERS,
+ DOMAIN_ALIAS_RID_ADMINS,
+ &AliasHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &Attributes,
+ &MemberCount
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Members != NULL || Attributes != NULL) {
+
+ printf("Succeeded\n");
+
+
+ printf(" Member Count: %d Users\n", MemberCount);
+ for ( i=0; i<MemberCount; i++) {
+
+ // printf(" User[%d] Sid: 0x%lx\n",
+ // i, Members[i]);
+
+
+ }
+
+ SamFreeMemory( AliasMembers );
+ SamFreeMemory( Attributes );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+#endif
+
+
+ printf(" Get Members of Empty Alias. . . . . . . . . . . . . . ");
+
+ //
+ // This alias was created earlier in the test
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeAlias);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+
+
+ AliasHandle1 = NULL;
+
+ NtStatus = SamOpenAlias( DomainHandle, ALIAS_LIST_MEMBERS, LookedUpRids[0], &AliasHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (MemberCount == 0) {
+
+ printf("Succeeded\n");
+
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Member Count > 0 : %d\n", MemberCount);
+ for ( i=0; i<MemberCount; i++) {
+
+ // printf(" User[%d] Rid/Attributes: 0x%lx/0x%lx\n",
+ // i, Members[i], Attributes[i]);
+ }
+
+ SamFreeMemory( AliasMembers );
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set Alias Suite (pass 1) //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set Alias . . . . . . . . . . . . . . . . . . . . . . Suite\n");
+ //
+
+
+ // Get the rid of an alias created earlier in the test
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeAlias);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+ AliasRid = LookedUpRids[0];
+
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+
+
+ printf(" Set Admin Comment . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenAlias(
+ DomainHandle,
+ ALIAS_WRITE_ACCOUNT | ALIAS_READ_INFORMATION,
+ AliasRid,
+ &AliasHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationAlias(
+ AliasHandle1,
+ AliasAdminCommentInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((ALIAS_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((ALIAS_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString2;
+ } else {
+ ((ALIAS_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString1;
+ }
+
+ NtStatus = SamSetInformationAlias(
+ AliasHandle1,
+ AliasAdminCommentInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ Buffer2 = NULL;
+ NtStatus = SamQueryInformationAlias(
+ AliasHandle1,
+ AliasAdminCommentInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((ALIAS_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment,
+ (PSTRING)&((ALIAS_ADM_COMMENT_INFORMATION *)Buffer2)->AdminComment,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((ALIAS_ADM_COMMENT_INFORMATION *)Buffer1)->AdminComment);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((ALIAS_ADM_COMMENT_INFORMATION *)Buffer2)->AdminComment);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+
+ } // END PASS #1
+ if (Pass == 2) {
+
+ printf("\n");
+ printf("\n");
+ printf(" Alias (Pass #2) . . . . . . . . . . . . . . . . . . . Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Delete Alias Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Delete Alias . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Delete Normal Alias . . . . . . . . . . . . . . . . . ");
+
+ //
+ // This alias was created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeAlias);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+
+
+ AliasHandle1 = NULL;
+
+ NtStatus = SamOpenAlias( DomainHandle, DELETE, LookedUpRids[0], &AliasHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+ NtStatus = SamDeleteAlias( AliasHandle1 );
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+#ifdef LATER // ALIAS_LATER - well know aliases ?
+
+
+ printf(" Delete Well Known Alias . . . . . . . . . . . . . . . ");
+
+ AliasHandle1 = NULL;
+
+ NtStatus = SamOpenAlias( DomainHandle, DELETE, DOMAIN_GROUP_RID_USERS, &AliasHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamDeleteAlias( AliasHandle1 );
+ if (NtStatus == STATUS_SPECIAL_ACCOUNT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ NtStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ printf(" Delete Admin Alias. . . . . . . . . . . . . . . . . . ");
+ AliasHandle1 = NULL;
+
+ NtStatus = SamOpenAlias( DomainHandle, DELETE, DOMAIN_ALIAS_RID_ADMINS, &AliasHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamDeleteAlias( AliasHandle1 );
+ if (NtStatus == STATUS_SPECIAL_ACCOUNT) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ NtStatus = SamCloseHandle( AliasHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+#endif
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Add/Remove Member Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Add/Remove Member Suite . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Add Member . . . . . . . . . . . . . . . . . . . . . ");
+
+ //
+ // This test sets things up for the next test
+ //
+
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ UserRid = LookedUpRids[0];
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ UserRid,
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // This account won't exist yet
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME2 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ UserRid2 = 0;
+ UserHandle2 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle2,
+ &UserRid2
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // create the alias
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ AliasRid = 0;
+ AliasHandle1 = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ ALIAS_ALL_ACCESS,
+ &AliasHandle1,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Make user1 a member of this alias
+ //
+
+ UserSid1 = CreateUserSid(DomainSid, UserRid);
+ ASSERT(UserSid1 != NULL);
+
+ UserSid2 = CreateUserSid(DomainSid, UserRid2);
+ ASSERT(UserSid2 != NULL);
+
+
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid1
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid1)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid1,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in alias1
+ // User2 should be no aliases.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 1) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ if (Members[0] != AliasRid) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+
+
+
+ printf(" Add another member to another alias . . . . . . . . . ");
+
+
+
+
+
+
+ //
+ // Make user2 a member of alias2
+ //
+
+ //
+ // This alias was created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME2 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeAlias);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+ AliasHandle2 = NULL;
+ AliasRid2 = LookedUpRids[0];
+
+ NtStatus = SamOpenAlias( DomainHandle, ALIAS_ALL_ACCESS, LookedUpRids[0], &AliasHandle2 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle2,
+ UserSid2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle2,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid2) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in alias1
+ // User2 should be in alias2.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 2) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ if (((Members[0] == AliasRid) && (Members[1] == AliasRid2)) ||
+ ((Members[0] == AliasRid2) && (Members[1] == AliasRid)) ) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Remove user2 from alias2 again
+ //
+
+ NtStatus = SamRemoveMemberFromAlias(
+ AliasHandle2,
+ UserSid2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle2,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NtStatus != STATUS_MEMBER_NOT_IN_ALIAS) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid2) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in alias1
+ // User2 should be in no aliases.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 1) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ if (Members[0] == AliasRid) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ NtStatus = SamCloseHandle( AliasHandle2 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+
+ printf(" Add Another Member . . . . . . . . . . . . . . . . . ");
+
+
+ //
+ // Make user2 a member of this alias
+ //
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in alias1
+ // User2 should be in alias1.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 1) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ if (Members[0] != AliasRid) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ printf(" Remove Member . . . . . . . . . . . . . . . . . . . . ");
+
+ //
+ // The previous test sets this one up.
+ //
+
+ //
+ // Now try to remove the user from the alias
+ //
+
+ NtStatus = SamRemoveMemberFromAlias(AliasHandle1, UserSid1);
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid1)) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( AliasMembers );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid1,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in no aliases
+ // User2 should be in alias1.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 1) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ if (Members[0] != AliasRid) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+ printf("Succeeded\n");
+ }
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Add A User to ADMIN Alias . . . . . . . . . . . . . . ");
+
+ //
+ // Make user2 a member of the ADMIN alias
+ //
+
+ NtStatus = SamOpenAlias(
+ BuiltinDomainHandle,
+ ALIAS_ALL_ACCESS,
+ DOMAIN_ALIAS_RID_ADMINS,
+ &AdminAliasHandle
+ );
+
+ ASSERT( NT_SUCCESS( NtStatus ) );
+
+ NtStatus = SamAddMemberToAlias(
+ AdminAliasHandle,
+ UserSid2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AdminAliasHandle,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ BuiltinDomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == DOMAIN_ALIAS_RID_ADMINS) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ printf(" Add A Group to ADMIN Alias . . . . . . . . . . . . . . ");
+
+ //
+ // Make a group a member of the ADMIN alias
+ //
+
+ GroupSid = CreateUserSid(DomainSid, DOMAIN_GROUP_RID_USERS );
+ ASSERT(GroupSid != NULL);
+
+ NtStatus = SamAddMemberToAlias(
+ AdminAliasHandle,
+ GroupSid
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AdminAliasHandle,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], GroupSid)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ BuiltinDomainHandle,
+ 1,
+ &GroupSid,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == DOMAIN_ALIAS_RID_ADMINS) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+// NOTE: user is already created in the group below. Should keep this
+// test, AND add another with an all-new group that's been added to the ADMIN
+// alias (then ADD user to group, rather than create in it).
+ printf(" Create user in ADMIN ALIAS'd Group. . . . . . . . . . . ");
+
+ RtlInitString( &AccountNameAnsi, USER_NAME3 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ UserRid3 = 0;
+ UserHandle3 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle3,
+ &UserRid3
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+//NOTE: doesn't work because this is primary group.
+//put back in when all-new group is created, above
+// printf(" Remove user from ADMIN ALIAS'd Group. . . . . . . . . . . ");
+//
+// NtStatus = SamOpenGroup(
+// DomainHandle,
+// GROUP_ALL_ACCESS,
+// DOMAIN_GROUP_RID_USERS,
+// &GroupHandle
+// );
+//
+// ASSERT(NT_SUCCESS(NtStatus));
+//
+// NtStatus = SamRemoveMemberFromGroup(
+// GroupHandle,
+// UserRid3
+// );
+//
+// if ( NT_SUCCESS( NtStatus ) ) {
+//
+// printf("Succeeded\n");
+//
+// } else {
+//
+// printf("Failed\n");
+// printf(" Completion status is 0x%lx\n", NtStatus);
+// TestStatus = FALSE;
+// }
+//
+// IgnoreStatus = SamCloseHandle( GroupHandle );
+// ASSERT(NT_SUCCESS(IgnoreStatus));
+ IgnoreStatus = SamCloseHandle( UserHandle3 );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+
+ printf(" Remove User from ADMIN alias. . . . . . . . . . . ");
+
+ //
+ // The previous test sets this one up.
+ //
+ // Now try to remove the user from the alias
+ //
+
+ NtStatus = SamRemoveMemberFromAlias(AdminAliasHandle, UserSid2);
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AdminAliasHandle,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( AliasMembers );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ BuiltinDomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == DOMAIN_ALIAS_RID_ADMINS) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ //
+ // Make user2 a member of the ADMIN alias again, so we can test
+ // the new function SamRemoveMemberFromForeignDomain().
+ // NOTE: we should make this a real test item.
+ //
+
+ NtStatus = SamAddMemberToAlias(
+ AdminAliasHandle,
+ UserSid2
+ );
+
+ ASSERT (NT_SUCCESS(NtStatus));
+
+ NtStatus = SamRemoveMemberFromForeignDomain(
+ BuiltinDomainHandle,
+ UserSid2 );
+
+ ASSERT (NT_SUCCESS(NtStatus));
+
+
+
+ printf(" Remove Group from ADMIN alias. . . . . . . . . . . ");
+
+ //
+ // The previous test sets this one up.
+ //
+ // Now try to remove the group from the alias
+ //
+
+ NtStatus = SamRemoveMemberFromAlias(AdminAliasHandle, GroupSid);
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetMembersInAlias(
+ AdminAliasHandle,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], GroupSid)) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( AliasMembers );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ BuiltinDomainHandle,
+ 1,
+ &GroupSid,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == DOMAIN_ALIAS_RID_ADMINS) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+
+ printf("Succeeded\n");
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ IgnoreStatus = SamCloseHandle( AdminAliasHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Delete account while member of alias. . . . . . . . . ");
+
+
+ //
+ // Now delete user2 and check the alias member list is updated
+ //
+
+ NtStatus = SamDeleteUser( UserHandle2 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid2)) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user still in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ SamFreeMemory( AliasMembers );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid2,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 0) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in alias membership list for account.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+ //
+ // Check for correct alias membership for multiple accounts
+ // User1 should be in no aliases
+ // User2 should be in no aliases.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID SidArray[2];
+ SidArray[0] = UserSid1;
+ SidArray[1] = UserSid2;
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 2,
+ SidArray,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (MemberCount != 0) {
+
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but combined alias membership count for 2 accounts not correct.\n");
+ printf("Combined Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+
+ } else {
+ printf("Succeeded\n");
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+ }
+
+
+
+
+ printf(" Delete alias with members . . . . . . . . . . . . . . ");
+
+ //
+ // Make the user a member of this alias (again)
+ //
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid1
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Now delete the alias and check the membership list for user is updated
+ //
+
+ NtStatus = SamDeleteAlias( AliasHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid1,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias still in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+
+
+
+ DeleteUserSid(UserSid1);
+ DeleteUserSid(UserSid2);
+
+ //
+ // and clean up
+ //
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+
+
+
+ printf(" Add Foreign Domain Member . . . . . . . . . . . . . . ");
+
+ //
+ // create the alias
+ //
+
+ RtlInitString( &AccountNameAnsi, ALIAS_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ AliasRid = 0;
+ AliasHandle1 = NULL;
+ NtStatus = SamCreateAliasInDomain(
+ DomainHandle,
+ &AccountName,
+ ALIAS_ALL_ACCESS,
+ &AliasHandle1,
+ &AliasRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Specify a non-existant user be added to this alias
+ //
+
+
+ {
+ PSID ForeignDomainSid;
+
+ ForeignDomainSid = CreateUserSid(DomainSid, 307333); // random domain sub-authority
+ ASSERT(ForeignDomainSid != NULL);
+
+ UserRid = 45728; // Random user rid
+
+ UserSid1 = CreateUserSid(ForeignDomainSid, UserRid);
+ ASSERT(UserSid1 != NULL);
+
+ DeleteUserSid(ForeignDomainSid);
+ }
+
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid1
+ );
+
+ if (NtStatus == STATUS_SUCCESS) {
+
+ NtStatus = SamGetMembersInAlias(
+ AliasHandle1,
+ &AliasMembers,
+ &MemberCount
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (RtlEqualSid(AliasMembers[i], UserSid1)) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but user not in member list for alias.\n");
+ printf("Member list :\n");
+ for (i=0; i<MemberCount; i++) {
+ printfSid(AliasMembers[i]);
+ printf("\n");
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+
+ if (AliasMembers != NULL) {
+ SamFreeMemory( AliasMembers );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &UserSid1,
+ &MemberCount,
+ &Members
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ for ( i=0; i<MemberCount; i++) {
+ if (Members[i] == AliasRid) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+ } else {
+ printf("Failed\n");
+ printf("Service returned SUCCESS, but alias not in account alias membership list.\n");
+ printf("Alias Membership :\n");
+ for (i=0; i<MemberCount; i++) {
+ printf("0x%lx\n", Members[i]);
+ }
+ DebugBreak();
+ TestStatus = FALSE;
+ }
+
+ if (Members != NULL) {
+ SamFreeMemory( Members );
+ }
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ DeleteUserSid(UserSid1);
+
+
+
+
+ printf(" Add alias as member . . . . . . . . . . . . . . . . . ");
+
+ //
+ // Specify an alias in the current domain be added to this alias
+ //
+
+
+ UserSid1 = CreateUserSid(DomainSid, AliasRid2);
+ ASSERT(UserSid1 != NULL);
+
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid1
+ );
+
+ if (NtStatus != STATUS_INVALID_MEMBER) {
+
+ printf("Failed\n");
+ printf("Expected service to return STATUS_INVALID_MEMBER, actually returned 0x%lx.\n", NtStatus);
+ DebugBreak();
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+ DeleteUserSid(UserSid1);
+
+
+
+ printf(" Add non-existant account in this domain as member . . ");
+
+ //
+ // Specify a non-existant account in the current domain be added to this alias
+ //
+
+
+ UserSid1 = CreateUserSid(DomainSid, 32567); // Random rid
+ ASSERT(UserSid1 != NULL);
+
+
+ NtStatus = SamAddMemberToAlias(
+ AliasHandle1,
+ UserSid1
+ );
+
+ if (NtStatus != STATUS_NO_SUCH_MEMBER) {
+
+ printf("Failed\n");
+ printf("Expected service to return STATUS_NO_SUCH_MEMBER, actually returned 0x%lx.\n", NtStatus);
+ DebugBreak();
+ TestStatus = FALSE;
+ } else {
+ printf("Succeeded\n");
+ }
+
+ DeleteUserSid(UserSid1);
+
+
+
+ printf(" Remove Non-member . . . . . . . . . . . . . . . . . . ");
+
+ //
+ // Specify a non-existant user be removed from this alias
+ //
+
+ {
+ PSID ForeignDomainSid;
+
+ ForeignDomainSid = CreateUserSid(DomainSid, 35775); // random domain sub-authority
+ ASSERT(ForeignDomainSid != NULL);
+
+ UserRid = 623545; // Random user rid
+
+ UserSid1 = CreateUserSid(ForeignDomainSid, UserRid);
+ ASSERT(UserSid1 != NULL);
+
+ DeleteUserSid(ForeignDomainSid);
+ }
+
+ NtStatus = SamRemoveMemberFromAlias( AliasHandle1, UserSid1 );
+
+ if (NtStatus == STATUS_MEMBER_NOT_IN_ALIAS) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ DeleteUserSid(UserSid1);
+
+ NtStatus = SamDeleteAlias( AliasHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ }
+
+ return(TestStatus);
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// User Object Test Suite //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+UserTestSuite(
+ HANDLE DomainHandle,
+ ULONG Pass
+ )
+
+
+{
+
+ PUSER_ALL_INFORMATION All, All2;
+ NTSTATUS NtStatus, IgnoreStatus, TmpStatus;
+ HANDLE UserHandle1, UserHandle2, GroupHandle1;
+ ULONG CountReturned, NameLength, MembershipCount, i;
+ ULONG UserRid, GroupRid;
+ PVOID Buffer, Buffer1, Buffer2;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ USER_GENERAL_INFORMATION GeneralInformation;
+ USER_LOGON_INFORMATION LogonInformation;
+ USER_ACCOUNT_INFORMATION AccountInformation;
+ PSID_NAME_USE LookedUpUses;
+ PULONG LookedUpRids;
+ UNICODE_STRING AccountNames[10], AccountName;
+ STRING AccountNameAnsi, TmpAnsiString;
+
+
+
+
+ BOOLEAN IndividualTestSucceeded, DeleteUser;
+ BOOLEAN TestStatus = TRUE;
+
+
+
+
+ if (Pass == 1) {
+ // This test suite assumes that lookup and enumeration API funciton
+ // properly.
+ //
+
+ printf("\n");
+ printf("\n");
+ printf(" User (Pass #1) . . . . . . . . . . . . . . . . . . . Test\n");
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Open User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf(" Open User . . . . . . . . . . . . . . . . . . . . . Suite\n");
+ printf(" Open Users . . . . . . . . . . . . . . . . . . . . . ");
+ IndividualTestSucceeded = TRUE;
+ EnumerationContext = 0;
+ NtStatus = SamEnumerateUsersInDomain(
+ DomainHandle,
+ &EnumerationContext,
+ 0,
+ &Buffer,
+ 12000, // PreferedMaximumLength
+ &CountReturned
+ );
+
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer != NULL);
+ ASSERT(CountReturned > 0);
+
+ for (i=0; i<CountReturned; i++) {
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &UserHandle1
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ GENERIC_READ,
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId,
+ &UserHandle2
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SamCloseHandle( UserHandle2 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening User second time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Failed opening User for first time.\n");
+ printf(" Rid of account is: 0x%lx\n",
+ ((PSAM_RID_ENUMERATION)(Buffer))[i].RelativeId);
+ printf(" Name of account is: %wZ\n",
+ &((PSAM_RID_ENUMERATION)(Buffer))[i].Name );
+ TestStatus = FALSE;
+ IndividualTestSucceeded = FALSE;
+ }
+
+ }
+
+
+ SamFreeMemory( Buffer );
+ if (IndividualTestSucceeded) {
+ printf("Succeeded\n");
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Query User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Query User . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Query User General Information . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserGeneralInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_GENERAL_INFORMATION *)Buffer)->UserName.MaximumLength
+ >= 0) &&
+ (((USER_GENERAL_INFORMATION *)Buffer)->UserName.Buffer != NULL)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Primary Group is: 0x%lx\n",
+ (((USER_GENERAL_INFORMATION *)Buffer)->PrimaryGroupId) );
+ printf(" User Name is: *%wZ*\n",
+ &(((USER_GENERAL_INFORMATION *)Buffer)->UserName) );
+ printf(" Full Name is: *%wZ*\n",
+ &(((USER_GENERAL_INFORMATION *)Buffer)->FullName) );
+ printf(" Admin Comment is: *%wZ*\n",
+ &(((USER_GENERAL_INFORMATION *)Buffer)->AdminComment) );
+ printf(" User Comment is: *%wZ*\n",
+ &(((USER_GENERAL_INFORMATION *)Buffer)->UserComment) );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" One of the UNICODE_STRINGs not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Name Information . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_NAME_INFORMATION *)Buffer)->UserName.MaximumLength > 0) &&
+ (((USER_NAME_INFORMATION *)Buffer)->UserName.Buffer != NULL)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" User Name is: *%wZ*\n",
+ &(((USER_NAME_INFORMATION *)Buffer)->UserName) );
+ printf(" Full Name is: *%wZ*\n",
+ &(((USER_NAME_INFORMATION *)Buffer)->FullName) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" One of the UNICODE_STRINGs not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Account Name Information . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAccountNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_ACCOUNT_NAME_INFORMATION *)Buffer)->UserName.MaximumLength > 0) &&
+ (((USER_ACCOUNT_NAME_INFORMATION *)Buffer)->UserName.Buffer != NULL)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" User Name is: *%wZ*\n",
+ &(((USER_ACCOUNT_NAME_INFORMATION *)Buffer)->UserName) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" UNICODE_STRING not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Full Name Information . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserFullNameInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_FULL_NAME_INFORMATION *)Buffer)->FullName.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Full Name is: *%wZ*\n",
+ &(((USER_FULL_NAME_INFORMATION *)Buffer)->FullName) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" UNICODE_STRING not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Admin Comment Information . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAdminCommentInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_ADMIN_COMMENT_INFORMATION *)Buffer)->AdminComment.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Admin Comment is: *%wZ*\n",
+ &(((USER_ADMIN_COMMENT_INFORMATION *)Buffer)->AdminComment) );
+
+ } else {
+ printf("Failed\n");
+ printf(" User Admin Comment not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Primary Group Information . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+
+ printf("Succeeded\n");
+
+ printf(" Primary Group is: 0x%lx\n",
+ (((USER_PRIMARY_GROUP_INFORMATION *)Buffer)->PrimaryGroupId) );
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Control Information . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_ACCOUNT,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserControlInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+
+ printf("Succeeded\n");
+
+ printf(" Account Control is: 0x%lx\n",
+ (((USER_CONTROL_INFORMATION *)Buffer)->UserAccountControl) );
+
+ SamFreeMemory( Buffer );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Expiration Information . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_ACCOUNT,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserExpiresInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+
+ printf("Succeeded\n");
+
+ printf(" Account Expires on: (0x%lx, 0x%lx)\n",
+ (((USER_EXPIRES_INFORMATION *)Buffer)->AccountExpires.HighPart),
+ (((USER_EXPIRES_INFORMATION *)Buffer)->AccountExpires.LowPart) );
+
+ SamFreeMemory( Buffer );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+ printf(" Query User Preferences Information . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_PREFERENCES | USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserPreferencesInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_PREFERENCES_INFORMATION *)Buffer)->UserComment.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" User Comment is: *%wZ*\n",
+ &(((USER_PREFERENCES_INFORMATION *)Buffer)->UserComment) );
+
+ } else {
+ printf("Failed\n");
+ printf(" One of the UNICODE_STRINGs not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ printf(" Query User Home Directory Information . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserHomeInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_HOME_INFORMATION *)Buffer)->HomeDirectory.MaximumLength
+ >= 0) &&
+ (((USER_HOME_INFORMATION *)Buffer)->HomeDirectoryDrive.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Home Directory is: *%wZ*\n",
+ &(((USER_HOME_INFORMATION *)Buffer)->HomeDirectory) );
+ printf(" Home Directory Drive is: *%wZ*\n",
+ &(((USER_HOME_INFORMATION *)Buffer)->HomeDirectoryDrive) );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" String not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Script Path Information . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserScriptInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_SCRIPT_INFORMATION *)Buffer)->ScriptPath.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Script Path is: *%wZ*\n",
+ &(((USER_SCRIPT_INFORMATION *)Buffer)->ScriptPath) );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" String not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Query User ProfilePath Information . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserProfileInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_PROFILE_INFORMATION *)Buffer)->ProfilePath.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Profile Path is: *%wZ*\n",
+ &(((USER_PROFILE_INFORMATION *)Buffer)->ProfilePath) );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" String not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Query User Logon Information . . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_ACCOUNT | USER_READ_GENERAL | USER_READ_PREFERENCES | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserLogonInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_LOGON_INFORMATION *)Buffer)->UserName.MaximumLength > 0) &&
+ (((USER_LOGON_INFORMATION *)Buffer)->UserName.Buffer != NULL)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" User RID is: 0x%lx\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->UserId) );
+ printf(" Primary Group is: 0x%lx\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->PrimaryGroupId) );
+ printf(" Logon Units are: 0x%lx\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->LogonHours.UnitsPerWeek) );
+ printf(" Bad PWD count is: 0x%lx\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->BadPasswordCount) );
+ printf(" Logon count is: 0x%lx\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->LogonCount) );
+
+ printf(" last Logon is: (0x%lx, 0x%lx)\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->LastLogon.HighPart),
+ (((USER_LOGON_INFORMATION *)Buffer)->LastLogon.LowPart) );
+ printf(" last Logoff is: (0x%lx, 0x%lx)\n",
+ (((USER_LOGON_INFORMATION *)Buffer)->LastLogoff.HighPart),
+ (((USER_LOGON_INFORMATION *)Buffer)->LastLogoff.LowPart) );
+
+
+ printf(" User Name is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->UserName) );
+ printf(" Full Name is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->FullName) );
+ printf(" Home Dir is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->HomeDirectory) );
+ printf(" Home Dir Drive is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->HomeDirectoryDrive) );
+ printf(" Script Path is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->ScriptPath) );
+ printf(" Profile Path is: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->ProfilePath) );
+ printf(" WorkStations are: *%wZ*\n",
+ &(((USER_LOGON_INFORMATION *)Buffer)->WorkStations) );
+
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" One of the UNICODE_STRINGs not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query User Logon Hours . . . . . . . . . . . . . . . ");
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserLogonHoursInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ printf("Succeeded\n");
+
+ printf(" Logon Units are: 0x%lx\n",
+ (((USER_LOGON_HOURS_INFORMATION *)Buffer)->LogonHours.UnitsPerWeek) );
+
+
+ SamFreeMemory( Buffer );
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ printf(" Query Account Information . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL | USER_READ_PREFERENCES |
+ USER_READ_LOGON | USER_READ_ACCOUNT,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAccountInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_ACCOUNT_INFORMATION *)Buffer)->UserName.MaximumLength > 0) &&
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->UserName.Buffer != NULL)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" User RID is: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->UserId) );
+ printf(" Primary Group is: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->PrimaryGroupId) );
+ printf(" Logon Units are: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LogonHours.UnitsPerWeek) );
+ printf(" Bad PWD count is: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->BadPasswordCount) );
+ printf(" Logon count is: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LogonCount) );
+ printf(" Account Ctrl is: 0x%lx\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->UserAccountControl) );
+
+ printf(" last Logon is: (0x%lx, 0x%lx)\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LastLogon.HighPart),
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LastLogon.LowPart) );
+ printf(" last Logoff is: (0x%lx, 0x%lx)\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LastLogoff.HighPart),
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->LastLogoff.LowPart) );
+ printf(" Pwd Last Set is: (0x%lx, 0x%lx)\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->PasswordLastSet.HighPart),
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->PasswordLastSet.LowPart) );
+ printf(" Account Expires is: (0x%lx, 0x%lx)\n",
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->AccountExpires.HighPart),
+ (((USER_ACCOUNT_INFORMATION *)Buffer)->AccountExpires.LowPart) );
+
+
+ printf(" User Name is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->UserName) );
+ printf(" Full Name is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->FullName) );
+ printf(" Home Dir is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->HomeDirectory) );
+ printf(" Home Dir Drive is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->HomeDirectoryDrive) );
+ printf(" Script Path is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->ScriptPath) );
+ printf(" Profile Path is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->ProfilePath) );
+ printf(" Admin Comment is: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->AdminComment) );
+ printf(" WorkStations are: *%wZ*\n",
+ &(((USER_ACCOUNT_INFORMATION *)Buffer)->WorkStations) );
+
+
+
+ } else {
+ printf("Failed\n");
+ printf(" One of the UNICODE_STRINGs not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+
+
+
+ printf(" Query Workstations Information . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserWorkStationsInformation,
+ &Buffer
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ if ( (((USER_WORKSTATIONS_INFORMATION *)Buffer)->WorkStations.MaximumLength
+ >= 0)
+ ) {
+
+ printf("Succeeded\n");
+
+ printf(" Workstations is: *%wZ*\n",
+ &(((USER_WORKSTATIONS_INFORMATION *)Buffer)->WorkStations) );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" String not returned.\n");
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer );
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ printf(" Query Internal1 Information . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserInternal1Information,
+ &Buffer
+ );
+
+ if ( NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ //
+ // We're not a trusted client, so we expected this to fail.
+ //
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Status was %lx.\n", NtStatus );
+ TestStatus = FALSE;
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SamFreeMemory( Buffer );
+ }
+ }
+
+// This is the code that USED to test this function, when it was allowed
+// for non-trusted clients.
+//
+// if (NT_SUCCESS(NtStatus)) {
+// if (Buffer != NULL) {
+//
+// if ( (((USER_INTERNAL1_INFORMATION *)Buffer)->CaseInsensitiveDbcs.MaximumLength > 0) &&
+// (((USER_INTERNAL1_INFORMATION *)Buffer)->CaseInsensitiveDbcs.Buffer != NULL) &&
+// (((USER_INTERNAL1_INFORMATION *)Buffer)->CaseSensitiveUnicode.MaximumLength > 0) &&
+// (((USER_INTERNAL1_INFORMATION *)Buffer)->CaseSensitiveUnicode.Buffer != NULL)
+// ) {
+//
+// printf("Succeeded\n");
+//
+// //
+// // Print them out as strings, even though they've been
+// // through a OWF.
+// //
+//
+// printf(" CaseInsensitiveDbcs is: *%s*\n",
+// &(((USER_INTERNAL1_INFORMATION *)Buffer)->CaseInsensitiveDbcs) );
+//
+// printf(" CaseSensitiveUnicode is: *%s*\n",
+// &(((USER_INTERNAL1_INFORMATION *)Buffer)->CaseSensitiveUnicode) );
+//
+//
+// } else {
+// printf("Failed\n");
+// printf(" One of the strings not returned.\n");
+// TestStatus = FALSE;
+// }
+// SamFreeMemory( Buffer );
+// } else {
+// printf("Failed\n");
+// printf(" Buffer address not set on return.\n");
+// printf(" RPC should have allocated a buffer.\n");
+// TestStatus = FALSE;
+// }
+// } else {
+// printf("Failed\n");
+// printf(" Completion status is 0x%lx\n", NtStatus);
+// TestStatus = FALSE;
+// }
+
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ printf(" Query Internal2 Information . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserInternal2Information,
+ &Buffer
+ );
+
+ if ( NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ //
+ // We're not a trusted client, so we don't expect to be able
+ // to do this.
+ //
+
+ printf("Succeeded.\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer );
+ }
+
+// This is the code that USED to test this function, when non-trusted
+// clients were allowed to do this...
+//
+// if (NT_SUCCESS(NtStatus)) {
+// if (Buffer != NULL) {
+//
+// printf("Succeeded\n");
+//
+// printf(" last Logon is: (0x%lx, 0x%lx)\n",
+// (((USER_INTERNAL2_INFORMATION *)Buffer)->LastLogon.HighPart),
+// (((USER_INTERNAL2_INFORMATION *)Buffer)->LastLogon.LowPart) );
+// printf(" last Logoff is: (0x%lx, 0x%lx)\n",
+// (((USER_INTERNAL2_INFORMATION *)Buffer)->LastLogoff.HighPart),
+// (((USER_INTERNAL2_INFORMATION *)Buffer)->LastLogoff.LowPart) );
+// printf(" BadPwdCount is: (0x%x)\n",
+// ((USER_INTERNAL2_INFORMATION *)Buffer)->BadPasswordCount );
+// printf(" LogonCount is: (0x%x)\n",
+// ((USER_INTERNAL2_INFORMATION *)Buffer)->LogonCount );
+//
+// SamFreeMemory( Buffer );
+// } else {
+// printf("Failed\n");
+// printf(" Buffer address not set on return.\n");
+// printf(" RPC should have allocated a buffer.\n");
+// TestStatus = FALSE;
+// }
+// } else {
+// printf("Failed\n");
+// printf(" Completion status is 0x%lx\n", NtStatus);
+// TestStatus = FALSE;
+// }
+
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+
+ printf(" Query Set Password Information . . . . . . . . . . . ");
+
+
+
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserSetPasswordInformation,
+ &Buffer
+ );
+ if (NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected 0x%lx (INVALID_INFO_CLASS)\n", STATUS_INVALID_INFO_CLASS);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Get Groups For User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Get Groups For User . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Get Groups For Well-Known Account . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_LIST_GROUPS,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+ NtStatus = SamGetGroupsForUser(
+ UserHandle1,
+ (PGROUP_MEMBERSHIP *)&Buffer,
+ &MembershipCount
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ if (Buffer != NULL) {
+
+ printf("Succeeded\n");
+
+
+ printf(" Member of: %d groups\n", MembershipCount);
+ for ( i=0; i<MembershipCount; i++) {
+
+ printf(" Group[%d] Rid/Attributes: 0x%lx/0x%lx\n",
+ i,
+ (((PGROUP_MEMBERSHIP)Buffer)[i].RelativeId),
+ (((PGROUP_MEMBERSHIP)Buffer)[i].Attributes)
+ );
+
+ }
+
+ SamFreeMemory( Buffer );
+
+
+ } else {
+ printf("Failed\n");
+ printf(" Buffer address not set on return.\n");
+ printf(" RPC should have allocated a buffer.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set User . . . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Set General Information . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Make the parameter marshallable, but don't worry about values.
+ //
+
+ GeneralInformation.UserName = DummyName1;
+ GeneralInformation.FullName = DummyName1;
+ GeneralInformation.AdminComment = DummyName1;
+ GeneralInformation.UserComment = DummyName1;
+
+ Buffer = &GeneralInformation;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserGeneralInformation,
+ Buffer
+ );
+ if (NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected 0x%lx (INVALID_INFO_CLASS)\n", STATUS_INVALID_INFO_CLASS);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Set Preferences Information . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_READ_GENERAL | USER_WRITE_PREFERENCES | USER_READ_PREFERENCES,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserPreferencesInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the fields to new values and write them out.
+ //
+
+ NameLength = ((USER_PREFERENCES_INFORMATION *)Buffer1)->UserComment.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->UserComment = DummyString2;
+ } else {
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->UserComment = DummyString1;
+ }
+
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->CountryCode += 1;
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->CodePage += 1;
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPreferencesInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserPreferencesInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((USER_PREFERENCES_INFORMATION *)Buffer1)->UserComment,
+ (PSTRING)&((USER_PREFERENCES_INFORMATION *)Buffer2)->UserComment,
+ TRUE)
+ &&
+ (((USER_PREFERENCES_INFORMATION *)Buffer1)->CountryCode ==
+ ((USER_PREFERENCES_INFORMATION *)Buffer2)->CountryCode)
+ &&
+ (((USER_PREFERENCES_INFORMATION *)Buffer1)->CodePage ==
+ ((USER_PREFERENCES_INFORMATION *)Buffer2)->CodePage)
+ ) {
+
+ printf("Succeeded\n");
+
+ //
+ // Change back some fields to keep from screwing up our database
+ //
+
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->CountryCode -= 1;
+ ((USER_PREFERENCES_INFORMATION *)Buffer1)->CodePage -= 1;
+
+ IgnoreStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPreferencesInformation,
+ Buffer1
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Values queried don't match values written\n");
+ printf(" UserComment Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_PREFERENCES_INFORMATION *)Buffer1)->UserComment);
+ printf(" UserComment Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_PREFERENCES_INFORMATION *)Buffer2)->UserComment);
+ printf(" CountryCode Written is 0x%lx\n",
+ (ULONG)((USER_PREFERENCES_INFORMATION *)Buffer1)->CountryCode);
+ printf(" CountryCode Retrieved is 0x%lx\n",
+ (ULONG)((USER_PREFERENCES_INFORMATION *)Buffer2)->CountryCode);
+ printf(" CodePage Written is 0x%lx\n",
+ (ULONG)((USER_PREFERENCES_INFORMATION *)Buffer1)->CodePage);
+ printf(" CodePage Retrieved is 0x%lx\n",
+ (ULONG)((USER_PREFERENCES_INFORMATION *)Buffer2)->CodePage);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Logon Information . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Make the parameter marshallable, but don't worry about values.
+ //
+
+ LogonInformation.UserName = DummyName1;
+ LogonInformation.FullName = DummyName1;
+ LogonInformation.HomeDirectory = DummyName1;
+ LogonInformation.HomeDirectoryDrive = DummyName1;
+ LogonInformation.ScriptPath = DummyName1;
+ LogonInformation.ProfilePath = DummyName1;
+ LogonInformation.WorkStations = DummyName1;
+
+ LogonInformation.LogonHours = DummyLogonHours;
+
+ Buffer = &LogonInformation;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserLogonInformation,
+ Buffer
+ );
+ if (NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected 0x%lx (INVALID_INFO_CLASS)\n", STATUS_INVALID_INFO_CLASS);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+
+ printf(" Set Logon Hours Information . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserLogonHoursInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+ ASSERT( ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours.LogonHours
+ != NULL); //Don't support zero length bit masks in this test yet.
+
+
+ //
+ // Change the field to a new value and write it out.
+ // We have two choices for out test:
+ // NoLogonRestriction
+ // DummyLogonHours
+ //
+ // They are guaranteed to have different values in the
+ // LOGON_HOURS_DIFFERENT_OFFSET byte of their respective bit masks.
+ //
+
+ if ( 0 == ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours.LogonHours[LOGON_HOURS_DIFFERENT_OFFSET]) {
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours = DummyLogonHours;
+ } else {
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours = NoLogonRestriction;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserLogonHoursInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserLogonHoursInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours.LogonHours[LOGON_HOURS_DIFFERENT_OFFSET]
+ ==
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer2)->LogonHours.LogonHours[LOGON_HOURS_DIFFERENT_OFFSET]
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Units Written are 0x%lx\n",
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours.UnitsPerWeek);
+ printf(" Units Retrieved are 0x%lx\n",
+ ((USER_LOGON_HOURS_INFORMATION *)Buffer2)->LogonHours.UnitsPerWeek);
+
+ printf(" Byte 0x%lx of the written bit mask is 0x%lx\n",
+ LOGON_HOURS_DIFFERENT_OFFSET,
+ (ULONG)((USER_LOGON_HOURS_INFORMATION *)Buffer1)->LogonHours.LogonHours[LOGON_HOURS_DIFFERENT_OFFSET]
+ );
+ printf(" Byte 0x%lx of the retrieved bit mask is 0x%lx\n",
+ LOGON_HOURS_DIFFERENT_OFFSET,
+ (ULONG)((USER_LOGON_HOURS_INFORMATION *)Buffer2)->LogonHours.LogonHours[LOGON_HOURS_DIFFERENT_OFFSET]
+ );
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+
+ printf(" Set Account Information . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT |
+ USER_READ_GENERAL |
+ USER_READ_PREFERENCES |
+ USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Make the parameter marshallable, but don't worry about values.
+ //
+
+ AccountInformation.UserName = DummyName1;
+ AccountInformation.FullName = DummyName1;
+ AccountInformation.HomeDirectory = DummyName1;
+ AccountInformation.HomeDirectoryDrive = DummyName1;
+ AccountInformation.ScriptPath = DummyName1;
+ AccountInformation.ProfilePath = DummyName1;
+ AccountInformation.AdminComment = DummyName1;
+ AccountInformation.WorkStations = DummyName1;
+
+ AccountInformation.LogonHours = DummyLogonHours;
+
+ Buffer = &AccountInformation;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserAccountInformation,
+ Buffer
+ );
+ if (NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ printf(" Expected 0x%lx (INVALID_INFO_CLASS)\n", STATUS_INVALID_INFO_CLASS);
+ TestStatus = FALSE;
+ }
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+ printf(" Set Home . . . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserHomeInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectory.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectory = DummyString2;
+ } else {
+ ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectory = DummyString1;
+ }
+
+ NameLength = ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectoryDrive.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectoryDrive = DummyString2;
+ } else {
+ ((USER_HOME_INFORMATION *)Buffer1)->HomeDirectoryDrive = DummyString1;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserHomeInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserHomeInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+
+ if (!RtlCompareString(
+ (PSTRING)&((USER_HOME_INFORMATION *)Buffer1)->HomeDirectory,
+ (PSTRING)&((USER_HOME_INFORMATION *)Buffer2)->HomeDirectory,
+ TRUE) ) {
+
+ if (!RtlCompareString(
+ (PSTRING)&((USER_HOME_INFORMATION *)Buffer1)->HomeDirectoryDrive,
+ (PSTRING)&((USER_HOME_INFORMATION *)Buffer2)->HomeDirectoryDrive,
+ TRUE)
+ ) {
+ printf("Succeeded\n");
+ } else {
+
+ printf("Failed\n");
+ printf(" Drive Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_HOME_INFORMATION *)Buffer1)->HomeDirectoryDrive);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_HOME_INFORMATION *)Buffer2)->HomeDirectoryDrive);
+
+ TestStatus = FALSE;
+ }
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Directory Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_HOME_INFORMATION *)Buffer1)->HomeDirectory);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_HOME_INFORMATION *)Buffer2)->HomeDirectory);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Script . . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserScriptInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((USER_SCRIPT_INFORMATION *)Buffer1)->ScriptPath.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_SCRIPT_INFORMATION *)Buffer1)->ScriptPath = DummyString2;
+ } else {
+ ((USER_SCRIPT_INFORMATION *)Buffer1)->ScriptPath = DummyString1;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserScriptInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserScriptInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((USER_SCRIPT_INFORMATION *)Buffer1)->ScriptPath,
+ (PSTRING)&((USER_SCRIPT_INFORMATION *)Buffer2)->ScriptPath,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_SCRIPT_INFORMATION *)Buffer1)->ScriptPath);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_SCRIPT_INFORMATION *)Buffer2)->ScriptPath);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Profile . . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserProfileInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((USER_PROFILE_INFORMATION *)Buffer1)->ProfilePath.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_PROFILE_INFORMATION *)Buffer1)->ProfilePath = DummyString2;
+ } else {
+ ((USER_PROFILE_INFORMATION *)Buffer1)->ProfilePath = DummyString1;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserProfileInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserProfileInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((USER_PROFILE_INFORMATION *)Buffer1)->ProfilePath,
+ (PSTRING)&((USER_PROFILE_INFORMATION *)Buffer2)->ProfilePath,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_PROFILE_INFORMATION *)Buffer1)->ProfilePath);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_PROFILE_INFORMATION *)Buffer2)->ProfilePath);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+
+
+ printf(" Set Admin Comment . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_GENERAL,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAdminCommentInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((USER_ADMIN_COMMENT_INFORMATION *)Buffer1)->AdminComment.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_ADMIN_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString2;
+ } else {
+ ((USER_ADMIN_COMMENT_INFORMATION *)Buffer1)->AdminComment = DummyString1;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserAdminCommentInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAdminCommentInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((USER_ADMIN_COMMENT_INFORMATION *)Buffer1)->AdminComment,
+ (PSTRING)&((USER_ADMIN_COMMENT_INFORMATION *)Buffer2)->AdminComment,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_ADMIN_COMMENT_INFORMATION *)Buffer1)->AdminComment);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_ADMIN_COMMENT_INFORMATION *)Buffer2)->AdminComment);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+
+
+ printf(" Set Workstations . . . . . . . . . . . . . . . . . . ");
+ printf("BROKEN TEST - NOT TESTED\n");
+#ifdef BROKEN
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Get the current value...
+ //
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserWorkStationsInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+
+ //
+ // Change the field to a new value and write it out.
+ //
+
+ NameLength = ((USER_WORKSTATIONS_INFORMATION *)Buffer1)->WorkStations.Length;
+ if ( NameLength == DummyString1.Length ) {
+ ((USER_WORKSTATIONS_INFORMATION *)Buffer1)->WorkStations = DummyString2;
+ } else {
+ ((USER_WORKSTATIONS_INFORMATION *)Buffer1)->WorkStations = DummyString1;
+ }
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserWorkStationsInformation,
+ Buffer1
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now check that the change was really made...
+ //
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserWorkStationsInformation,
+ &Buffer2
+ );
+ ASSERT(NT_SUCCESS( NtStatus ) );
+ if (
+ !RtlCompareString(
+ (PSTRING)&((USER_WORKSTATIONS_INFORMATION *)Buffer1)->WorkStations,
+ (PSTRING)&((USER_WORKSTATIONS_INFORMATION *)Buffer2)->WorkStations,
+ TRUE)
+ ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Value queried doesn't match value written\n");
+ printf(" Value Written is %wZ\n",
+ (PUNICODE_STRING)&((USER_WORKSTATIONS_INFORMATION *)Buffer1)->WorkStations);
+ printf(" Value Retrieved is %wZ\n",
+ (PUNICODE_STRING)&((USER_WORKSTATIONS_INFORMATION *)Buffer2)->WorkStations);
+
+ TestStatus = FALSE;
+
+ }
+
+ SamFreeMemory( Buffer1 );
+ SamFreeMemory( Buffer2 );
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ SamFreeMemory( Buffer1 );
+
+ }
+#endif //BROKEN
+
+
+ printf(" Set Internal1 . . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON | USER_FORCE_PASSWORD_CHANGE,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // We can't get the current values, since this level is only
+ // queryable by trusted clients. So just try setting a couple
+ // of values and make sure that we don't get an error.
+ //
+
+ Buffer1 = RtlAllocateHeap( RtlProcessHeap(), 0, sizeof(USER_INTERNAL1_INFORMATION) );
+ ASSERT( Buffer1 != NULL );
+
+ ((PUSER_INTERNAL1_INFORMATION)Buffer1)->NtPasswordPresent = FALSE;
+ ((PUSER_INTERNAL1_INFORMATION)Buffer1)->LmPasswordPresent = FALSE;
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserInternal1Information,
+ Buffer1
+ );
+
+ if (NtStatus != STATUS_PASSWORD_RESTRICTION) {
+
+ printf("Failed\n");
+ printf(" Expected Status = 0x%lx\n", STATUS_PASSWORD_RESTRICTION);
+ printf(" Received Status = 0x%lx\n", NtStatus );
+ TestStatus = FALSE;
+
+ } else {
+
+ //
+ // The NULL password worked, so let's try a real password.
+ //
+
+ NtStatus = RtlCalculateNtOwfPassword(
+ &DummyName1,
+ &((PUSER_INTERNAL1_INFORMATION)Buffer1)->NtOwfPassword
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ((PUSER_INTERNAL1_INFORMATION)Buffer1)->NtPasswordPresent = TRUE;
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ DUMMY_STRING1,
+ &((PUSER_INTERNAL1_INFORMATION)Buffer1)->LmOwfPassword
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ((PUSER_INTERNAL1_INFORMATION)Buffer1)->LmPasswordPresent = TRUE;
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserInternal1Information,
+ Buffer1
+ );
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Return status was %lx\n", NtStatus );
+ TestStatus = FALSE;
+ }
+ }
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Buffer1 );
+
+
+// This is the code that used to be here, when UserInternal1Information was
+// queryable by non-trusted clients...
+//
+// Buffer1 = NULL;
+// NtStatus = SamQueryInformationUser(
+// UserHandle1,
+// UserInternal1Information,
+// &Buffer1
+// );
+// TST_SUCCESS_ASSERT(NtStatus);
+// ASSERT(Buffer1 != NULL);
+//
+// //
+// // The passwords were initially empty. Put in some random
+// // OWF passwords, and have them written out.
+// //
+//
+// NtStatus = RtlCalculateNtOwfPassword(
+// (PNT_PASSWORD)&DummyName1,
+// &EncryptedPasswordBuffer
+// );
+//
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseSensitiveUnicode.Buffer = (PCHAR)&EncryptedPasswordBuffer;
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseSensitiveUnicode.Length = 16;
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseSensitiveUnicode.MaximumLength = 16;
+//
+// NtStatus = RtlCalculateNtOwfPassword(
+// (PNT_PASSWORD)&DummyName2,
+// &EncryptedPasswordBuffer2
+// );
+//
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseInsensitiveDbcs.Buffer = (PCHAR)&EncryptedPasswordBuffer2;
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseInsensitiveDbcs.Length = 16;
+// ((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseInsensitiveDbcs.MaximumLength = 16;
+//
+// NtStatus = SamSetInformationUser(
+// UserHandle1,
+// UserInternal1Information,
+// Buffer1
+// );
+// if ( NT_SUCCESS(NtStatus) ) {
+//
+// //
+// // Now check that the change was really made...
+// //
+//
+// NtStatus = SamQueryInformationUser(
+// UserHandle1,
+// UserInternal1Information,
+// &Buffer2
+// );
+// ASSERT(NT_SUCCESS( NtStatus ) );
+//
+// if ( (
+// !RtlCompareString(
+// (PSTRING)&((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseSensitiveUnicode,
+// (PSTRING)&((USER_INTERNAL1_INFORMATION *)Buffer2)->CaseSensitiveUnicode,
+// TRUE)
+// ) || (
+// !RtlCompareString(
+// (PSTRING)&((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseInsensitiveDbcs,
+// (PSTRING)&((USER_INTERNAL1_INFORMATION *)Buffer2)->CaseInsensitiveDbcs,
+// TRUE)
+// ) ) {
+//
+// printf("Succeeded\n");
+//
+// } else {
+//
+// printf("Failed\n");
+// printf(" Value queried doesn't match value written\n");
+// printf(" CaseInsensitiveDbcs Written is %wZ\n",
+// (PUNICODE_STRING)&((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseInsensitiveDbcs);
+// printf(" CaseInsensitiveDbcs Retrieved is %wZ\n",
+// (PUNICODE_STRING)&((USER_INTERNAL1_INFORMATION *)Buffer2)->CaseInsensitiveDbcs);
+// printf(" CaseSensitiveUnicode Written is %wZ\n",
+// (PUNICODE_STRING)&((USER_INTERNAL1_INFORMATION *)Buffer1)->CaseSensitiveUnicode);
+// printf(" CaseSensitiveUnicode Retrieved is %wZ\n",
+// (PUNICODE_STRING)&((USER_INTERNAL1_INFORMATION *)Buffer2)->CaseSensitiveUnicode);
+//
+// TestStatus = FALSE;
+//
+// }
+//
+// SamFreeMemory( Buffer1 );
+// SamFreeMemory( Buffer2 );
+//
+// } else {
+// printf("Failed\n");
+// printf(" Completion status is 0x%lx\n", NtStatus);
+// TestStatus = FALSE;
+// SamFreeMemory( Buffer1 );
+//
+// }
+
+
+
+ printf(" Set Internal2 . . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_LOGON,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // We can't get the current values, since this level is only
+ // queryable by trusted clients. We can't set either, but
+ // try it and make sure we get the correct error.
+ //
+
+ Buffer1 = RtlAllocateHeap( RtlProcessHeap(), 0, sizeof(USER_INTERNAL2_INFORMATION) );
+ ASSERT( Buffer1 != NULL );
+
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.HighPart = 1;
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.HighPart = 2;
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.LowPart = 3;
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.LowPart = 4;
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->BadPasswordCount = 5;
+ ((USER_INTERNAL2_INFORMATION *)Buffer1)->LogonCount = 6;
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserInternal2Information,
+ Buffer1
+ );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Buffer1 );
+
+ if ( NtStatus == STATUS_INVALID_INFO_CLASS ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Expected Status = 0x%lx\n", STATUS_INVALID_INFO_CLASS);
+ printf(" Received Status = 0x%lx\n", NtStatus );
+ TestStatus = FALSE;
+ }
+
+// This is the code that was here when UserInternal2Information could be
+// queried and set by non-trusted clients...
+//
+// //
+// // Get the current values...
+// //
+//
+// Buffer1 = NULL;
+// NtStatus = SamQueryInformationUser(
+// UserHandle1,
+// UserInternal2Information,
+// &Buffer1
+// );
+// TST_SUCCESS_ASSERT(NtStatus);
+// ASSERT(Buffer1 != NULL);
+//
+// //
+// // Now change the fields and write them out.
+// //
+//
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.HighPart += 1;
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.HighPart += 1;
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.LowPart += 2;
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.LowPart += 2;
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->BadPasswordCount += 1;
+// ((USER_INTERNAL2_INFORMATION *)Buffer1)->LogonCount += 1;
+//
+// NtStatus = SamSetInformationUser(
+// UserHandle1,
+// UserInternal2Information,
+// Buffer1
+// );
+// if ( NT_SUCCESS(NtStatus) ) {
+//
+// //
+// // Now check that the change was really made...
+// //
+//
+// NtStatus = SamQueryInformationUser(
+// UserHandle1,
+// UserInternal2Information,
+// &Buffer2
+// );
+// ASSERT(NT_SUCCESS( NtStatus ) );
+// if (
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.HighPart ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->LastLogon.HighPart) &&
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogon.LowPart ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->LastLogon.LowPart) &&
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.HighPart ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->LastLogoff.HighPart) &&
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->LastLogoff.LowPart ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->LastLogoff.LowPart) &&
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->BadPasswordCount ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->BadPasswordCount) &&
+// (((USER_INTERNAL2_INFORMATION *)Buffer1)->LogonCount ==
+// ((USER_INTERNAL2_INFORMATION *)Buffer2)->LogonCount)
+// ) {
+//
+// printf("Succeeded\n");
+//
+// } else {
+//
+// printf("Failed\n");
+// printf(" Value queried doesn't match value written\n");
+//
+// TestStatus = FALSE;
+//
+// }
+//
+// SamFreeMemory( Buffer1 );
+// SamFreeMemory( Buffer2 );
+//
+// } else {
+// printf("Failed\n");
+// printf(" Completion status is 0x%lx\n", NtStatus);
+// TestStatus = FALSE;
+// SamFreeMemory( Buffer1 );
+//
+// }
+
+
+
+ printf(" Set Password . . . . . . . . . . . . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_FORCE_PASSWORD_CHANGE,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ //
+ // Create a fake cleartext UNICODE password and write it out.
+ //
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserSetPasswordInformation,
+ &DummyName2
+ );
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // We can't verify that it really worked, so we just have
+ // to trust the return code.
+ //
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Return code was %lx\n", NtStatus );
+ TestStatus = FALSE;
+ }
+
+
+
+ printf(" Set Control . . . . . . . . . . . . . . . . . . . . . ");
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_ACCOUNT,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserControlInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+ //
+ // Change the value and write it back
+ //
+
+ ((USER_CONTROL_INFORMATION *)Buffer1)->UserAccountControl ^= USER_HOME_DIRECTORY_REQUIRED;
+
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserControlInformation,
+ Buffer1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check the written value to make sure it stuck
+ //
+
+ Buffer2 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserControlInformation,
+ &Buffer2
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer2 != NULL);
+
+ if ( ((USER_CONTROL_INFORMATION *)Buffer1)->UserAccountControl ==
+ ((USER_CONTROL_INFORMATION *)Buffer2)->UserAccountControl ) {
+
+ printf("Succeeded\n");
+
+ SamFreeMemory( Buffer2 );
+
+ //
+ // Make sure the account is left enabled to prevent problems.
+ //
+
+ ((USER_CONTROL_INFORMATION *)Buffer1)->UserAccountControl &= ~USER_ACCOUNT_DISABLED;
+
+ IgnoreStatus = SamSetInformationUser(
+ UserHandle1,
+ UserControlInformation,
+ Buffer1
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+ printf("Failed\n");
+ printf(" Returned Value Doesn't Match Set Value.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer1 );
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+
+ printf(" Set Expires . . . . . . . . . . . . . . . . . . . . . ");
+ printf("BROKEN TEST - NOT TESTED\n");
+#ifdef BROKEN
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_WRITE_ACCOUNT | USER_READ_ACCOUNT,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserExpiresInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+ //
+ // Change the value and write it back
+ //
+
+ ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.LowPart += 1234;
+ ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.HighPart += 1234;
+
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserExpiresInformation,
+ Buffer1
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check the written value to make sure it stuck
+ //
+
+ Buffer2 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserExpiresInformation,
+ &Buffer2
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer2 != NULL);
+
+ if ( ( ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.LowPart ==
+ ((USER_EXPIRES_INFORMATION *)Buffer2)->AccountExpires.LowPart ) &&
+ ( ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.HighPart ==
+ ((USER_EXPIRES_INFORMATION *)Buffer2)->AccountExpires.HighPart ) ) {
+
+ printf("Succeeded\n");
+
+ SamFreeMemory( Buffer2 );
+
+ //
+ // Change the values back
+ //
+
+ ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.LowPart += 1234;
+ ((USER_EXPIRES_INFORMATION *)Buffer1)->AccountExpires.HighPart += 1234;
+
+ IgnoreStatus = SamSetInformationUser(
+ UserHandle1,
+ UserExpiresInformation,
+ Buffer1
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+ printf("Failed\n");
+ printf(" Returned Value Doesn't Match Set Value.\n");
+ TestStatus = FALSE;
+ }
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+ SamFreeMemory( Buffer1 );
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+#endif //BROKEN
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Change Password For User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Change Password For User . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Change Password For Well-Known User . . . . . . . . . ");
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_CHANGE_PASSWORD,
+ DOMAIN_USER_RID_ADMIN,
+ &UserHandle1
+ );
+ ASSERT(NT_SUCCESS(NtStatus) );
+
+ Buffer = NULL;
+
+ //
+ // The current password is DummyName2. Using DummyName2 as the
+ // old password, change it to DummyName1 and make sure we get
+ // STATUS_SUCCESS.
+ //
+
+ NtStatus = SamChangePasswordUser(
+ UserHandle1,
+ &DummyName2,
+ &DummyName1
+ );
+
+ //
+ // The current password is DummyName1. Using something WRONG for
+ // the old password, try to change it to DummyName2 and make sure
+ // it doesn't succeed.
+ //
+
+ if ( NtStatus == STATUS_SUCCESS ) {
+
+ NtStatus = SamChangePasswordUser(
+ UserHandle1,
+ &DummyName2,
+ &DummyName2
+ );
+
+ if ( NtStatus == STATUS_SUCCESS ) {
+
+ NtStatus = STATUS_UNSUCCESSFUL;
+
+ } else {
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+
+ //
+ // The current password is DummyName1. Using DummyName1 as the
+ // old password, change it to DummyName2 and make sure it works
+ // since by default there is no password history.
+ //
+
+ if ( NtStatus == STATUS_SUCCESS ) {
+
+ NtStatus = SamChangePasswordUser(
+ UserHandle1,
+ &DummyName1,
+ &DummyName2
+ );
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ printf("Succeeded\n");
+
+ } else {
+
+ printf("Failed\n");
+ printf(" Status is %lx\n", NtStatus);
+
+ TestStatus = FALSE;
+ }
+
+ IgnoreStatus = SamCloseHandle( UserHandle1 );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ }
+
+// END PASS #1
+
+ if (Pass == 2) {
+
+ printf("\n");
+ printf("\n");
+ printf(" User (Pass #2) . . . . . . . . . . . . . . . . . . . Test\n");
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Delete User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Delete User . . . . . . . . . . . . . . . . . . . . Suite\n");
+
+
+
+ printf(" Delete Normal User . . . . . . . . . . . . . . . . . ");
+
+ //
+ // This User was created in pass #1
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ RtlFreeUnicodeString( &AccountNames[0] );
+
+
+
+ UserHandle1 = NULL;
+
+ NtStatus = SamOpenUser( DomainHandle, DELETE, LookedUpRids[0], &UserHandle1 );
+ TST_SUCCESS_ASSERT(NtStatus);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+
+ NtStatus = SamDeleteUser( UserHandle1 );
+ if (NT_SUCCESS(NtStatus)) {
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+
+
+ printf(" Delete Admin Group Member . . . . . . . . . . . . . . ");
+ printf("(Unimplemented)\n");
+
+ printf(" Delete Last Admin Group Member . . . . . . . . . . . ");
+ printf("(Unimplemented)\n");
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // //
+ // Set User Suite //
+ // //
+ ///////////////////////////////////////////////////////////////////////////
+
+ printf("\n");
+ printf(" Set User (Pass 2) . . . . . . . . . . . . . . . . . . Suite\n");
+
+ printf(" Set ALL information. . . . . . . . . . . . ");
+ printf("BROKEN TEST - NOT TESTED\n");
+#ifdef BROKEN
+
+ RtlInitString( &AccountNameAnsi, "AllUser" );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ All = NULL;
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAllInformation,
+ &All
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Now change some of the data, and set it
+ //
+
+ RtlInitString( &TmpAnsiString, "FullName" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->FullName),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "HomeDirectory" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->HomeDirectory),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "HomeDirectoryDrive" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->HomeDirectoryDrive),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "ScriptPath" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->ScriptPath),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "ProfilePath" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->ProfilePath),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "AdminComment" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->AdminComment),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "WorkStations" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->WorkStations),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "UserComment" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->UserComment),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ RtlInitString( &TmpAnsiString, "Parameters" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->Parameters),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ All->CountryCode = 7;
+ All->CodePage = 8;
+
+ All->PasswordExpired = TRUE;
+ All->NtPasswordPresent = TRUE;
+ All->LmPasswordPresent = FALSE;
+
+ RtlInitString( &TmpAnsiString, "NtPassword" );
+ TmpStatus = RtlAnsiStringToUnicodeString(
+ (PUNICODE_STRING)(&All->NtPassword),
+ &TmpAnsiString,
+ TRUE );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ 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_COUNTRYCODE |
+ USER_ALL_CODEPAGE |
+ USER_ALL_PASSWORDEXPIRED |
+ USER_ALL_NTPASSWORDPRESENT |
+ USER_ALL_LOGONHOURS );
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserAllInformation,
+ All
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserAllInformation,
+ &All2
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Verify that queried info is as we set it
+ //
+
+ if (
+
+ //
+ // Fields that we didn't touch. Note that
+ // PasswordMustChange changed anyway, since we
+ // changed from a null to a non-null password.
+ //
+
+ ( All2->WhichFields != (USER_ALL_READ_GENERAL_MASK |
+ USER_ALL_READ_PREFERENCES_MASK |
+ USER_ALL_READ_ACCOUNT_MASK |
+ USER_ALL_READ_LOGON_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) ||
+ ( All->UserId != All2->UserId ) ||
+ ( All->PrimaryGroupId != All2->PrimaryGroupId ) ||
+ ( All->UserAccountControl != All2->UserAccountControl ) ||
+ ( All->PrivateDataSensitive !=
+ All2->PrivateDataSensitive ) ||
+
+ // Fields that we changed. Note that we set
+ // NtPasswordSet, but it shouldn't be set on return.
+
+ (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->CountryCode != All2->CountryCode ) ||
+ ( All->CodePage != All2->CodePage ) ||
+ ( All->LmPasswordPresent != All2->LmPasswordPresent ) ||
+ ( All->NtPasswordPresent == All2->NtPasswordPresent ) ||
+ ( All->LogonHours.UnitsPerWeek !=
+ All2->LogonHours.UnitsPerWeek )
+ ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+
+ SamFreeMemory( All2 );
+ }
+ }
+
+ SamFreeMemory( All );
+ }
+
+ if (NtStatus == STATUS_SUCCESS) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+ //
+ // Now get rid of the user account if necessary
+ //
+
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+#endif //BROKEN
+
+
+ printf(" Set Primary Group (non member). . . . . . . . . . . . ");
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ RtlFreeUnicodeString( &AccountNames[0] );
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ LookedUpRids[0],
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // The user is not a member of DOMAIN_GROUP_RID_ADMINS.
+ // See if we can make this group the user's primary group
+ //
+
+ ASSERT(sizeof(GroupRid) == sizeof(USER_PRIMARY_GROUP_INFORMATION));
+ GroupRid = DOMAIN_GROUP_RID_ADMINS;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &GroupRid
+ );
+
+ if (NtStatus == STATUS_MEMBER_NOT_IN_GROUP) {
+
+ printf("Succeeded\n");
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Now get rid of the user account if necessary
+ //
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+ printf(" Set Primary Group (member). . . . . . . . . . . . . . ");
+
+ //
+ // Make a user (might already exist)
+ // Make a group
+ // Make the group the user's primary group
+ // Change the user so the group isn't the primary group
+ // remove the group
+ // delete the group
+ // If we created the user, delete it.
+
+ //
+ // The following user might already exist (from earlier in the test)
+ //
+
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ UserRid = 0;
+ UserHandle1 = NULL;
+ NtStatus = SamCreateUserInDomain(
+ DomainHandle,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserHandle1,
+ &UserRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ DeleteUser = TRUE;
+ if (NtStatus == STATUS_USER_EXISTS) {
+ DeleteUser = FALSE;
+ RtlInitString( &AccountNameAnsi, USER_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountNames[0], &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AccountNames[0],
+ &LookedUpRids,
+ &LookedUpUses
+ );
+ RtlFreeUnicodeString( &AccountNames[0] );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(LookedUpUses[0] == SidTypeUser);
+ UserRid = LookedUpRids[0];
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_ALL_ACCESS,
+ UserRid,
+ &UserHandle1);
+ SamFreeMemory( LookedUpUses ); SamFreeMemory( LookedUpRids );
+ }
+
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // create the group
+ //
+
+ RtlInitString( &AccountNameAnsi, GROUP_NAME1 );
+ NtStatus = RtlAnsiStringToUnicodeString( &AccountName, &AccountNameAnsi, TRUE );
+ TST_SUCCESS_ASSERT(NtStatus);
+
+ //InitializeObjectAttributes( &ObjectAttributes, &AccountName, 0, 0, NULL );
+
+ GroupRid = 0;
+ GroupHandle1 = NULL;
+ NtStatus = SamCreateGroupInDomain(
+ DomainHandle,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupHandle1,
+ &GroupRid
+ );
+ RtlFreeUnicodeString( &AccountName );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Make the user a member of this group
+ //
+
+ NtStatus = SamAddMemberToGroup(
+ GroupHandle1,
+ UserRid,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // Set the user's primary group Id to be this group
+ //
+
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &GroupRid
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ Buffer1 = NULL;
+ NtStatus = SamQueryInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &Buffer1
+ );
+ TST_SUCCESS_ASSERT(NtStatus);
+ ASSERT(Buffer1 != NULL);
+
+ if ( ((USER_PRIMARY_GROUP_INFORMATION *)Buffer1)->PrimaryGroupId ==
+ GroupRid ) {
+
+ printf("Succeeded\n");
+
+ SamFreeMemory( Buffer1 );
+ } else {
+
+ printf("Failed\n");
+ printf(" Returned Value Doesn't Match Set Value.\n");
+ printf(" Value written is: 0x%lx\n", GroupRid);
+ printf(" Value retrieved is: 0x%lx\n",
+ ((USER_PRIMARY_GROUP_INFORMATION *)Buffer1)->PrimaryGroupId);
+ TestStatus = FALSE;
+
+ }
+
+ } else {
+ printf("Failed\n");
+ printf(" Completion status is 0x%lx\n", NtStatus);
+ TestStatus = FALSE;
+ }
+
+
+ //
+ // Set the user's primary group Id back and remove the user
+ // from the group
+ //
+
+ GroupRid = DOMAIN_GROUP_RID_USERS;
+ NtStatus = SamSetInformationUser(
+ UserHandle1,
+ UserPrimaryGroupInformation,
+ &GroupRid
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+ NtStatus = SamRemoveMemberFromGroup(GroupHandle1, UserRid);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+ //
+ // Now get rid of the group and possibly the user account
+ //
+
+
+ NtStatus = SamDeleteGroup( GroupHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ if (DeleteUser == TRUE) {
+ NtStatus = SamDeleteUser( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ } else {
+ NtStatus = SamCloseHandle( UserHandle1 );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+
+
+
+
+
+
+ printf(" Set Name Information . . . . . . . . . . . . . . . . ");
+ printf("(Untested)\n");
+
+
+ }
+
+ return(TestStatus);
+
+
+
+}
diff --git a/private/newsam/client/tshut.c b/private/newsam/client/tshut.c
new file mode 100644
index 000000000..2def0d2d1
--- /dev/null
+++ b/private/newsam/client/tshut.c
@@ -0,0 +1,119 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tshut.c
+
+Abstract:
+
+ This test is used to shut-down a SAM server. This might be useful
+ for killing SAM without rebooting during development.
+
+Author:
+
+ Jim Kelly (JimK) 12-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <nt.h>
+#include <ntsam.h>
+#include <ntrtl.h> // DbgPrint()
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+VOID
+main (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This is the main entry routine for this test.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAM_HANDLE ServerHandle;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
+
+
+ NtStatus = SamConnect(
+ NULL, // ServerName (Local machine)
+ &ServerHandle,
+ SAM_SERVER_ALL_ACCESS,
+ &ObjectAttributes
+ );
+
+ DbgPrint("SAM TEST (Tshut): Status of SamConnect() is: 0x%lx\n", NtStatus);
+ if (!NT_SUCCESS(NtStatus)) { return; }
+
+
+ NtStatus = SamShutdownSamServer( ServerHandle );
+ DbgPrint("SAM TEST (Tshut): Status of SamShutdownSamServer() is: 0x%lx\n", NtStatus);
+ if (!NT_SUCCESS(NtStatus)) { return; }
+
+
+ //
+ // I'm not sure why, but it seems to take another awakening of the
+ // server to make it die.
+ //
+
+ NtStatus = SamConnect(
+ NULL, // ServerName (Local machine)
+ &ServerHandle,
+ SAM_SERVER_ALL_ACCESS,
+ &ObjectAttributes
+ );
+
+
+
+ return;
+}
diff --git a/private/newsam/client/wrappers.c b/private/newsam/client/wrappers.c
new file mode 100644
index 000000000..8e00f5ea8
--- /dev/null
+++ b/private/newsam/client/wrappers.c
@@ -0,0 +1,8849 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ wrappers.c
+
+Abstract:
+
+ This file contains all SAM rpc wrapper routines.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "samclip.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Private defines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#define SAMP_MAXIMUM_SUB_LOOKUP_COUNT ((ULONG) 0x00000200)
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Local data types //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// This structure is used to pass a (potentially) very large
+// user requested name lookup into (possibly many) smaller
+// remote lookup requests. This is necessary to prevent the
+// server from being asked to allocate huge chunks of memory,
+// potentially running out of memory.
+//
+// There will be one of these structures for each server call
+// that is necessary.
+//
+
+typedef struct _SAMP_NAME_LOOKUP_CALL {
+
+ //
+ // Each call is represented by one of these structures.
+ // The structures are chained together to show the order
+ // the calls were made (allowing an easy way to build the
+ // buffer that is to be returned to the user).
+ //
+
+ LIST_ENTRY Link;
+
+ //
+ // These fields define the beginning and ending indexes into
+ // the user passed Names buffer that are being represented by
+ // this call.
+ //
+
+ ULONG StartIndex;
+ ULONG Count;
+
+
+ //
+ // These fields will receive the looked up RIDs and USE buffers.
+ // Notice that the .Element fields of these fields will receive
+ // pointers to the bulk of the returned information.
+ //
+
+ SAMPR_ULONG_ARRAY RidBuffer;
+ SAMPR_ULONG_ARRAY UseBuffer;
+
+} SAMP_NAME_LOOKUP_CALL, *PSAMP_NAME_LOOKUP_CALL;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampLookupIdsInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN PULONG RelativeIds,
+ OUT PUNICODE_STRING *Names,
+ OUT PSID_NAME_USE *Use OPTIONAL
+ );
+
+NTSTATUS
+SampMapCompletionStatus(
+ IN NTSTATUS Status
+ );
+
+NTSTATUS
+SampCalculateLmPassword(
+ IN PUNICODE_STRING NtPassword,
+ OUT PCHAR *LmPasswordBuffer
+ );
+
+NTSTATUS
+SampCheckPasswordRestrictions(
+ IN SAMPR_HANDLE UserHandle,
+ IN PUNICODE_STRING NewNtPassword,
+ OUT PBOOLEAN UseOwfPasswords
+ );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// General services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SamFreeMemory(
+ IN PVOID Buffer
+ )
+
+/*++
+Routine Description:
+
+
+ Some SAM services that return a potentially large amount of memory,
+ such as an enumeration might, allocate the buffer in which the data
+ is returned. This function is used to free those buffers when they
+ are no longer needed.
+
+Parameters:
+
+ Buffer - Pointer to the buffer to be freed. This buffer must
+ have been allocated by a previous SAM service call.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+
+--*/
+{
+ MIDL_user_free( Buffer );
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SamSetSecurityObject(
+ IN SAM_HANDLE ObjectHandle,
+ IN SECURITY_INFORMATION SecurityInformation,
+ IN PSECURITY_DESCRIPTOR SecurityDescriptor
+ )
+
+/*++
+Routine Description:
+
+
+ This function (SamSetSecurityObject) takes a well formed Security
+ Descriptor provided by the caller and assigns specified portions of
+ it to an object. Based on the flags set in the SecurityInformation
+ parameter and the caller's access rights, this procedure will
+ replace any or all of the security information associated with an
+ object.
+
+ This is the only function available to users and applications for
+ changing security information, including the owner ID, group ID, and
+ the discretionary and system ACLs of an object. The caller must
+ have WRITE_OWNER access to the object to change the owner or primary
+ group of the object. The caller must have WRITE_DAC access to the
+ object to change the discretionary ACL. The caller must have
+ ACCESS_SYSTEM_SECURITY access to an object to assign a system ACL
+ to the object.
+
+ This API is modelled after the NtSetSecurityObject() system service.
+
+
+Parameters:
+
+ ObjectHandle - A handle to an existing object.
+
+ SecurityInformation - Indicates which security information is to
+ be applied to the object. The value(s) to be assigned are
+ passed in the SecurityDescriptor parameter.
+
+ SecurityDescriptor - A pointer to a well formed Security
+ Descriptor.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ either WRITE_OWNER, WRITE_DAC, or ACCESS_SYSTEM_SECURITY
+ access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened SAM object.
+
+ STATUS_BAD_DESCRIPTOR_FORMAT - Indicates something about security descriptor
+ is not valid. This may indicate that the structure of the descriptor is
+ not valid or that a component of the descriptor specified via the
+ SecurityInformation parameter is not present in the security descriptor.
+
+ STATUS_INVALID_PARAMETER - Indicates no security information was specified.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ ULONG SDLength;
+ SAMPR_SR_SECURITY_DESCRIPTOR DescriptorToPass;
+
+
+
+
+
+ //
+ // Make a self relative security descriptor for use in the RPC call..
+ //
+
+
+ SDLength = 0;
+ NtStatus = RtlMakeSelfRelativeSD(
+ SecurityDescriptor,
+ NULL,
+ &SDLength
+ );
+
+ if (NtStatus != STATUS_BUFFER_TOO_SMALL) {
+
+ return(STATUS_INVALID_PARAMETER);
+
+ } else {
+
+
+ DescriptorToPass.SecurityDescriptor = MIDL_user_allocate( SDLength );
+ DescriptorToPass.Length = SDLength;
+ if (DescriptorToPass.SecurityDescriptor == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+
+ //
+ // make an appropriate self-relative security descriptor
+ //
+
+ NtStatus = RtlMakeSelfRelativeSD(
+ SecurityDescriptor,
+ (PSECURITY_DESCRIPTOR)DescriptorToPass.SecurityDescriptor,
+ &SDLength
+ );
+ }
+
+ }
+
+
+
+
+
+
+
+ //
+ // Call the server ...
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ RpcTryExcept{
+
+ NtStatus =
+ SamrSetSecurityObject(
+ (SAMPR_HANDLE)ObjectHandle,
+ SecurityInformation,
+ &DescriptorToPass
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+ }
+
+ MIDL_user_free( DescriptorToPass.SecurityDescriptor );
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamQuerySecurityObject(
+ IN SAM_HANDLE ObjectHandle,
+ IN SECURITY_INFORMATION SecurityInformation,
+ OUT PSECURITY_DESCRIPTOR * SecurityDescriptor
+ )
+/*++
+
+Routine Description:
+
+
+ This function (SamQuerySecurityObject) returns to the caller requested
+ security information currently assigned to an object.
+
+ Based on the caller's access rights this procedure
+ will return a security descriptor containing any or all of the
+ object's owner ID, group ID, discretionary ACL or system ACL. To
+ read the owner ID, group ID, or the discretionary ACL the caller
+ must be granted READ_CONTROL access to the object. To read the
+ system ACL the caller must be granted ACCESS_SYSTEM_SECURITY
+ access.
+
+ This API is modelled after the NtQuerySecurityObject() system
+ service.
+
+
+Parameters:
+
+ ObjectHandle - A handle to an existing object.
+
+ SecurityInformation - Supplies a value describing which pieces of
+ security information are being queried.
+
+ SecurityDescriptor - Receives a pointer to the buffer containing
+ the requested security information. This information is
+ returned in the form of a self-relative security descriptor.
+ The caller is responsible for freeing the returned buffer
+ (using SamFreeMemory()) when the security descriptor
+ is no longer needed.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ either READ_CONTROL or ACCESS_SYSTEM_SECURITY
+ access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened SAM object.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAMPR_SR_SECURITY_DESCRIPTOR ReturnedSD;
+ PSAMPR_SR_SECURITY_DESCRIPTOR PReturnedSD;
+
+ //
+ // The retrieved security descriptor is returned via a data structure that
+ // looks like:
+ //
+ // +-----------------------+
+ // | Length (bytes) |
+ // |-----------------------| +--------------+
+ // | SecurityDescriptor ---|--------->| Self-Relative|
+ // +-----------------------+ | Security |
+ // | Descriptor |
+ // +--------------+
+ //
+ // The first of these buffers is a local stack variable. The buffer containing
+ // the self-relative security descriptor is allocated by the RPC runtime. The
+ // pointer to the self-relative security descriptor is what is passed back to our
+ // caller.
+ //
+ //
+
+ //
+ // To prevent RPC from trying to marshal a self-relative security descriptor,
+ // make sure its field values are appropriately initialized to zero and null.
+ //
+
+ ReturnedSD.Length = 0;
+ ReturnedSD.SecurityDescriptor = NULL;
+
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ PReturnedSD = &ReturnedSD;
+ NtStatus =
+ SamrQuerySecurityObject(
+ (SAMPR_HANDLE)ObjectHandle,
+ SecurityInformation,
+ &PReturnedSD
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ (*SecurityDescriptor) = ReturnedSD.SecurityDescriptor;
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamCloseHandle(
+ OUT SAM_HANDLE SamHandle
+ )
+
+/*++
+Routine Description:
+
+ This API closes a currently opened SAM object.
+
+Arguments:
+
+ SamHandle - Specifies the handle of a previously opened SAM object to
+ close.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The object was successfully closed.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAMPR_HANDLE TmpHandle;
+
+
+ if (SamHandle == NULL) {
+ return(STATUS_INVALID_HANDLE);
+ }
+
+ //
+ // Call the server ...
+ //
+
+ TmpHandle = (SAMPR_HANDLE)SamHandle;
+
+ RpcTryExcept{
+
+ NtStatus = SamrCloseHandle(
+ (SAMPR_HANDLE *)(&TmpHandle)
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+ if ((NtStatus != RPC_NT_SS_CONTEXT_MISMATCH) &&
+ (NtStatus != RPC_NT_INVALID_BINDING)) {
+ (void) RpcSsDestroyClientContext( &TmpHandle);
+ }
+
+ } RpcEndExcept;
+
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Server object related services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamConnect(
+ IN PUNICODE_STRING ServerName,
+ OUT PSAM_HANDLE ServerHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN POBJECT_ATTRIBUTES ObjectAttributes
+ )
+
+/*++
+Routine Description:
+
+ Establish a session with a SAM subsystem and subsequently open the
+ SamServer object of that subsystem. The caller must have
+ SAM_SERVER_CONNECT access to the SamServer object of the subsystem
+ being connected to.
+
+ The handle returned is for use in future calls.
+
+
+Arguments:
+
+ ServerName - Name of the server to use, or NULL if local.
+
+ ServerHandle - A handle to be used in future requests. This handle
+ represents both the handle to the SamServer object and the RPC
+ context handle for the connection to the SAM subsystem.
+
+ DesiredAccess - Is an access mask indicating which access types are
+ desired to the SamServer. These access types are reconciled
+ with the Discretionary Access Control list of the SamServer to
+ determine whether the accesses will be granted or denied. The
+ access type of SAM_SERVER_CONNECT is always implicitly included
+ in this access mask.
+
+ ObjectAttributes - Pointer to the set of object attributes to use for
+ this connection. Only the security Quality Of Service
+ information is used and should provide SecurityIdentification
+ level of impersonation.
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Access was denied.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ PSAMPR_SERVER_NAME RServerName;
+ PSAMPR_SERVER_NAME RServerNameWithNull;
+ USHORT RServerNameWithNullLength;
+
+ //
+ // Hmmm - what to do with security QOS???
+ //
+
+
+ //
+ // Call the server, passing either a NULL Server Name pointer, or
+ // a pointer to a Unicode Buffer with a Wide Character NULL terminator.
+ // Since the input name is contained in a counted Unicode String, there
+ // is no NULL terminator necessarily provided, so we must append one.
+ //
+
+ RServerNameWithNull = NULL;
+
+ if (ARGUMENT_PRESENT(ServerName)) {
+
+ RServerName = (PSAMPR_SERVER_NAME)(ServerName->Buffer);
+ RServerNameWithNullLength = ServerName->Length + (USHORT) sizeof(WCHAR);
+ RServerNameWithNull = MIDL_user_allocate( RServerNameWithNullLength );
+
+ if (RServerNameWithNull == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlCopyMemory( RServerNameWithNull, RServerName, ServerName->Length);
+ RServerNameWithNull[ServerName->Length/sizeof(WCHAR)] = L'\0';
+ }
+
+ RpcTryExcept {
+
+ NtStatus = SamrConnect2(
+ RServerNameWithNull,
+ (SAMPR_HANDLE *)ServerHandle,
+ DesiredAccess
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ //
+ // If the new connect call failed because it didn't exist,
+ // try the old one.
+ //
+
+ if ((NtStatus == RPC_NT_UNKNOWN_IF) ||
+ (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) {
+
+ RpcTryExcept {
+
+ NtStatus = SamrConnect(
+ RServerNameWithNull,
+ (SAMPR_HANDLE *)ServerHandle,
+ DesiredAccess
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+ }
+
+ if (RServerNameWithNull != NULL) {
+ MIDL_user_free( RServerNameWithNull );
+ }
+
+ return(SampMapCompletionStatus(NtStatus));
+
+ DBG_UNREFERENCED_PARAMETER(ObjectAttributes);
+
+}
+
+
+NTSTATUS
+SamShutdownSamServer(
+ IN SAM_HANDLE ServerHandle
+ )
+
+/*++
+Routine Description:
+
+ This is the wrapper routine for SamShutdownSamServer().
+
+Arguments:
+
+ ServerHandle - Handle from a previous SamConnect() call.
+
+Return Value:
+
+
+ STATUS_SUCCESS The service completed successfully or the server
+ has already shutdown.
+
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate access
+ to complete the operation.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ NtStatus = SamrShutdownSamServer(ServerHandle);
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+
+ //
+ // If the error status is one that would result from a server
+ // not being there, then replace it with success.
+ //
+
+ if (NtStatus == RPC_NT_CALL_FAILED) {
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamLookupDomainInSamServer(
+ IN SAM_HANDLE ServerHandle,
+ IN PUNICODE_STRING Name,
+ OUT PSID *DomainId
+ )
+
+/*++
+
+Routine Description:
+
+ This service returns the SID corresponding to the specified domain.
+ The domain is specified by name.
+
+
+Arguments:
+
+ ServerHandle - Handle from a previous SamConnect() call.
+
+ Name - The name of the domain whose ID is to be looked up. A
+ case-insensitive comparison of this name will be performed for
+ the lookup operation.
+
+ DomainId - Receives a pointer to a buffer containing the SID of the
+ looked up domain. This buffer must be freed using
+ SamFreeMemory() when no longer needed.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate access
+ to complete the operation. SAM_SERVER_LOOKUP_DOMAIN access is
+ needed.
+
+ STATUS_NO_SUCH_DOMAIN - The specified domain does not exist at this
+ server.
+
+ STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently
+ disabled.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ (*DomainId) = 0;
+
+ NtStatus =
+ SamrLookupDomainInSamServer(
+ (SAMPR_HANDLE)ServerHandle,
+ (PRPC_UNICODE_STRING)Name,
+ (PRPC_SID *)DomainId
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamEnumerateDomainsInSamServer(
+ IN SAM_HANDLE ServerHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PVOID * Buffer,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG CountReturned
+)
+
+/*++
+
+Routine Description:
+
+ This API lists all the domains defined in the account database.
+ Since there may be more domains 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 SAM_SERVER_ENUMERATE_DOMAINS access to the
+ SamServer object.
+
+
+Parameters:
+
+ ServerHandle - Handle obtained from a previous SamConnect call.
+
+ EnumerationContext - API specific handle to allow multiple calls
+ (see routine description). This is a zero based index.
+
+ Buffer - Receives a pointer to the buffer where the information
+ is placed. The information returned is contiguous
+ SAM_RID_ENUMERATION data structures. However, the
+ RelativeId field of each of these structures is not valid.
+ This buffer must be freed when no longer needed 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 the access required
+ to enumerate the domains.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_SERVER_STATE - Indicates the SAM server is
+ currently disabled.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSAMPR_ENUMERATION_BUFFER LocalBuffer;
+
+ //
+ // Make sure we aren't trying to have RPC allocate the EnumerationContext.
+ //
+
+ if ( !ARGUMENT_PRESENT(EnumerationContext) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Buffer) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(CountReturned) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ (*Buffer) = NULL;
+ LocalBuffer = NULL;
+
+ RpcTryExcept{
+
+ NtStatus = SamrEnumerateDomainsInSamServer(
+ (SAMPR_HANDLE)ServerHandle,
+ EnumerationContext,
+ (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer,
+ PreferedMaximumLength,
+ CountReturned
+ );
+
+ if (LocalBuffer != NULL) {
+
+ //
+ // What comes back is a three level structure:
+ //
+ // Local +-------------+
+ // Buffer ---> | EntriesRead |
+ // |-------------| +-------+
+ // | Enumeration |--->| Name0 | --- > (NameBuffer0)
+ // | Return | |-------| o
+ // | Buffer | | ... | o
+ // +-------------+ |-------| o
+ // | NameN | --- > (NameBufferN)
+ // +-------+
+ //
+ // The buffer containing the EntriesRead field is not returned
+ // to our caller. Only the buffers containing name information
+ // are returned.
+ //
+
+ if (LocalBuffer->Buffer != NULL) {
+ (*Buffer) = LocalBuffer->Buffer;
+ }
+
+ MIDL_user_free( LocalBuffer);
+
+
+ }
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Domain object related services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SamOpenDomain(
+ IN SAM_HANDLE ServerHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN PSID DomainId,
+ OUT PSAM_HANDLE DomainHandle
+ )
+
+/*++
+Routine Description:
+
+ This API opens a domain object. It returns a handle to the newly
+ opened domain that must be used for successive operations on the
+ domain. This handle may be closed with the SamCloseHandle API.
+
+
+Arguments:
+
+ ServerHandle - Handle from a previous SamConnect() call.
+
+ DesiredAccess - Is an access mask indicating which access types are
+ desired to the domain. These access types are reconciled with
+ the Discretionary Access Control list of the domain to determine
+ whether the accesses will be granted or denied.
+
+ DomainId - The SID assigned to the domain to open.
+
+ DomainHandle - Receives a handle referencing the newly opened domain.
+ This handle will be required in successive calls to operate on
+ the domain.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The domain was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate access
+ to complete the operation.
+
+ STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently
+ disabled.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ (*DomainHandle) = 0;
+
+ NtStatus =
+ SamrOpenDomain(
+ (SAMPR_HANDLE)ServerHandle,
+ DesiredAccess,
+ (PRPC_SID)DomainId,
+ (SAMPR_HANDLE *)DomainHandle
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamQueryInformationDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ OUT PVOID *Buffer
+ )
+
+/*++
+Routine Description:
+
+ This API retrieves the domain information. This API requires either
+ DOMAIN_READ_PASSWORD_PARAMETERS or DOMAIN_READ_OTHER_PARAMETERS.
+
+
+Arguments:
+
+ DomainHandle - Handle from a previous SamOpenDomain() call.
+
+ DomainInformationClass - Class of information desired. The accesses
+ required for each class is shown below:
+
+ Info Level Required Access Type
+ --------------------------- -------------------------------
+ DomainGeneralInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainPasswordInformation DOMAIN_READ_PASSWORD_PARAMS
+ DomainLogoffInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainOemInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainNameInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainServerRoleInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainReplicationInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainModifiedInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainStateInformation DOMAIN_READ_OTHER_PARAMETERS
+ DomainUasInformation DOMAIN_READ_OTHER_PARAMETERS
+ Added for NT1.0A...
+ DomainGeneralInformation2 DOMAIN_READ_OTHER_PARAMETERS
+ DomainLockoutInformation DOMAIN_READ_OTHER_PARAMETERS
+
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this buffer
+ must be freed using SamFreeMemory().
+
+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.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*Buffer) = NULL;
+
+ RpcTryExcept{
+
+ if (DomainInformationClass <= DomainUasInformation) {
+ NtStatus = SamrQueryInformationDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ DomainInformationClass,
+ (PSAMPR_DOMAIN_INFO_BUFFER *)Buffer
+ );
+ } else {
+ NtStatus = SamrQueryInformationDomain2(
+ (SAMPR_HANDLE)DomainHandle,
+ DomainInformationClass,
+ (PSAMPR_DOMAIN_INFO_BUFFER *)Buffer
+ );
+
+ }
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // If the exception indicates the server doesn't have
+ // the selected api, that means the server doesn't know
+ // about the info level we passed. Set our completion
+ // status appropriately.
+ //
+
+ if (RpcExceptionCode() == RPC_S_INVALID_LEVEL ||
+ RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE ||
+ RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ } else {
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+ }
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamSetInformationDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ IN PVOID DomainInformation
+)
+
+/*++
+
+Routine Description:
+
+ This API sets the domain information to the values passed in the
+ buffer.
+
+
+Parameters:
+
+ 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
+
+ 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
+
+ DomainInformation - Buffer where the domain information can be
+ found.
+
+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_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;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrSetInformationDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ DomainInformationClass,
+ (PSAMPR_DOMAIN_INFO_BUFFER)DomainInformation
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamCreateGroupInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT PSAM_HANDLE GroupHandle,
+ 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.
+
+
+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, alias 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.
+
+Return Values:
+
+ 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;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*GroupHandle) = NULL;
+ (*RelativeId) = 0;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrCreateGroupInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ (PRPC_UNICODE_STRING)AccountName,
+ DesiredAccess,
+ (SAMPR_HANDLE *)GroupHandle,
+ RelativeId
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamEnumerateGroupsInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ IN PVOID * 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 handle becomes invalid for
+ future use.
+
+ This API requires DOMAIN_LIST_ACCOUNTS 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
+ (see routine description). 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 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;
+ PSAMPR_ENUMERATION_BUFFER LocalBuffer;
+
+ //
+ // Make sure we aren't trying to have RPC allocate the EnumerationContext.
+ //
+
+ if ( !ARGUMENT_PRESENT(EnumerationContext) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Buffer) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(CountReturned) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ (*Buffer) = NULL;
+ LocalBuffer = NULL;
+
+
+ RpcTryExcept{
+
+ NtStatus = SamrEnumerateGroupsInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ EnumerationContext,
+ (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer,
+ PreferedMaximumLength,
+ CountReturned
+ );
+
+
+ if (LocalBuffer != NULL) {
+
+ //
+ // What comes back is a three level structure:
+ //
+ // Local +-------------+
+ // Buffer ---> | EntriesRead |
+ // |-------------| +-------+
+ // | Enumeration |--->| Name0 | --- > (NameBuffer0)
+ // | Return | |-------| o
+ // | Buffer | | ... | o
+ // +-------------+ |-------| o
+ // | NameN | --- > (NameBufferN)
+ // +-------+
+ //
+ // The buffer containing the EntriesRead field is not returned
+ // to our caller. Only the buffers containing name information
+ // are returned.
+ //
+
+ if (LocalBuffer->Buffer != NULL) {
+ (*Buffer) = LocalBuffer->Buffer;
+ }
+
+ MIDL_user_free( LocalBuffer);
+
+
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+NTSTATUS
+SamCreateUser2InDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName,
+ IN ULONG AccountType,
+ IN ACCESS_MASK DesiredAccess,
+ OUT PSAM_HANDLE UserHandle,
+ OUT PULONG GrantedAccess,
+ 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. When creating an account on a down-level
+ server, this value may be unattainable. In this case, it
+ will be returned as zero (0).
+
+ 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;
+
+
+ USER_CONTROL_INFORMATION
+ UserControlInfoBuffer;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*UserHandle) = NULL;
+ (*RelativeId) = 0;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrCreateUser2InDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ (PRPC_UNICODE_STRING)AccountName,
+ AccountType,
+ DesiredAccess,
+ (SAMPR_HANDLE *)UserHandle,
+ GrantedAccess,
+ RelativeId
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ if (RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE ||
+ RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
+ NtStatus = RPC_NT_PROCNUM_OUT_OF_RANGE;
+ } else {
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+ }
+
+ } RpcEndExcept;
+
+
+
+ //
+ // If the server doesn't support the new api, then
+ // do the equivalent work with the old apis.
+ //
+
+ if (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE) {
+
+ DesiredAccess = DesiredAccess | USER_WRITE_ACCOUNT;
+ NtStatus =
+ SamCreateUserInDomain(
+ DomainHandle,
+ AccountName,
+ DesiredAccess,
+ UserHandle,
+ RelativeId );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+
+ //
+ // Set the AccountType (unless it is normal)
+ //
+
+ if (~(AccountType & USER_NORMAL_ACCOUNT)) {
+
+ UserControlInfoBuffer.UserAccountControl =
+ AccountType |
+ USER_ACCOUNT_DISABLED |
+ USER_PASSWORD_NOT_REQUIRED;
+
+ NtStatus = SamSetInformationUser(
+ (*UserHandle),
+ UserControlInformation,
+ &UserControlInfoBuffer
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SamDeleteUser( UserHandle );
+ }
+
+ //
+ // We can't be positive what accesses have been
+ // granted, so don't try lying.
+ //
+
+ (*GrantedAccess) = 0;
+
+ }
+ }
+ }
+
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamCreateUserInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT PSAM_HANDLE UserHandle,
+ 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.
+
+ Note that DOMAIN_CREATE_USER access type is needed by this API.
+ Also, the caller of this API becomes the owner of the user object
+ upon creation.
+
+ 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.
+
+ 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:
+
+ USER_ACCOUNT_DISABLED,
+ USER_NORMAL_ACCOUNT,
+ USER_PASSWORD_NOT_REQUIRED
+
+ ScriptPath - will be null.
+
+ WorkStations - will be null.
+
+ CaseInsensitiveDbcs - will be null.
+
+ CaseSensitiveUnicode - will be null.
+
+ LastLogon - will be zero.
+
+ LastLogoff - will be zero.
+
+ AccountExpires - will be very far into the future.
+
+ BadPasswordCount - will be negative 1 (-1).
+
+ LogonCount - will be negative 1 (-1).
+
+ AdminComment - will be null.
+
+ Password - will contain any value, but is not used because the
+ USER_PASSWORD_NOT_REQUIRED control flag is set. If a password
+ is to be required, then this field must be set to a
+ specific value and the USER_PASSWORD_NOT_REQUIRED flag must be
+ cleared.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ AccountName - The name to be assigned to the new account.
+
+ 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.
+
+ 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;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*UserHandle) = NULL;
+ (*RelativeId) = 0;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrCreateUserInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ (PRPC_UNICODE_STRING)AccountName,
+ DesiredAccess,
+ (SAMPR_HANDLE *)UserHandle,
+ RelativeId
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+
+NTSTATUS
+SamEnumerateUsersInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ IN ULONG UserAccountControl,
+ OUT PVOID * 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_ACCOUNTS 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
+ (see routine description). 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;
+ PSAMPR_ENUMERATION_BUFFER LocalBuffer;
+
+ //
+ // Make sure we aren't trying to have RPC allocate the EnumerationContext.
+ //
+
+ if ( !ARGUMENT_PRESENT(EnumerationContext) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Buffer) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(CountReturned) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ (*Buffer) = NULL;
+ LocalBuffer = NULL;
+
+
+ RpcTryExcept{
+
+ NtStatus = SamrEnumerateUsersInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ EnumerationContext,
+ UserAccountControl,
+ (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer,
+ PreferedMaximumLength,
+ CountReturned
+ );
+
+
+ if (LocalBuffer != NULL) {
+
+ //
+ // What comes back is a three level structure:
+ //
+ // Local +-------------+
+ // Buffer ---> | EntriesRead |
+ // |-------------| +-------+
+ // | Enumeration |--->| Name0 | --- > (NameBuffer0)
+ // | Return | |-------| o
+ // | Buffer | | ... | o
+ // +-------------+ |-------| o
+ // | NameN | --- > (NameBufferN)
+ // +-------+
+ //
+ // The buffer containing the EntriesRead field is not returned
+ // to our caller. Only the buffers containing name information
+ // are returned.
+ //
+
+ if (LocalBuffer->Buffer != NULL) {
+ (*Buffer) = LocalBuffer->Buffer;
+ }
+
+ MIDL_user_free( LocalBuffer);
+
+
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamCreateAliasInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN PUNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT PSAM_HANDLE AliasHandle,
+ OUT PULONG RelativeId
+)
+
+/*++
+
+Routine Description:
+
+ This API adds a new alias to the account database. Initially, this
+ alias does not contain any members.
+
+ This call returns a handle to the newly created account that may be
+ used for successive operations on the object. 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 Alias object manipulation services.
+
+ Name - the name of the account will be as specified in the creation
+ API.
+
+ MemberCount - Zero. Initially the alias has no members.
+
+ RelativeId - will be a uniquelly allocated ID.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain. The handle must be open for DOMAIN_CREATE_ALIAS
+ access.
+
+ AccountName - The name of the alias to be added.
+
+ 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.
+ The SID of the new alias is this relative ID value prefixed with
+ the domain's SID value.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The account 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 aliases.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*AliasHandle) = NULL;
+ (*RelativeId) = 0;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrCreateAliasInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ (PRPC_UNICODE_STRING)AccountName,
+ DesiredAccess,
+ (SAMPR_HANDLE *)AliasHandle,
+ RelativeId
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamEnumerateAliasesInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ IN PVOID *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 aliases 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_ACCOUNTS 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
+ (see routine description). 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 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;
+ PSAMPR_ENUMERATION_BUFFER LocalBuffer;
+
+ //
+ // Make sure we aren't trying to have RPC allocate the EnumerationContext.
+ //
+
+ if ( !ARGUMENT_PRESENT(EnumerationContext) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Buffer) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(CountReturned) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ (*Buffer) = NULL;
+ LocalBuffer = NULL;
+
+
+ RpcTryExcept{
+
+ NtStatus = SamrEnumerateAliasesInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ EnumerationContext,
+ (PSAMPR_ENUMERATION_BUFFER *)&LocalBuffer,
+ PreferedMaximumLength,
+ CountReturned
+ );
+
+
+ if (LocalBuffer != NULL) {
+
+ //
+ // What comes back is a three level structure:
+ //
+ // Local +-------------+
+ // Buffer ---> | EntriesRead |
+ // |-------------| +-------+
+ // | Enumeration |--->| Name0 | --- > (NameBuffer0)
+ // | Return | |-------| o
+ // | Buffer | | ... | o
+ // +-------------+ |-------| o
+ // | NameN | --- > (NameBufferN)
+ // +-------+
+ //
+ // The buffer containing the EntriesRead field is not returned
+ // to our caller. Only the buffers containing name information
+ // are returned.
+ //
+
+ if (LocalBuffer->Buffer != NULL) {
+ (*Buffer) = LocalBuffer->Buffer;
+ }
+
+ MIDL_user_free( LocalBuffer);
+
+
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+NTSTATUS
+SamGetAliasMembership(
+ IN SAM_HANDLE DomainHandle,
+ IN ULONG PassedCount,
+ IN PSID *Sids,
+ OUT PULONG MembershipCount,
+ OUT PULONG *Aliases
+)
+
+/*++
+
+Routine Description:
+
+ This API searches the set of aliases in the specified domain to see
+ which aliases, if any, the passed SIDs are members of. Any aliases
+ that any of the SIDs are found to be members of are returned.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ PassedCount - Specifies the number of Sids being passed.
+
+ Sids - Pointer to an array of Count pointers to Sids whose alias
+ memberships are to be looked up.
+
+ MembershipCount - Receives the number of aliases that are being
+ returned via the Aliases parameter.
+
+ Aliases - Receives a pointer to an array of SIDs. This is the set
+ of aliases the passed SIDs were found to be members of. If
+ MembershipCount is returned as zero, then a null value will be
+ returned here.
+
+ When this information is no longer needed, it must be released
+ by passing the returned pointer to SamFreeMemory().
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ SAMPR_PSID_ARRAY Accounts;
+ SAMPR_ULONG_ARRAY Membership;
+
+ //
+ // Make sure we aren't trying to have RPC allocate the EnumerationContext.
+ //
+
+ if ( !ARGUMENT_PRESENT(Sids) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(MembershipCount) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Aliases) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ Membership.Element = NULL;
+
+ RpcTryExcept{
+
+ Accounts.Count = PassedCount;
+ Accounts.Sids = (PSAMPR_SID_INFORMATION)Sids;
+
+ NtStatus = SamrGetAliasMembership(
+ (SAMPR_HANDLE)DomainHandle,
+ &Accounts,
+ &Membership
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ (*MembershipCount) = Membership.Count;
+ (*Aliases) = Membership.Element;
+ } else {
+
+ //
+ // Deallocate any returned buffers on error
+ //
+
+ if (Membership.Element != NULL) {
+ MIDL_user_free(Membership.Element);
+ }
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+
+NTSTATUS
+SamLookupNamesInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN PUNICODE_STRING Names,
+ OUT PULONG *RelativeIds,
+ OUT PSID_NAME_USE *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 a pointer to an array of Count Relative IDs
+ that have been filled in. 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. This buffer must be
+ freed when no longer needed using SamFreeMemory().
+
+ Use - Recieves a pointer to an array of Count SID_NAME_USE
+ entries that have been filled in with what significance each
+ name has. The nth entry in this array indicates the meaning
+ of the nth name passed. This buffer must be freed when no longer
+ needed 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
+ ReturnStatus,
+ NtStatus;
+
+ LIST_ENTRY
+ CallHead;
+
+ PSAMP_NAME_LOOKUP_CALL
+ Next;
+
+ PSID_NAME_USE
+ UseBuffer;
+
+ PULONG
+ RidBuffer;
+
+ ULONG
+ Calls,
+ CallLength,
+ i;
+
+ BOOLEAN
+ NoneMapped = TRUE,
+ SomeNotMapped = FALSE;
+
+
+ if ( (Count == 0) || (Names == NULL) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Default error return
+ //
+
+ (*Use) = UseBuffer = NULL;
+ (*RelativeIds) = RidBuffer = NULL;
+
+
+ //
+ // Set up the call structures list
+ //
+
+ InitializeListHead( &CallHead );
+ Calls = 0;
+
+
+ //
+ // By default we will return NONE_MAPPED.
+ // This will get superseded by either STATUS_SUCCESS
+ // or STATUS_SOME_NOT_MAPPED.
+ //
+
+ //
+ // Now build up and make each call
+ //
+
+ i = 0;
+ while ( i < Count ) {
+
+ //
+ // Make sure the next entry isn't too long.
+ // That would put us in an infinite loop.
+ //
+
+ if (Names[i].Length > SAM_MAXIMUM_LOOKUP_LENGTH) {
+ ReturnStatus = STATUS_INVALID_PARAMETER;
+ goto SampNameLookupFreeAndReturn;
+ }
+
+ //
+ // Get the next call structure
+ //
+
+ Next = (PSAMP_NAME_LOOKUP_CALL)MIDL_user_allocate( sizeof(SAMP_NAME_LOOKUP_CALL) );
+
+ if (Next == NULL) {
+ ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto SampNameLookupFreeAndReturn;
+ }
+
+ //
+ // Fill in the call structure.
+ // It takes a little to figure out how many entries to send in
+ // this call. It is limited by both Count (sam_MAXIMUM_LOOKUP_COUNT)
+ // and by size (SAM_MAXIMUM_LOOKUP_LENGTH).
+ //
+
+ Next->Count = 0;
+ Next->StartIndex = i;
+ Next->RidBuffer.Element = NULL;
+ Next->UseBuffer.Element = NULL;
+
+ CallLength = 0;
+ for ( i=i;
+ ( (i < Count) &&
+ (CallLength+Names[i].Length < SAM_MAXIMUM_LOOKUP_LENGTH) &&
+ (Next->Count < SAM_MAXIMUM_LOOKUP_COUNT)
+ );
+ i++ ) {
+
+ //
+ // Add in the next length and increment the number of entries
+ // being processed by this call.
+ //
+
+ CallLength += Names[i].Length;
+ Next->Count ++;
+
+ }
+
+
+
+ //
+ // Add this call structure to the list of call structures
+ //
+
+ Calls ++;
+ InsertTailList( &CallHead, &Next->Link );
+
+
+ //
+ // Now make the call
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SamrLookupNamesInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ Next->Count,
+ (PRPC_UNICODE_STRING)(&Names[Next->StartIndex]),
+ &Next->RidBuffer,
+ &Next->UseBuffer
+ );
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ //
+ // Keep track of what our completion status should be.
+ //
+
+ if (!NT_SUCCESS(NtStatus) &&
+ NtStatus != STATUS_NONE_MAPPED) {
+ ReturnStatus = NtStatus; // Unexpected error
+ goto SampNameLookupFreeAndReturn;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ NoneMapped = FALSE;
+ if (NtStatus == STATUS_SOME_NOT_MAPPED) {
+ SomeNotMapped = TRUE;
+ }
+ }
+ }
+
+
+ //
+ // Set our return status...
+ //
+
+ if (NoneMapped) {
+ ASSERT(SomeNotMapped == FALSE);
+ ReturnStatus = STATUS_NONE_MAPPED;
+ } else if (SomeNotMapped) {
+ ReturnStatus = STATUS_SOME_NOT_MAPPED;
+ } else {
+ ReturnStatus = STATUS_SUCCESS;
+ }
+
+
+
+
+ //
+ // At this point we have (potentially) a lot of call structures.
+ // The RidBuffer and UseBuffer elements of each call structure
+ // is allocated and returned by the RPC call and looks
+ // like:
+ //
+ // RidBuffer
+ // +-------------+
+ // | Count |
+ // |-------------| +-------+ *
+ // | Element ---|--->| Rid-0 | | /
+ // +-------------+ |-------| | / Only this part
+ // | ... | > < is allocated by
+ // |-------| | \ the rpc call.
+ // | Rid-N | | \
+ // +-------+ *
+ //
+ // If only one RPC call was made, we can return this information
+ // directly. Otherwise, we need to copy the information from
+ // all the calls into a single large buffer and return that buffer
+ // (freeing all the individual call buffers).
+ //
+ // The user is responsible for freeing whichever buffer we do
+ // return.
+ //
+
+ ASSERT(Calls != 0); // Error go around this path, success always has calls
+
+
+ //
+ // Optimize for a single call
+ //
+
+ if (Calls == 1) {
+ (*Use) = (PSID_NAME_USE)
+ (((PSAMP_NAME_LOOKUP_CALL)(CallHead.Flink))->
+ UseBuffer.Element);
+ (*RelativeIds) = ((PSAMP_NAME_LOOKUP_CALL)(CallHead.Flink))->
+ RidBuffer.Element;
+ MIDL_user_free( CallHead.Flink ); // Free the call structure
+ return(ReturnStatus);
+ }
+
+
+ //
+ // More than one call.
+ // Allocate return buffers large enough to copy all the information into.
+ //
+
+ RidBuffer = MIDL_user_allocate( sizeof(ULONG) * Count );
+ if (RidBuffer == NULL) {
+ ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto SampNameLookupFreeAndReturn;
+ }
+
+ UseBuffer = MIDL_user_allocate( sizeof(SID_NAME_USE) * Count );
+ if (UseBuffer == NULL) {
+ MIDL_user_free( RidBuffer );
+ RidBuffer = NULL;
+ ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto SampNameLookupFreeAndReturn;
+ }
+
+
+
+
+SampNameLookupFreeAndReturn:
+
+ //
+ // Walk the list of calls.
+ // For each call:
+ //
+ // If we have a return buffer, copy the results into it.
+ // Free the call buffers.
+ // Free the call structure itself.
+ //
+ // Completion status has already been set appropriatly in ReturnStatus.
+ //
+
+ Next = (PSAMP_NAME_LOOKUP_CALL)RemoveHeadList( &CallHead );
+ while (Next != (PSAMP_NAME_LOOKUP_CALL)&CallHead) {
+
+ //
+ // Copy RID information and then free the call buffer
+ //
+
+ if (RidBuffer != NULL) {
+ RtlMoveMemory(
+ &RidBuffer[ Next->StartIndex ], // Destination
+ &Next->RidBuffer.Element[0], // Source
+ Next->Count * sizeof(ULONG) // Length
+ );
+ }
+
+ if (Next->RidBuffer.Element != NULL) {
+ MIDL_user_free( Next->RidBuffer.Element );
+ }
+
+
+ //
+ // Copy USE information and then free the call buffer
+ //
+
+ if (UseBuffer != NULL) {
+ RtlMoveMemory(
+ &UseBuffer[ Next->StartIndex ], // Destination
+ &Next->UseBuffer.Element[0], // Source
+ Next->Count * sizeof(SID_NAME_USE) // Length
+ );
+ }
+
+ if (Next->UseBuffer.Element != NULL) {
+ MIDL_user_free( Next->UseBuffer.Element );
+ }
+
+ //
+ // Free the call structure itself
+ //
+
+ MIDL_user_free( Next );
+
+ Next = (PSAMP_NAME_LOOKUP_CALL)RemoveHeadList( &CallHead );
+ } // end-while
+
+
+
+ //
+ // For better or worse, we're all done
+ //
+
+ (*Use) = UseBuffer;
+ (*RelativeIds) = RidBuffer;
+
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+NTSTATUS
+SamLookupIdsInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN PULONG RelativeIds,
+ OUT PUNICODE_STRING *Names,
+ OUT PSID_NAME_USE *Use OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This API maps a number of relative IDs to their corresponding names.
+ The use of the name (domain, group, alias, user, or unknown) is also
+ returned.
+
+ The API stores the actual names in Buffer, then creates an array of
+ UNICODE_STRINGs in the Names OUT parameter. 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.
+
+ 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 array of Count UNICODE_STRINGs that
+ have been filled in. The nth pointer within this array will
+ correspond the nth relative id passed . Each name string buffer
+ will be in a separately allocated block of memory. Any entry is
+ not successfully translated will have a NULL name buffer pointer
+ returned. This Names buffer must be freed using SamFreeMemory()
+ when no longer needed.
+
+ Use - Optionally, receives a pointer to an array of Count SID_NAME_USE
+ entries that have been filled in with what significance each
+ name has. The nth entry in this array indicates the meaning
+ of the nth name passed. This buffer must be freed when no longer
+ needed 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;
+ ULONG SubRequest, SubRequests;
+ ULONG TotalCountToDate;
+ ULONG Index, UsedLength, Length, NamesLength;
+ ULONG UsesLength, LastSubRequestCount;
+ PULONG UstringStructDisps;
+ PULONG Counts = NULL;
+ PULONG RidIndices = NULL;
+ PUNICODE_STRING *SubRequestNames = NULL;
+ PSID_NAME_USE *SubRequestUses = NULL;
+ PUNICODE_STRING OutputNames = NULL;
+ PSID_NAME_USE OutputUses = NULL;
+ PUCHAR Destination = NULL, Source = NULL;
+ PUNICODE_STRING DestUstring = NULL;
+ ULONG SomeNotMappedStatusCount = 0;
+ ULONG NoneMappedStatusCount = 0;
+
+ //
+ // If the Count for this request does not exceed the maximum limit that
+ // can be looked up in a single call, just call the Sub Request version
+ // of the routine.
+ //
+
+ if (Count <= SAM_MAXIMUM_LOOKUP_COUNT) {
+
+ NtStatus = SampLookupIdsInDomain(
+ DomainHandle,
+ Count,
+ RelativeIds,
+ Names,
+ Use
+ );
+
+ return(NtStatus);
+ }
+
+ //
+ // Break down larger requests into smaller chunks
+ //
+
+ SubRequests = Count / SAMP_MAXIMUM_SUB_LOOKUP_COUNT;
+ LastSubRequestCount = Count % SAMP_MAXIMUM_SUB_LOOKUP_COUNT;
+
+ if (LastSubRequestCount > 0) {
+
+ SubRequests++;
+ }
+
+ //
+ // Allocate memory for array of starting Rid Indices, Rid Counts and
+ // Unicode String block offsets for each SubRequest.
+ //
+
+ NtStatus = STATUS_NO_MEMORY;
+
+ RidIndices = MIDL_user_allocate( SubRequests * sizeof(ULONG) );
+
+ if (RidIndices == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ Counts = MIDL_user_allocate( SubRequests * sizeof(ULONG) );
+
+ if (Counts == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ SubRequestNames = MIDL_user_allocate( SubRequests * sizeof(PUNICODE_STRING) );
+
+ if (SubRequestNames == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ SubRequestUses = MIDL_user_allocate( SubRequests * sizeof(SID_NAME_USE) );
+
+ if (SubRequestUses == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ UstringStructDisps = MIDL_user_allocate( SubRequests * sizeof(ULONG) );
+
+ if (UstringStructDisps == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ TotalCountToDate = 0;
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ RidIndices[SubRequest] = TotalCountToDate;
+
+ if ((Count - TotalCountToDate) > SAMP_MAXIMUM_SUB_LOOKUP_COUNT) {
+
+ Counts[SubRequest] = SAMP_MAXIMUM_SUB_LOOKUP_COUNT;
+
+ } else {
+
+ Counts[SubRequest] = Count - TotalCountToDate;
+ }
+
+ TotalCountToDate += Counts[SubRequest];
+
+ NtStatus = SampLookupIdsInDomain(
+ DomainHandle,
+ Counts[SubRequest],
+ &RelativeIds[RidIndices[SubRequest]],
+ &SubRequestNames[SubRequest],
+ &SubRequestUses[SubRequest]
+ );
+
+ //
+ // We keep a tally of the number of times STATUS_SOME_NOT_MAPPED
+ // and STATUS_NONE_MAPPED were returned. This is so that we
+ // can return the appropriate status at the end based on the
+ // global picture. We continue lookups after either status code
+ // is encountered.
+ //
+
+ if (NtStatus == STATUS_SOME_NOT_MAPPED) {
+
+ SomeNotMappedStatusCount++;
+
+ } else if (NtStatus == STATUS_NONE_MAPPED) {
+
+ NoneMappedStatusCount++;
+ NtStatus = STATUS_SUCCESS;
+
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ //
+ // Now allocate a single buffer for the Names
+ //
+
+ NamesLength = Count * sizeof(UNICODE_STRING);
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ for (Index = 0; Index < Counts[SubRequest]; Index++) {
+
+ NamesLength += (SubRequestNames[SubRequest] + Index)->MaximumLength;
+ }
+ }
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ OutputNames = MIDL_user_allocate( NamesLength );
+
+ if (OutputNames == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ //
+ // Now copy in the Unicode String Structures for the Names returned from
+ // each subrequest. We will later overwrite the Buffer fields in them
+ // when we assign space and move in each Unicode String.
+ //
+
+ Destination = (PUCHAR) OutputNames;
+ UsedLength = 0;
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ Source = (PUCHAR) SubRequestNames[SubRequest];
+ Length = Counts[SubRequest] * sizeof(UNICODE_STRING);
+ UstringStructDisps[SubRequest] = (Destination - Source);
+ RtlMoveMemory( Destination, Source, Length );
+ Destination += Length;
+ UsedLength += Length;
+ }
+
+ //
+ // Now copy in the Unicode Strings themselves. These are appended to
+ // the array of Unicode String structures. As we go, update the
+ // Unicode string buffer pointers to point to the copied version
+ // of each string.
+ //
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ for (Index = 0; Index < Counts[SubRequest]; Index++) {
+
+ Source = (PUCHAR)(SubRequestNames[SubRequest] + Index)->Buffer;
+ Length = (ULONG)(SubRequestNames[SubRequest] + Index)->MaximumLength;
+
+ //
+ // It is possible that a returned Unicode String has 0 length
+ // because an Id was not mapped. In this case, skip to the next
+ // one.
+ //
+
+ if (Length == 0) {
+
+ continue;
+ }
+
+ DestUstring = (PUNICODE_STRING)
+ (((PUCHAR)(SubRequestNames[SubRequest] + Index)) +
+ UstringStructDisps[SubRequest]);
+
+ DestUstring->Buffer = (PWSTR) Destination;
+
+ ASSERT(UsedLength + Length <= NamesLength);
+
+ RtlMoveMemory( Destination, Source, Length );
+ Destination += Length;
+ UsedLength += Length;
+ continue;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ //
+ // Now allocate a single buffer for the Uses
+ //
+
+ UsesLength = Count * sizeof(SID_NAME_USE);
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ OutputUses = MIDL_user_allocate( UsesLength );
+
+ if (OutputUses == NULL) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ //
+ // Now copy in the SID_NAME_USE Structures for the Uses returned from
+ // each subrequest.
+ //
+
+ Destination = (PUCHAR) OutputUses;
+ UsedLength = 0;
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ Source = (PUCHAR) SubRequestUses[SubRequest];
+ Length = Counts[SubRequest] * sizeof(SID_NAME_USE);
+ RtlMoveMemory( Destination, Source, Length );
+ Destination += Length;
+ UsedLength += Length;
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto LookupIdsInDomainError;
+ }
+
+ *Names = OutputNames;
+ *Use = OutputUses;
+
+ //
+ // Determine the final status to return. This is the normal NtStatus code
+ // except that if NtStatus is a success status and none/not all Ids were
+ // mapped, NtStatus will be set to either STATUS_SOME_NOT_MAPPED or
+ // STATUS_NONE_MAPPED as appropriate. If STATUS_SOME_NOT_MAPPED was
+ // returned on at least one call, return that status. If STATUS_NONE_MAPPED
+ // was returned on all calls, return that status.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (SomeNotMappedStatusCount > 0) {
+
+ NtStatus = STATUS_SOME_NOT_MAPPED;
+
+ } else if (NoneMappedStatusCount == SubRequests) {
+
+ NtStatus = STATUS_NONE_MAPPED;
+
+ } else if (NoneMappedStatusCount > 0) {
+
+ //
+ // One or more calls returned STATUS_NONE_MAPPED but not all.
+ // So at least one Id was mapped, but not all ids.
+ //
+
+ NtStatus = STATUS_SOME_NOT_MAPPED;
+ }
+ }
+
+LookupIdsInDomainFinish:
+
+ //
+ // If necessary, free the buffer containing the starting Rid Indices for
+ // each Sub Request.
+ //
+
+ if (RidIndices != NULL) {
+
+ MIDL_user_free(RidIndices);
+ RidIndices = NULL;
+ }
+
+ //
+ // If necessary, free the buffer containing the Rid Counts for
+ // each Sub Request.
+ //
+
+ if (Counts != NULL) {
+
+ MIDL_user_free(Counts);
+ Counts = NULL;
+ }
+
+ //
+ // If necessary, free the buffer containing the offsets from the
+ // source and destination Unicode String structures.
+ //
+
+ if (UstringStructDisps != NULL) {
+
+ MIDL_user_free(UstringStructDisps);
+ UstringStructDisps = NULL;
+ }
+
+ //
+ // If necessary, free the SubRequestNames output returned for each
+ // Sub Request.
+ //
+
+ if (SubRequestNames != NULL) {
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ if (SubRequestNames[SubRequest] != NULL) {
+
+ MIDL_user_free(SubRequestNames[SubRequest]);
+ SubRequestNames[SubRequest] = NULL;
+ }
+ }
+
+ MIDL_user_free(SubRequestNames);
+ SubRequestNames = NULL;
+ }
+
+ //
+ // If necessary, free the SubRequestUses output returned for each
+ // Sub Request.
+ //
+
+ if (SubRequestUses != NULL) {
+
+ for (SubRequest = 0; SubRequest < SubRequests; SubRequest++) {
+
+ if (SubRequestUses[SubRequest] != NULL) {
+
+ MIDL_user_free(SubRequestUses[SubRequest]);
+ SubRequestUses[SubRequest] = NULL;
+ }
+ }
+
+ MIDL_user_free(SubRequestUses);
+ SubRequestUses = NULL;
+ }
+
+ return(NtStatus);
+
+LookupIdsInDomainError:
+
+ //
+ // If necessary, free the buffers we would hande returned for
+ // Names and Use.
+ //
+
+ if (OutputNames != NULL) {
+
+ MIDL_user_free(OutputNames);
+ OutputNames = NULL;
+ }
+
+ if (OutputUses != NULL) {
+
+ MIDL_user_free(OutputUses);
+ OutputUses = NULL;
+ }
+
+ *Names = NULL;
+ *Use = NULL;
+
+ goto LookupIdsInDomainFinish;
+}
+
+
+NTSTATUS
+SampLookupIdsInDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN PULONG RelativeIds,
+ OUT PUNICODE_STRING *Names,
+ OUT PSID_NAME_USE *Use OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This API maps a number of relative IDs to their corresponding names.
+ The use of the name (domain, group, alias, user, or unknown) is also
+ returned.
+
+ The API stores the actual names in Buffer, then creates an array of
+ UNICODE_STRINGs in the Names OUT parameter. 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.
+
+ 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 array of Count UNICODE_STRINGs that
+ have been filled in. The nth pointer within this array will
+ correspond the nth relative id passed . Each name string buffer
+ will be in a separately allocated block of memory. Any entry is
+ not successfully translated will have a NULL name buffer pointer
+ returned. This Names buffer must be freed using SamFreeMemory()
+ when no longer needed.
+
+ Use - Optionally, receives a pointer to an array of Count SID_NAME_USE
+ entries that have been filled in with what significance each
+ name has. The nth entry in this array indicates the meaning
+ of the nth name passed. This buffer must be freed when no longer
+ needed 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;
+ SAMPR_RETURNED_USTRING_ARRAY NameBuffer;
+ SAMPR_ULONG_ARRAY UseBuffer;
+
+ //
+ // Make sure our parameters are within bounds
+ //
+
+ if (RelativeIds == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ if (Count > SAM_MAXIMUM_LOOKUP_COUNT) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+
+ //
+ // Call the server ...
+ //
+
+
+ NameBuffer.Element = NULL;
+ UseBuffer.Element = NULL;
+
+
+ RpcTryExcept{
+
+ NtStatus = SamrLookupIdsInDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ Count,
+ RelativeIds,
+ &NameBuffer,
+ &UseBuffer
+ );
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ //
+ // What comes back for the "Names" OUT parameter is a two level
+ // structure, the first level of which is on our stack:
+ //
+ // NameBuffer
+ // +-------------+
+ // | Count |
+ // |-------------| +-------+
+ // | Element ---|--->| Name0 | --- > (NameBuffer0)
+ // +-------------+ |-------| o
+ // | ... | o
+ // |-------| o
+ // | NameN | --- > (NameBufferN)
+ // +-------+
+ //
+ // The buffer containing the EntriesRead field is not returned
+ // to our caller. Only the buffers containing name information
+ // are returned.
+ //
+ // NOTE: The buffers containing name information are allocated
+ // by the RPC runtime in a single buffer. This is caused
+ // by a line the the client side .acf file.
+ //
+
+ //
+ // What comes back for the "Use" OUT parameter is a two level
+ // structure, the first level of which is on our stack:
+ //
+ // UseBuffer
+ // +-------------+
+ // | Count |
+ // |-------------| +-------+
+ // | Element ---|--->| Use-0 |
+ // +-------------+ |-------|
+ // | ... |
+ // |-------|
+ // | Use-N |
+ // +-------+
+ //
+ // The Pointer in the Element field is what gets returned
+ // to our caller. The caller is responsible for deallocating
+ // this when no longer needed.
+ //
+
+ (*Names) = (PUNICODE_STRING)NameBuffer.Element;
+ if ( ARGUMENT_PRESENT(Use) ) {
+ (*Use) = (PSID_NAME_USE) UseBuffer.Element;
+ } else {
+ if (UseBuffer.Element != NULL) {
+ MIDL_user_free(UseBuffer.Element);
+ UseBuffer.Element = NULL;
+ }
+ }
+
+
+ //
+ // Don't force our caller to deallocate things on unexpected
+ // return value.
+ //
+
+ if (NtStatus != STATUS_SUCCESS &&
+ NtStatus != STATUS_SOME_NOT_MAPPED
+ ) {
+ if ( ARGUMENT_PRESENT(Use) ) {
+ (*Use) = NULL;
+ }
+ if (UseBuffer.Element != NULL) {
+ MIDL_user_free(UseBuffer.Element);
+ UseBuffer.Element = NULL;
+ }
+
+ (*Names) = NULL;
+ if (NameBuffer.Element != NULL) {
+ MIDL_user_free(NameBuffer.Element);
+ NameBuffer.Element = NULL;
+ }
+
+ }
+
+
+ return(SampMapCompletionStatus(NtStatus));
+
+
+
+
+}
+
+
+
+NTSTATUS
+SamQueryDisplayInformation (
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ IN ULONG EntryCount,
+ IN ULONG PreferredMaximumLength,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PULONG ReturnedEntryCount,
+ OUT PVOID *SortedBuffer
+ )
+/*++
+
+Routine Description:
+
+ This routine provides fast return of information commonly
+ needed to be displayed in user interfaces.
+
+ NT User Interface has a requirement for quick enumeration of SAM
+ accounts for display in list boxes. (Replication has similar but
+ broader requirements.)
+
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which information is to be enumerated.
+
+ Index - The index of the first entry to be retrieved.
+
+ PreferedMaximumLength - A recommended upper limit to the number of
+ bytes to be returned. The returned information is allocated by
+ this routine.
+
+ TotalAvailable - Total number of bytes availabe in the specified info
+ class. This parameter is optional (and is not returned) for
+ the following info levels:
+
+ DomainDisplayOemUser
+ DomainDisplayOemGroup
+
+
+ TotalReturned - Number of bytes actually returned for this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+ ReturnedEntryCount - Number of entries returned by this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+
+ SortedBuffer - Receives a pointer to a buffer containing a sorted
+ list of the requested information. This buffer is allocated
+ by this routine and contains the following structure:
+
+
+ DomainDisplayUser --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_USER. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_USER structures.
+
+ DomainDisplayMachine --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_MACHINE. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_MACHINE structures.
+
+ DomainDisplayGroup --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_GROUP. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_GROUP structures.
+
+ DomainDisplayOemUser --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_OEM_USER. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_OEM_user structures.
+
+ DomainDisplayOemGroup --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_OEM_GROUP. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_OEM_GROUP structures.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+ STATUS_INVALID_INFO_CLASS - The requested class of information
+ is not legitimate for this service.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ SAMPR_DISPLAY_INFO_BUFFER
+ DisplayInformationBuffer;
+
+ ULONG
+ LocalTotalAvailable;
+
+ //
+ // Check parameters
+ //
+
+ if ( !ARGUMENT_PRESENT(TotalAvailable) &&
+ (DisplayInformation != DomainDisplayOemUser) &&
+ (DisplayInformation != DomainDisplayOemGroup) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(TotalReturned) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(ReturnedEntryCount) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(SortedBuffer) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ if (DisplayInformation <= DomainDisplayMachine) {
+ NtStatus = SamrQueryDisplayInformation(
+ (SAMPR_HANDLE)DomainHandle,
+ DisplayInformation,
+ Index,
+ EntryCount,
+ PreferredMaximumLength,
+ &LocalTotalAvailable,
+ TotalReturned,
+ &DisplayInformationBuffer);
+
+ } else if (DisplayInformation <= DomainDisplayGroup) {
+ NtStatus = SamrQueryDisplayInformation2(
+ (SAMPR_HANDLE)DomainHandle,
+ DisplayInformation,
+ Index,
+ EntryCount,
+ PreferredMaximumLength,
+ &LocalTotalAvailable,
+ TotalReturned,
+ &DisplayInformationBuffer);
+ } else {
+ NtStatus = SamrQueryDisplayInformation3(
+ (SAMPR_HANDLE)DomainHandle,
+ DisplayInformation,
+ Index,
+ EntryCount,
+ PreferredMaximumLength,
+ &LocalTotalAvailable,
+ TotalReturned,
+ &DisplayInformationBuffer);
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (ARGUMENT_PRESENT(TotalAvailable)) {
+ (*TotalAvailable) = LocalTotalAvailable;
+ }
+
+ switch (DisplayInformation) {
+
+ case DomainDisplayUser:
+ *ReturnedEntryCount = DisplayInformationBuffer.UserInformation.EntriesRead;
+ *SortedBuffer = DisplayInformationBuffer.UserInformation.Buffer;
+ break;
+
+ case DomainDisplayMachine:
+ *ReturnedEntryCount = DisplayInformationBuffer.MachineInformation.EntriesRead;
+ *SortedBuffer = DisplayInformationBuffer.MachineInformation.Buffer;
+ break;
+
+ case DomainDisplayGroup:
+ *ReturnedEntryCount = DisplayInformationBuffer.GroupInformation.EntriesRead;
+ *SortedBuffer = DisplayInformationBuffer.GroupInformation.Buffer;
+ break;
+
+ case DomainDisplayOemUser:
+ *ReturnedEntryCount = DisplayInformationBuffer.OemUserInformation.EntriesRead;
+ *SortedBuffer = DisplayInformationBuffer.OemUserInformation.Buffer;
+ break;
+
+ case DomainDisplayOemGroup:
+ *ReturnedEntryCount = DisplayInformationBuffer.OemGroupInformation.EntriesRead;
+ *SortedBuffer = DisplayInformationBuffer.OemGroupInformation.Buffer;
+ break;
+
+ }
+
+ } else {
+ *ReturnedEntryCount = 0;
+ *SortedBuffer = NULL;
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // If the exception indicates the server doesn't have
+ // the selected api, that means the server doesn't know
+ // about the info level we passed. Set our completion
+ // status appropriately.
+ //
+
+ if (RpcExceptionCode() == RPC_S_INVALID_LEVEL ||
+ RpcExceptionCode() == RPC_S_PROCNUM_OUT_OF_RANGE ||
+ RpcExceptionCode() == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ } else {
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+ }
+
+ } RpcEndExcept;
+
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamGetDisplayEnumerationIndex (
+ IN SAM_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN PUNICODE_STRING Prefix,
+ OUT PULONG Index
+ )
+/*++
+
+Routine Description:
+
+ This routine returns the index of the entry which alphabetically
+ immediatly preceeds a specified prefix. If no such entry exists,
+ then zero is returned as the index.
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which sorted information class is
+ to be searched.
+
+ Prefix - The prefix to compare.
+
+ Index - Receives the index of the entry of the information class
+ with a LogonName (or MachineName) which immediatly preceeds the
+ provided prefix string. If there are no elements which preceed
+ the prefix, then zero is returned.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+ STATUS_NO_MORE_ENTRIES - There are no entries for this information class.
+ The returned index is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ //
+ // Check parameters
+ //
+
+ if ( !ARGUMENT_PRESENT(Prefix) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+ if ( !ARGUMENT_PRESENT(Index) ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+ if (DisplayInformation <= DomainDisplayMachine) {
+ //
+ // Info levels supported via original API in NT1.0
+ //
+
+ NtStatus = SamrGetDisplayEnumerationIndex (
+ (SAMPR_HANDLE)DomainHandle,
+ DisplayInformation,
+ (PRPC_UNICODE_STRING)Prefix,
+ Index
+ );
+ } else {
+
+ //
+ // Info levels added in NT1.0A via new API
+ //
+
+ NtStatus = SamrGetDisplayEnumerationIndex2 (
+ (SAMPR_HANDLE)DomainHandle,
+ DisplayInformation,
+ (PRPC_UNICODE_STRING)Prefix,
+ Index
+ );
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+
+}
+
+
+
+
+NTSTATUS
+SamOpenGroup(
+ IN SAM_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG GroupId,
+ OUT PSAM_HANDLE GroupHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API opens an existing group in the account database. The group
+ is specified by a ID value that is relative to the SID of the
+ domain. The operations that will be performed on the group must be
+ declared at this time.
+
+ This call returns a handle to the newly opened group that may be
+ used for successive operations on the group. This handle may be
+ closed with the SamCloseHandle API.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the group. These access types are reconciled
+ with the Discretionary Access Control list of the group to
+ determine whether the accesses will be granted or denied.
+
+ GroupId - Specifies the relative ID value of the group to be
+ opened.
+
+ GroupHandle - Receives a handle referencing the newly opened
+ group. This handle will be required in successive calls to
+ operate on the group.
+
+Return Values:
+
+ STATUS_SUCCESS - The group was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_NO_SUCH_GROUP - The specified group does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ (*GroupHandle) = 0;
+
+ NtStatus =
+ SamrOpenGroup(
+ (SAMPR_HANDLE)DomainHandle,
+ DesiredAccess,
+ GroupId,
+ (SAMPR_HANDLE *)GroupHandle
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamQueryInformationGroup(
+ IN SAM_HANDLE GroupHandle,
+ IN GROUP_INFORMATION_CLASS GroupInformationClass,
+ OUT PVOID * Buffer
+)
+
+/*++
+
+Routine Description:
+
+ This API retrieves information on the group specified.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ GroupInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ---------------------- ----------------------
+ GroupGeneralInformation GROUP_READ_INFORMATION
+ GroupNameInformation GROUP_READ_INFORMATION
+ GroupAttributeInformation GROUP_READ_INFORMATION
+ GroupAdminInformation GROUP_READ_INFORMATION
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this
+ buffer must be freed 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 handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*Buffer) = NULL;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrQueryInformationGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ GroupInformationClass,
+ (PSAMPR_GROUP_INFO_BUFFER *)Buffer
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamSetInformationGroup(
+ IN SAM_HANDLE GroupHandle,
+ IN GROUP_INFORMATION_CLASS GroupInformationClass,
+ IN PVOID Buffer
+)
+/*++
+
+
+Routine Description:
+
+ This API allows the caller to modify group information.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ GroupInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ------------------------ -------------------------
+
+ GroupGeneralInformation (can't write)
+
+ GroupNameInformation GROUP_WRITE_ACCOUNT
+ GroupAttributeInformation GROUP_WRITE_ACCOUNT
+ GroupAdminInformation GROUP_WRITE_ACCOUNT
+
+ Buffer - Buffer where information retrieved is placed.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_GROUP - The group specified is unknown.
+
+ STATUS_SPECIAL_GROUP - The group specified is a special group and
+ cannot be operated on in the requested fashion.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrSetInformationGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ GroupInformationClass,
+ Buffer
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamAddMemberToGroup(
+ IN SAM_HANDLE GroupHandle,
+ IN ULONG MemberId,
+ IN ULONG Attributes
+)
+
+/*++
+
+Routine Description:
+
+ This API adds a member to a group. Note that this API requires the
+ GROUP_ADD_MEMBER access type for the group.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ MemberId - Relative ID of the member to add.
+
+ Attributes - The attributes of the group assigned to the user. The
+ attributes assigned here must be compatible with the attributes
+ assigned to the group as a whole. Compatible attribute
+ assignments are:
+
+ Mandatory - If the Mandatory attribute is assigned to the
+ group as a whole, then it must be assigned to the
+ group for each member of the group.
+
+ EnabledByDefault - This attribute may be set to any value
+ for each member of the group. It does not matter
+ what the attribute value for the group as a whole
+ is.
+
+ Enabled - This attribute may be set to any value for each
+ member of the group. It does not matter what the
+ attribute value for the group as a whole is.
+
+ Owner - The Owner attribute may be assigned any value.
+ However, if the Owner attribute of the group as a
+ whole is not set, then the value assigned to
+ members is ignored.
+
+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_NO_SUCH_MEMBER - The member specified is unknown.
+
+ STATUS_MEMBER_IN_GROUP - The member already belongs to the group.
+
+ STATUS_INVALID_GROUP_ATTRIBUTES - Indicates the group attribute
+ values being assigned to the member are not compatible with
+ the attribute values of the group as a whole.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrAddMemberToGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ MemberId,
+ Attributes
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamDeleteGroup(
+ IN SAM_HANDLE GroupHandle
+)
+
+/*++
+
+Routine Description:
+
+ This API removes a group from the account database. There may be no
+ members in the group or the deletion request will be rejected. Note
+ that this API requires DELETE access to the specific group being
+ deleted.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+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_SPECIAL_GROUP - The group specified is a special group and
+ cannot be operated on in the requested fashion.
+
+ STATUS_MEMBER_IN_GROUP - The group still has members. A group may
+ not be deleted unless it has no members.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ SAMPR_HANDLE LocalHandle;
+
+ LocalHandle = (SAMPR_HANDLE)GroupHandle;
+
+ if (LocalHandle == 0) {
+ return(STATUS_INVALID_HANDLE);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SamrDeleteGroup( &LocalHandle );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamRemoveMemberFromGroup(
+ IN SAM_HANDLE GroupHandle,
+ IN ULONG MemberId
+)
+
+/*++
+
+Routine Description:
+
+ This API removes a single member from a group. Note that this API
+ requires the GROUP_REMOVE_MEMBER access type.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ MemberId - Relative ID of the member to remove.
+
+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_SPECIAL_GROUP - The group specified is a special group and
+ cannot be operated on in the requested fashion.
+
+ STATUS_MEMBER_NOT_IN_GROUP - The specified user does not belong
+ to the group.
+
+ STATUS_MEMBERS_PRIMARY_GROUP - A user may not be removed from its
+ primary group. The primary group of the user account must first
+ be changed.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrRemoveMemberFromGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ MemberId
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamGetMembersInGroup(
+ IN SAM_HANDLE GroupHandle,
+ OUT PULONG * MemberIds,
+ OUT PULONG * Attributes,
+ OUT PULONG MemberCount
+)
+
+/*++
+
+Routine Description:
+
+ This API lists all the members in a group. This API requires
+ GROUP_LIST_MEMBERS access to the group.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ MemberIds - Receives a pointer to a buffer containing An array of
+ ULONGs. These ULONGs contain the relative IDs of the members
+ of the group. When this information is no longer needed,
+ this buffer must be freed using SamFreeMemory().
+
+ Attributes - Receives a pointer to a buffer containing an array of
+ ULONGs. These ULONGs contain the attributes of the
+ corresponding member ID returned via the MemberId paramter.
+
+ MemberCount - number of members in the group (and, thus, the
+ number relative IDs returned).
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no additional entries.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSAMPR_GET_MEMBERS_BUFFER GetMembersBuffer;
+
+
+
+ //
+ // Call the server ...
+ //
+
+
+ GetMembersBuffer = NULL;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrGetMembersInGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ &GetMembersBuffer
+ );
+
+ //
+ // What we get back is the following:
+ //
+ // +-------------+
+ // --------->| MemberCount |
+ // |-------------+ +-------+
+ // | Members --|------------------->| Rid-0 |
+ // |-------------| +------------+ | ... |
+ // | Attributes-|-->| Attribute0 | | |
+ // +-------------+ | ... | | Rid-N |
+ // | AttributeN | +-------+
+ // +------------+
+ //
+ // Each block allocated with MIDL_user_allocate. We return the
+ // RID and ATTRIBUTE blocks, and free the block containing
+ // the MemberCount ourselves.
+ //
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ (*MemberCount) = GetMembersBuffer->MemberCount;
+ (*MemberIds) = GetMembersBuffer->Members;
+ (*Attributes) = GetMembersBuffer->Attributes;
+ MIDL_user_free( GetMembersBuffer );
+ } else {
+
+ //
+ // Deallocate any returned buffers on error
+ //
+
+ if (GetMembersBuffer != NULL) {
+ if (GetMembersBuffer->Members != NULL) {
+ MIDL_user_free(GetMembersBuffer->Members);
+ }
+ if (GetMembersBuffer->Attributes != NULL) {
+ MIDL_user_free(GetMembersBuffer->Attributes);
+ }
+ MIDL_user_free(GetMembersBuffer);
+ }
+ }
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+
+}
+
+
+
+NTSTATUS
+SamSetMemberAttributesOfGroup(
+ IN SAM_HANDLE GroupHandle,
+ IN ULONG MemberId,
+ IN ULONG Attributes
+)
+
+/*++
+
+Routine Description:
+
+ This routine modifies the group attributes of a member of the group.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on. This
+ handle must be open for GROUP_ADD_MEMBER access to the group.
+
+ MemberId - Contains the relative ID of member whose attributes
+ are to be modified.
+
+ Attributes - The group attributes to set for the member. These
+ attributes must not conflict with the attributes of the group
+ as a whole. See SamAddMemberToGroup() for more information
+ on compatible attribute settings.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no additional entries.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_USER - The user specified does not exist.
+
+ STATUS_MEMBER_NOT_IN_GROUP - Indicates the specified relative ID
+ is not a member of the group.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrSetMemberAttributesOfGroup(
+ (SAMPR_HANDLE)GroupHandle,
+ MemberId,
+ Attributes
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamOpenAlias(
+ IN SAM_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG AliasId,
+ OUT PSAM_HANDLE AliasHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API opens an existing Alias object. The Alias is specified by
+ a ID value that is relative to the SID of the domain. The operations
+ that will be performed on the Alias must be declared at this time.
+
+ This call returns a handle to the newly opened Alias that may be used
+ for successive operations on the Alias. This handle may be closed
+ with the SamCloseHandle API.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types are
+ desired to the alias.
+
+ AliasId - Specifies the relative ID value of the Alias to be opened.
+
+ AliasHandle - Receives a handle referencing the newly opened Alias.
+ This handle will be required in successive calls to operate on
+ the Alias.
+
+Return Values:
+
+ STATUS_SUCCESS - The Alias was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate access
+ to complete the operation.
+
+ STATUS_NO_SUCH_ALIAS - The specified Alias does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ (*AliasHandle) = 0;
+
+ NtStatus =
+ SamrOpenAlias(
+ (SAMPR_HANDLE)DomainHandle,
+ DesiredAccess,
+ AliasId,
+ (SAMPR_HANDLE *)AliasHandle
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamQueryInformationAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN ALIAS_INFORMATION_CLASS AliasInformationClass,
+ OUT PVOID * Buffer
+)
+
+/*++
+
+Routine Description:
+
+ This API retrieves information on the alias specified.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ AliasInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ---------------------- ----------------------
+ AliasGeneralInformation ALIAS_READ_INFORMATION
+ AliasNameInformation ALIAS_READ_INFORMATION
+ AliasAdminInformation ALIAS_READ_INFORMATION
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this
+ buffer and any memory pointed to through this buffer must be
+ freed 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 handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*Buffer) = NULL;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrQueryInformationAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ AliasInformationClass,
+ (PSAMPR_ALIAS_INFO_BUFFER *)Buffer
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamSetInformationAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN ALIAS_INFORMATION_CLASS AliasInformationClass,
+ IN PVOID Buffer
+)
+/*++
+
+
+Routine Description:
+
+ This API allows the caller to modify alias information.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ AliasInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ------------------------ -------------------------
+
+ AliasGeneralInformation (can't write)
+
+ AliasNameInformation ALIAS_WRITE_ACCOUNT
+ AliasAdminInformation ALIAS_WRITE_ACCOUNT
+
+ Buffer - Buffer where information retrieved is placed.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_ALIAS - The alias specified is unknown.
+
+ STATUS_SPECIAL_ALIAS - The alias specified is a special alias and
+ cannot be operated on in the requested fashion.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrSetInformationAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ AliasInformationClass,
+ Buffer
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+}
+
+
+
+NTSTATUS
+SamDeleteAlias(
+ IN SAM_HANDLE AliasHandle
+)
+
+/*++
+
+Routine Description:
+
+ This API deletes an Alias from the account database. The Alias does
+ not have to be empty.
+
+ Note that following this call, the AliasHandle is no longer valid.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+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_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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAMPR_HANDLE LocalHandle;
+
+ LocalHandle = (SAMPR_HANDLE)AliasHandle;
+
+ if (LocalHandle == 0) {
+ return(STATUS_INVALID_HANDLE);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SamrDeleteAlias( &LocalHandle );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamAddMemberToAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This API adds a member to an Alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias object to operate on.
+ The handle must be open for ALIAS_ADD_MEMBER.
+
+ MemberId - The SID of the member to add.
+
+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_MEMBER_IN_ALIAS - The member already belongs to the 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 for this operation.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect
+ role (primary or backup) to perform the requested operation.
+
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrAddMemberToAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ MemberId
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+NTSTATUS
+SamRemoveMemberFromAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This API removes a member from an Alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias object to operate on.
+ The handle must be open for ALIAS_REMOVE_MEMBER.
+
+ MemberId - The SID of the member to remove.
+
+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_SPECIAL_ALIAS - The group specified is a special alias and
+ cannot be operated on in the requested fashion.
+
+ STATUS_MEMBER_NOT_IN_ALIAS - The specified member does not belong to
+ the 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 for this operation.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect
+ role (primary or backup) to perform the requested operation.
+
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrRemoveMemberFromAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ MemberId
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+NTSTATUS
+SamRemoveMemberFromForeignDomain(
+ IN SAM_HANDLE DomainHandle,
+ IN PSID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This API removes a member from an all Aliases in the domain specified.
+
+
+Parameters:
+
+ DomainHandle - The handle of an opened domain to operate in. All
+ aliases in the domain that the member is a part of must be
+ accessible by the caller with ALIAS_REMOVE_MEMBER.
+
+ MemberId - The SID of the member to remove.
+
+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_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 for this operation.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect
+ role (primary or backup) to perform the requested operation.
+
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrRemoveMemberFromForeignDomain(
+ (SAMPR_HANDLE)DomainHandle,
+ MemberId
+ );
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+NTSTATUS
+SamGetMembersInAlias(
+ IN SAM_HANDLE AliasHandle,
+ OUT PSID **MemberIds,
+ OUT PULONG MemberCount
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all members in an Alias. This API requires
+ ALIAS_LIST_MEMBERS access to the Alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+ MemberIds - Receives a pointer to a buffer containing an array of
+ pointers to SIDs. These SIDs are the SIDs of the members of the
+ Alias. When this information is no longer needed, this buffer
+ must be freed using SamFreeMemory().
+
+ MemberCount - number of members in the Alias (and, thus, the number
+ of relative IDs returned).
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there are
+ no additional entries.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ SAMPR_PSID_ARRAY MembersBuffer;
+
+ //
+ // Validate parameters
+ //
+
+ if ( !ARGUMENT_PRESENT(MemberIds) ) {
+ return(STATUS_INVALID_PARAMETER_2);
+ }
+ if ( !ARGUMENT_PRESENT(MemberCount) ) {
+ return(STATUS_INVALID_PARAMETER_3);
+ }
+
+
+ RpcTryExcept{
+
+ //
+ // Prepare for failure
+ //
+
+ *MemberIds = NULL;
+ *MemberCount = 0;
+
+ //
+ // Call the server ...
+ //
+
+ MembersBuffer.Sids = NULL;
+
+ NtStatus = SamrGetMembersInAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ &MembersBuffer
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Return the member list
+ //
+
+ *MemberCount = MembersBuffer.Count;
+ *MemberIds = (PSID *)(MembersBuffer.Sids);
+
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamAddMultipleMembersToAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID *MemberIds,
+ IN ULONG MemberCount
+ )
+
+/*++
+
+Routine Description:
+
+ This API adds the SIDs listed in MemberIds to the specified Alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+ MemberIds - Points to an array of SID pointers containing MemberCount
+ entries.
+
+ MemberCount - number of members in the array.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully. All of the
+ listed members are now members of the alias. However, some of
+ the members may already have been members of the alias (this is
+ NOT an error or warning condition).
+
+ STATUS_ACCESS_DENIED - Caller does not have the object open for
+ the required access.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_MEMBER - The member has the wrong account type.
+
+ STATUS_INVALID_SID - The member sid is corrupted.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+--*/
+
+{
+ NTSTATUS NtStatus;
+ SAMPR_PSID_ARRAY MembersBuffer;
+
+ //
+ // Validate parameters
+ //
+
+ if ( !ARGUMENT_PRESENT(MemberIds) ) {
+ return(STATUS_INVALID_PARAMETER_2);
+ }
+
+
+ RpcTryExcept{
+
+ //
+ // Call the server ...
+ //
+
+ MembersBuffer.Count = MemberCount;
+ MembersBuffer.Sids = (PSAMPR_SID_INFORMATION)MemberIds;
+
+ NtStatus = SamrAddMultipleMembersToAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ &MembersBuffer
+ );
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamRemoveMultipleMembersFromAlias(
+ IN SAM_HANDLE AliasHandle,
+ IN PSID *MemberIds,
+ IN ULONG MemberCount
+ )
+
+/*++
+
+Routine Description:
+
+ This API Removes the SIDs listed in MemberIds from the specified Alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+ MemberIds - Points to an array of SID pointers containing MemberCount
+ entries.
+
+ MemberCount - number of members in the array.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully. All of the
+ listed members are now NOT members of the alias. However, some of
+ the members may already have not been members of the alias (this
+ is NOT an error or warning condition).
+
+ STATUS_ACCESS_DENIED - Caller does not have the object open for
+ the required access.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ SAMPR_PSID_ARRAY MembersBuffer;
+
+ //
+ // Validate parameters
+ //
+
+ if ( !ARGUMENT_PRESENT(MemberIds) ) {
+ return(STATUS_INVALID_PARAMETER_2);
+ }
+
+
+ RpcTryExcept{
+
+ //
+ // Call the server ...
+ //
+
+ MembersBuffer.Count = MemberCount;
+ MembersBuffer.Sids = (PSAMPR_SID_INFORMATION)MemberIds;
+
+ NtStatus = SamrRemoveMultipleMembersFromAlias(
+ (SAMPR_HANDLE)AliasHandle,
+ &MembersBuffer
+ );
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamOpenUser(
+ IN SAM_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG UserId,
+ OUT PSAM_HANDLE UserHandle
+ )
+/*++
+
+Routine Description:
+
+ This API opens an existing user in the account database. The user
+ is specified by SID value. The operations that will be performed on
+ the user must be declared at this time.
+
+ This call returns a handle to the newly opened user that may be used
+ for successive operations on the user. This handle may be closed
+ with the SamCloseHandle API.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the user. These access types are reconciled
+ with the Discretionary Access Control list of the user to
+ determine whether the accesses will be granted or denied.
+
+ UserId - Specifies the relative ID value of the user account to
+ be opened.
+
+ UserHandle - Receives a handle referencing the newly opened User.
+ This handle will be required in successive calls to operate
+ on the user.
+
+Return Values:
+
+ STATUS_SUCCESS - The group was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_NO_SUCH_USER - The specified user does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ RpcTryExcept{
+
+ (*UserHandle) = 0;
+
+ NtStatus =
+ SamrOpenUser(
+ (SAMPR_HANDLE)DomainHandle,
+ DesiredAccess,
+ UserId,
+ (SAMPR_HANDLE *)UserHandle
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamDeleteUser(
+ IN SAM_HANDLE UserHandle
+)
+
+/*++
+
+Routine Description:
+
+ This API deletes a user from the account database. If the account
+ being deleted is the last account in the database in the ADMIN
+ group, then STATUS_LAST_ADMIN is returned, and the Delete fails. Note
+ that this API required DOMAIN_DELETE_USER access.
+
+ Note that following this call, the UserHandle is no longer valid.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on. The handle
+ must be opened for DELETE access.
+
+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_LAST_ADMIN - Cannot delete the last administrator.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAMPR_HANDLE LocalHandle;
+
+ LocalHandle = (SAMPR_HANDLE)UserHandle;
+
+ if (LocalHandle == 0) {
+ return(STATUS_INVALID_HANDLE);
+ }
+
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SamrDeleteUser( &LocalHandle );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamQueryInformationUser(
+ IN SAM_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ OUT PVOID * Buffer
+)
+
+/*++
+
+
+Routine Description:
+
+ This API looks up some level of information about a particular user.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ UserInformationClass - Class of information desired about this
+ user. The accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ---------------------- --------------------------
+
+ UserGeneralInformation USER_READ_GENERAL
+ UserPreferencesInformation USER_READ_PREFERENCES
+ UserLogonInformation USER_READ_GENERAL and
+ USER_READ_PREFERENCES and
+ USER_READ_LOGON
+
+ UserLogonHoursInformation USER_READ_LOGON
+
+ UserAccountInformation USER_READ_GENERAL and
+ USER_READ_PREFERENCES and
+ USER_READ_LOGON and
+ USER_READ_ACCOUNT
+
+ UserParametersInformation USER_READ_ACCOUNT
+
+ UserNameInformation USER_READ_GENERAL
+ UserAccountNameInformation USER_READ_GENERAL
+ UserFullNameInformation USER_READ_GENERAL
+ UserPrimaryGroupInformation USER_READ_GENERAL
+ UserHomeInformation USER_READ_LOGON
+ UserScriptInformation USER_READ_LOGON
+ UserProfileInformation USER_READ_LOGON
+ UserAdminCommentInformation USER_READ_GENERAL
+ UserWorkStationsInformation USER_READ_LOGON
+
+ UserSetPasswordInformation (Can't query)
+
+ UserControlInformation USER_READ_ACCOUNT
+ UserExpiresInformation USER_READ_ACCOUNT
+
+ UserInternal1Information (trusted client use only)
+ UserInternal2Information (trusted client use only)
+
+ UserAllInformation Will return fields that user
+ has access to.
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this
+ buffer must be freed 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 handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+ //
+ // Call the server ...
+ //
+
+
+ (*Buffer) = NULL;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrQueryInformationUser(
+ (SAMPR_HANDLE)UserHandle,
+ UserInformationClass,
+ (PSAMPR_USER_INFO_BUFFER *)Buffer
+ );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SampOwfPassword(
+ IN SAM_HANDLE UserHandle,
+ IN PUNICODE_STRING UnicodePassword,
+ IN BOOLEAN IgnorePasswordRestrictions,
+ OUT PBOOLEAN NtPasswordPresent,
+ OUT PNT_OWF_PASSWORD NtOwfPassword,
+ OUT PBOOLEAN LmPasswordPresent,
+ OUT PLM_OWF_PASSWORD LmOwfPassword
+)
+
+/*++
+
+Routine Description:
+
+ This routine takes a cleartext unicode NT password from the user,
+ makes sure it meets our high standards for password quality,
+ converts it to an LM password if possible, and runs both passwords
+ through a one-way function (OWF).
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ UnicodePassword - the cleartext unicode NT password.
+
+ IgnorePasswordRestrictions - When TRUE, indicates that the password
+ should be accepted as legitimate regardless of what the domain's
+ password restrictions indicate (e.g., can be less than
+ required password length). This is expected to be used when
+ setting up a new machine account.
+
+ NtPasswordPresent - receives a boolean that says whether the NT
+ password is present or not.
+
+ NtOwfPassword - receives the OWF'd version of the NT password.
+
+ LmPasswordPresent - receives a boolean that says whether the LM
+ password is present or not.
+
+ LmOwfPassword - receives the OWF'd version of the LM password.
+
+
+Return Values:
+
+ STATUS_SUCCESS - the service has completed. The booleans say which
+ of the OWFs are valid.
+
+ Errors are returned by SampCheckPasswordRestrictions(),
+ RtlCalculateNtOwfPassword(), SampCalculateLmPassword(), and
+ RtlCalculateLmOwfPassword().
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PCHAR LmPasswordBuffer;
+ BOOLEAN UseOwfPasswords;
+
+ //
+ // We ignore the UseOwfPasswords flag since we already are.
+ //
+
+ if (IgnorePasswordRestrictions) {
+ NtStatus = STATUS_SUCCESS;
+ } else {
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ UnicodePassword,
+ &UseOwfPasswords
+ );
+ }
+
+ //
+ // Compute the NT One-Way-Function of the password
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ *NtPasswordPresent = TRUE;
+
+ NtStatus = RtlCalculateNtOwfPassword(
+ UnicodePassword,
+ NtOwfPassword
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Calculate the LM version of the password
+ //
+
+ NtStatus = SampCalculateLmPassword(
+ UnicodePassword,
+ &LmPasswordBuffer);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Compute the One-Way-Function of the LM password
+ //
+
+ *LmPasswordPresent = TRUE;
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ LmPasswordBuffer,
+ LmOwfPassword);
+
+ //
+ // We're finished with the LM password
+ //
+
+ MIDL_user_free(LmPasswordBuffer);
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+NTSTATUS
+SampRandomFill(
+ IN ULONG BufferSize,
+ IN OUT PUCHAR Buffer
+)
+/*++
+
+Routine Description:
+
+ This routine fills a buffer with random data.
+
+Parameters:
+
+ BufferSize - Length of the input buffer, in bytes.
+
+ Buffer - Input buffer to be filled with random data.
+
+Return Values:
+
+ Errors from NtQuerySystemTime()
+
+
+--*/
+{
+ ULONG Index;
+ LARGE_INTEGER Time;
+ ULONG Seed;
+ NTSTATUS NtStatus;
+
+
+ NtStatus = NtQuerySystemTime(&Time);
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ Seed = Time.LowPart ^ Time.HighPart;
+
+ for (Index = 0 ; Index < BufferSize ; Index++ )
+ {
+ *Buffer++ = (UCHAR) (RtlRandom(&Seed) % 256);
+ }
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+NTSTATUS
+SampEncryptClearPassword(
+ IN SAM_HANDLE UserHandle,
+ IN PUNICODE_STRING UnicodePassword,
+ OUT PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedUserPassword
+)
+
+/*++
+
+Routine Description:
+
+ This routine takes a cleartext unicode NT password from the user,
+ and encrypts it with the session key.
+
+Parameters:
+
+ UserHandle - SAM_HANDLE used to acquiring a session key.
+
+ UnicodePassword - the cleartext unicode NT password.
+
+ EncryptedUserPassword - receives the encrypted cleartext password.
+
+Return Values:
+
+ STATUS_SUCCESS - the service has completed. The booleans say which
+ of the OWFs are valid.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USER_SESSION_KEY UserSessionKey;
+ struct RC4_KEYSTRUCT Rc4Key;
+ PSAMPR_USER_PASSWORD UserPassword = (PSAMPR_USER_PASSWORD) EncryptedUserPassword;
+
+ if (UnicodePassword->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) {
+ return(STATUS_PASSWORD_RESTRICTION);
+ }
+
+ NtStatus = RtlGetUserSessionKeyClient(
+ (RPC_BINDING_HANDLE)UserHandle,
+ &UserSessionKey
+ );
+
+ //
+ // Convert the session key into an RC4 key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ rc4_key(
+ &Rc4Key,
+ sizeof(USER_SESSION_KEY),
+ (PUCHAR) &UserSessionKey
+ );
+
+ RtlCopyMemory(
+ ((PCHAR) UserPassword->Buffer) +
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ UnicodePassword->Length,
+ UnicodePassword->Buffer,
+ UnicodePassword->Length
+ );
+ UserPassword->Length = UnicodePassword->Length;
+
+ NtStatus = SampRandomFill(
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ UnicodePassword->Length,
+ (PUCHAR) UserPassword->Buffer
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ rc4(
+ &Rc4Key,
+ sizeof(SAMPR_ENCRYPTED_USER_PASSWORD),
+ (PUCHAR) UserPassword
+ );
+
+ }
+
+
+ }
+
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampEncryptOwfs(
+ IN SAM_HANDLE UserHandle,
+ IN BOOLEAN NtPasswordPresent,
+ IN PNT_OWF_PASSWORD NtOwfPassword,
+ OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword,
+ IN BOOLEAN LmPasswordPresent,
+ IN PLM_OWF_PASSWORD LmOwfPassword,
+ OUT PENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword
+)
+
+/*++
+
+Routine Description:
+
+ This routine takes NT and LM passwords that have already been OWF'd,
+ and encrypts them so that they can be safely sent to the server.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ NtPasswordPresent - indicates whether NtOwfPassword is valid or not.
+
+ NtOwfPassword - an OWF'd NT password, if NtPasswordPresent is true.
+
+ EncryptedNtOwfPassword - an encrypted version of the OWF'd NT password
+ that can be safely sent to the server.
+
+ LmPasswordPresent - indicates whether LmOwfPassword is valid or not.
+
+ LmOwfPassword - an OWF'd LM password, if LmPasswordPresent is true.
+
+ EncryptedLmOwfPassword - an encrypted version of the OWF'd LM password
+ that can be safely sent to the server.
+
+Return Values:
+
+ STATUS_SUCCESS - the passwords were encrypted and may be sent to the
+ server.
+
+ Errors may be returned by RtlGetUserSessionKeyClient(),
+ RtlEncryptNtOwfPwdWithUserKey(), and RtlEncryptLmOwfPwdWithUserKey().
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USER_SESSION_KEY UserSessionKey;
+
+ NtStatus = RtlGetUserSessionKeyClient(
+ (RPC_BINDING_HANDLE)UserHandle,
+ &UserSessionKey
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if (NtPasswordPresent) {
+
+ //
+ // Encrypt the Nt OWF Password with the user session key
+ // and store it the buffer to send
+ //
+
+ NtStatus = RtlEncryptNtOwfPwdWithUserKey(
+ NtOwfPassword,
+ &UserSessionKey,
+ EncryptedNtOwfPassword
+ );
+ }
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if (LmPasswordPresent) {
+
+ //
+ // Encrypt the Lm OWF Password with the user session key
+ // and store it the buffer to send
+ //
+
+ NtStatus = RtlEncryptLmOwfPwdWithUserKey(
+ LmOwfPassword,
+ &UserSessionKey,
+ EncryptedLmOwfPassword
+ );
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamSetInformationUser(
+ IN SAM_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ IN PVOID Buffer
+)
+
+/*++
+
+
+Routine Description:
+
+ This API modifies information in a user record. The data modified
+ is determined by the UserInformationClass parameter. To change
+ information here requires access to the user object defined above.
+ Each structure has both a read and write access type associated with
+ it. In general, a user may call GetInformation with class
+ UserLogonInformation, but may only call SetInformation with class
+ UserPreferencesInformation. Access type USER_WRITE_ACCOUNT allows
+ changes to be made to all fields.
+
+ NOTE: If the password is set to a new password then the password-
+ set timestamp is reset as well.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ UserInformationClass - Class of information provided. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ----------------------- ------------------------
+ UserGeneralInformation (Can't set)
+
+ UserPreferencesInformation USER_WRITE_PREFERENCES
+
+ UserParametersInformation USER_WRITE_ACCOUNT
+
+ UserLogonInformation (Can't set)
+
+ UserLogonHoursInformation USER_WRITE_ACCOUNT
+
+ UserAccountInformation (Can't set)
+
+ UserNameInformation USER_WRITE_ACCOUNT
+ UserAccountNameInformation USER_WRITE_ACCOUNT
+ UserFullNameInformation USER_WRITE_ACCOUNT
+ UserPrimaryGroupInformation USER_WRITE_ACCOUNT
+ UserHomeInformation USER_WRITE_ACCOUNT
+ UserScriptInformation USER_WRITE_ACCOUNT
+ UserProfileInformation USER_WRITE_ACCOUNT
+ UserAdminCommentInformation USER_WRITE_ACCOUNT
+ UserWorkStationsInformation USER_WRITE_ACCOUNT
+ UserSetPasswordInformation USER_FORCE_PASSWORD_CHANGE (also see note below)
+ UserControlInformation USER_WRITE_ACCOUNT
+ UserExpiresInformation USER_WRITE_ACCOUNT
+ UserInternal1Information USER_FORCE_PASSWORD_CHANGE (also see note below)
+ UserInternal2Information (trusted client use only)
+ UserAllInformation Will set fields that user
+ specifies, if accesses are
+ held as described above.
+
+
+ NOTE: When setting a password (with either
+ UserSetPasswordInformation or UserInternal1Information),
+ you MUST open the user account via a DomainHandle that
+ was opened for DOMAIN_READ_PASSWORD_PARAMETERS.
+
+ Buffer - Buffer containing a user info struct.
+
+
+
+
+
+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_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 enabled for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ SAMPR_USER_INTERNAL1_INFORMATION Internal1RpcBuffer;
+ USER_INTERNAL1_INFORMATION Internal1Buffer;
+ SAMPR_USER_INTERNAL4_INFORMATION Internal4RpcBuffer;
+ SAMPR_USER_INTERNAL5_INFORMATION Internal5RpcBuffer;
+ PVOID BufferToPass;
+ PUSER_ALL_INFORMATION UserAll;
+ USER_ALL_INFORMATION LocalAll;
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ BOOLEAN IgnorePasswordRestrictions;
+ ULONG Pass = 0;
+ USER_INFORMATION_CLASS ClassToUse = UserInformationClass;
+ BOOLEAN SendOwfs = FALSE;
+
+ do
+ {
+
+ RpcTryExcept{
+
+ //
+ // Normally just pass the info buffer through to rpc
+ //
+
+ BufferToPass = Buffer;
+
+
+ //
+ // Deal with special cases
+ //
+
+ switch (UserInformationClass) {
+
+
+ case UserPreferencesInformation: {
+
+ //
+ // Field is unused, but make sure RPC doesn't choke on it.
+ //
+
+ ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.Length = 0;
+ ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.MaximumLength = 0;
+ ((PUSER_PREFERENCES_INFORMATION)(Buffer))->Reserved1.Buffer = NULL;
+
+ break;
+ }
+
+ case UserSetPasswordInformation:
+
+ if (Pass == 0) {
+
+ //
+ // On the zeroth pass try sending a UserInternal5 structure.
+ // This is only available on 3.51 and above releases.
+ //
+
+ //
+ // Check password restrictions.
+ //
+
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password,
+ &SendOwfs
+ );
+
+ //
+ // If password restrictions told us we could send reversibly
+ // encrypted passwords, compute them. Otherwise drop through
+ // to the OWF case.
+ //
+
+ if (!SendOwfs) {
+
+ //
+ // Encrypt the cleatext password - we don't need to
+ // restrictions because that can be done on the server.
+ //
+
+ NtStatus = SampEncryptClearPassword(
+ UserHandle,
+ &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password,
+ &Internal5RpcBuffer.UserPassword
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ Internal5RpcBuffer.PasswordExpired =
+ ((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->PasswordExpired;
+
+
+ //
+ // Set the class and buffer to send over.
+ //
+
+ ClassToUse = UserInternal5Information;
+ BufferToPass = &Internal5RpcBuffer;
+ break;
+
+ }
+
+ } else {
+
+ //
+ // Set the pass counter to one since we aren't trying a new
+ // interface and don't want to retry.
+ //
+
+ Pass = 1;
+ SendOwfs = TRUE;
+ }
+
+ ASSERT(SendOwfs);
+
+ //
+ // We're going to calculate the OWFs for the password and
+ // turn this into an INTERNAL1 set info request by dropping
+ // through to the INTERNAL1 code with Buffer pointing at our
+ // local INTERNAL1 buffer. First, make sure that the password
+ // meets our quality requirements.
+ //
+
+ NtStatus = SampOwfPassword(
+ UserHandle,
+ &((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->Password,
+ FALSE, // Don't ignore password restrictions
+ &Internal1Buffer.NtPasswordPresent,
+ &Internal1Buffer.NtOwfPassword,
+ &Internal1Buffer.LmPasswordPresent,
+ &Internal1Buffer.LmOwfPassword
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+ //
+ // Copy the PasswordExpired flag
+ //
+
+ Internal1Buffer.PasswordExpired =
+ ((PUSER_SET_PASSWORD_INFORMATION)(Buffer))->PasswordExpired;
+
+
+ //
+ // We now have a USER_INTERNAL1_INFO buffer in Internal1Buffer.
+ // Point Buffer at Internal1buffer and drop through to the code
+ // that handles INTERNAL1 requests
+
+ Buffer = &Internal1Buffer;
+ ClassToUse = UserInternal1Information;
+
+ //
+ // drop through.....
+ //
+
+
+ case UserInternal1Information:
+
+
+ //
+ // We're going to pass a different data structure to rpc
+ //
+
+ BufferToPass = &Internal1RpcBuffer;
+
+
+ //
+ // Copy the password present flags
+ //
+
+ Internal1RpcBuffer.NtPasswordPresent =
+ ((PUSER_INTERNAL1_INFORMATION)Buffer)->NtPasswordPresent;
+
+ Internal1RpcBuffer.LmPasswordPresent =
+ ((PUSER_INTERNAL1_INFORMATION)Buffer)->LmPasswordPresent;
+
+
+ //
+ // Copy the PasswordExpired flag
+ //
+
+ Internal1RpcBuffer.PasswordExpired =
+ ((PUSER_INTERNAL1_INFORMATION)Buffer)->PasswordExpired;
+
+
+ //
+ // Encrypt the OWFs with the user session key before we send
+ // them over the Rpc link
+ //
+
+ NtStatus = SampEncryptOwfs(
+ UserHandle,
+ Internal1RpcBuffer.NtPasswordPresent,
+ &((PUSER_INTERNAL1_INFORMATION)Buffer)->NtOwfPassword,
+ &Internal1RpcBuffer.EncryptedNtOwfPassword,
+ Internal1RpcBuffer.LmPasswordPresent,
+ &((PUSER_INTERNAL1_INFORMATION)Buffer)->LmOwfPassword,
+ &Internal1RpcBuffer.EncryptedLmOwfPassword
+ );
+
+ break;
+
+
+
+ case UserAllInformation:
+
+ UserAll = (PUSER_ALL_INFORMATION)Buffer;
+
+ //
+ // If the caller is passing passwords we need to convert them
+ // into OWFs and encrypt them.
+ //
+
+ if (UserAll->WhichFields & (USER_ALL_LMPASSWORDPRESENT |
+ USER_ALL_NTPASSWORDPRESENT) ) {
+
+ //
+ // We'll need a private copy of the buffer which we can edit
+ // and then send over RPC.
+ //
+
+
+
+
+ if (UserAll->WhichFields & USER_ALL_OWFPASSWORD) {
+
+ LocalAll = *UserAll;
+ BufferToPass = &LocalAll;
+ SendOwfs = TRUE;
+
+ //
+ // The caller is passing OWFS directly
+ // Check they're valid and copy them into the
+ // Internal1Buffer in preparation for encryption.
+ //
+
+ if (LocalAll.WhichFields & USER_ALL_NTPASSWORDPRESENT) {
+
+ if (LocalAll.NtPasswordPresent) {
+
+ if (LocalAll.NtPassword.Length != NT_OWF_PASSWORD_LENGTH) {
+ NtStatus = STATUS_INVALID_PARAMETER;
+ } else {
+ Internal1Buffer.NtOwfPassword =
+ *((PNT_OWF_PASSWORD)LocalAll.NtPassword.Buffer);
+ }
+
+ } else {
+ LocalAll.NtPasswordPresent = FALSE;
+ }
+ }
+
+ if (LocalAll.WhichFields & USER_ALL_LMPASSWORDPRESENT) {
+
+ if (LocalAll.LmPasswordPresent) {
+
+ if (LocalAll.LmPassword.Length != LM_OWF_PASSWORD_LENGTH) {
+ NtStatus = STATUS_INVALID_PARAMETER;
+ } else {
+ Internal1Buffer.LmOwfPassword =
+ *((PNT_OWF_PASSWORD)LocalAll.LmPassword.Buffer);
+ }
+
+ } else {
+ LocalAll.LmPasswordPresent = FALSE;
+ }
+ }
+
+
+ //
+ // Always remove the OWF_PASSWORDS flag. This is used
+ // only on the client side to determine the mode
+ // of password input and will be rejected by the server
+ //
+
+ LocalAll.WhichFields &= ~USER_ALL_OWFPASSWORD;
+
+
+
+ } else {
+
+
+
+ //
+ // The caller is passing text passwords.
+ // Check for validity and convert to OWFs.
+ //
+
+ if (UserAll->WhichFields & USER_ALL_LMPASSWORDPRESENT) {
+
+ //
+ // User clients are only allowed to put a unicode string
+ // in the NT password. We always calculate the LM password
+ //
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // The caller might be simultaneously setting
+ // the password and changing the account to be
+ // a machine or trust account. In this case,
+ // we don't validate the password (e.g., length).
+ //
+
+ IgnorePasswordRestrictions = FALSE;
+ if (UserAll->WhichFields &
+ USER_ALL_USERACCOUNTCONTROL) {
+ if (UserAll->UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)
+ ) {
+ IgnorePasswordRestrictions = TRUE;
+ }
+ }
+
+
+ SendOwfs = TRUE;
+ if (Pass == 0) {
+
+ //
+ // On the first pass, try sending the cleatext
+ // password.
+ //
+
+ Internal4RpcBuffer.I1 = *(PSAMPR_USER_ALL_INFORMATION)
+ UserAll;
+
+ BufferToPass = &Internal4RpcBuffer;
+ ClassToUse = UserInternal4Information;
+ SendOwfs = FALSE;
+
+ //
+ // Check the password restrictions. We also
+ // want to get the information on whether
+ // we can send reversibly encrypted passwords.
+ //
+
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ &UserAll->NtPassword,
+ &SendOwfs
+ );
+
+ if (IgnorePasswordRestrictions) {
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ if (!SendOwfs) {
+ //
+ // Encrypt the clear password
+ //
+
+ NtStatus = SampEncryptClearPassword(
+ UserHandle,
+ &UserAll->NtPassword,
+ &Internal4RpcBuffer.UserPassword
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ //
+ // Zero the password NT password
+ //
+
+ RtlZeroMemory(
+ &Internal4RpcBuffer.I1.NtOwfPassword,
+ sizeof(UNICODE_STRING)
+ );
+
+ }
+ }
+
+ if (SendOwfs) {
+
+
+ //
+ // On the second pass, do the normal thing.
+ //
+
+ LocalAll = *UserAll;
+ BufferToPass = &LocalAll;
+ SendOwfs = TRUE;
+
+ ClassToUse = UserAllInformation;
+ if ( LocalAll.WhichFields & USER_ALL_NTPASSWORDPRESENT ) {
+
+ //
+ // The user specified a password. We must validate
+ // it, convert it to LM, and calculate the OWFs
+ //
+
+ LocalAll.WhichFields |= USER_ALL_LMPASSWORDPRESENT;
+
+
+ //
+ // Stick the OWFs in the Internal1Buffer - just
+ // until we use them in the SampEncryptOwfs().
+ //
+
+ NtStatus = SampOwfPassword(
+ UserHandle,
+ &LocalAll.NtPassword,
+ IgnorePasswordRestrictions,
+ &LocalAll.NtPasswordPresent,
+ &(Internal1Buffer.NtOwfPassword),
+ &LocalAll.LmPasswordPresent,
+ &(Internal1Buffer.LmOwfPassword)
+ );
+ }
+ }
+
+ }
+ }
+
+
+
+
+ //
+ // We now have one or more OWFs in Internal1 buffer.
+ // We got these either directly or we calculated them
+ // from the text strings.
+ // Encrypt these OWFs with the session key and
+ // store the result in Internal1RpcBuffer.
+ //
+ // Note the Password present flags are in LocalAll.
+ // (The ones in Internal1Buffer are not used.)
+ //
+
+ if ( NT_SUCCESS( NtStatus ) && SendOwfs ) {
+
+ //
+ // Make all LocalAll's password strings point to
+ // the buffers in Internal1RpcBuffer
+ //
+
+ LocalAll.NtPassword.Length =
+ sizeof( ENCRYPTED_NT_OWF_PASSWORD );
+ LocalAll.NtPassword.MaximumLength =
+ sizeof( ENCRYPTED_NT_OWF_PASSWORD );
+ LocalAll.NtPassword.Buffer = (PWSTR)
+ &Internal1RpcBuffer.EncryptedNtOwfPassword;
+
+ LocalAll.LmPassword.Length =
+ sizeof( ENCRYPTED_LM_OWF_PASSWORD );
+ LocalAll.LmPassword.MaximumLength =
+ sizeof( ENCRYPTED_LM_OWF_PASSWORD );
+ LocalAll.LmPassword.Buffer = (PWSTR)
+ &Internal1RpcBuffer.EncryptedLmOwfPassword;
+
+ //
+ // Encrypt the Owfs
+ //
+
+ NtStatus = SampEncryptOwfs(
+ UserHandle,
+ LocalAll.NtPasswordPresent,
+ &Internal1Buffer.NtOwfPassword,
+ &Internal1RpcBuffer.EncryptedNtOwfPassword,
+ LocalAll.LmPasswordPresent,
+ &Internal1Buffer.LmOwfPassword,
+ &Internal1RpcBuffer.EncryptedLmOwfPassword
+ );
+ }
+ }
+
+ break;
+
+ default:
+
+ break;
+
+ } // switch
+
+
+
+
+ //
+ // Call the server ...
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we are trying one of the new info levels, use the new
+ // api.
+ //
+
+ if ((ClassToUse == UserInternal4Information) ||
+ (ClassToUse == UserInternal5Information)) {
+
+ NtStatus =
+ SamrSetInformationUser2(
+ (SAMPR_HANDLE)UserHandle,
+ ClassToUse,
+ BufferToPass
+ );
+
+ } else {
+ NtStatus =
+ SamrSetInformationUser(
+ (SAMPR_HANDLE)UserHandle,
+ ClassToUse,
+ BufferToPass
+ );
+ }
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ Pass++;
+
+ //
+ // If this is the first pass and the status indicated that the
+ // server did not support the info class or the api
+ // and we were trying one of the new info levels, try again.
+ //
+
+ } while ( (Pass < 2) &&
+ ((NtStatus == RPC_NT_INVALID_TAG) ||
+ (NtStatus == RPC_NT_UNKNOWN_IF) ||
+ (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)));
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+
+NTSTATUS
+SamiLmChangePasswordUser(
+ IN SAM_HANDLE UserHandle,
+ IN PENCRYPTED_LM_OWF_PASSWORD LmOldEncryptedWithLmNew,
+ IN PENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithLmOld
+)
+
+/*++
+
+
+Routine Description:
+
+ Changes the password of a user account. This routine is intended to be
+ called by down-level system clients who have only the cross-encrypted
+ LM passwords available to them.
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+ This api will fail unless UAS Compatibility is enabled for the domain.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ LmOldEncryptedWithLmNew - the OWF of the old LM password encrypted using
+ the OWF of the new LM password as a key.
+
+ LmNewEncryptedWithLmOld - the OWF of the new LM password encrypted using
+ the OWF of the old LM password as a key.
+
+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_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - The old password is incorrect.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ //
+ // Check parameter validity
+ //
+
+ if (LmOldEncryptedWithLmNew == NULL) {
+ return(STATUS_INVALID_PARAMETER_1);
+ }
+ if (LmNewEncryptedWithLmOld == NULL) {
+ return(STATUS_INVALID_PARAMETER_2);
+ }
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SamrChangePasswordUser(
+ (SAMPR_HANDLE)UserHandle,
+
+ TRUE, // LmOldPresent
+ LmOldEncryptedWithLmNew,
+ LmNewEncryptedWithLmOld,
+
+ FALSE, // NtPresent
+ NULL, // NtOldEncryptedWithNtNew
+ NULL, // NtNewEncryptedWithNtOld
+
+ FALSE, // NtCrossEncryptionPresent
+ NULL,
+
+ FALSE, // LmCrossEncryptionPresent
+ NULL
+
+ );
+
+ //
+ // We should never get asked for cross-encrypted data
+ // since the server knows we don't have any NT data.
+ //
+
+ ASSERT (NtStatus != STATUS_NT_CROSS_ENCRYPTION_REQUIRED);
+ ASSERT (NtStatus != STATUS_LM_CROSS_ENCRYPTION_REQUIRED);
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+
+
+NTSTATUS
+SamiChangePasswordUser(
+ IN SAM_HANDLE UserHandle,
+ IN BOOLEAN LmOldPresent,
+ IN PLM_OWF_PASSWORD LmOldOwfPassword,
+ IN PLM_OWF_PASSWORD LmNewOwfPassword,
+ IN BOOLEAN NtPresent,
+ IN PNT_OWF_PASSWORD NtOldOwfPassword,
+ IN PNT_OWF_PASSWORD NtNewOwfPassword
+)
+
+/*++
+
+
+Routine Description:
+
+ Changes the password of a user account. This is the worker routine for
+ SamChangePasswordUser and can be called by OWF-aware clients.
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ LMOldPresent - TRUE if the LmOldOwfPassword is valid. This should only
+ be FALSE if the old password is too long to be represented
+ by a LM password. (Complex NT password).
+ Note the LMNewOwfPassword must always be valid.
+ If the new password is complex, the LMNewOwfPassword should
+ be the well-known LM OWF of a NULL password.
+
+ LmOldOwfPassword - One-way-function of the current LM password for the user.
+ - Ignored if LmOldPresent == FALSE
+
+ LmNewOwfPassword - One-way-function of the new LM password for the user.
+
+ NtPresent - TRUE if the NT one-way-functions are valid.
+ - i.e. This will be FALSE if we're called from a down-level client.
+
+ NtOldOwfPassword - One-way-function of the current NT password for the user.
+
+ NtNewOwfPassword - One-way-function of the new NT password for the user.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+ STATUS_INVALID_PARAMETER_MIX - LmOldPresent or NtPresent or both
+ must be TRUE.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithNtOld;
+ ENCRYPTED_NT_OWF_PASSWORD NtOldEncryptedWithNtNew;
+ ENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithLmNew;
+ ENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithLmOld;
+ ENCRYPTED_LM_OWF_PASSWORD LmOldEncryptedWithLmNew;
+ ENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithNtNew;
+
+ PENCRYPTED_NT_OWF_PASSWORD pNtNewEncryptedWithNtOld;
+ PENCRYPTED_NT_OWF_PASSWORD pNtOldEncryptedWithNtNew;
+ PENCRYPTED_LM_OWF_PASSWORD pLmNewEncryptedWithLmOld;
+ PENCRYPTED_LM_OWF_PASSWORD pLmOldEncryptedWithLmNew;
+
+ //
+ // Check parameter validity
+ //
+
+ if (!LmOldPresent && !NtPresent) {
+ return(STATUS_INVALID_PARAMETER_MIX);
+ }
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ //
+ // We're going to encrypt the oldLM with the newLM and vice-versa.
+ // We're going to encrypt the oldNT with the newNT and vice-versa.
+ // We're going to send these 4 encryptions and see if we're successful.
+ //
+ // If we get a return code of STATUS_LM_CROSS_ENCRYPTION_REQUIRED,
+ // we'll also encrypt the newLM with the newNT and send it all again.
+ //
+ // If we get a return code of STATUS_NT_CROSS_ENCRYPTION_REQUIRED,
+ // we'll also encrypt the newNT with the newLM and send it all again.
+ //
+ // We don't always send the cross-encryption otherwise we would be
+ // compromising security on pure NT systems with long passwords.
+ //
+
+ //
+ // Do the LM Encryption
+ //
+
+ if (!LmOldPresent) {
+
+ pLmOldEncryptedWithLmNew = NULL;
+ pLmNewEncryptedWithLmOld = NULL;
+
+ } else {
+
+ pLmOldEncryptedWithLmNew = &LmOldEncryptedWithLmNew;
+ pLmNewEncryptedWithLmOld = &LmNewEncryptedWithLmOld;
+
+ NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ LmOldOwfPassword,
+ LmNewOwfPassword,
+ &LmOldEncryptedWithLmNew);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ LmNewOwfPassword,
+ LmOldOwfPassword,
+ &LmNewEncryptedWithLmOld);
+ }
+ }
+
+ //
+ // Do the NT Encryption
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (!NtPresent) {
+
+ pNtOldEncryptedWithNtNew = NULL;
+ pNtNewEncryptedWithNtOld = NULL;
+
+ } else {
+
+ pNtOldEncryptedWithNtNew = &NtOldEncryptedWithNtNew;
+ pNtNewEncryptedWithNtOld = &NtNewEncryptedWithNtOld;
+
+ NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ NtOldOwfPassword,
+ NtNewOwfPassword,
+ &NtOldEncryptedWithNtNew);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ NtNewOwfPassword,
+ NtOldOwfPassword,
+ &NtNewEncryptedWithNtOld);
+ }
+ }
+ }
+
+
+ //
+ // Call the server (with no cross-encryption)
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamrChangePasswordUser(
+ (SAMPR_HANDLE)UserHandle,
+
+ LmOldPresent,
+ pLmOldEncryptedWithLmNew,
+ pLmNewEncryptedWithLmOld,
+
+ NtPresent,
+ pNtOldEncryptedWithNtNew,
+ pNtNewEncryptedWithNtOld,
+
+ FALSE, // NtCrossEncryptionPresent
+ NULL,
+
+ FALSE, // LmCrossEncryptionPresent
+ NULL
+
+ );
+
+ if (NtStatus == STATUS_NT_CROSS_ENCRYPTION_REQUIRED) {
+
+ //
+ // We should only get this if we have both LM and NT data
+ // (This is not obvious - it results from the server-side logic)
+ //
+
+ ASSERT(NtPresent && LmOldPresent);
+
+ //
+ // Compute the cross-encryption of the new Nt password
+ //
+
+ ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ NtNewOwfPassword,
+ (PNT_OWF_PASSWORD)LmNewOwfPassword,
+ &NtNewEncryptedWithLmNew);
+
+
+ //
+ // Call the server (with NT cross-encryption)
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamrChangePasswordUser(
+ (SAMPR_HANDLE)UserHandle,
+
+ LmOldPresent,
+ pLmOldEncryptedWithLmNew,
+ pLmNewEncryptedWithLmOld,
+
+ NtPresent,
+ pNtOldEncryptedWithNtNew,
+ pNtNewEncryptedWithNtOld,
+
+ TRUE,
+ &NtNewEncryptedWithLmNew,
+
+ FALSE,
+ NULL
+ );
+ }
+
+ } else {
+
+ if (NtStatus == STATUS_LM_CROSS_ENCRYPTION_REQUIRED) {
+
+ //
+ // We should only get this if we have NT but no old LM data
+ // (This is not obvious - it results from the server-side logic)
+ //
+
+ ASSERT(NtPresent && !LmOldPresent);
+
+ //
+ // Compute the cross-encryption of the new Nt password
+ //
+
+ ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ LmNewOwfPassword,
+ (PLM_OWF_PASSWORD)NtNewOwfPassword,
+ &LmNewEncryptedWithNtNew);
+
+
+ //
+ // Call the server (with LM cross-encryption)
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamrChangePasswordUser(
+ (SAMPR_HANDLE)UserHandle,
+
+ LmOldPresent,
+ pLmOldEncryptedWithLmNew,
+ pLmNewEncryptedWithLmOld,
+
+ NtPresent,
+ pNtOldEncryptedWithNtNew,
+ pNtNewEncryptedWithNtOld,
+
+ FALSE,
+ NULL,
+
+ TRUE,
+ &LmNewEncryptedWithNtNew
+ );
+ }
+ }
+ }
+
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamChangePasswordUser(
+ IN SAM_HANDLE UserHandle,
+ IN PUNICODE_STRING OldNtPassword,
+ IN PUNICODE_STRING NewNtPassword
+)
+
+/*++
+
+
+Routine Description:
+
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ OldPassword - Current password for the user.
+
+ NewPassword - Desired new password for the user.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword;
+ NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword;
+ BOOLEAN LmOldPresent;
+ PCHAR LmPassword;
+ NTSTATUS NtStatus;
+ BOOLEAN UseOwfPasswords;
+
+ //
+ // Call the server ...
+ //
+
+ RpcTryExcept{
+
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ NewNtPassword,
+ &UseOwfPasswords
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Calculate the one-way-functions of the NT passwords
+ //
+
+ NtStatus = RtlCalculateNtOwfPassword(
+ OldNtPassword,
+ &OldNtOwfPassword
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = RtlCalculateNtOwfPassword(
+ NewNtPassword,
+ &NewNtOwfPassword
+ );
+ }
+
+
+ //
+ // Calculate the one-way-functions of the LM passwords
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Calculate the LM version of the old password
+ //
+
+ NtStatus = SampCalculateLmPassword(
+ OldNtPassword,
+ &LmPassword);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (NtStatus == STATUS_NULL_LM_PASSWORD) {
+ LmOldPresent = FALSE;
+ } else {
+ LmOldPresent = TRUE;
+
+ //
+ // Compute the One-Way-Function of the old LM password
+ //
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ LmPassword,
+ &OldLmOwfPassword);
+ }
+
+ //
+ // We're finished with the LM password
+ //
+
+ MIDL_user_free(LmPassword);
+ }
+
+ //
+ // Calculate the LM version of the new password
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCalculateLmPassword(
+ NewNtPassword,
+ &LmPassword);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Compute the One-Way-Function of the new LM password
+ //
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ LmPassword,
+ &NewLmOwfPassword);
+
+ //
+ // We're finished with the LM password
+ //
+
+ MIDL_user_free(LmPassword);
+ }
+ }
+ }
+
+
+ //
+ // Call our worker routine with the one-way-functions
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SamiChangePasswordUser(
+ UserHandle,
+ LmOldPresent,
+ &OldLmOwfPassword,
+ &NewLmOwfPassword,
+ TRUE, // NT present
+ &OldNtOwfPassword,
+ &NewNtOwfPassword
+ );
+ }
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamGetGroupsForUser(
+ IN SAM_HANDLE UserHandle,
+ OUT PGROUP_MEMBERSHIP * Groups,
+ OUT PULONG MembershipCount
+)
+
+/*++
+
+
+Routine Description:
+
+ This service returns the list of groups that a user is a member of.
+ It returns a structure for each group that includes the relative ID
+ of the group, and the attributes of the group that are assigned to
+ the user.
+
+ This service requires USER_LIST_GROUPS access to the user account
+ object.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ Groups - Receives a pointer to a buffer containing an array of
+ GROUP_MEMBERSHIPs data structures. When this information is
+ no longer needed, this buffer must be freed using
+ SamFreeMemory().
+
+ MembershipCount - Receives the number of groups the user is a
+ member of, and, thus, the number elements returned in the
+ Groups array.
+
+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.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMPR_GET_GROUPS_BUFFER GetGroupsBuffer;
+
+
+
+ //
+ // Call the server ...
+ //
+
+
+ GetGroupsBuffer = NULL;
+
+ RpcTryExcept{
+
+ NtStatus =
+ SamrGetGroupsForUser(
+ (SAMPR_HANDLE)UserHandle,
+ &GetGroupsBuffer
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ (*MembershipCount) = GetGroupsBuffer->MembershipCount;
+ (*Groups) = GetGroupsBuffer->Groups;
+ MIDL_user_free( GetGroupsBuffer );
+ } else {
+
+ //
+ // Deallocate any returned buffers on error
+ //
+
+ if (GetGroupsBuffer != NULL) {
+ if (GetGroupsBuffer->Groups != NULL) {
+ MIDL_user_free(GetGroupsBuffer->Groups);
+ }
+ MIDL_user_free(GetGroupsBuffer);
+ }
+ }
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+
+NTSTATUS
+SamTestPrivateFunctionsDomain(
+ 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 a domain to be tested.
+
+Return Value:
+
+ STATUS_SUCCESS - The tests completed successfully.
+
+ Any errors are as propogated from the tests.
+
+
+--*/
+{
+#ifdef SAM_SERVER_TESTS
+ return( SamrTestPrivateFunctionsDomain( DomainHandle ) );
+#else
+ return( STATUS_NOT_IMPLEMENTED );
+ UNREFERENCED_PARAMETER(DomainHandle);
+#endif
+}
+
+
+
+NTSTATUS
+SamTestPrivateFunctionsUser(
+ 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 a user to be tested.
+
+Return Value:
+
+ STATUS_SUCCESS - The tests completed successfully.
+
+ Any errors are as propogated from the tests.
+
+
+--*/
+{
+#ifdef SAM_SERVER_TESTS
+ return( SamrTestPrivateFunctionsUser( UserHandle ) );
+#else
+ return( STATUS_NOT_IMPLEMENTED );
+ UNREFERENCED_PARAMETER(UserHandle);
+#endif
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampMapCompletionStatus(
+ IN NTSTATUS Status
+ )
+
+/*++
+
+Routine Description:
+
+ This service maps completion status received back from an RPC call
+ into a completion status to be returned from SAM api.
+
+
+Parameters:
+
+ Status - Status value to be mapped.
+
+Return Values:
+
+ The mapped SAM status value.
+
+
+--*/
+{
+
+ if (Status == RPC_NT_INVALID_BINDING) {
+ Status = STATUS_INVALID_HANDLE;
+ }
+// if (Status == RPC_ACCESS_DENIED) {
+// Status = STATUS_ACCESS_DENIED;
+// }
+
+
+
+ return( Status );
+
+}
+
+
+
+NTSTATUS
+SampCalculateLmPassword(
+ IN PUNICODE_STRING NtPassword,
+ OUT PCHAR *LmPasswordBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This service converts an NT password into a LM password.
+
+Parameters:
+
+ NtPassword - The Nt password to be converted.
+
+ LmPasswordBuffer - On successful return, points at the LM password
+ The buffer should be freed using MIDL_user_free
+
+Return Values:
+
+ STATUS_SUCCESS - LMPassword contains the LM version of the password.
+
+ STATUS_NULL_LM_PASSWORD - The password is too complex to be represented
+ by a LM password. The LM password returned is a NULL string.
+
+
+--*/
+{
+
+#define LM_BUFFER_LENGTH (LM20_PWLEN + 1)
+
+ NTSTATUS NtStatus;
+ ANSI_STRING LmPassword;
+
+ //
+ // Prepare for failure
+ //
+
+ *LmPasswordBuffer = NULL;
+
+
+ //
+ // Compute the Ansi version to the Unicode password.
+ //
+ // The Ansi version of the Cleartext password is at most 14 bytes long,
+ // exists in a trailing zero filled 15 byte buffer,
+ // is uppercased.
+ //
+
+ LmPassword.Buffer = MIDL_user_allocate(LM_BUFFER_LENGTH);
+ if (LmPassword.Buffer == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ LmPassword.MaximumLength = LmPassword.Length = LM_BUFFER_LENGTH;
+ RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
+
+ NtStatus = RtlUpcaseUnicodeStringToOemString( &LmPassword, NtPassword, FALSE );
+
+
+ if ( !NT_SUCCESS(NtStatus) ) {
+
+ //
+ // The password is longer than the max LM password length
+ //
+
+ NtStatus = STATUS_NULL_LM_PASSWORD; // Informational return code
+ RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
+
+ }
+
+
+
+
+ //
+ // Return a pointer to the allocated LM password
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ *LmPasswordBuffer = LmPassword.Buffer;
+
+ } else {
+
+ MIDL_user_free(LmPassword.Buffer);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampCheckPasswordRestrictions(
+ IN SAMPR_HANDLE UserHandle,
+ IN PUNICODE_STRING NewNtPassword,
+ OUT PBOOLEAN UseOwfPasswords
+ )
+
+/*++
+
+Routine Description:
+
+ This service is called to make sure that the password presented meets
+ our quality requirements.
+
+
+Arguments:
+
+ UserHandle - Handle to a user.
+
+ NewNtPassword - Pointer to the UNICODE_STRING containing the new
+ password.
+
+ UseOwfPasswords - Indicates that reversibly encrypted passwords should
+ not be sent over the network.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The password is acceptable.
+
+ STATUS_PASSWORD_RESTRICTION - The password is too short, or is not
+ complex enough, etc.
+
+ STATUS_INVALID_RESOURCES - There was not enough memory to do the
+ password checking.
+
+
+--*/
+{
+ USER_DOMAIN_PASSWORD_INFORMATION DomainPasswordInformationBuffer;
+ NTSTATUS NtStatus;
+ PWORD CharInfoBuffer = NULL;
+ ULONG i;
+
+ //
+ // If the new password is zero length the server side will do
+ // the necessary checking.
+ //
+
+ if (NewNtPassword->Length == 0) {
+ return(STATUS_SUCCESS);
+ }
+
+ *UseOwfPasswords = FALSE;
+
+
+ //
+ // Query information domain to get password length and
+ // complexity requirements.
+ //
+
+ NtStatus = SamrGetUserDomainPasswordInformation(
+ UserHandle,
+ &DomainPasswordInformationBuffer
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( (USHORT)( NewNtPassword->Length / sizeof(WCHAR) ) < DomainPasswordInformationBuffer.MinPasswordLength ) {
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+
+ } else {
+
+ //
+ // Check whether policy allows us to send reversibly encrypted
+ // passwords.
+ //
+
+ if ( DomainPasswordInformationBuffer.PasswordProperties &
+ DOMAIN_PASSWORD_NO_CLEAR_CHANGE ) {
+ *UseOwfPasswords = TRUE;
+ }
+
+ //
+ // Check password complexity.
+ //
+
+ if ( DomainPasswordInformationBuffer.PasswordProperties & DOMAIN_PASSWORD_COMPLEX ) {
+
+ //
+ // Make sure that the password meets our requirements for
+ // complexity. If it's got an odd byte count, it's
+ // obviously not a hand-entered UNICODE string so we'll
+ // consider it complex by default.
+ //
+
+ if ( !( NewNtPassword->Length & 1 ) ) {
+
+ USHORT NumsInPassword = 0;
+ USHORT UppersInPassword = 0;
+ USHORT LowersInPassword = 0;
+ USHORT OthersInPassword = 0;
+
+ CharInfoBuffer = MIDL_user_allocate( NewNtPassword->Length );
+
+ if ( CharInfoBuffer == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ if ( GetStringTypeW(
+ CT_CTYPE1,
+ NewNtPassword->Buffer,
+ NewNtPassword->Length / 2,
+ CharInfoBuffer ) ) {
+
+ for ( i = 0; i < (ULONG)( NewNtPassword->Length / sizeof(WCHAR) ); i++ ) {
+
+ if ( CharInfoBuffer[i] & C1_DIGIT ) {
+
+ NumsInPassword = 1;
+ }
+
+ if ( CharInfoBuffer[i] & C1_UPPER ) {
+
+ UppersInPassword = 1;
+ }
+
+ if ( CharInfoBuffer[i] & C1_LOWER ) {
+
+ LowersInPassword = 1;
+ }
+
+ if ( !( CharInfoBuffer[i] & ( C1_ALPHA | C1_DIGIT ) ) ) {
+
+ //
+ // Having any "other" characters is
+ // sufficient to make the password
+ // complex.
+ //
+
+ OthersInPassword = 2;
+ }
+ }
+
+ if ( ( NumsInPassword + UppersInPassword +
+ LowersInPassword + OthersInPassword ) < 2 ) {
+
+ //
+ // It didn't have at least two of the four
+ // types of characters, so it's not complex
+ // enough.
+ //
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+ }
+
+ } else {
+
+ //
+ // GetStringTypeW failed; dunno why. Perhaps the
+ // password is binary. Consider it complex by
+ // default.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ MIDL_user_free( CharInfoBuffer );
+ }
+ }
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamiEncryptPasswords(
+ IN PUNICODE_STRING OldPassword,
+ IN PUNICODE_STRING NewPassword,
+ OUT PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt,
+ OUT PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt,
+ OUT PBOOLEAN LmPresent,
+ OUT PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
+ OUT PENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewNt
+)
+/*++
+
+Routine Description:
+
+ This routine takes old and new cleartext passwords, converts them to
+ LM passwords, generates OWF passwords, and produces reversibly
+ encrypted cleartext and OWF passwords.
+
+Arguments:
+
+ OldPassword - The current cleartext password for the user.
+
+ NewPassword - The new cleartext password for the user.
+
+ NewEncryptedWithOldNt - The new password, in an SAMPR_USER_PASSWORD
+ structure, reversibly encrypted with the old NT OWF password.
+
+ OldNtOwfEncryptedWithNewNt - The old NT OWF password reversibly
+ encrypted with the new NT OWF password.
+
+ LmPresent - Indicates whether or not LM versions of the passwords could
+ be calculated.
+
+ NewEncryptedWithOldLm - The new password, in an SAMPR_USER_PASSWORD
+ structure, reversibly encrypted with the old LM OWF password.
+
+ OldLmOwfEncryptedWithNewNt - The old LM OWF password reversibly
+ encrypted with the new NT OWF password.
+
+
+Return Value:
+
+ Errors from RtlEncryptXXX functions
+
+--*/
+{
+ PCHAR OldLmPassword = NULL;
+ PCHAR NewLmPassword = NULL;
+ LM_OWF_PASSWORD OldLmOwfPassword;
+ NT_OWF_PASSWORD OldNtOwfPassword;
+ NT_OWF_PASSWORD NewNtOwfPassword;
+ PSAMPR_USER_PASSWORD NewNt = (PSAMPR_USER_PASSWORD) NewEncryptedWithOldNt;
+ PSAMPR_USER_PASSWORD NewLm = (PSAMPR_USER_PASSWORD) NewEncryptedWithOldLm;
+ struct RC4_KEYSTRUCT Rc4Key;
+ NTSTATUS NtStatus;
+ BOOLEAN OldLmPresent = TRUE;
+ BOOLEAN NewLmPresent = TRUE;
+
+
+ //
+ // Initialization
+ //
+
+ *LmPresent = TRUE;
+
+ //
+ // Make sure the password isn't too long.
+ //
+
+ if (NewPassword->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) {
+ return(STATUS_PASSWORD_RESTRICTION);
+ }
+
+ //
+ // Calculate the LM passwords. This may fail because the passwords are
+ // too complex, but we can deal with that, so just remember what failed.
+ //
+
+ NtStatus = SampCalculateLmPassword(
+ OldPassword,
+ &OldLmPassword
+ );
+
+ if (NtStatus != STATUS_SUCCESS) {
+ OldLmPresent = FALSE;
+ *LmPresent = FALSE;
+
+ //
+ // If the error was that it couldn't calculate the password, that
+ // is o.k.
+ //
+
+ if (NtStatus == STATUS_NULL_LM_PASSWORD) {
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ }
+
+
+
+ //
+ // Calculate the LM OWF passwords
+ //
+
+ if (NT_SUCCESS(NtStatus) && OldLmPresent) {
+ NtStatus = RtlCalculateLmOwfPassword(
+ OldLmPassword,
+ &OldLmOwfPassword
+ );
+ }
+
+
+ //
+ // Calculate the NT OWF passwords
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlCalculateNtOwfPassword(
+ OldPassword,
+ &OldNtOwfPassword
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlCalculateNtOwfPassword(
+ NewPassword,
+ &NewNtOwfPassword
+ );
+ }
+
+ //
+ // Calculate the encrypted old passwords
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ &OldNtOwfPassword,
+ &NewNtOwfPassword,
+ OldNtOwfEncryptedWithNewNt
+ );
+ }
+
+ //
+ // Compute the encrypted old LM password. Always use the new NT OWF
+ // to encrypt it, since we may not have a new LM OWF password.
+ //
+
+
+ if (NT_SUCCESS(NtStatus) && OldLmPresent) {
+ ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ &OldLmOwfPassword,
+ (PLM_OWF_PASSWORD) &NewNtOwfPassword,
+ OldLmOwfEncryptedWithNewNt
+ );
+ }
+
+ //
+ // Calculate the encrypted new passwords
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT(sizeof(SAMPR_ENCRYPTED_USER_PASSWORD) == sizeof(SAMPR_USER_PASSWORD));
+
+ //
+ // Compute the encrypted new password with NT key.
+ //
+
+ rc4_key(
+ &Rc4Key,
+ NT_OWF_PASSWORD_LENGTH,
+ (PUCHAR) &OldNtOwfPassword
+ );
+
+ RtlCopyMemory(
+ ((PUCHAR) NewNt->Buffer) +
+ SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR) -
+ NewPassword->Length,
+ NewPassword->Buffer,
+ NewPassword->Length
+ );
+
+ *(ULONG UNALIGNED *) &NewNt->Length = NewPassword->Length;
+
+ //
+ // Fill the rest of the buffer with random numbers
+ //
+
+ NtStatus = SampRandomFill(
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ NewPassword->Length,
+ (PUCHAR) NewNt->Buffer
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus))
+ {
+ rc4(&Rc4Key,
+ sizeof(SAMPR_USER_PASSWORD),
+ (PUCHAR) NewEncryptedWithOldNt
+ );
+
+ }
+
+ //
+ // Compute the encrypted new password with LM key if it exists.
+ //
+
+
+ if (NT_SUCCESS(NtStatus) && OldLmPresent) {
+
+ rc4_key(
+ &Rc4Key,
+ LM_OWF_PASSWORD_LENGTH,
+ (PUCHAR) &OldLmOwfPassword
+ );
+
+ RtlCopyMemory(
+ ((PUCHAR) NewLm->Buffer) +
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ NewPassword->Length,
+ NewPassword->Buffer,
+ NewPassword->Length
+ );
+
+ *(ULONG UNALIGNED *) &NewLm->Length = NewPassword->Length;
+
+ NtStatus = SampRandomFill(
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ NewPassword->Length,
+ (PUCHAR) NewLm->Buffer
+ );
+
+
+ }
+
+ //
+ // Encrypt the password (or, if the old LM OWF password does not exist,
+ // zero it).
+
+ if (NT_SUCCESS(NtStatus) && OldLmPresent) {
+
+ rc4(&Rc4Key,
+ sizeof(SAMPR_USER_PASSWORD),
+ (PUCHAR) NewEncryptedWithOldLm
+ );
+
+ } else {
+ RtlZeroMemory(
+ NewLm,
+ sizeof(SAMPR_ENCRYPTED_USER_PASSWORD)
+ );
+ }
+
+
+
+ //
+ // Make sure to zero the passwords before freeing so we don't have
+ // passwords floating around in the page file.
+ //
+
+ if (OldLmPassword != NULL) {
+
+ RtlZeroMemory(
+ OldLmPassword,
+ lstrlenA(OldLmPassword)
+ );
+
+ MIDL_user_free(OldLmPassword);
+ }
+
+
+ return(NtStatus);
+
+}
+
+
+
+
+
+NTSTATUS
+SampChangePasswordUser2(
+ IN PUNICODE_STRING ServerName,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING OldPassword,
+ IN PUNICODE_STRING NewPassword
+)
+
+/*++
+
+
+Routine Description:
+
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ OldPassword - Current password for the user.
+
+ NewPassword - Desired new password for the user.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAM_HANDLE SamServerHandle = NULL;
+ SAM_HANDLE DomainHandle = NULL;
+ SAM_HANDLE UserHandle = NULL;
+ LSA_HANDLE PolicyHandle = NULL;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
+ PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;
+ PULONG UserId = NULL;
+ PSID_NAME_USE NameUse = NULL;
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ NULL,
+ 0,
+ NULL,
+ NULL
+ );
+
+ //
+ // The InitializeObjectAttributes call doesn't initialize the
+ // quality of serivce, so do that separately.
+ //
+
+ SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
+ SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation;
+ SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
+ SecurityQualityOfService.EffectiveOnly = FALSE;
+
+ ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService;
+
+
+
+ NtStatus = LsaOpenPolicy(
+ ServerName,
+ &ObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &PolicyHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = LsaQueryInformationPolicy(
+ PolicyHandle,
+ PolicyAccountDomainInformation,
+ &AccountDomainInfo
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = SamConnect(
+ ServerName,
+ &SamServerHandle,
+ SAM_SERVER_LOOKUP_DOMAIN,
+ &ObjectAttributes
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = SamOpenDomain(
+ SamServerHandle,
+ GENERIC_EXECUTE,
+ AccountDomainInfo->DomainSid,
+ &DomainHandle
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ UserName,
+ &UserId,
+ &NameUse
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ if (NtStatus == STATUS_NONE_MAPPED) {
+ NtStatus = STATUS_NO_SUCH_USER;
+ }
+ goto Cleanup;
+ }
+
+ NtStatus = SamOpenUser(
+ DomainHandle,
+ USER_CHANGE_PASSWORD,
+ *UserId,
+ &UserHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = SamChangePasswordUser(
+ UserHandle,
+ OldPassword,
+ NewPassword
+ );
+Cleanup:
+ if (UserHandle != NULL) {
+ SamCloseHandle(UserHandle);
+ }
+ if (DomainHandle != NULL) {
+ SamCloseHandle(DomainHandle);
+ }
+ if (SamServerHandle != NULL) {
+ SamCloseHandle(SamServerHandle);
+ }
+ if (PolicyHandle != NULL){
+ LsaClose(PolicyHandle);
+ }
+ if (AccountDomainInfo != NULL) {
+ LsaFreeMemory(AccountDomainInfo);
+ }
+ if (UserId != NULL) {
+ SamFreeMemory(UserId);
+ }
+ if (NameUse != NULL) {
+ SamFreeMemory(NameUse);
+ }
+
+ return(NtStatus);
+
+}
+
+NTSTATUS
+SamiChangePasswordUser2(
+ PUNICODE_STRING ServerName,
+ PUNICODE_STRING UserName,
+ PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldNt,
+ PENCRYPTED_NT_OWF_PASSWORD OldNtOwfPasswordEncryptedWithNewNt,
+ BOOLEAN LmPresent,
+ PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm,
+ PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPasswordEncryptedWithNewLmOrNt
+ )
+/*++
+
+
+Routine Description:
+
+ Changes the password of a user account. This is the worker routine for
+ SamChangePasswordUser2 and can be called by OWF-aware clients.
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ ServerName - The server to operate on, or NULL for this machine.
+
+ UserName - Name of user whose password is to be changed
+
+ NewPasswordEncryptedWithOldNt - The new cleartext password encrypted
+ with the old NT OWF password.
+
+ OldNtOwfPasswordEncryptedWithNewNt - The old NT OWF password encrypted
+ with the new NT OWF password.
+
+ LmPresent - If TRUE, indicates that the following two last parameter
+ was encrypted with the LM OWF password not the NT OWF password.
+
+ NewPasswordEncryptedWithOldLm - The new cleartext password encrypted
+ with the old LM OWF password.
+
+ OldLmOwfPasswordEncryptedWithNewLmOrNt - The old LM OWF password encrypted
+ with the new LM OWF password.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+
+{
+ handle_t BindingHandle;
+ PSAMPR_SERVER_NAME RServerNameWithNull;
+ USHORT RServerNameWithNullLength;
+ PSAMPR_SERVER_NAME RServerName;
+ ULONG Tries = 2;
+ NTSTATUS NtStatus;
+ USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation;
+
+ RServerNameWithNull = NULL;
+
+ if (ARGUMENT_PRESENT(ServerName)) {
+
+ RServerName = (PSAMPR_SERVER_NAME)(ServerName->Buffer);
+ RServerNameWithNullLength = ServerName->Length + (USHORT) sizeof(WCHAR);
+ RServerNameWithNull = MIDL_user_allocate( RServerNameWithNullLength );
+
+ if (RServerNameWithNull == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlCopyMemory( RServerNameWithNull, RServerName, ServerName->Length);
+ RServerNameWithNull[ServerName->Length/sizeof(WCHAR)] = L'\0';
+
+ }
+
+
+ do
+ {
+ //
+ // Try privacy level first, and if that failed with unknown authn
+ // level or invalid binding try with a lower level (none).
+ //
+
+ if (Tries == 2) {
+ BindingHandle = SampSecureBind(
+ RServerNameWithNull,
+ RPC_C_AUTHN_LEVEL_PKT_PRIVACY
+ );
+
+
+ } else if ((NtStatus == RPC_NT_UNKNOWN_AUTHN_LEVEL) ||
+ (NtStatus == RPC_NT_UNKNOWN_AUTHN_TYPE) ||
+ (NtStatus == RPC_NT_UNKNOWN_AUTHN_SERVICE) ||
+ (NtStatus == RPC_NT_INVALID_BINDING) ||
+ (NtStatus == STATUS_ACCESS_DENIED) ) {
+ SampSecureUnbind(BindingHandle);
+
+ BindingHandle = SampSecureBind(
+ RServerNameWithNull,
+ RPC_C_AUTHN_LEVEL_NONE
+ );
+
+ } else {
+ break;
+ }
+
+ if (BindingHandle != NULL) {
+
+ RpcTryExcept{
+
+ //
+ // Get password information to make sure this operation
+ // is allowed. We do it now because we wanted to bind
+ // before trying it.
+ //
+
+ NtStatus = SamrGetDomainPasswordInformation(
+ BindingHandle,
+ (PRPC_UNICODE_STRING) ServerName,
+ &PasswordInformation
+ );
+
+ if (NtStatus == STATUS_SUCCESS) {
+
+ if (!( PasswordInformation.PasswordProperties &
+ DOMAIN_PASSWORD_NO_CLEAR_CHANGE) ) {
+
+ NtStatus = SamrUnicodeChangePasswordUser2(
+ BindingHandle,
+ (PRPC_UNICODE_STRING) ServerName,
+ (PRPC_UNICODE_STRING) UserName,
+ NewPasswordEncryptedWithOldNt,
+ OldNtOwfPasswordEncryptedWithNewNt,
+ LmPresent,
+ NewPasswordEncryptedWithOldLm,
+ OldLmOwfPasswordEncryptedWithNewLmOrNt
+ );
+
+ } else {
+
+ //
+ // Set the error to indicate that we should try the
+ // downlevel way to change passwords.
+ //
+
+ NtStatus = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+
+ //
+ // The mapping function doesn't handle this error so
+ // special case it by hand.
+ //
+ NtStatus = RpcExceptionCode();
+
+ if (NtStatus == RPC_S_SEC_PKG_ERROR) {
+ NtStatus = STATUS_ACCESS_DENIED;
+ } else {
+ NtStatus = I_RpcMapWin32Status(NtStatus);
+ }
+
+
+ } RpcEndExcept;
+
+ } else {
+ NtStatus = RPC_NT_INVALID_BINDING;
+ }
+
+ Tries--;
+ } while ( (Tries > 0) && (!NT_SUCCESS(NtStatus)) );
+ if (RServerNameWithNull != NULL) {
+ MIDL_user_free( RServerNameWithNull );
+ }
+
+ if (BindingHandle != NULL) {
+ SampSecureUnbind(BindingHandle);
+ }
+
+ //
+ // Map these errors to STATUS_NOT_SUPPORTED
+ //
+
+ if ((NtStatus == RPC_NT_UNKNOWN_IF) ||
+ (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) {
+
+ NtStatus = STATUS_NOT_SUPPORTED;
+ }
+ return(SampMapCompletionStatus(NtStatus));
+
+
+}
+
+NTSTATUS
+SamiOemChangePasswordUser2(
+ PSTRING ServerName,
+ PSTRING UserName,
+ PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm,
+ PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPasswordEncryptedWithNewLm
+ )
+/*++
+
+
+Routine Description:
+
+ Changes the password of a user account. This can be called by OWF-aware
+ clients. Password will be set to NewPassword only if OldPassword matches
+ the current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ ServerName - The server to operate on, or NULL for this machine.
+
+ UserName - Name of user whose password is to be changed
+
+
+ NewPasswordEncryptedWithOldLm - The new cleartext password encrypted
+ with the old LM OWF password.
+
+ OldLmOwfPasswordEncryptedWithNewLm - The old LM OWF password encrypted
+ with the new LM OWF password.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+
+{
+ handle_t BindingHandle;
+ UNICODE_STRING RemoteServerName;
+ ULONG Tries = 2;
+ NTSTATUS NtStatus;
+ USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation;
+
+ RemoteServerName.Buffer = NULL;
+ RemoteServerName.Length = 0;
+
+ if (ARGUMENT_PRESENT(ServerName)) {
+
+ NtStatus = RtlAnsiStringToUnicodeString(
+ &RemoteServerName,
+ ServerName,
+ TRUE // allocate destination
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ ASSERT(RemoteServerName.Buffer[RemoteServerName.Length/sizeof(WCHAR)] == L'\0');
+ }
+
+
+ do
+ {
+ //
+ // Try privacy level first, and if that failed with unknown authn
+ // level or invalid binding try with a lower level (none).
+ //
+
+ if (Tries == 2) {
+ BindingHandle = SampSecureBind(
+ RemoteServerName.Buffer,
+ RPC_C_AUTHN_LEVEL_PKT_PRIVACY
+ );
+
+
+ } else if ((NtStatus == RPC_NT_UNKNOWN_AUTHN_LEVEL) ||
+ (NtStatus == RPC_NT_UNKNOWN_AUTHN_TYPE) ||
+ (NtStatus == RPC_NT_INVALID_BINDING) ||
+ (NtStatus == STATUS_ACCESS_DENIED) ) {
+ SampSecureUnbind(BindingHandle);
+
+ BindingHandle = SampSecureBind(
+ RemoteServerName.Buffer,
+ RPC_C_AUTHN_LEVEL_NONE
+ );
+
+ } else {
+ break;
+ }
+
+ if (BindingHandle != NULL) {
+
+ RpcTryExcept{
+
+ //
+ // Get password information to make sure this operation
+ // is allowed. We do it now because we wanted to bind
+ // before trying it.
+ //
+
+ NtStatus = SamrGetDomainPasswordInformation(
+ BindingHandle,
+ (PRPC_UNICODE_STRING) ServerName,
+ &PasswordInformation
+ );
+
+ if (NtStatus == STATUS_SUCCESS) {
+
+ if (!( PasswordInformation.PasswordProperties &
+ DOMAIN_PASSWORD_NO_CLEAR_CHANGE) ) {
+
+ NtStatus = SamrOemChangePasswordUser2(
+ BindingHandle,
+ (PRPC_STRING) ServerName,
+ (PRPC_STRING) UserName,
+ NewPasswordEncryptedWithOldLm,
+ OldLmOwfPasswordEncryptedWithNewLm
+ );
+
+ } else {
+
+ //
+ // Set the error to indicate that we should try the
+ // downlevel way to change passwords.
+ //
+
+ NtStatus = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+
+ //
+ // The mappin function doesn't handle this error so
+ // special case it by hand.
+ //
+
+ if (NtStatus == RPC_S_SEC_PKG_ERROR) {
+ NtStatus = STATUS_ACCESS_DENIED;
+ } else {
+ NtStatus = I_RpcMapWin32Status(RpcExceptionCode());
+ }
+
+
+ } RpcEndExcept;
+
+ } else {
+ NtStatus = RPC_NT_INVALID_BINDING;
+ }
+
+ Tries--;
+ } while ( (Tries > 0) && (!NT_SUCCESS(NtStatus)) );
+
+ RtlFreeUnicodeString( &RemoteServerName );
+
+ if (BindingHandle != NULL) {
+ SampSecureUnbind(BindingHandle);
+ }
+
+ //
+ // Map these errors to STATUS_NOT_SUPPORTED
+ //
+
+ if ((NtStatus == RPC_NT_UNKNOWN_IF) ||
+ (NtStatus == RPC_NT_PROCNUM_OUT_OF_RANGE)) {
+
+ NtStatus = STATUS_NOT_SUPPORTED;
+ }
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
+
+
+NTSTATUS
+SamChangePasswordUser2(
+ IN PUNICODE_STRING ServerName,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING OldPassword,
+ IN PUNICODE_STRING NewPassword
+)
+
+/*++
+
+
+Routine Description:
+
+ Password will be set to NewPassword only if OldPassword matches the
+ current user password for this user and the NewPassword is not the
+ same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ ServerName - The server to operate on, or NULL for this machine.
+
+ UserName - Name of user whose password is to be changed
+
+ OldPassword - Current password for the user.
+
+ NewPassword - Desired new password for the user.
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+ SAMPR_ENCRYPTED_USER_PASSWORD NewNtEncryptedWithOldNt;
+ SAMPR_ENCRYPTED_USER_PASSWORD NewNtEncryptedWithOldLm;
+ ENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt;
+ ENCRYPTED_NT_OWF_PASSWORD OldLmOwfEncryptedWithNewNt;
+ NTSTATUS NtStatus;
+ BOOLEAN LmPresent = TRUE;
+ ULONG AuthnLevel;
+ ULONG Tries = 2;
+ USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation;
+
+
+ //
+ // Call the server, passing either a NULL Server Name pointer, or
+ // a pointer to a Unicode Buffer with a Wide Character NULL terminator.
+ // Since the input name is contained in a counted Unicode String, there
+ // is no NULL terminator necessarily provided, so we must append one.
+ //
+
+ //
+ // Encrypted the passwords
+ //
+
+ NtStatus = SamiEncryptPasswords(
+ OldPassword,
+ NewPassword,
+ &NewNtEncryptedWithOldNt,
+ &OldNtOwfEncryptedWithNewNt,
+ &LmPresent,
+ &NewNtEncryptedWithOldLm,
+ &OldLmOwfEncryptedWithNewNt
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Try the remote call...
+ //
+
+
+ NtStatus = SamiChangePasswordUser2(
+ ServerName,
+ UserName,
+ &NewNtEncryptedWithOldNt,
+ &OldNtOwfEncryptedWithNewNt,
+ LmPresent,
+ &NewNtEncryptedWithOldLm,
+ &OldLmOwfEncryptedWithNewNt
+ );
+
+
+ //
+ // If the new API failed, try calling the old API.
+ //
+
+ if (NtStatus == STATUS_NOT_SUPPORTED) {
+
+ NtStatus = SampChangePasswordUser2(
+ ServerName,
+ UserName,
+ OldPassword,
+ NewPassword
+ );
+ }
+
+ return(SampMapCompletionStatus(NtStatus));
+
+}
diff --git a/private/newsam/dirs b/private/newsam/dirs
new file mode 100644
index 000000000..228ce1cd4
--- /dev/null
+++ b/private/newsam/dirs
@@ -0,0 +1,28 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ dirs.
+
+Abstract:
+
+ This file specifies the subdirectories of the current directory that
+ contain component makefiles.
+
+
+Author:
+
+ Steve Wood (stevewo) 17-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\dirs.tpl
+
+!ENDIF
+
+DIRS= \
+ client \
+ server \
+ passfilt
+
+OPTIONAL_DIRS=
diff --git a/private/newsam/makefil0 b/private/newsam/makefil0
new file mode 100644
index 000000000..35960958c
--- /dev/null
+++ b/private/newsam/makefil0
@@ -0,0 +1,78 @@
+#
+# This is the MIDL compile phase of the build process.
+#
+# The following symbols should be defined in your environment:
+# NOTE: This file is designed to provide separate generation
+# of client and server stubs. Right now, it uses an
+# .acf for only the client stub generation. However,
+# lines to cause a server .acf file to be used are present
+# but commented out.
+# The following is where you put the name of your .idl file without
+# the .idl extension:
+
+!INCLUDE $(NTMAKEENV)\makefile.plt
+
+IDL_NAME = samrpc
+CLIENT_ACF = samcli.acf
+SERVER_ACF = samsrv.acf
+
+CLIENT_INC_FILE = samrpc_c.h
+SERVER_INC_FILE = samrpc.h
+
+SDKINC = $(BASEDIR)\public\sdk\inc
+SDKCRTINC = $(BASEDIR)\public\sdk\inc\crt
+PRIVATEINC = ..\inc
+
+CLIENT_FLAGS = -acf $(CLIENT_ACF) -header $(CLIENT_INC_FILE)
+SERVER_FLAGS = -acf $(SERVER_ACF) -header $(SERVER_INC_FILE)
+INCS = -I$(SDKINC) -I$(SDKCRTINC) -I$(PRIVATEINC)
+
+CLIENT_CPP = -cpp_cmd "$(MIDL_CPP)" $(MIDL_FLAGS)
+SERVER_CPP = -cpp_cmd "$(MIDL_CPP)" $(MIDL_FLAGS)
+
+# seperate client and server targets. The .h file is built by both.
+
+CLIENT_TARGETS = client\$(IDL_NAME)_c.c \
+ .\client\$(CLIENT_INC_FILE)
+
+SERVER_TARGETS = server\$(IDL_NAME)_s.c \
+ $(PRIVATEINC)\$(SERVER_INC_FILE)
+
+TARGETS = $(CLIENT_TARGETS) \
+ $(SERVER_TARGETS)
+
+CLIENT_EXTRN_DEPENDS = $(CLIENT_ACF)
+EXTRN_DEPENDS = $(CLIENT_EXTRN_DEPENDS)
+
+# Define Products and Dependencies
+
+all: $(CLIENT_TARGETS) $(SERVER_TARGETS) $(EXTRN_DEPENDS)
+!IF "$(BUILDMSG)" != ""
+ @ech ; $(BUILDMSG) ;
+!ENDIF
+
+clean: delete_source all
+
+delete_source:
+ -erase $(TARGETS)
+
+#
+# MIDL COMPILE
+#
+
+$(CLIENT_TARGETS) : $(IDL_NAME).idl $(CLIENT_EXTRN_DEPENDS)
+ copy $(PRIVATEINC)\samimp.h .
+ midl -Oi -oldnames -error allocation -error ref -ms_ext -c_ext $(CLIENT_CPP) $(CLIENT_FLAGS) $(IDL_NAME).idl $(INCS)
+ IF EXIST $(IDL_NAME)_c.c copy $(IDL_NAME)_c.c .\client & del $(IDL_NAME)_c.c
+ IF EXIST $(IDL_NAME)_s.c del $(IDL_NAME)_s.c
+ IF EXIST $(CLIENT_INC_FILE) copy $(CLIENT_INC_FILE) .\client & del $(CLIENT_INC_FILE)
+
+#$(SERVER_TARGETS) : $(IDL_NAME).idl $(SERVER_EXTRN_DEPENDS)
+$(SERVER_TARGETS) : $(IDL_NAME).idl
+ IF EXIST .\inc\$(IDL_NAME).h del .\inc\$(IDL_NAME).h
+ IF EXIST .\server\$(IDL_NAME).h del .\server\$(IDL_NAME).h
+ copy $(PRIVATEINC)\samimp.h .
+ midl -oldnames -error allocation -error ref -ms_ext -c_ext $(SERVER_CPP) $(SERVER_FLAGS) $(IDL_NAME).idl $(INCS)
+ IF EXIST $(IDL_NAME)_c.c del $(IDL_NAME)_c.c
+ IF EXIST $(IDL_NAME)_s.c copy $(IDL_NAME)_s.c .\server & del $(IDL_NAME)_s.c
+ IF EXIST $(SERVER_INC_FILE) copy $(SERVER_INC_FILE) $(PRIVATEINC) & del $(SERVER_INC_FILE)
diff --git a/private/newsam/passfilt/makefile b/private/newsam/passfilt/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/newsam/passfilt/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/newsam/passfilt/passfilt.c b/private/newsam/passfilt/passfilt.c
new file mode 100644
index 000000000..cdab84742
--- /dev/null
+++ b/private/newsam/passfilt/passfilt.c
@@ -0,0 +1,354 @@
+/*++
+
+ Copyright (c) 1995, 1996 Microsoft Corporation
+
+ Module Name:
+
+ pswdntfy.c
+
+ Abstract:
+
+ This module illustrates how to implement password change
+ notification and password filtering in Windows NT 4.0.
+
+ Password change notification is useful for synchronization of
+ non-Windows NT account databases.
+
+ Password change filtering is useful for enforcing quality or
+ strength of passwords in an Windows NT account database.
+
+ This sample illustrates one approach to enforcing additional
+ password quality.
+
+ Author:
+
+ Scott Field (sfield) 14-May-96
+ Gurdeep Singh Pall (gurdeep) 9/30/96 Added code to enforce the following discipline.
+
+ The new " Strong" Password must meet the following criteria:
+ 1. Password must be at least 6 characters long.
+ 2. Password must contain characters from at least 3 of the following 4 classes:
+
+ Description Examples
+ 1 English Upper Case Letters A, B, C, Z
+ 2 English Lower Case Letters a, b, c, z
+ 3 Westernized Arabic Numerals 0, 1, 2, 9
+ 4 Non-alphanumeric ("Special characters") E.g., punctuation symbols.
+
+ Mike Swift (mikesw) 10/31/96 Cleaned Up
+
+
+ --*/
+
+#include <windows.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ntsecapi.h>
+#include <lmcons.h>
+
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+
+
+//+-------------------------------------------------------------------------
+//
+// Function: LocalStrTok
+//
+// Synopsis: takes a pointer to a string, returns a pointer to the next
+// token in the string and sets StringStart to point to the
+// end of the string.
+//
+// Effects:
+//
+// Arguments:
+//
+// Requires:
+//
+// Returns:
+//
+// Notes:
+//
+//
+//--------------------------------------------------------------------------
+
+
+LPSTR
+LocalStrTok(
+ LPSTR String,
+ LPSTR Token,
+ LPSTR * NextStringStart
+ )
+{
+ ULONG Index;
+ ULONG Tokens;
+ LPSTR StartString;
+ LPSTR EndString;
+ BOOLEAN Found;
+
+ if (String == NULL)
+ {
+ *NextStringStart = NULL;
+ return(NULL);
+ }
+ Tokens = lstrlenA(Token);
+
+ //
+ // Find the beginning of the string.
+ //
+
+ StartString = (LPSTR) String;
+ while (*StartString != '\0')
+ {
+ Found = FALSE;
+ for (Index = 0; Index < Tokens; Index++)
+ {
+ if (*StartString == Token[Index])
+ {
+ StartString++;
+ Found = TRUE;
+ break;
+ }
+ }
+ if (!Found)
+ {
+ break;
+ }
+ }
+
+ //
+ // There are no more tokens in this string.
+ //
+
+ if (*StartString == '\0')
+ {
+ *NextStringStart = NULL;
+ return(NULL);
+ }
+
+ EndString = StartString + 1;
+ while (*EndString != '\0')
+ {
+ for (Index = 0; Index < Tokens; Index++)
+ {
+ if (*EndString == Token[Index])
+ {
+ *EndString = '\0';
+ *NextStringStart = EndString+1;
+ return(StartString);
+ }
+ }
+ EndString++;
+ }
+ *NextStringStart = NULL;
+ return(StartString);
+
+}
+
+BOOLEAN
+NTAPI
+PasswordFilter(
+ PUNICODE_STRING UserName,
+ PUNICODE_STRING FullName,
+ PUNICODE_STRING Password,
+ BOOLEAN SetOperation
+ )
+/*++
+
+Routine Description:
+
+ This (optional) routine is notified of a password change.
+
+Arguments:
+
+ UserName - Name of user whose password changed
+
+ FullName - Full name of the user whose password changed
+
+ NewPassword - Cleartext new password for the user
+
+ SetOperation - TRUE if the password was SET rather than CHANGED
+
+Return Value:
+
+ TRUE if the specified Password is suitable (complex, long, etc).
+ The system will continue to evaluate the password update request
+ through any other installed password change packages.
+
+ FALSE if the specified Password is unsuitable. The password change
+ on the specified account will fail.
+
+--*/
+{
+
+ BOOLEAN bComplex = FALSE; // assume the password in not complex enough
+ DWORD cchPassword;
+ DWORD i;
+ DWORD j;
+ DWORD count;
+ DWORD dwNum = 0;
+ DWORD dwUpper = 0;
+ DWORD dwLower = 0;
+ DWORD dwSpecialChar = 0 ;
+ PCHAR token ;
+ CHAR _password[PWLEN+1];
+ CHAR _username[UNLEN+1];
+ PCHAR _fullname = NULL;
+ WORD CharType[PWLEN+1];
+ PCHAR TempString;
+
+ //
+ // If the password was explicitly set, allow it through.
+ //
+
+ if (SetOperation)
+ {
+ bComplex = TRUE;
+ goto end;
+ }
+
+ //
+ // Make sure the password and username will fit in our local buffers
+ //
+
+ if (Password->Length > PWLEN * sizeof(WCHAR))
+ {
+ goto end;
+ }
+
+ if (UserName->Length > UNLEN * sizeof(WCHAR))
+ {
+ goto end;
+ }
+
+ _fullname = HeapAlloc(GetProcessHeap(), 0, FullName->Length + sizeof(WCHAR));
+ if (_fullname == NULL)
+ {
+ goto end;
+ }
+
+
+ //
+ // check if the password is complex enough for our liking by
+ // checking that at least two of the four character types are
+ // present.
+ //
+
+ cchPassword = Password->Length / sizeof(WCHAR);
+
+
+ if(GetStringTypeW(
+ CT_CTYPE1,
+ Password->Buffer,
+ cchPassword,
+ CharType
+ )) {
+
+ for(i = 0 ; i < cchPassword ; i++) {
+
+ //
+ // keep track of what type of characters we have encountered
+ //
+
+ if(CharType[i] & C1_DIGIT) {
+ dwNum = 1;
+ continue;
+ }
+
+ if(CharType[i] & C1_UPPER) {
+ dwUpper = 1;
+ continue;
+ }
+
+ if(CharType[i] & C1_LOWER) {
+ dwLower = 1;
+ continue;
+ }
+
+ } // for
+
+ //
+ // Indicate whether we encountered enough password complexity
+ //
+
+ if( (dwNum + dwLower + dwUpper) < 2) {
+
+ bComplex = FALSE ;
+ goto end ;
+
+ } else {
+
+ //
+ // now we resort to more complex checking
+ //
+ wcstombs(_password, Password->Buffer, PWLEN+1) ;
+ wcstombs(_username, UserName->Buffer, UNLEN+1) ;
+ wcstombs(_fullname, FullName->Buffer, 1+FullName->Length/sizeof(WCHAR)) ;
+
+ _strupr(_password) ;
+ _password[Password->Length/sizeof(WCHAR)] = '\0' ;
+ _strupr(_username) ;
+ _username[UserName->Length/sizeof(WCHAR)] = '\0' ;
+ _strupr(_fullname) ;
+ _fullname[FullName->Length/sizeof(WCHAR)] = '\0' ;
+
+ if (strpbrk (_password, "(`~!@#$%^&*_-+=|\\{}[]:;\"'<>,.?)") != NULL) {
+ dwSpecialChar = 1 ;
+ }
+
+ if ((dwNum + dwLower + dwUpper + dwSpecialChar) < 3) {
+ bComplex = FALSE ;
+ goto end ;
+ }
+
+ if ((UserName->Length >= 3 * sizeof(WCHAR)) && strstr (_password, _username)) {
+
+ bComplex = FALSE ;
+ goto end ;
+ }
+
+
+ //
+ // Tokenize the full name and check if the password is derived from it
+ //
+
+ token = LocalStrTok(_fullname, " ,.\t-_#",&TempString);
+ while( token != NULL ) {
+
+ if (lstrlenA(token) > 3 && strstr(_password, token)) {
+ bComplex = FALSE ;
+ goto end ;
+ }
+
+ token = LocalStrTok(NULL, " ,.\t-_#",&TempString);
+ }
+
+ bComplex = TRUE ;
+
+ }
+
+
+ } // if
+
+end:
+
+ ZeroMemory( CharType, Password->Length );
+ ZeroMemory( _password, Password->Length );
+ HeapFree(GetProcessHeap(), 0, _fullname);
+
+ return bComplex;
+}
+
+
+
+BOOL WINAPI DllMain(
+ HINSTANCE hinstDLL, // DLL instance handle
+ DWORD fdwReason, // Why is it called
+ LPVOID lpvReserved
+ ) {
+
+ return TRUE ;
+
+}
diff --git a/private/newsam/passfilt/passfilt.def b/private/newsam/passfilt/passfilt.def
new file mode 100644
index 000000000..4e911a2ac
--- /dev/null
+++ b/private/newsam/passfilt/passfilt.def
@@ -0,0 +1,7 @@
+LIBRARY passfilt
+
+DESCRIPTION 'Password Change Filter'
+
+EXPORTS
+ PasswordFilter
+
diff --git a/private/newsam/passfilt/passfilt.rc b/private/newsam/passfilt/passfilt.rc
new file mode 100644
index 000000000..f37da995f
--- /dev/null
+++ b/private/newsam/passfilt/passfilt.rc
@@ -0,0 +1,10 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DLL
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "Password Change Filter DLL"
+#define VER_INTERNALNAME_STR "passfilt.dll"
+
+#include "common.ver"
+
diff --git a/private/newsam/passfilt/passtest.c b/private/newsam/passfilt/passtest.c
new file mode 100644
index 000000000..1c3bcda4e
--- /dev/null
+++ b/private/newsam/passfilt/passtest.c
@@ -0,0 +1,85 @@
+/*++
+
+Copyright (c) 1995 Microsoft Corporation
+
+Module Name:
+
+
+Abstract:
+
+Revision History:
+
+
+--*/
+
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <crt\stddef.h>
+
+BOOLEAN
+NTAPI
+PasswordFilter(
+ PUNICODE_STRING UserName,
+ PUNICODE_STRING FullName,
+ PUNICODE_STRING Password,
+ BOOLEAN SetOperation
+ ) ;
+
+
+INT _cdecl main()
+{
+ CHAR FullName[100];
+ CHAR UserName[100];
+ CHAR Password[100];
+ WCHAR _fullname[100] ;
+ WCHAR _username[100] ;
+ WCHAR _password[100] ;
+ UNICODE_STRING ufn ;
+ UNICODE_STRING uun ;
+ UNICODE_STRING up ;
+
+ ufn.MaximumLength = 100 ;
+ uun.MaximumLength = 100 ;
+ up.MaximumLength = 100 ;
+
+ while(TRUE)
+ {
+
+ printf("\n----- IP PASSWORD TEST -----\n");
+ printf("\tEnter Full Name:\t");
+ gets (FullName) ;
+ printf("\tEnter User Name:\t");
+ gets (UserName) ;
+ printf("\tEnter Password:\t\t");
+ gets (Password) ;
+
+ mbstowcs (_fullname, FullName, strlen(FullName)) ;
+ ufn.Buffer = _fullname ;
+ ufn.Length = strlen(FullName) ;
+
+ mbstowcs (_username, UserName, strlen(UserName)) ;
+ uun.Buffer = _username ;
+ uun.Length = strlen(UserName) ;
+
+ mbstowcs (_password, Password, strlen(Password)) ;
+ up.Buffer = _password ;
+ up.Length = strlen(Password) ;
+
+ if (!PasswordFilter (&uun, &ufn, &up, TRUE)) {
+ printf ("\n\n\t PASSWORD NOT SUITABLE!!!!\n") ;
+ Beep(5000, 500) ;
+ } else {
+ printf ("\n\n\t PASSWORD SUITABLE!!!!\n") ;
+ }
+
+ }
+ return(0);
+}
+
diff --git a/private/newsam/passfilt/sources b/private/newsam/passfilt/sources
new file mode 100644
index 000000000..cd719434d
--- /dev/null
+++ b/private/newsam/passfilt/sources
@@ -0,0 +1,54 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+MAJORCOMP=SAM
+MINORCOMP=passfilt
+
+TARGETNAME=passfilt
+TARGETPATH=$(BASEDIR)\public\sdk\lib
+
+TARGETLIBS=
+
+TARGETTYPE=DYNLINK
+
+#
+# Define COMPILED_BY_DEVELOPER to hit a breakpoint during initialization.
+#
+#C_DEFINES= -DCOMPILED_BY_DEVELOPER -DDUMP_CACHE_INFO
+
+C_DEFINES=-DUNICODE=1 -DRPC_NO_WINDOWS_H
+
+
+#DLLBASE=@$(BASEDIR)\PUBLIC\SDK\LIB\coffbase.txt,samsrv
+
+
+
+SOURCES= \
+ passfilt.c \
+ passfilt.rc
+
+TARGETLIBS= $(BASEDIR)\public\sdk\lib\*\kernel32.lib \
+ $(BASEDIR)\public\sdk\lib\*\user32.lib
+
+USE_NTDLL=1
diff --git a/private/newsam/samcli.acf b/private/newsam/samcli.acf
new file mode 100644
index 000000000..2349f072d
--- /dev/null
+++ b/private/newsam/samcli.acf
@@ -0,0 +1,96 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ samcli.acf
+
+Abstract:
+
+ Security Account Manager CLIENT rpc stub attribute configuration file.
+
+ This file contains the attribute configuration information necessary
+ for generating the client stubs for remotable SAM functions. The
+ definitions in this file qualify the information in samrpc.idl.
+
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! !!
+ !! This .acf file is USED ONLY WHEN GENERATING SAM CLIENT STUBS. !!
+ !! !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+ Use samsrv.acf when generating server stubs.
+
+
+
+
+ The client likes to have all returned data in a single block of
+ allocated memory. This allows them to free the returned information
+ with a single call, rather than walking down some random tree of
+ allocated blocks.
+
+
+
+Author:
+
+ Jim Kelly (JimK) July 2, 1991
+
+Environment:
+
+ User Mode
+
+Revision History:
+
+--*/
+
+
+[ implicit_handle( handle_t samcli_handle) ]
+interface samr
+
+{
+
+
+//typedef [explicit_handle] PSAMPR_SERVER_NAME;
+
+//typedef [context_handle] SAMPR_HANDLE;
+
+
+
+//
+// define complex [out] parameters to be [allocate(all_nodes)]...
+//
+
+
+typedef [allocate(all_nodes)] PSAMPR_RID_ENUMERATION;
+
+typedef [allocate(all_nodes)] PSAMPR_SID_ENUMERATION;
+
+typedef [allocate(all_nodes)] PSAMPR_RETURNED_STRING;
+
+typedef [allocate(all_nodes)] PSAMPR_RETURNED_NORMAL_STRING;
+
+typedef [allocate(all_nodes)] PSAMPR_SID_INFORMATION;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_DISPLAY_USER;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_DISPLAY_MACHINE;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_DISPLAY_GROUP;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_DISPLAY_OEM_USER;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_DISPLAY_OEM_GROUP;
+
+typedef [allocate(all_nodes)] PSAMPR_DOMAIN_INFO_BUFFER;
+
+typedef [allocate(all_nodes)] PSAMPR_USER_INFO_BUFFER;
+
+typedef [allocate(all_nodes)] PSAMPR_GROUP_INFO_BUFFER;
+
+typedef [allocate(all_nodes)] PSAMPR_ALIAS_INFO_BUFFER;
+
+
+
+}
diff --git a/private/newsam/samimp.idl b/private/newsam/samimp.idl
new file mode 100644
index 000000000..200606c31
--- /dev/null
+++ b/private/newsam/samimp.idl
@@ -0,0 +1,56 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ samimp.idl
+
+Abstract:
+
+ This file is necessary to create RPC interfaces that require the use
+ of ntos2 types. The .idl file for the RPC product should contain a
+ line in the interface body that imports this file. For example:
+
+ import "samimp.h";
+
+ Doing this causes the MIDL generated header file to contain the
+ following line:
+
+ #include "samimp.h"
+
+ If this technique is not used, and instead the .idl file for the RPC
+ product simply contains #include <samimp.h>, then the contents of
+ samimp.h will be expanded in the MIDL generated header file. This
+ can lead to duplicate definition problems later when the RPC client
+ or RPC server code needs to include both the MIDL generated header file
+ and a file that is included in samimp.h.
+
+Author:
+ Jim Kelly (JimK) May 23, 1991
+
+Environment:
+
+ User Mode
+
+Revision History:
+
+--*/
+[
+ uuid(12345678-1234-ABCD-EF00-0123476589AB), //FIX, FIX Need real uuid
+#ifdef __midl
+ ms_union,
+#endif // __midl
+ version(0.0),
+ endpoint("mscn_np:[\pipe\samimp]")
+]
+
+interface samimp
+
+{
+
+#define SAM_MIDL_PASS
+#define MIDL_PASS
+#include "samimp.h"
+
+}
diff --git a/private/newsam/samrpc.idl b/private/newsam/samrpc.idl
new file mode 100644
index 000000000..047bf559c
--- /dev/null
+++ b/private/newsam/samrpc.idl
@@ -0,0 +1,1446 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ samrpc.idl
+
+Abstract:
+
+ Security Account Manager RPC Interface Definition File
+
+ This file contains the RPC Interface Definition Language file for
+ remotable SAM functions.
+
+ These functions are internal versions of API and are NOT visible to
+ SAM clients. SAM clients call the SAM API defined in file ntsam.h.
+ Those routines, in turn, call corresponding RPC routines defined by
+ this file.
+
+Author:
+
+ Jim Kelly (JimK) May 23, 1991
+
+Environment:
+
+ User Mode
+
+Revision History:
+
+--*/
+
+[
+ uuid(12345778-1234-ABCD-EF00-0123456789AC), //FIX,FIX - Need real uuid
+ version(1.0),
+#ifdef __midl
+ ms_union,
+#endif // __midl
+ pointer_default(unique)
+]
+
+interface samr
+
+{
+
+//
+// Import a dummy interface containing #includes for public .h files. This
+// trick is necessary so that midl will only generate marshalling routines
+// for subtypes that are relevant to the parameters specified on the RPC
+// interface. midl also ingores function prototypes contained therein.
+//
+
+import "samimp.idl";
+
+
+/////////////////////////// TEMPORARY //////////////////////////////////////
+// //
+// I'm tired of fighting MIDL trying to figure out how to import common //
+// definitions for the following data structures. SOOO. I'm just going //
+// to hard-code them in here for a while. //
+// //
+/////////////////////////// TEMPORARY //////////////////////////////////////
+
+
+//
+// Unicode strings are counted 16-bit character strings.
+// The Length field and MaximumLength fields specify number of bytes,
+// (not wide-characters) in the string. So, this definition differs
+// a bit from the real unicode string type.
+//
+// The Length field does not include a null terminating character
+// if present.
+//
+// NOTE: A null termination character (two bytes of zero), if present,
+// will not be copied with the RPC. It must be explicitly added
+// on the client or server side.
+//
+//
+
+typedef struct _RPC_UNICODE_STRING {
+ USHORT Length;
+ USHORT MaximumLength;
+// [size_is(MaximumLength/sizeof(WCHAR)), length_is(Length/sizeof(WCHAR))] PWCH Buffer;
+ [size_is(MaximumLength/2), length_is(Length/2)] PWCH Buffer;
+} RPC_UNICODE_STRING, *PRPC_UNICODE_STRING;
+
+
+typedef struct _RPC_CYPHER_DATA {
+ ULONG Length;
+ ULONG MaximumLength;
+ [size_is(MaximumLength), length_is(Length)] PCHAR Buffer;
+} RPC_CYPHER_DATA, *PRPC_CYPHER_DATA;
+
+
+//
+// ANSI counted string
+//
+
+typedef struct _RPC_STRING {
+ USHORT Length;
+ USHORT MaximumLength;
+ [size_is(MaximumLength), length_is(Length)] PCHAR Buffer;
+} RPC_STRING, *PRPC_STRING, RPC_ANSI_STRING, *PRPC_ANSI_STRING;
+
+
+
+
+
+//
+// RPC definition of the SID structure. Note the use of the [size_is()]
+// qualifier to specify the number of elements in the variable size
+// imbedded SubAuthorityCount array at runtime.
+//
+//
+
+typedef struct _RPC_SID {
+ UCHAR Revision;
+ UCHAR SubAuthorityCount;
+ SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
+ [size_is(SubAuthorityCount)] ULONG SubAuthority[*];
+} RPC_SID, *PRPC_SID, **PPRPC_SID;
+
+
+
+
+
+////////////////////// END TEMPORARY //////////////////////////////////////
+////////////////////// END TEMPORARY //////////////////////////////////////
+////////////////////// END TEMPORARY //////////////////////////////////////
+
+
+//
+// SAM Generic Handle used to bind from client to server.
+// This handle is used for both LOCAL and REMOTE services.
+//
+
+typedef [handle] LPWSTR PSAMPR_SERVER_NAME;
+
+//
+// SAM RPC Context Handle (Internal definition of SAM_HANDLE)
+//
+
+typedef [context_handle] PVOID SAMPR_HANDLE;
+
+
+//
+// Sam enumeration return buffer format
+//
+
+typedef struct _SAMPR_RID_ENUMERATION {
+ ULONG RelativeId;
+ RPC_UNICODE_STRING Name;
+} SAMPR_RID_ENUMERATION, *PSAMPR_RID_ENUMERATION;
+
+typedef struct _SAMPR_SID_ENUMERATION {
+ PSID Sid;
+ RPC_UNICODE_STRING Name;
+} SAMPR_SID_ENUMERATION, *PSAMPR_SID_ENUMERATION;
+
+typedef struct _SAMPR_ENUMERATION_BUFFER {
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_RID_ENUMERATION Buffer;
+} SAMPR_ENUMERATION_BUFFER, *PSAMPR_ENUMERATION_BUFFER;
+
+
+
+//
+// Used for passing and/or returning self-relative security descriptors
+//
+
+typedef struct _SAMPR_SR_SECURITY_DESCRIPTOR {
+ ULONG Length;
+ [size_is(Length)] PUCHAR SecurityDescriptor;
+} SAMPR_SR_SECURITY_DESCRIPTOR, *PSAMPR_SR_SECURITY_DESCRIPTOR;
+
+
+
+//
+// Sam get groups return buffer format
+//
+
+typedef struct _SAMPR_GET_GROUPS_BUFFER {
+ ULONG MembershipCount;
+ [size_is(MembershipCount)] PGROUP_MEMBERSHIP Groups;
+} SAMPR_GET_GROUPS_BUFFER, *PSAMPR_GET_GROUPS_BUFFER;
+
+
+//
+// Sam get members in group return buffer format
+//
+
+typedef struct _SAMPR_GET_MEMBERS_BUFFER {
+ ULONG MemberCount;
+ [size_is(MemberCount)] PULONG Members;
+ [size_is(MemberCount)] PULONG Attributes;
+} SAMPR_GET_MEMBERS_BUFFER, *PSAMPR_GET_MEMBERS_BUFFER;
+
+
+//
+// Logon hours points to an array of bytes that varies in length
+// depending upon how many units per-week are specified.
+//
+
+typedef struct _SAMPR_LOGON_HOURS {
+
+ USHORT UnitsPerWeek;
+
+ //
+ // Points to an array of bitmask.
+ // The bits represent either days, hours or minutes in the week
+ // depending upon the value of UnitsPerWeek. (Technically, they
+ // could represent any division of time not finer than minute
+ // granularity).
+ // Day granularity is specified by specifying SAM_DAYS_PER_WEEK.
+ // Hours granularity is specified by specifying SAM_HOURS_PER_WEEK.
+ // Minute granularity is specified by specifying SAM_MINUTES_PER_WEEK.
+ // The number of bytes pointed to by this field is
+ // ((UnitsPerWeek + 7) / 8) and may not exceed
+ // ((SAM_MINUTES_PER_WEEK+7)/8 == 1260).
+ //
+
+ [size_is(1260), length_is((UnitsPerWeek+7)/8)] PUCHAR LogonHours;
+
+} SAMPR_LOGON_HOURS, *PSAMPR_LOGON_HOURS;
+
+
+
+
+typedef struct _SAMPR_ULONG_ARRAY {
+
+ //
+ // Indicates the number of Elements in the array.
+ //
+
+ ULONG Count;
+
+ //
+ // Normally, the client wrapper wants to set this to NULL
+ // before calling the stub. This causes the client stub
+ // to allocate a buffe for the returned information which
+ // can be passed back to the caller of the wrapper routine.
+ //
+
+ [size_is(Count)] ULONG * Element;
+
+} SAMPR_ULONG_ARRAY, *PSAMPR_ULONG_ARRAY;
+
+
+//
+// We must hide the PSID in a structure to avoid too many *'s in a
+// field that uses size_is - otherwise MIDL has a fit.
+//
+
+typedef struct _SAMPR_SID_INFORMATION {
+
+ PRPC_SID SidPointer;
+
+} SAMPR_SID_INFORMATION, *PSAMPR_SID_INFORMATION;
+
+
+//
+// Define an array of pointers to SIDs
+//
+
+typedef struct _SAMPR_PSID_ARRAY {
+
+ //
+ // Indicates the number of Elements in the array.
+ //
+
+ ULONG Count;
+
+ //
+ // Points to the array of sid-pointers
+ //
+
+ [size_is(Count)] PSAMPR_SID_INFORMATION Sids;
+
+} SAMPR_PSID_ARRAY, *PSAMPR_PSID_ARRAY;
+
+
+//
+// The following structure is used to receive (as an out parameter) a list
+// of relative IDs (or other ULONGS). This structure is necessary because
+// RPC needs the count to be in the same structure as the returned ULONGs.
+// A wrapper routine is expected to initialize this structure on its stack
+// and pass its address.
+//
+// WARNING: before passing this structure to an RPC stub, the UlongArrayBuffer
+// field must be set to NULL. This causes the stub to allocate
+// the return buffer.
+//
+
+//typedef struct _SAMPR_RETURNED_ULONG_ARRAY {
+// ULONG Count;
+// [size_is(Count)] ULONG * UlongArrayBuffer; // Set to NULL before call !!!
+//} SAMPR_RETURNED_ULONG_ARRAY, *PSAMPR_RETURNED_ULONG_ARRAY;
+
+
+
+
+
+
+
+typedef struct _SAMPR_UNICODE_STRING_ARRAY {
+
+ //
+ // Indicates the number of Elements in the array.
+ //
+
+ ULONG Count;
+
+ //
+ // Normally, the client wrapper wants to set this to NULL
+ // before calling the stub. This causes the client stub
+ // to allocate a buffer for the returned information which
+ // can be passed back to the caller of the wrapper routine.
+ //
+
+ [size_is(Count)] RPC_UNICODE_STRING * Element;
+
+} SAMPR_UNICODE_STRING_ARRAY, *PSAMPR_UNICODE_STRING_ARRAY;
+
+
+
+
+//
+// The allocation scheme used on the client side and server side is
+// different when a list of unicode string names is to be returned.
+// The server wants to be able to return multiple allocation
+// blocks and the client only wants to have to worry about freeing a single
+// allocation block.
+//
+// To accomplish this, a notion of unicode name arrays are introduced.
+// one representing an array passed by a client and another representing
+// an array returned by a server. Then, a client .acf file is used to get
+// these to look correct when generating client stubs and a server .acf
+// file is used to get them to be correct for the server when generating
+// the server stubs.
+//
+
+
+typedef RPC_UNICODE_STRING SAMPR_RETURNED_STRING;
+typedef RPC_UNICODE_STRING *PSAMPR_RETURNED_STRING;
+
+typedef STRING SAMPR_RETURNED_NORMAL_STRING;
+typedef STRING *PSAMPR_RETURNED_NORMAL_STRING;
+
+
+
+//
+// The following structure is used to receive (as an out parameter) a list
+// of unicode string names. This structure is necessary because
+// RPC needs the count to be in the same structure as the returned names.
+// A wrapper routine is expected to initialize this structure on its stack
+// and pass its address.
+//
+// WARNING: before passing this structure to an RPC stub, the UnameArrayBuffer
+// field must be set to NULL. This causes the stub to allocate
+// the return buffer.
+//
+
+typedef struct _SAMPR_RETURNED_USTRING_ARRAY {
+ ULONG Count;
+ [size_is(Count)] PSAMPR_RETURNED_STRING Element; // Set to NULL before call !!!
+} SAMPR_RETURNED_USTRING_ARRAY, *PSAMPR_RETURNED_USTRING_ARRAY;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Server Object Related RPC Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+// (None)
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Domain Object Related RPC Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma pack(4)
+typedef struct _SAMPR_DOMAIN_GENERAL_INFORMATION {
+ OLD_LARGE_INTEGER ForceLogoff;
+ RPC_UNICODE_STRING OemInformation;
+ RPC_UNICODE_STRING DomainName;
+ RPC_UNICODE_STRING ReplicaSourceNodeName;
+ OLD_LARGE_INTEGER DomainModifiedCount;
+ ULONG DomainServerState;
+ ULONG DomainServerRole;
+ BOOLEAN UasCompatibilityRequired;
+ ULONG UserCount;
+ ULONG GroupCount;
+ ULONG AliasCount;
+} SAMPR_DOMAIN_GENERAL_INFORMATION, *PSAMPR_DOMAIN_GENERAL_INFORMATION;
+#pragma pack()
+
+#pragma pack(4)
+typedef struct _SAMPR_DOMAIN_GENERAL_INFORMATION2 {
+ SAMPR_DOMAIN_GENERAL_INFORMATION I1;
+
+ //
+ // New fields added for this structure (NT1.0A).
+ //
+
+#if defined(MIDL_PASS)
+ OLD_LARGE_INTEGER LockoutDuration; //Must be a Delta time
+ OLD_LARGE_INTEGER LockoutObservationWindow; //Must be a Delta time
+#else
+ LARGE_INTEGER LockoutDuration; //Must be a Delta time
+ LARGE_INTEGER LockoutObservationWindow; //Must be a Delta time
+#endif
+ USHORT LockoutThreshold;
+
+} SAMPR_DOMAIN_GENERAL_INFORMATION2, *PSAMPR_DOMAIN_GENERAL_INFORMATION2;
+#pragma pack()
+
+typedef struct _SAMPR_DOMAIN_OEM_INFORMATION {
+ RPC_UNICODE_STRING OemInformation;
+} SAMPR_DOMAIN_OEM_INFORMATION, *PSAMPR_DOMAIN_OEM_INFORMATION;
+
+typedef struct _SAMPR_DOMAIN_NAME_INFORMATION {
+ RPC_UNICODE_STRING DomainName;
+} SAMPR_DOMAIN_NAME_INFORMATION, *PSAMPR_DOMAIN_NAME_INFORMATION;
+
+
+typedef struct SAMPR_DOMAIN_REPLICATION_INFORMATION {
+ RPC_UNICODE_STRING ReplicaSourceNodeName;
+} SAMPR_DOMAIN_REPLICATION_INFORMATION, *PSAMPR_DOMAIN_REPLICATION_INFORMATION;
+
+typedef struct _SAMPR_DOMAIN_LOCKOUT_INFORMATION {
+#if defined(MIDL_PASS)
+ OLD_LARGE_INTEGER LockoutDuration; //Must be a Delta time
+ OLD_LARGE_INTEGER LockoutObservationWindow; //Must be a Delta time
+#else
+ LARGE_INTEGER LockoutDuration; //Must be a Delta time
+ LARGE_INTEGER LockoutObservationWindow; //Must be a Delta time
+#endif
+ USHORT LockoutThreshold; //Zero means no lockout
+} SAMPR_DOMAIN_LOCKOUT_INFORMATION, *PSAMPR_DOMAIN_LOCKOUT_INFORMATION;
+
+
+typedef [switch_type(DOMAIN_INFORMATION_CLASS)] union
+_SAMPR_DOMAIN_INFO_BUFFER {
+ [case(DomainPasswordInformation)] DOMAIN_PASSWORD_INFORMATION Password;
+ [case(DomainGeneralInformation)] SAMPR_DOMAIN_GENERAL_INFORMATION General;
+ [case(DomainLogoffInformation)] DOMAIN_LOGOFF_INFORMATION Logoff;
+ [case(DomainOemInformation)] SAMPR_DOMAIN_OEM_INFORMATION Oem;
+ [case(DomainNameInformation)] SAMPR_DOMAIN_NAME_INFORMATION Name;
+ [case(DomainServerRoleInformation)] DOMAIN_SERVER_ROLE_INFORMATION Role;
+ [case(DomainReplicationInformation)] SAMPR_DOMAIN_REPLICATION_INFORMATION Replication;
+ [case(DomainModifiedInformation)] DOMAIN_MODIFIED_INFORMATION Modified;
+ [case(DomainStateInformation)] DOMAIN_STATE_INFORMATION State;
+ [case(DomainGeneralInformation2)] SAMPR_DOMAIN_GENERAL_INFORMATION2 General2;
+ [case(DomainLockoutInformation)] SAMPR_DOMAIN_LOCKOUT_INFORMATION Lockout;
+ [case(DomainModifiedInformation2)] DOMAIN_MODIFIED_INFORMATION2 Modified2;
+} SAMPR_DOMAIN_INFO_BUFFER, *PSAMPR_DOMAIN_INFO_BUFFER;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Group Object Related Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct _SAMPR_GROUP_GENERAL_INFORMATION {
+ RPC_UNICODE_STRING Name;
+ ULONG Attributes;
+ ULONG MemberCount;
+ RPC_UNICODE_STRING AdminComment;
+} SAMPR_GROUP_GENERAL_INFORMATION, *PSAMPR_GROUP_GENERAL_INFORMATION;
+
+typedef struct _SAMPR_GROUP_NAME_INFORMATION {
+ RPC_UNICODE_STRING Name;
+} SAMPR_GROUP_NAME_INFORMATION, *PSAMPR_GROUP_NAME_INFORMATION;
+
+
+typedef struct _SAMPR_GROUP_ADM_COMMENT_INFORMATION {
+ RPC_UNICODE_STRING AdminComment;
+} SAMPR_GROUP_ADM_COMMENT_INFORMATION, *PSAMPR_GROUP_ADM_COMMENT_INFORMATION;
+
+
+
+typedef [switch_type(GROUP_INFORMATION_CLASS)] union
+_SAMPR_GROUP_INFO_BUFFER {
+ [case(GroupGeneralInformation)] SAMPR_GROUP_GENERAL_INFORMATION General;
+ [case(GroupNameInformation)] SAMPR_GROUP_NAME_INFORMATION Name;
+ [case(GroupAttributeInformation)] GROUP_ATTRIBUTE_INFORMATION Attribute;
+ [case(GroupAdminCommentInformation)] SAMPR_GROUP_ADM_COMMENT_INFORMATION AdminComment;
+} SAMPR_GROUP_INFO_BUFFER, *PSAMPR_GROUP_INFO_BUFFER;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Alias Object Related Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct _SAMPR_ALIAS_GENERAL_INFORMATION {
+ RPC_UNICODE_STRING Name;
+ ULONG MemberCount;
+ RPC_UNICODE_STRING AdminComment;
+} SAMPR_ALIAS_GENERAL_INFORMATION, *PSAMPR_ALIAS_GENERAL_INFORMATION;
+
+typedef struct _SAMPR_ALIAS_NAME_INFORMATION {
+ RPC_UNICODE_STRING Name;
+} SAMPR_ALIAS_NAME_INFORMATION, *PSAMPR_ALIAS_NAME_INFORMATION;
+
+
+typedef struct _SAMPR_ALIAS_ADM_COMMENT_INFORMATION {
+ RPC_UNICODE_STRING AdminComment;
+} SAMPR_ALIAS_ADM_COMMENT_INFORMATION, *PSAMPR_ALIAS_ADM_COMMENT_INFORMATION;
+
+
+
+typedef [switch_type(ALIAS_INFORMATION_CLASS)] union
+_SAMPR_ALIAS_INFO_BUFFER {
+ [case(AliasGeneralInformation)] SAMPR_ALIAS_GENERAL_INFORMATION General;
+ [case(AliasNameInformation)] SAMPR_ALIAS_NAME_INFORMATION Name;
+ [case(AliasAdminCommentInformation)] SAMPR_ALIAS_ADM_COMMENT_INFORMATION AdminComment;
+} SAMPR_ALIAS_INFO_BUFFER, *PSAMPR_ALIAS_INFO_BUFFER;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// User Object Related Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#pragma pack(4)
+typedef struct _SAMPR_USER_ALL_INFORMATION {
+ OLD_LARGE_INTEGER LastLogon;
+ OLD_LARGE_INTEGER LastLogoff;
+ OLD_LARGE_INTEGER PasswordLastSet;
+ OLD_LARGE_INTEGER AccountExpires;
+ OLD_LARGE_INTEGER PasswordCanChange;
+ OLD_LARGE_INTEGER PasswordMustChange;
+ RPC_UNICODE_STRING UserName;
+ RPC_UNICODE_STRING FullName;
+ RPC_UNICODE_STRING HomeDirectory;
+ RPC_UNICODE_STRING HomeDirectoryDrive;
+ RPC_UNICODE_STRING ScriptPath;
+ RPC_UNICODE_STRING ProfilePath;
+ RPC_UNICODE_STRING AdminComment;
+ RPC_UNICODE_STRING WorkStations;
+ RPC_UNICODE_STRING UserComment;
+ RPC_UNICODE_STRING Parameters;
+ RPC_UNICODE_STRING LmOwfPassword;
+ RPC_UNICODE_STRING NtOwfPassword;
+ RPC_UNICODE_STRING PrivateData;
+ SAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ ULONG UserAccountControl;
+ ULONG WhichFields;
+ SAMPR_LOGON_HOURS LogonHours;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ USHORT CountryCode;
+ USHORT CodePage;
+ BOOLEAN LmPasswordPresent;
+ BOOLEAN NtPasswordPresent;
+ BOOLEAN PasswordExpired;
+ BOOLEAN PrivateDataSensitive;
+} SAMPR_USER_ALL_INFORMATION, *PSAMPR_USER_ALL_INFORMATION;
+#pragma pack()
+
+#pragma pack(4)
+typedef struct _SAMPR_USER_INTERNAL3_INFORMATION {
+ SAMPR_USER_ALL_INFORMATION I1;
+#if defined(MIDL_PASS)
+ OLD_LARGE_INTEGER LastBadPasswordTime;
+#else
+ LARGE_INTEGER LastBadPasswordTime;
+#endif
+} SAMPR_USER_INTERNAL3_INFORMATION, *PSAMPR_USER_INTERNAL3_INFORMATION;
+#pragma pack()
+
+
+typedef struct _SAMPR_USER_GENERAL_INFORMATION {
+ RPC_UNICODE_STRING UserName;
+ RPC_UNICODE_STRING FullName;
+ ULONG PrimaryGroupId;
+ RPC_UNICODE_STRING AdminComment;
+ RPC_UNICODE_STRING UserComment;
+} SAMPR_USER_GENERAL_INFORMATION, *PSAMPR_USER_GENERAL_INFORMATION;
+
+typedef struct _SAMPR_USER_PREFERENCES_INFORMATION {
+ RPC_UNICODE_STRING UserComment;
+ RPC_UNICODE_STRING Reserved1;
+ USHORT CountryCode;
+ USHORT CodePage;
+} SAMPR_USER_PREFERENCES_INFORMATION, *PSAMPR_USER_PREFERENCES_INFORMATION;
+
+typedef struct _SAMPR_USER_PARAMETERS_INFORMATION {
+ RPC_UNICODE_STRING Parameters;
+} SAMPR_USER_PARAMETERS_INFORMATION, *PSAMPR_USER_PARAMETERS_INFORMATION;
+
+#pragma pack(4)
+typedef struct _SAMPR_USER_LOGON_INFORMATION {
+ RPC_UNICODE_STRING UserName;
+ RPC_UNICODE_STRING FullName;
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ RPC_UNICODE_STRING HomeDirectory;
+ RPC_UNICODE_STRING HomeDirectoryDrive;
+ RPC_UNICODE_STRING ScriptPath;
+ RPC_UNICODE_STRING ProfilePath;
+ RPC_UNICODE_STRING WorkStations;
+ OLD_LARGE_INTEGER LastLogon;
+ OLD_LARGE_INTEGER LastLogoff;
+ OLD_LARGE_INTEGER PasswordLastSet;
+ OLD_LARGE_INTEGER PasswordCanChange;
+ OLD_LARGE_INTEGER PasswordMustChange;
+ SAMPR_LOGON_HOURS LogonHours;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ ULONG UserAccountControl;
+} SAMPR_USER_LOGON_INFORMATION, *PSAMPR_USER_LOGON_INFORMATION;
+#pragma pack()
+
+#pragma pack(4)
+typedef struct _SAMPR_USER_ACCOUNT_INFORMATION {
+ RPC_UNICODE_STRING UserName;
+ RPC_UNICODE_STRING FullName;
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ RPC_UNICODE_STRING HomeDirectory;
+ RPC_UNICODE_STRING HomeDirectoryDrive;
+ RPC_UNICODE_STRING ScriptPath;
+ RPC_UNICODE_STRING ProfilePath;
+ RPC_UNICODE_STRING AdminComment;
+ RPC_UNICODE_STRING WorkStations;
+ OLD_LARGE_INTEGER LastLogon;
+ OLD_LARGE_INTEGER LastLogoff;
+ SAMPR_LOGON_HOURS LogonHours;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ OLD_LARGE_INTEGER PasswordLastSet;
+ OLD_LARGE_INTEGER AccountExpires;
+ ULONG UserAccountControl;
+} SAMPR_USER_ACCOUNT_INFORMATION, *PSAMPR_USER_ACCOUNT_INFORMATION;
+#pragma pack()
+
+typedef struct _SAMPR_USER_A_NAME_INFORMATION {
+ RPC_UNICODE_STRING UserName;
+} SAMPR_USER_A_NAME_INFORMATION, *PSAMPR_USER_A_NAME_INFORMATION;
+
+typedef struct _SAMPR_USER_F_NAME_INFORMATION {
+ RPC_UNICODE_STRING FullName;
+} SAMPR_USER_F_NAME_INFORMATION, *PSAMPR_USER_F_NAME_INFORMATION;
+
+typedef struct _SAMPR_USER_NAME_INFORMATION {
+ RPC_UNICODE_STRING UserName;
+ RPC_UNICODE_STRING FullName;
+} SAMPR_USER_NAME_INFORMATION, *PSAMPR_USER_NAME_INFORMATION;
+
+typedef struct _SAMPR_USER_HOME_INFORMATION {
+ RPC_UNICODE_STRING HomeDirectory;
+ RPC_UNICODE_STRING HomeDirectoryDrive;
+} SAMPR_USER_HOME_INFORMATION, *PSAMPR_USER_HOME_INFORMATION;
+
+typedef struct _SAMPR_USER_SCRIPT_INFORMATION {
+ RPC_UNICODE_STRING ScriptPath;
+} SAMPR_USER_SCRIPT_INFORMATION, *PSAMPR_USER_SCRIPT_INFORMATION;
+
+typedef struct _SAMPR_USER_PROFILE_INFORMATION {
+ RPC_UNICODE_STRING ProfilePath;
+} SAMPR_USER_PROFILE_INFORMATION, *PSAMPR_USER_PROFILE_INFORMATION;
+
+typedef struct _SAMPR_USER_ADMIN_COMMENT_INFORMATION {
+ RPC_UNICODE_STRING AdminComment;
+} SAMPR_USER_ADMIN_COMMENT_INFORMATION, *PSAMPR_USER_ADMIN_COMMENT_INFORMATION;
+
+typedef struct _SAMPR_USER_WORKSTATIONS_INFORMATION {
+ RPC_UNICODE_STRING WorkStations;
+} SAMPR_USER_WORKSTATIONS_INFORMATION, *PSAMPR_USER_WORKSTATIONS_INFORMATION;
+
+typedef struct _SAMPR_USER_LOGON_HOURS_INFORMATION {
+ SAMPR_LOGON_HOURS LogonHours;
+} SAMPR_USER_LOGON_HOURS_INFORMATION, *PSAMPR_USER_LOGON_HOURS_INFORMATION;
+
+typedef struct _SAMPR_USER_INTERNAL1_INFORMATION {
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ BOOLEAN NtPasswordPresent;
+ BOOLEAN LmPasswordPresent;
+ BOOLEAN PasswordExpired;
+} SAMPR_USER_INTERNAL1_INFORMATION, *PSAMPR_USER_INTERNAL1_INFORMATION;
+
+
+typedef struct _SAMPR_USER_INTERNAL4_INFORMATION {
+ SAMPR_USER_ALL_INFORMATION I1;
+ SAMPR_ENCRYPTED_USER_PASSWORD UserPassword;
+} SAMPR_USER_INTERNAL4_INFORMATION, *PSAMPR_USER_INTERNAL4_INFORMATION;
+
+typedef struct _SAMPR_USER_INTERNAL5_INFORMATION {
+ SAMPR_ENCRYPTED_USER_PASSWORD UserPassword;
+ BOOLEAN PasswordExpired;
+} SAMPR_USER_INTERNAL5_INFORMATION, *PSAMPR_USER_INTERNAL5_INFORMATION;
+
+
+typedef [switch_type(USER_INFORMATION_CLASS)] union
+_SAMPR_USER_INFO_BUFFER {
+ [case(UserGeneralInformation)] SAMPR_USER_GENERAL_INFORMATION General;
+ [case(UserPreferencesInformation)] SAMPR_USER_PREFERENCES_INFORMATION Preferences;
+ [case(UserLogonInformation)] SAMPR_USER_LOGON_INFORMATION Logon;
+ [case(UserLogonHoursInformation)] SAMPR_USER_LOGON_HOURS_INFORMATION LogonHours;
+ [case(UserAccountInformation)] SAMPR_USER_ACCOUNT_INFORMATION Account;
+ [case(UserNameInformation)] SAMPR_USER_NAME_INFORMATION Name;
+ [case(UserAccountNameInformation)] SAMPR_USER_A_NAME_INFORMATION AccountName;
+ [case(UserFullNameInformation)] SAMPR_USER_F_NAME_INFORMATION FullName;
+ [case(UserPrimaryGroupInformation)] USER_PRIMARY_GROUP_INFORMATION PrimaryGroup;
+ [case(UserHomeInformation)] SAMPR_USER_HOME_INFORMATION Home;
+ [case(UserScriptInformation)] SAMPR_USER_SCRIPT_INFORMATION Script;
+ [case(UserProfileInformation)] SAMPR_USER_PROFILE_INFORMATION Profile;
+ [case(UserAdminCommentInformation)] SAMPR_USER_ADMIN_COMMENT_INFORMATION AdminComment;
+ [case(UserWorkStationsInformation)] SAMPR_USER_WORKSTATIONS_INFORMATION WorkStations;
+ [case(UserControlInformation)] USER_CONTROL_INFORMATION Control;
+ [case(UserExpiresInformation)] USER_EXPIRES_INFORMATION Expires;
+ [case(UserInternal1Information)] SAMPR_USER_INTERNAL1_INFORMATION Internal1;
+ [case(UserInternal2Information)] USER_INTERNAL2_INFORMATION Internal2;
+ [case(UserParametersInformation)] SAMPR_USER_PARAMETERS_INFORMATION Parameters;
+ [case(UserAllInformation)] SAMPR_USER_ALL_INFORMATION All;
+ [case(UserInternal3Information)] SAMPR_USER_INTERNAL3_INFORMATION Internal3;
+ [case(UserInternal4Information)] SAMPR_USER_INTERNAL4_INFORMATION Internal4;
+ [case(UserInternal5Information)] SAMPR_USER_INTERNAL5_INFORMATION Internal5;
+} SAMPR_USER_INFO_BUFFER, *PSAMPR_USER_INFO_BUFFER;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Domain display information Related Definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_USER {
+ ULONG Index;
+ ULONG Rid;
+ ULONG AccountControl;
+ SAMPR_RETURNED_STRING LogonName;
+ SAMPR_RETURNED_STRING AdminComment;
+ SAMPR_RETURNED_STRING FullName;
+} SAMPR_DOMAIN_DISPLAY_USER, *PSAMPR_DOMAIN_DISPLAY_USER;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_MACHINE {
+ ULONG Index;
+ ULONG Rid;
+ ULONG AccountControl;
+ SAMPR_RETURNED_STRING Machine;
+ SAMPR_RETURNED_STRING Comment;
+} SAMPR_DOMAIN_DISPLAY_MACHINE, *PSAMPR_DOMAIN_DISPLAY_MACHINE;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_GROUP { // Added for NT1.0A
+ ULONG Index;
+ ULONG Rid;
+ ULONG Attributes;
+ SAMPR_RETURNED_STRING Group;
+ SAMPR_RETURNED_STRING Comment;
+} SAMPR_DOMAIN_DISPLAY_GROUP, *PSAMPR_DOMAIN_DISPLAY_GROUP;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_OEM_USER { // Added for NT1.0A
+ ULONG Index;
+ SAMPR_RETURNED_NORMAL_STRING OemUser;
+} SAMPR_DOMAIN_DISPLAY_OEM_USER, *PSAMPR_DOMAIN_DISPLAY_OEM_USER;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_OEM_GROUP { // Added for NT1.0A
+ ULONG Index;
+ SAMPR_RETURNED_NORMAL_STRING OemGroup;
+} SAMPR_DOMAIN_DISPLAY_OEM_GROUP, *PSAMPR_DOMAIN_DISPLAY_OEM_GROUP;
+
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_USER_BUFFER {
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_DOMAIN_DISPLAY_USER Buffer;
+} SAMPR_DOMAIN_DISPLAY_USER_BUFFER, *PSAMPR_DOMAIN_DISPLAY_USER_BUFFER;
+
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER {
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_DOMAIN_DISPLAY_MACHINE Buffer;
+} SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER, *PSAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER { // Added for NT1.0A
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_DOMAIN_DISPLAY_GROUP Buffer;
+} SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER, *PSAMPR_DOMAIN_DISPLAY_GROUP_BUFFER;
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_OEM_USER_BUFFER { // Added for NT1.0A
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_DOMAIN_DISPLAY_OEM_USER Buffer;
+} SAMPR_DOMAIN_DISPLAY_OEM_USER_BUFFER, *PSAMPR_DOMAIN_DISPLAY_OEM_USER_BUFFER;
+
+
+typedef struct _SAMPR_DOMAIN_DISPLAY_OEM_GROUP_BUFFER { // Added for NT1.0A
+ ULONG EntriesRead;
+ [size_is(EntriesRead)] PSAMPR_DOMAIN_DISPLAY_OEM_GROUP Buffer;
+} SAMPR_DOMAIN_DISPLAY_OEM_GROUP_BUFFER, *PSAMPR_DOMAIN_DISPLAY_OEM_GROUP_BUFFER;
+
+
+
+typedef [switch_type(DOMAIN_DISPLAY_INFORMATION)] union
+_SAMPR_DISPLAY_INFO_BUFFER {
+ [case(DomainDisplayUser)] SAMPR_DOMAIN_DISPLAY_USER_BUFFER UserInformation;
+ [case(DomainDisplayMachine)] SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER MachineInformation;
+ [case(DomainDisplayGroup)] SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER GroupInformation;
+ [case(DomainDisplayOemUser)] SAMPR_DOMAIN_DISPLAY_OEM_USER_BUFFER OemUserInformation;
+ [case(DomainDisplayOemGroup)] SAMPR_DOMAIN_DISPLAY_OEM_GROUP_BUFFER OemGroupInformation;
+} SAMPR_DISPLAY_INFO_BUFFER, *PSAMPR_DISPLAY_INFO_BUFFER;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// APIs Exported By SAM //
+// //
+// Note that MIDL will prefix each of these routine names with the //
+// interface name. So, we will end up with names like Samr_Connect(). //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamrConnect(
+ [in,unique] PSAMPR_SERVER_NAME ServerName,
+ [out] SAMPR_HANDLE * ServerHandle,
+ [in] ACCESS_MASK DesiredAccess
+ );
+
+NTSTATUS
+SamrCloseHandle(
+ [in,out] SAMPR_HANDLE * SamHandle
+ );
+
+
+NTSTATUS
+SamrSetSecurityObject(
+ [in] SAMPR_HANDLE ObjectHandle,
+ [in] SECURITY_INFORMATION SecurityInformation,
+ [in] PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor
+ );
+
+NTSTATUS
+SamrQuerySecurityObject(
+ [in] SAMPR_HANDLE ObjectHandle,
+ [in] SECURITY_INFORMATION SecurityInformation,
+ [out] PSAMPR_SR_SECURITY_DESCRIPTOR *SecurityDescriptor
+ );
+
+
+
+
+
+//
+// Server related API
+//
+
+NTSTATUS
+SamrShutdownSamServer(
+ [in] SAMPR_HANDLE ServerHandle
+ );
+
+
+NTSTATUS
+SamrLookupDomainInSamServer(
+ [in] SAMPR_HANDLE ServerHandle,
+ [in] PRPC_UNICODE_STRING Name,
+ [out] PRPC_SID * DomainId
+ );
+
+NTSTATUS
+SamrEnumerateDomainsInSamServer(
+ [in] SAMPR_HANDLE ServerHandle,
+ [in,out] PSAM_ENUMERATE_HANDLE EnumerationContext,
+ [out] PSAMPR_ENUMERATION_BUFFER *Buffer,
+ [in] ULONG PreferedMaximumLength,
+ [out] PULONG CountReturned
+ );
+
+
+
+//
+// Domain related API
+//
+
+NTSTATUS
+SamrOpenDomain(
+ [in] SAMPR_HANDLE ServerHandle,
+ [in] ACCESS_MASK DesiredAccess,
+ [in] PRPC_SID DomainId,
+ [out] SAMPR_HANDLE * DomainHandle
+ );
+
+//
+// Don't call the following api with an InfoClass level beyond
+// DomainUasInformation. This is the highest info level supported
+// in NT1.0, and RPC didn't raise an exception when an invalid
+// info level was passed, so the server side stub tries to unmarshal
+// the [out] parameter (which isn't there) and can access violate.
+//
+// CALL SamrQueryInformationDomain2() instead (see end of this file)
+//
+
+NTSTATUS
+SamrQueryInformationDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ [out, switch_is(DomainInformationClass)]
+ PSAMPR_DOMAIN_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrSetInformationDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ [in, switch_is(DomainInformationClass)]
+ PSAMPR_DOMAIN_INFO_BUFFER DomainInformation
+ );
+
+NTSTATUS
+SamrCreateGroupInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PRPC_UNICODE_STRING Name,
+ [in] ACCESS_MASK DesiredAccess,
+ [out] SAMPR_HANDLE * GroupHandle,
+ [out] PULONG RelativeId
+ );
+
+
+NTSTATUS
+SamrEnumerateGroupsInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in,out] PSAM_ENUMERATE_HANDLE EnumerationContext,
+ [out] PSAMPR_ENUMERATION_BUFFER *Buffer,
+ [in] ULONG PreferedMaximumLength,
+ [out] PULONG CountReturned
+ );
+
+NTSTATUS
+SamrCreateUserInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PRPC_UNICODE_STRING Name,
+ [in] ACCESS_MASK DesiredAccess,
+ [out] SAMPR_HANDLE * UserHandle,
+ [out] PULONG RelativeId
+ );
+
+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
+ );
+
+NTSTATUS
+SamrCreateAliasInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PRPC_UNICODE_STRING AccountName,
+ [in] ACCESS_MASK DesiredAccess,
+ [out] SAMPR_HANDLE * AliasHandle,
+ [out] PULONG RelativeId
+ );
+
+NTSTATUS
+SamrEnumerateAliasesInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in,out] PSAM_ENUMERATE_HANDLE EnumerationContext,
+ [out] PSAMPR_ENUMERATION_BUFFER *Buffer,
+ [in] ULONG PreferedMaximumLength,
+ [out] PULONG CountReturned
+ );
+
+NTSTATUS
+SamrGetAliasMembership(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PSAMPR_PSID_ARRAY SidArray,
+ [out] PSAMPR_ULONG_ARRAY Membership
+ );
+
+//
+// The format of parameters in LookupNamesInDomain() differs between
+// client and server side. This is accomplished using multiple .acf files.
+// Please see samclient.acf and samsrvr.acf for further descriptions of
+// parameter formats.
+//
+
+
+NTSTATUS
+SamrLookupNamesInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] ULONG Count,
+ //
+ // The following count must match SAM_MAXIMUM_LOOKUP_COUNT,
+ // defined in ntsam.h
+ //
+ [in,size_is(1000), length_is(Count)]
+ RPC_UNICODE_STRING Names[*],
+ [out] PSAMPR_ULONG_ARRAY RelativeIds,
+ [out] PSAMPR_ULONG_ARRAY Use
+ );
+
+//
+//
+// The format of parameters in LookupIdsInDomain() differs between
+// client and server side. This is accomplished using multiple .acf files.
+// Please see samclient.acf and samsrvr.acf for further descriptions of
+// parameter formats.
+//
+
+NTSTATUS
+SamrLookupIdsInDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] ULONG Count,
+ //
+ // The following count must match SAM_MAXIMUM_LOOKUP_COUNT,
+ // defined in ntsam.h
+ //
+ [in,size_is(1000), length_is(Count)]
+ PULONG RelativeIds,
+ [out] PSAMPR_RETURNED_USTRING_ARRAY Names,
+ [out] PSAMPR_ULONG_ARRAY Use
+ );
+
+
+
+
+
+
+
+//
+// Group related API
+//
+
+NTSTATUS
+SamrOpenGroup(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] ACCESS_MASK DesiredAccess,
+ [in] ULONG GroupId,
+ [out] SAMPR_HANDLE * GroupHandle
+ );
+
+NTSTATUS
+SamrQueryInformationGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [in] GROUP_INFORMATION_CLASS GroupInformationClass,
+ [out, switch_is(GroupInformationClass)]
+ PSAMPR_GROUP_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrSetInformationGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [in] GROUP_INFORMATION_CLASS GroupInformationClass,
+ [in, switch_is(GroupInformationClass)]
+ PSAMPR_GROUP_INFO_BUFFER Buffer
+ );
+
+NTSTATUS
+SamrAddMemberToGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [in] ULONG MemberId,
+ [in] ULONG Attributes
+ );
+
+NTSTATUS
+SamrDeleteGroup(
+ [in,out] SAMPR_HANDLE * GroupHandle
+ );
+
+NTSTATUS
+SamrRemoveMemberFromGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [in] ULONG MemberId
+ );
+
+NTSTATUS
+SamrGetMembersInGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [out] PSAMPR_GET_MEMBERS_BUFFER *Members
+ );
+
+NTSTATUS
+SamrSetMemberAttributesOfGroup(
+ [in] SAMPR_HANDLE GroupHandle,
+ [in] ULONG MemberId,
+ [in] ULONG Attributes
+ );
+
+
+
+
+
+
+//
+// Alias related API
+//
+
+NTSTATUS
+SamrOpenAlias(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] ACCESS_MASK DesiredAccess,
+ [in] ULONG AliasId,
+ [out] SAMPR_HANDLE * AliasHandle
+ );
+
+NTSTATUS
+SamrQueryInformationAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] ALIAS_INFORMATION_CLASS AliasInformationClass,
+ [out, switch_is(AliasInformationClass)]
+ PSAMPR_ALIAS_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrSetInformationAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] ALIAS_INFORMATION_CLASS AliasInformationClass,
+ [in, switch_is(AliasInformationClass)]
+ PSAMPR_ALIAS_INFO_BUFFER Buffer
+ );
+
+NTSTATUS
+SamrDeleteAlias(
+ [in, out] SAMPR_HANDLE * AliasHandle
+ );
+
+NTSTATUS
+SamrAddMemberToAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] PRPC_SID MemberId
+ );
+
+NTSTATUS
+SamrRemoveMemberFromAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] PRPC_SID MemberId
+ );
+
+NTSTATUS
+SamrGetMembersInAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [out] PSAMPR_PSID_ARRAY Members
+ );
+
+
+
+
+
+
+//
+// User related API
+//
+
+NTSTATUS
+SamrOpenUser(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] ACCESS_MASK DesiredAccess,
+ [in] ULONG UserId,
+ [out] SAMPR_HANDLE * UserHandle
+ );
+
+NTSTATUS
+SamrDeleteUser(
+ [in,out] SAMPR_HANDLE * UserHandle
+ );
+
+//
+// Don't call the following api with an InfoClass level beyond
+// UserAllInformation. This is the highest info level supported
+// in NT1.0, and RPC didn't raise an exception when an invalid
+// info level was passed, so the server side stub tries to unmarshal
+// the [out] parameter (which isn't there) and can access violate.
+//
+// CALL SamrQueryInformationUser2() instead (see end of this file)
+//
+
+NTSTATUS
+SamrQueryInformationUser(
+ [in] SAMPR_HANDLE UserHandle,
+ [in] USER_INFORMATION_CLASS UserInformationClass,
+ [out, switch_is(UserInformationClass)]
+ PSAMPR_USER_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrSetInformationUser(
+ [in] SAMPR_HANDLE UserHandle,
+ [in] USER_INFORMATION_CLASS UserInformationClass,
+ [in, switch_is(UserInformationClass)]
+ PSAMPR_USER_INFO_BUFFER Buffer
+ );
+
+
+NTSTATUS
+SamrChangePasswordUser(
+ [in] SAMPR_HANDLE UserHandle,
+
+ [in] BOOLEAN LmPresent,
+ [in, unique] PENCRYPTED_LM_OWF_PASSWORD LmOldEncryptedWithLmNew,
+ [in, unique] PENCRYPTED_LM_OWF_PASSWORD LmNewEncryptedWithLmOld,
+
+ [in] BOOLEAN NtPresent,
+ [in, unique] PENCRYPTED_NT_OWF_PASSWORD NtOldEncryptedWithNtNew,
+ [in, unique] PENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithNtOld,
+
+ [in] BOOLEAN NtCrossEncryptionPresent,
+ [in, unique] PENCRYPTED_NT_OWF_PASSWORD NtNewEncryptedWithLmNew,
+
+ [in] BOOLEAN LmCrossEncryptionPresent,
+ [in, unique] PENCRYPTED_LM_OWF_PASSWORD LmNtNewEncryptedWithNtNew
+ );
+
+NTSTATUS
+SamrGetGroupsForUser(
+ [in] SAMPR_HANDLE UserHandle,
+ [out] PSAMPR_GET_GROUPS_BUFFER *Groups
+ );
+
+//
+// Don't call the following api with an InfoClass level beyond
+// DomainDisplayMachine. This is the highest info level supported
+// in NT1.0, and RPC didn't raise an exception when an invalid
+// info level was passed, so the server side stub tries to unmarshal
+// the [out] parameter (which isn't there) and can access violate.
+//
+// CALL SamrQueryDisplayInformation2() instead (see end of this file)
+//
+
+NTSTATUS
+SamrQueryDisplayInformation (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_DISPLAY_INFORMATION DisplayInformationClass,
+ [in] ULONG Index,
+ [in] ULONG EntryCount,
+ [in] ULONG PreferredMaximumLength,
+ [out] PULONG TotalAvailable,
+ [out] PULONG TotalReturned,
+ [out, switch_is(DisplayInformationClass)]
+ PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ );
+
+//
+// Don't call the following api with an InfoClass level beyond
+// DomainDisplayMachine. This is the highest info level supported
+// in NT1.0, and RPC didn't raise an exception when an invalid
+// info level was passed, so the server side stub tries to unmarshal
+// the [out] parameter (which isn't there) and can access violate.
+//
+// CALL SamrGetDisplayEnumerationIndex2() instead (see end of this file)
+//
+
+NTSTATUS
+SamrGetDisplayEnumerationIndex (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_DISPLAY_INFORMATION DisplayInformationClass,
+ [in] PRPC_UNICODE_STRING Prefix,
+ [out] PULONG Index
+ );
+
+
+//
+// Test APIs.
+// These only do anything on special builds.
+//
+
+NTSTATUS
+SamrTestPrivateFunctionsDomain (
+ [in] SAMPR_HANDLE DomainHandle
+ );
+
+NTSTATUS
+SamrTestPrivateFunctionsUser (
+ [in] SAMPR_HANDLE UserHandle
+ );
+
+
+//
+// The following is only for use by WRAPPERS.C, although there's no
+// harm done if an application calls it.
+//
+
+NTSTATUS
+SamrGetUserDomainPasswordInformation (
+ [in] SAMPR_HANDLE UserHandle,
+ [out] PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
+ );
+
+//
+// API added late, so we want it at the end.
+//
+
+NTSTATUS
+SamrRemoveMemberFromForeignDomain (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PRPC_SID MemberSid
+ );
+
+//
+// API added for NT1.0A.
+// These must be added at the end of the file to ensure operability
+// with down-level systems. That is, the API number of all existing
+// API must be kept the same as it was.
+//
+
+NTSTATUS
+SamrQueryInformationDomain2( // Added for NT1.0A
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ [out, switch_is(DomainInformationClass)]
+ PSAMPR_DOMAIN_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrQueryInformationUser2(
+ [in] SAMPR_HANDLE UserHandle,
+ [in] USER_INFORMATION_CLASS UserInformationClass,
+ [out, switch_is(UserInformationClass)]
+ PSAMPR_USER_INFO_BUFFER *Buffer
+ );
+
+NTSTATUS
+SamrQueryDisplayInformation2 (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_DISPLAY_INFORMATION DisplayInformationClass,
+ [in] ULONG Index,
+ [in] ULONG EntryCount,
+ [in] ULONG PreferredMaximumLength,
+ [out] PULONG TotalAvailable,
+ [out] PULONG TotalReturned,
+ [out, switch_is(DisplayInformationClass)]
+ PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ );
+
+NTSTATUS
+SamrGetDisplayEnumerationIndex2 (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_DISPLAY_INFORMATION DisplayInformationClass,
+ [in] PRPC_UNICODE_STRING Prefix,
+ [out] PULONG Index
+ );
+
+
+NTSTATUS
+SamrCreateUser2InDomain(
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] PRPC_UNICODE_STRING Name,
+ [in] ULONG AccountType,
+ [in] ACCESS_MASK DesiredAccess,
+ [out] SAMPR_HANDLE * UserHandle,
+ [out] PULONG GrantedAccess,
+ [out] PULONG RelativeId
+ );
+
+NTSTATUS
+SamrQueryDisplayInformation3 (
+ [in] SAMPR_HANDLE DomainHandle,
+ [in] DOMAIN_DISPLAY_INFORMATION DisplayInformationClass,
+ [in] ULONG Index,
+ [in] ULONG EntryCount,
+ [in] ULONG PreferredMaximumLength,
+ [out] PULONG TotalAvailable,
+ [out] PULONG TotalReturned,
+ [out, switch_is(DisplayInformationClass)]
+ PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ );
+
+NTSTATUS
+SamrAddMultipleMembersToAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] PSAMPR_PSID_ARRAY MembersBuffer
+ );
+
+NTSTATUS
+SamrRemoveMultipleMembersFromAlias(
+ [in] SAMPR_HANDLE AliasHandle,
+ [in] PSAMPR_PSID_ARRAY MembersBuffer
+ );
+
+
+NTSTATUS
+SamrOemChangePasswordUser2(
+ [in] handle_t BindingHandle,
+ [in,unique] PRPC_STRING ServerName,
+ [in] PRPC_STRING UserName,
+ [in,unique] PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm,
+ [in,unique] PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPassswordEncryptedWithNewLm
+ );
+
+NTSTATUS
+SamrUnicodeChangePasswordUser2(
+ [in] handle_t BindingHandle,
+ [in,unique] PRPC_UNICODE_STRING ServerName,
+ [in] PRPC_UNICODE_STRING UserName,
+ [in,unique] PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldNt,
+ [in,unique] PENCRYPTED_NT_OWF_PASSWORD OldNtOwfPasswordEncryptedWithNewNt,
+ [in] BOOLEAN LmPresent,
+ [in,unique] PSAMPR_ENCRYPTED_USER_PASSWORD NewPasswordEncryptedWithOldLm,
+ [in,unique] PENCRYPTED_LM_OWF_PASSWORD OldLmOwfPassswordEncryptedWithNewLmOrNt
+ );
+
+NTSTATUS
+SamrGetDomainPasswordInformation (
+ [in] handle_t BindingHandle,
+ [in,unique] PRPC_UNICODE_STRING ServerName,
+ [out] PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
+ );
+
+NTSTATUS
+SamrConnect2(
+ [in,unique,string] PSAMPR_SERVER_NAME ServerName,
+ [out] SAMPR_HANDLE * ServerHandle,
+ [in] ACCESS_MASK DesiredAccess
+ );
+
+
+NTSTATUS
+SamrSetInformationUser2(
+ [in] SAMPR_HANDLE UserHandle,
+ [in] USER_INFORMATION_CLASS UserInformationClass,
+ [in, switch_is(UserInformationClass)]
+ PSAMPR_USER_INFO_BUFFER Buffer
+ );
+
+}
+
+
diff --git a/private/newsam/samsrv.acf b/private/newsam/samsrv.acf
new file mode 100644
index 000000000..2da84a2ec
--- /dev/null
+++ b/private/newsam/samsrv.acf
@@ -0,0 +1,64 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ samsrv.acf
+
+Abstract:
+
+ Security Account Manager SERVER rpc stub attribute configuration file.
+
+ This file contains the attribute configuration information necessary
+ for generating the server stubs for remotable SAM functions. The
+ definitions in this file qualify the information in samrpc.idl.
+
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! !!
+ !! This .acf file is USED ONLY WHEN GENERATING SAM SERVER STUBS. !!
+ !! !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+ Use samcli.acf when generating client stubs.
+
+
+
+
+ The server likes to have all passed data in a single block of
+ allocated memory. This allows it to free the returned information
+ with a single call, rather than walking down some random tree of
+ allocated blocks.
+
+
+
+Author:
+
+ Jim Kelly (JimK) July 3, 1991
+
+Environment:
+
+ User Mode
+
+Revision History:
+
+--*/
+
+
+
+[ implicit_handle( handle_t samsrv_handle) ]
+interface samr
+
+{
+
+
+//
+// define complex [in] parameters to be [allocate(all_nodes)]...
+//
+
+//typedef [allocate(all_nodes)] PSAMPR_PASSED_NAMES;
+
+typedef [allocate(all_nodes)] PSAMPR_SID_INFORMATION;
+
+}
diff --git a/private/newsam/server/alias.c b/private/newsam/server/alias.c
new file mode 100644
index 000000000..c56ce4687
--- /dev/null
+++ b/private/newsam/server/alias.c
@@ -0,0 +1,3975 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ alias.c
+
+Abstract:
+
+ This file contains services related to the SAM "alias" object.
+
+
+Author:
+
+ Chad Schwitters (chads) 15-Jan-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+#include <samsrvp.h>
+#include <msaudite.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampAddAccountToAlias(
+ IN PSAMP_OBJECT AccountContext,
+ IN PSID AccountSid
+ );
+
+NTSTATUS
+SampRemoveAccountFromAlias(
+ IN PSAMP_OBJECT AccountContext,
+ IN PSID AccountSid
+ );
+
+NTSTATUS
+SampAddAliasToAccountMembership(
+ IN ULONG AliasRid,
+ IN PSID AccountSid
+ );
+
+NTSTATUS
+SampRemoveAliasFromAccountMembership(
+ IN ULONG AliasRid,
+ IN PSID AccountSid
+ );
+
+NTSTATUS
+SampRemoveAliasFromAllAccounts(
+ IN PSAMP_OBJECT AliasContext
+ );
+
+NTSTATUS
+SampDeleteAliasKeys(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampRetrieveAliasMembers(
+ IN PSAMP_OBJECT AliasContext,
+ IN PULONG MemberCount,
+ IN PSID **Members OPTIONAL
+ );
+
+NTSTATUS
+SampDeleteAliasMembershipKeysForAccount(
+ IN PSID AccountSid
+ );
+
+NTSTATUS
+SampAdjustAliasDomainsCount(
+ IN BOOLEAN Increment
+ );
+
+NTSTATUS
+SampValidateNewAliasMember(
+ IN PSID MemberId
+ );
+
+NTSTATUS
+SampChangeAliasAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Exposed RPC'able Services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SamrOpenAlias(
+ IN SAM_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG AliasId,
+ OUT PSAM_HANDLE AliasHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API opens an existing Alias object. The Alias is specified by
+ a ID value that is relative to the SID of the domain. The operations
+ that will be performed on the Alias must be declared at this time.
+
+ This call returns a handle to the newly opened Alias that may be used
+ for successive operations on the Alias. This handle may be closed
+ with the SamCloseHandle API.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types are
+ desired to the alias.
+
+ AliasId - Specifies the relative ID value of the Alias to be opened.
+
+ AliasHandle - Receives a handle referencing the newly opened Alias.
+ This handle will be required in successive calls to operate on
+ the Alias.
+
+Return Values:
+
+ STATUS_SUCCESS - The Alias was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate access
+ to complete the operation.
+
+ STATUS_NO_SUCH_ALIAS - The specified Alias does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ NtStatus = SampOpenAccount(
+ SampAliasObjectType,
+ DomainHandle,
+ DesiredAccess,
+ AliasId,
+ FALSE,
+ AliasHandle
+ );
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrQueryInformationAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ALIAS_INFORMATION_CLASS AliasInformationClass,
+ OUT PSAMPR_ALIAS_INFO_BUFFER *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API retrieves information on the alias specified.
+
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ AliasInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ----------------------- ----------------------
+
+ AliasGeneralInformation ALIAS_READ_INFORMATION
+ AliasNameInformation ALIAS_READ_INFORMATION
+ AliasAdminInformation ALIAS_READ_INFORMATION
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this
+ buffer and any memory pointed to through this buffer must be
+ freed 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 handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ ULONG i;
+
+ //
+ // Used for tracking allocated blocks of memory - so we can deallocate
+ // them in case of error. Don't exceed this number of allocated buffers.
+ // ||
+ // vv
+ PVOID AllocatedBuffer[10];
+ ULONG AllocatedBufferCount = 0;
+
+ #define RegisterBuffer(Buffer) \
+ { \
+ if ((Buffer) != NULL) { \
+ \
+ ASSERT(AllocatedBufferCount < \
+ sizeof(AllocatedBuffer) / sizeof(*AllocatedBuffer)); \
+ \
+ AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
+ } \
+ }
+
+ #define AllocateBuffer(NewBuffer, Size) \
+ { \
+ (NewBuffer) = MIDL_user_allocate(Size); \
+ RegisterBuffer(NewBuffer); \
+ } \
+
+
+
+ //
+ // 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 (AliasInformationClass) {
+
+ case AliasGeneralInformation:
+ case AliasNameInformation:
+ case AliasAdminCommentInformation:
+
+ DesiredAccess = ALIAS_READ_INFORMATION;
+ break;
+
+ default:
+ (*Buffer) = NULL;
+ return(STATUS_INVALID_INFO_CLASS);
+ } // end_switch
+
+
+
+ //
+ // Allocate the info structure
+ //
+
+ AllocateBuffer( *Buffer, sizeof(SAMPR_ALIAS_INFO_BUFFER) );
+ if ((*Buffer) == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)AliasHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // case on the type information requested
+ //
+
+ switch (AliasInformationClass) {
+
+ case AliasGeneralInformation:
+
+ //
+ // Get the member count
+ //
+
+ NtStatus = SampRetrieveAliasMembers(
+ AccountContext,
+ &(*Buffer)->General.MemberCount,
+ NULL // Only need members
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.Name)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.Name.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.AdminComment)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->General.AdminComment.Buffer);
+ }
+ }
+ }
+
+
+ break;
+
+
+ case AliasNameInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Name.Name)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->Name.Name.Buffer);
+ }
+
+ break;
+
+
+ case AliasAdminCommentInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->AdminComment.AdminComment)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->AdminComment.AdminComment.Buffer);
+ }
+
+
+ break;
+
+ } // end_switch
+
+
+ //
+ // De-reference the object, discard any changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // 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
+SamrSetInformationAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ALIAS_INFORMATION_CLASS AliasInformationClass,
+ IN PSAMPR_ALIAS_INFO_BUFFER Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API allows the caller to modify alias information.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ AliasInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ------------------------ -------------------------
+
+ AliasGeneralInformation (can't write)
+
+ AliasNameInformation ALIAS_WRITE_ACCOUNT
+ AliasAdminCommentInformation ALIAS_WRITE_ACCOUNT
+
+ Buffer - Buffer where information retrieved is placed.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_ALIAS - The alias specified is unknown.
+
+ STATUS_SPECIAL_ALIAS - The alias specified is a special alias and
+ cannot be operated on in the requested fashion.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS TmpStatus;
+ NTSTATUS IgnoreStatus;
+
+ PSAMP_OBJECT AccountContext;
+
+ SAMP_OBJECT_TYPE FoundType;
+
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ ACCESS_MASK DesiredAccess;
+
+ UNICODE_STRING OldAccountName;
+
+ ULONG AliasRid,
+ DomainIndex;
+
+ BOOLEAN Modified = FALSE;
+
+
+ OldAccountName.Buffer = NULL;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ if (Buffer == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+
+ //
+ // Set the desired access based upon the Info class
+ //
+
+ switch (AliasInformationClass) {
+
+ case AliasNameInformation:
+ case AliasAdminCommentInformation:
+
+ DesiredAccess = ALIAS_WRITE_ACCOUNT;
+ break;
+
+
+ case AliasGeneralInformation:
+ default:
+
+ return(STATUS_INVALID_INFO_CLASS);
+
+ } // end_switch
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)AliasHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // case on the type information requested
+ //
+
+ switch (AliasInformationClass) {
+
+ case AliasNameInformation:
+
+ NtStatus = SampChangeAliasAccountName(
+ AccountContext,
+ (PUNICODE_STRING)&(Buffer->Name.Name),
+ &OldAccountName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ OldAccountName.Buffer = NULL;
+ }
+
+ //
+ // Don't delete the old account name yet; we'll still need
+ // to pass it to Netlogon below.
+ //
+
+ break;
+
+
+ case AliasAdminCommentInformation:
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_ADMIN_COMMENT,
+ (PUNICODE_STRING)&(Buffer->AdminComment.AdminComment)
+ );
+
+ break;
+
+
+ } // end_switch
+
+
+ //
+ // Generate an audit if necessary
+ //
+
+ if ((NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(DomainIndex))) {
+
+ UNICODE_STRING
+ AccountName;
+
+ IgnoreStatus = SampGetUnicodeStringAttribute(
+ AccountContext, // Context
+ SAMP_ALIAS_NAME, // AttributeIndex
+ FALSE, // MakeCopy
+ &AccountName // UnicodeAttribute
+ );
+ if (NT_SUCCESS(IgnoreStatus)) {
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_LOCAL_GROUP_CHANGE, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &AccountContext->TypeBody.Alias.Rid, // Account Rid
+ NULL // Privileges used
+ );
+ }
+
+ }
+
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Save object RID before dereferencing context.
+ // RID is used in SampNotifyNetlogonOfDelta() call.
+ //
+
+ AliasRid = AccountContext->TypeBody.Alias.Rid;
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+ } //end_if
+
+ //
+ // Commit the transaction and notify netlogon of any changes
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ if ( AliasInformationClass == AliasNameInformation ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbRename,
+ SecurityDbObjectSamAlias,
+ AliasRid,
+ &OldAccountName,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ } else {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChange,
+ SecurityDbObjectSamAlias,
+ AliasRid,
+ NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+ }
+
+
+ //
+ // Free up our old account name if we have one
+ //
+
+ SampFreeUnicodeString( &OldAccountName );
+
+
+ //
+ // Now release the write lock and return, propogating any errors.
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SamrDeleteAlias(
+ IN SAM_HANDLE *AliasHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API deletes an Alias from the account database. The Alias does
+ not have to be empty.
+
+ Note that following this call, the AliasHandle is no longer valid.
+
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+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_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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+
+--*/
+{
+ UNICODE_STRING AliasName;
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG AliasRid,
+ DomainIndex;
+
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(*AliasHandle);
+
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DELETE,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ AliasRid = AccountContext->TypeBody.Alias.Rid;
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // Make sure the account is one that can be deleted.
+ // Can't be a built-in account, unless caller is trusted.
+ //
+
+ if ( !AccountContext->TrustedClient ) {
+
+ NtStatus = SampIsAccountBuiltIn( AliasRid );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove this alias from every account's alias-membership list
+ //
+
+ NtStatus = SampRemoveAliasFromAllAccounts(AccountContext);
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // First get and save the account name for
+ // I_NetNotifyLogonOfDelta.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ &AliasName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // This must be done before we invalidate contexts, because our
+ // own handle to the alias gets closed as well.
+ //
+
+ NtStatus = SampDeleteAliasKeys( AccountContext );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We must invalidate any open contexts to this alias
+ // This will close all handles to the alias's keys.
+ // THIS IS AN IRREVERSIBLE PROCESS.
+ //
+
+ SampInvalidateAliasContexts( AliasRid );
+
+ //
+ // Commit the whole mess
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Update the Alias Information Cache
+ //
+
+ IgnoreStatus = SampAlDeleteAlias( AliasHandle );
+
+ //
+ // Audit the deletion before we free the write lock
+ // so that we have access to the context block.
+ //
+
+ if (SampDoAccountAuditing(DomainIndex) &&
+ NT_SUCCESS(NtStatus) ) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_LOCAL_GROUP_DELETED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member sid (not used)
+ &AliasName, // Account Name
+ &Domain->ExternalName, // Domain
+ &AliasRid, // Account Rid
+ NULL // Privileges used
+ );
+
+ }
+
+ //
+ // Notify netlogon of the change
+ //
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbDelete,
+ SecurityDbObjectSamAlias,
+ AliasRid,
+ &AliasName,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ //
+ // Do delete auditing
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ (VOID) NtDeleteObjectAuditAlarm(
+ &SampSamSubsystem,
+ *AliasHandle,
+ AccountContext->AuditOnClose
+ );
+ }
+
+
+ }
+ }
+
+ SampFreeUnicodeString( &AliasName );
+ }
+ }
+ }
+
+
+
+ //
+ // De-reference the object, discard any changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we actually deleted the alias, then delete the context
+ // and let RPC know that the handle is invalid.
+ //
+
+ SampDeleteContext( AccountContext );
+
+ (*AliasHandle) = NULL;
+ }
+
+ } //end_if
+
+ //
+ // Free the lock -
+ //
+ // Everything has already been committed above, so we must indicate
+ // no additional changes have taken place.
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+ if (NtStatus == STATUS_SUCCESS) {
+ NtStatus = TmpStatus;
+ }
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrAddMemberToAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN PRPC_SID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This API adds a member to an alias. Note that this API requires the
+ ALIAS_ADD_MEMBER access type for the alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ MemberId - SID of the member to add.
+
+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_NO_SUCH_MEMBER - The member specified is unknown.
+
+ STATUS_MEMBER_IN_ALIAS - The member already belongs to the alias.
+
+ STATUS_INVALID_MEMBER - The member has the wrong account type.
+
+ STATUS_INVALID_SID - The member sid is corrupted.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+{
+
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG ObjectRid;
+ SAMP_MEMBERSHIP_DELTA AdminChange = NoChange;
+ SAMP_MEMBERSHIP_DELTA OperatorChange = NoChange;
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(AliasHandle);
+ NtStatus = SampLookupContext(
+ AccountContext,
+ ALIAS_ADD_MEMBER,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check the potential new member is OK
+ //
+
+ NtStatus = SampValidateNewAliasMember(MemberId);
+
+ //
+ // If the member is being added to an ADMIN alias, we must make
+ // sure the member ACL(s) don't allow access by account operators.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+ if ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_ADMINS ) {
+
+ AdminChange = AddToAdmin;
+
+ } else if ( ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_SYSTEM_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_PRINT_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_BACKUP_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_ACCOUNT_OPS ) ) {
+
+ OperatorChange = AddToAdmin;
+ }
+
+ //
+ // If either of these are changing, change account operator
+ // access to this member
+ //
+
+ if ( ( OperatorChange != NoChange ) ||
+ ( AdminChange != NoChange ) ) {
+
+ NtStatus = SampChangeAccountOperatorAccessToMember(
+ MemberId,
+ AdminChange,
+ OperatorChange
+ );
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Perform the user object side of things
+ //
+
+ NtStatus = SampAddAliasToAccountMembership(
+ AccountContext->TypeBody.Alias.Rid,
+ MemberId
+ );
+
+
+ //
+ // Now perform the alias side of things
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the user to the alias (should not fail)
+ //
+
+ NtStatus = SampAddAccountToAlias(
+ AccountContext,
+ MemberId
+ );
+ }
+ }
+
+
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Save object RID before dereferencing context.
+ // RID is used in SampNotifyNetlogonOfDelta() call.
+ //
+
+ ObjectRid = AccountContext->TypeBody.Alias.Rid;
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+
+
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Commit the whole mess
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAM_DELTA_DATA DeltaData;
+
+ //
+ // Update the Alias Information Cache
+ //
+
+ SAMPR_PSID_ARRAY MemberSids;
+ MemberSids.Count = 1;
+ MemberSids.Sids = (PSAMPR_SID_INFORMATION) &MemberId;
+
+ IgnoreStatus = SampAlAddMembersToAlias(
+ AliasHandle,
+ 0,
+ &MemberSids
+ );
+
+
+ //
+ // Fill in id of member being added
+ //
+
+ DeltaData.AliasMemberId.MemberSid = MemberId;
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangeMemberAdd,
+ SecurityDbObjectSamAlias,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ &DeltaData
+ );
+ }
+ }
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrAddMultipleMembersToAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN PSAMPR_PSID_ARRAY MembersBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This api adds multiple members to an alias.
+
+ NOTE: For now, this routine takes a brute force approach.
+ I tried to do it in a better (more efficient) manner,
+ but kept running into problems. Finally, when I ran
+ into problems in the way SAM uses RXACT, I gave up
+ and did this brute force approach.
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+ MembersBuffer - Contains a count of SIDs to be added to the
+ alias and a pointer to a buffer containing an array of
+ pointers to SIDs. These SIDs are the SIDs of the members to
+ be added to the Alias.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully. All of the
+ listed members are now members of the alias. However, some of
+ the members may already have been members of the alias (this is
+ NOT an error or warning condition).
+
+ STATUS_ACCESS_DENIED - Caller does not have the object open for
+ the required access.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_MEMBER - The member has the wrong account type.
+
+ STATUS_INVALID_SID - The member sid is corrupted.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+{
+
+ NTSTATUS
+ NtStatus;
+
+ LONG
+ MemberCount,
+ i;
+
+ PSID
+ *MemberId;
+
+ MemberCount = (LONG)MembersBuffer->Count;
+ MemberId = (PSID *)MembersBuffer->Sids;
+
+
+ //
+ // Set completion status in case there are no members
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+
+ //
+ // Loop through the SIDs, adding them to the alias.
+ // Ignore any status value indicating the member is already
+ // a member. Other errors, however, will cause us to abort.
+ //
+
+ for (i=0; i<MemberCount; i++) {
+
+ NtStatus = SamrAddMemberToAlias( AliasHandle, MemberId[i] );
+
+ if (NtStatus == STATUS_MEMBER_IN_ALIAS) {
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break; //for loop
+ }
+
+ } //end_for
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrRemoveMemberFromAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN PRPC_SID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This API removes a member from an alias. Note that this API requires the
+ ALIAS_REMOVE_MEMBER access type for the alias.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ MemberId - SID of the member to remove.
+
+Return Value:
+
+
+ ????
+
+
+--*/
+{
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG ObjectRid;
+ SAMP_MEMBERSHIP_DELTA AdminChange = NoChange;
+ SAMP_MEMBERSHIP_DELTA OperatorChange = NoChange;
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(AliasHandle);
+ NtStatus = SampLookupContext(
+ AccountContext,
+ ALIAS_REMOVE_MEMBER,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Validate the sid of the member.
+ //
+
+ if ((MemberId == NULL) || !RtlValidSid(MemberId)) {
+ NtStatus = STATUS_INVALID_SID;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Perform the user object side of things
+ //
+
+ NtStatus = SampRemoveAliasFromAccountMembership(
+ AccountContext->TypeBody.Alias.Rid,
+ (PSID)MemberId
+ );
+
+
+
+ //
+ // Now perform the alias side of things
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove the user from the alias (should not fail)
+ //
+
+ NtStatus = SampRemoveAccountFromAlias(
+ AccountContext,
+ (PSID)MemberId
+ );
+
+ //
+ // If the member is being removed from an ADMIN alias, we must make
+ // sure the member ACL(s) allow access by account operators.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+ if ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_ADMINS ) {
+
+ AdminChange = RemoveFromAdmin;
+
+ } else if ( ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_SYSTEM_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_PRINT_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_BACKUP_OPS ) ||
+ ( AccountContext->TypeBody.Alias.Rid == DOMAIN_ALIAS_RID_ACCOUNT_OPS ) ) {
+
+ OperatorChange = RemoveFromAdmin;
+ }
+
+ //
+ // If either of these are changing, change account operator
+ // access to this member
+ //
+
+ if ( ( OperatorChange != NoChange ) ||
+ ( AdminChange != NoChange ) ) {
+
+ NtStatus = SampChangeAccountOperatorAccessToMember(
+ MemberId,
+ AdminChange,
+ OperatorChange
+ );
+ }
+
+ }
+ }
+ }
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Save object RID before dereferencing context.
+ // RID is used in SampNotifyNetlogonOfDelta() call.
+ //
+
+ ObjectRid = AccountContext->TypeBody.Alias.Rid;
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAM_DELTA_DATA DeltaData;
+
+ //
+ // Update the Alias Information Cache
+ //
+
+ SAMPR_PSID_ARRAY MemberSids;
+ MemberSids.Count = 1;
+ MemberSids.Sids = (PSAMPR_SID_INFORMATION) &MemberId;
+
+ IgnoreStatus = SampAlRemoveMembersFromAlias(
+ AliasHandle,
+ 0,
+ &MemberSids
+ );
+
+
+ //
+ // Fill in id of member being deleted
+ //
+
+ DeltaData.AliasMemberId.MemberSid = MemberId;
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangeMemberDel,
+ SecurityDbObjectSamAlias,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ &DeltaData
+ );
+
+ }
+ }
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrRemoveMultipleMembersFromAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN PSAMPR_PSID_ARRAY MembersBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API removes members from an alias. Note that this API requires
+ the ALIAS_REMOVE_MEMBER access type for the alias.
+
+ NOTE: This api currently uses a brute-force approach to adding
+ members to the alias. This is because of problems
+ encountered when trying to do "the right thing".
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ MembersBuffer - Contains a count of SIDs to be added to the
+ alias and a pointer to a buffer containing an array of
+ pointers to SIDs. These SIDs are the SIDs of the members to
+ be added to the Alias.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully. All of the
+ listed members are now members of the alias. However, some of
+ the members may already have been members of the alias (this is
+ NOT an error or warning condition).
+
+ STATUS_ACCESS_DENIED - Caller does not have the object open for
+ the required access.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_SID - The member sid is corrupted.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+
+ NTSTATUS
+ NtStatus;
+
+ LONG
+ MemberCount,
+ i;
+
+ PSID
+ *MemberId;
+
+ MemberCount = (LONG)MembersBuffer->Count;
+ MemberId = (PSID *)MembersBuffer->Sids;
+
+
+ //
+ // Set completion status in case there are no members
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+
+ //
+ // Loop through the SIDs, adding them to the alias.
+ // Ignore any status value indicating the member is already
+ // a member. Other errors, however, will cause us to abort.
+ //
+
+ for (i=0; i<MemberCount; i++) {
+
+ NtStatus = SamrAddMemberToAlias( AliasHandle, MemberId[i] );
+
+ if (NtStatus == STATUS_MEMBER_NOT_IN_ALIAS) {
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break; //for loop
+ }
+
+ } //end_for
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrGetMembersInAlias(
+ IN SAM_HANDLE AliasHandle,
+ OUT PSAMPR_PSID_ARRAY GetMembersBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all members in an Alias. This API requires
+ ALIAS_LIST_MEMBERS access to the Alias.
+
+ NOTE: This function does not use the Alias cache.
+
+
+Parameters:
+
+ AliasHandle - The handle of an opened Alias to operate on.
+
+ MemberIds - Receives a pointer to a buffer containing an array of
+ pointers to SIDs. These SIDs are the SIDs of the members of the
+ Alias. When this information is no longer needed, this buffer
+ must be freed using SamFreeMemory().
+
+ MemberCount - number of members in the Alias (and, thus, the number
+ of relative IDs returned).
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there are
+ no additional entries.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (GetMembersBuffer != NULL);
+
+ //
+ // Grab the lock
+ //
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)AliasHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ ALIAS_LIST_MEMBERS,
+ SampAliasObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveAliasMembers(
+ AccountContext,
+ &(GetMembersBuffer->Count),
+ (PSID **)&(GetMembersBuffer->Sids)
+ );
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ //
+ // Tidy up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)){
+
+ GetMembersBuffer->Count = 0;
+ GetMembersBuffer->Sids = NULL;
+ }
+
+ return(NtStatus);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Internal Services Available For Use in Other SAM Modules //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampRemoveAccountFromAllAliases(
+ IN PSID AccountSid,
+ IN BOOLEAN CheckAccess,
+ IN SAMPR_HANDLE DomainHandle OPTIONAL,
+ IN PULONG MembershipCount OPTIONAL,
+ IN PULONG *Membership OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes the specified account from the member list of all
+ aliases in this domain.
+
+
+ The caller of this service is expected to be in the middle of a
+ RXACT transaction. This service simply adds some actions to that
+ RXACT transaction.
+
+
+Arguments:
+
+ AccountSid - The SID of the account being Removed.
+
+ CheckAccess - if TRUE, this routine will make sure that the caller
+ is allowed REMOVE_ALIAS_MEMBER access to this alias. If FALSE,
+ the caller is already known to have proper access.
+
+ DomainHandle - if CheckAccess is TRUE, this handle must be provided
+ to allow access to be checked.
+
+ MembershipCount - if CheckAccess is TRUE, this pointer must be
+ provided to receive the number of aliases the account was
+ deleted from.
+
+ Membership - if CheckAccess is TRUE, this pointer must be provided
+ to point to a list of aliases the account was removed from. The
+ caller must free this list with MIDL_user_free().
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user has been Removed from all aliases.
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ UNICODE_STRING DomainKeyName, AccountKeyName;
+ HANDLE TempHandle, AliasHandle;
+ ULONG LocalMembershipCount;
+ PULONG LocalMembership;
+ ULONG KeyValueLength;
+ ULONG i;
+ PSAMP_OBJECT AliasContext;
+
+
+ //
+ // Get the alias membership for this account
+ //
+
+ NtStatus = SampBuildAliasMembersKeyName(
+ AccountSid,
+ &DomainKeyName,
+ &AccountKeyName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &AccountKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if ((NtStatus == STATUS_OBJECT_PATH_NOT_FOUND) ||
+ (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) ) {
+
+ //
+ // This account is not a member of any of our aliases
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+ if ( CheckAccess ) {
+
+ //
+ // Return the list of aliases the account was
+ // removed from; in this case, none.
+ //
+
+ ( *MembershipCount ) = 0;
+ ( *Membership ) = NULL;
+ }
+
+ } else {
+
+ //
+ // Load in the alias membership list
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ KeyValueLength = 0;
+
+ NtStatus = RtlpNtQueryValueKey( TempHandle,
+ &LocalMembershipCount,
+ NULL,
+ &KeyValueLength,
+ NULL);
+ if (NT_SUCCESS(NtStatus)) {
+ ASSERT(LocalMembershipCount == 0);
+ }
+
+ if (NtStatus == STATUS_BUFFER_OVERFLOW) {
+
+ LocalMembership = MIDL_user_allocate( KeyValueLength );
+
+ if (LocalMembership == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ NULL,
+ LocalMembership,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove the account from each alias
+ //
+
+ for (i=0; i < LocalMembershipCount; i++) {
+
+ if ( CheckAccess ) {
+
+ //
+ // If account is being removed from
+ // the ADMIN alias, change ACL to
+ // allow account operators to access
+ // the account (unless account is an
+ // admin some other way). Kind of
+ // useless since the account is about
+ // to be deleted, but do it anyway
+ // in case something bad happens and
+ // it doesn't get deleted.
+ //
+
+ //
+ // BUGBUG: this may not do it - we may
+ // need to check the admin count on
+ // the group. MMS 9/5/95
+ //
+
+ if ( LocalMembership[i] ==
+ DOMAIN_ALIAS_RID_ADMINS ) {
+
+ NtStatus = SampChangeAccountOperatorAccessToMember(
+ AccountSid,
+ RemoveFromAdmin,
+ NoChange );
+ }
+
+ //
+ // Just open and close the alias
+ // to make sure we are allowed
+ // the necessary access.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampOpenAccount(
+ SampAliasObjectType,
+ DomainHandle,
+ ALIAS_REMOVE_MEMBER,
+ LocalMembership[i],
+ TRUE,
+ (SAMPR_HANDLE *)&AliasHandle
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ SampDeleteContext(
+ (PSAMP_OBJECT)( AliasHandle ) );
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ NtStatus = SampCreateAccountContext(
+ SampAliasObjectType,
+ LocalMembership[i],
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AliasContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRemoveAccountFromAlias(
+ AliasContext,
+ AccountSid );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Save the alias changes we just
+ // made. We'll delete the context,
+ // so don't let RXACT use the open
+ // key handle in the context.
+ //
+
+ NtStatus = SampStoreObjectAttributes(
+ AliasContext,
+ FALSE
+ );
+ }
+
+ SampDeleteContext(AliasContext);
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+ }
+
+ //
+ // Delete the account membership keys
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampDeleteAliasMembershipKeysForAccount(
+ AccountSid);
+ }
+
+ }
+
+ if ( CheckAccess ) {
+
+ //
+ // Return the list of aliases the account was
+ // removed from.
+ //
+
+ ( *MembershipCount ) = LocalMembershipCount;
+ ( *Membership ) = LocalMembership;
+
+ } else {
+
+ MIDL_user_free(LocalMembership);
+ }
+ }
+ }
+
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+ }
+
+ SampFreeUnicodeString( &DomainKeyName );
+ SampFreeUnicodeString( &AccountKeyName );
+
+ }
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampRetrieveAliasMembership(
+ IN PSID Account,
+ OUT PULONG MemberCount OPTIONAL,
+ IN OUT PULONG BufferSize OPTIONAL,
+ OUT PULONG Buffer OPTIONAL
+ )
+
+/*++
+Routine Description:
+
+ This service retrieves the number of aliases in the current domain
+ that the specified account is a member of. If desired it will also fill
+ in a buffer with the alias rids.
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+Arguments:
+
+ Account - the account whose membership we are interested in.
+
+ MemberCount - Receives the number of current-domain-aliases the
+ account is a member of.
+
+ BufferSize - (Optional) Specified the size of memory pointer to by buffer.
+
+ Buffer - (Otional) Is filled in with the list of alias membership rids.
+ If this value is NULL, then this information
+ is not returned. The returned buffer is allocated using
+ MIDL_user_allocate() and must be freed using MIDL_user_free() when
+ no longer needed.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ string to be returned in.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ UNICODE_STRING DomainKeyName, AccountKeyName;
+ HANDLE TempHandle;
+
+
+ //
+ // Get the membership count for this account
+ //
+
+ NtStatus = SampBuildAliasMembersKeyName(
+ Account,
+ &DomainKeyName,
+ &AccountKeyName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &AccountKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if ((NtStatus == STATUS_OBJECT_PATH_NOT_FOUND) ||
+ (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) ) {
+
+ //
+ // This account is not a member of any of our aliases
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+ if (ARGUMENT_PRESENT(MemberCount)) {
+ *MemberCount = 0;
+ }
+ if (ARGUMENT_PRESENT(BufferSize)) {
+ *BufferSize = 0;
+ }
+
+ } else {
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlpNtQueryValueKey( TempHandle,
+ MemberCount,
+ Buffer,
+ BufferSize,
+ NULL);
+
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+ }
+
+ SampFreeUnicodeString( &DomainKeyName );
+ SampFreeUnicodeString( &AccountKeyName );
+
+ }
+
+ return( NtStatus );
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Private to this file //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SampAddAccountToAlias(
+ IN PSAMP_OBJECT AccountContext,
+ IN PSID AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This service is used to add an account as a member of a specified alias
+ This is done by simply adding the account SID to the list of SIDs
+ in the MEMBERS attribute of the the specified alias
+
+
+ The caller of this service is expected to be in the middle of a
+ RXACT transaction. This service simply edits the in-memory copy of
+ the alias information.
+
+
+Arguments:
+
+ AliasRid - The RID of the alias the account is to be made a member of.
+
+ AccountSid - The Sid of the account being added as a new member.
+
+Return Value:
+
+ STATUS_SUCCESS - The account was added.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ ULONG MemberCount, i;
+ ULONG MemberArraySize;
+ PSID MemberArray;
+
+
+ NtStatus = SampGetSidArrayAttribute(
+ AccountContext,
+ SAMP_ALIAS_MEMBERS,
+ FALSE, // Reference directly
+ &MemberArray,
+ &MemberArraySize,
+ &MemberCount
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSID MemberPointer = MemberArray;
+
+ //
+ // Check the member is really new
+ //
+
+ for (i = 0; i<MemberCount ; i++ ) {
+
+ if (RtlEqualSid(MemberPointer, AccountSid)) {
+
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+
+ ((PCHAR)MemberPointer) += RtlLengthSid(MemberPointer);
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // MemberPointer now points at the byte beyond the end of the
+ // old member array
+ //
+
+ //
+ // Allocate a new membership buffer large enough for the existing
+ // member list and the new one.
+ //
+
+ ULONG OldTotalSize = ((PCHAR)MemberPointer) - ((PCHAR)MemberArray);
+ ULONG NewMemberSize = RtlLengthSid(AccountSid);
+ ULONG NewTotalSize = OldTotalSize + NewMemberSize;
+ PSID NewMemberArray;
+
+
+ NewMemberArray = MIDL_user_allocate( NewTotalSize );
+
+ if (NewMemberArray == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Copy the member list into the new array
+ //
+
+ RtlCopyMemory(NewMemberArray, MemberArray, OldTotalSize);
+
+ //
+ // Add the new member to the end
+ //
+
+ MemberCount += 1;
+
+ NtStatus = RtlCopySid(
+ NewMemberSize,
+ ((PCHAR)NewMemberArray) + OldTotalSize,
+ AccountSid);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Update the alias with it's new member list
+ //
+
+ NtStatus = SampSetSidArrayAttribute(
+ AccountContext,
+ SAMP_ALIAS_MEMBERS,
+ NewMemberArray,
+ NewTotalSize,
+ MemberCount
+ );
+
+ //
+ // audit this, if necessary.
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(AccountContext->DomainIndex)) {
+
+ UNICODE_STRING NameString;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE ObjectType;
+ NTSTATUS Status;
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ Status = SampLookupAccountName(
+ AccountContext->TypeBody.Alias.Rid,
+ &NameString,
+ &ObjectType
+ );
+
+ if ( !NT_SUCCESS( Status )) {
+ RtlInitUnicodeString( &NameString, L"-" );
+ }
+
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_LOCAL_GROUP_ADD, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid
+ AccountSid, // Member sid
+ &NameString, // Account Name
+ &Domain->ExternalName, // Domain
+ &AccountContext->TypeBody.Alias.Rid, // Account Rid
+ NULL // Privileges used
+ );
+
+ if ( NT_SUCCESS( Status )) {
+ MIDL_user_free( NameString.Buffer );
+ }
+ }
+ }
+
+ //
+ // Free up the membership array we allocated
+ //
+
+ MIDL_user_free( NewMemberArray );
+ }
+ }
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampRemoveAccountFromAlias(
+ IN PSAMP_OBJECT AccountContext,
+ IN PSID AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to Remove an account from a specified alias.
+ This is done by simply Removing the user's Sid From the list of Sids
+ in the MEMBERS sub-key of the the specified alias.
+
+ It is the caller's responsibility to know that the user is, in fact,
+ currently a member of the alias.
+
+
+ The caller of this service is expected to be in the middle of a
+ RXACT transaction. This service simply adds some actions to that
+ RXACT transaction.
+
+
+Arguments:
+
+ AliasRid - The RID of the alias the account is to be removed from.
+
+ AccountSid - The SID of the account being Removed.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user has been Removed.
+
+ STATUS_MEMBER_NOT_IN_ALIAS - The account was not a member of the alias.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG MemberCount, i;
+ ULONG MemberArraySize;
+ PSID MemberArray, Member, NextMember;
+
+ ULONG RemovedMemberSize = RtlLengthSid(AccountSid);
+
+ //
+ // Get a copy of the current member array.
+ //
+
+ NtStatus = SampGetSidArrayAttribute(
+ AccountContext,
+ SAMP_ALIAS_MEMBERS,
+ TRUE, // Make copy
+ &MemberArray,
+ &MemberArraySize,
+ &MemberCount
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // For each member sid, copy it from old to new member
+ // arrays if it is not the sid we're trying to delete
+ //
+
+ Member = MemberArray;
+
+ for (i = 0; i < MemberCount ; i++ ) {
+
+ NextMember = (PSID)(((PCHAR)Member) + RtlLengthSid(Member));
+
+ if (RtlEqualSid(Member, AccountSid)) {
+
+ //
+ // Found the member to delete. Shift subsequent members
+ //
+
+ while ((PCHAR)NextMember <
+ (((PCHAR)MemberArray) + MemberArraySize)) {
+
+ *((PCHAR)Member)++ = *((PCHAR)NextMember)++;
+ }
+
+ break;
+ }
+
+ //
+ // Advance the old pointer
+ //
+
+ Member = NextMember;
+
+ ASSERT((PCHAR)Member <= (((PCHAR)MemberArray) + MemberArraySize));
+ }
+
+
+ //
+ // If nothing was removed, we didn't find the account
+ //
+
+ if (i == MemberCount) {
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+
+ } else {
+
+ //
+ // The member has been removed, write out the new member list
+ //
+
+ ASSERT((PCHAR)Member ==
+ (((PCHAR)MemberArray)) + MemberArraySize - RemovedMemberSize);
+
+ NtStatus = SampSetSidArrayAttribute(
+ AccountContext,
+ SAMP_ALIAS_MEMBERS,
+ MemberArray,
+ MemberArraySize - RemovedMemberSize,
+ MemberCount - 1
+ );
+
+ //
+ // audit this, if necessary.
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(AccountContext->DomainIndex)) {
+
+ UNICODE_STRING NameString;
+ SAMP_OBJECT_TYPE ObjectType;
+ NTSTATUS Status;
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ Status = SampLookupAccountName(
+ AccountContext->TypeBody.Alias.Rid,
+ &NameString,
+ &ObjectType
+ );
+
+ if ( !NT_SUCCESS( Status )) {
+ RtlInitUnicodeString( &NameString, L"-" );
+ }
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_LOCAL_GROUP_REM, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid
+ AccountSid, // Member sid
+ &NameString, // Account Name
+ &Domain->ExternalName, // Domain
+ &AccountContext->TypeBody.Alias.Rid, // Account Rid
+ NULL // Privileges used
+ );
+
+ if ( NT_SUCCESS( Status )) {
+ MIDL_user_free( NameString.Buffer );
+ }
+ }
+ }
+
+ //
+ // Free up the member array
+ //
+
+ MIDL_user_free(MemberArray);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampAddAliasToAccountMembership(
+ IN ULONG AliasRid,
+ IN PSID AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This service adds the specified alias to the account's membership
+ list. It is not assumed that the caller knows anything about
+ the target account. In particular, the caller doesn't know whether
+ the account exists or not, nor whether the account is already a member
+ of the alias.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ AliasRid - The relative ID of the alias.
+
+ AccountSid - The SID of the account.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_MEMBER_IN_ALIAS - The account is already a member of the
+ specified alias.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING DomainKeyName;
+ UNICODE_STRING AccountKeyName;
+ HANDLE TempHandle;
+ ULONG MembershipCount, KeyValueLength;
+ ULONG DomainRidCount;
+ ULONG i;
+ PULONG MembershipArray;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ BOOLEAN NewAccount;
+
+ //
+ // Get the account membership
+ //
+
+ //
+ // Assume the account is a member of at least one of our aliases
+ //
+
+ NewAccount = FALSE;
+
+ NtStatus = SampBuildAliasMembersKeyName(
+ AccountSid,
+ &DomainKeyName,
+ &AccountKeyName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Try to open the domain alias/members/(domain) key for this account
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &DomainKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the current domain rid count
+ //
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &DomainRidCount,
+ NULL,
+ NULL,
+ NULL);
+
+ IgnoreStatus = NtClose(TempHandle);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+
+ if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ //
+ // No other accounts in this domain are members of any of our
+ // aliases.
+ //
+ // Create a new key for this domain with no accounts (rids).
+ //
+
+ NewAccount = TRUE;
+
+ DomainRidCount = 0; // No accounts yet
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &DomainKeyName,
+ DomainRidCount,
+ NULL,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Keep our domain count uptodate
+ //
+
+ NtStatus = SampAdjustAliasDomainsCount(TRUE);
+ }
+ }
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (!NewAccount) {
+
+ //
+ // Try to open the domain alias/members/(domain)/(account) key
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &AccountKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ }
+
+
+ if (NewAccount || (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND)) {
+
+ //
+ // This account is not a member of any of our aliases yet.
+ //
+
+ NewAccount = TRUE;
+
+ //
+ // Set up it's initial membership
+ //
+
+ MembershipCount = 1;
+ MembershipArray = &AliasRid;
+
+ NtStatus = STATUS_SUCCESS; // We're doing fine
+ }
+
+
+ if (NT_SUCCESS(NtStatus) && !NewAccount) {
+
+ //
+ // This account already exists
+ //
+ // Get the current membership buffer and add the new alias
+ //
+
+ KeyValueLength = 0;
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &MembershipCount,
+ NULL,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_BUFFER_OVERFLOW)) {
+
+ ASSERT(KeyValueLength == (MembershipCount * sizeof(ULONG)));
+
+ //
+ // Allocate a membership buffer large enough for an
+ // additional member.
+ //
+
+ KeyValueLength += sizeof(ULONG);
+ MembershipArray = MIDL_user_allocate( KeyValueLength );
+
+ if (MembershipArray == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ NULL,
+ MembershipArray,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // See if the account is already a member ...
+ //
+
+ for (i = 0; i<MembershipCount ; i++ ) {
+ if ( MembershipArray[i] == AliasRid ) {
+ NtStatus = STATUS_MEMBER_IN_ALIAS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the new alias's RID to the end
+ //
+
+ MembershipCount += 1;
+ MembershipArray[MembershipCount-1] = AliasRid;
+ }
+ }
+ }
+ }
+
+ //
+ // Close the account key handle
+ //
+
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ }
+
+ //
+ // We now have a new membership list desribed by :
+ // MembershipArray, MembershipCount
+ //
+ // Write it out and free it up
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ KeyValueLength = MembershipCount * sizeof(ULONG);
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &AccountKeyName,
+ MembershipCount,
+ MembershipArray,
+ KeyValueLength
+ );
+
+ if (MembershipArray != &AliasRid) {
+ MIDL_user_free( MembershipArray );
+ }
+ }
+
+ //
+ // If this is a new account, we need to increment the rid count
+ // in the account domain.
+ //
+
+ if (NewAccount) {
+
+ //
+ // Increment the domain rid count
+ //
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &DomainKeyName,
+ DomainRidCount + 1,
+ NULL,
+ 0
+ );
+ }
+
+ }
+
+ SampFreeUnicodeString( &DomainKeyName );
+ SampFreeUnicodeString( &AccountKeyName );
+
+ }
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampRemoveAliasFromAccountMembership(
+ IN ULONG AliasRid,
+ IN PSID AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This service removes the specified alias from the account's membership
+ list. It is not assumed that the caller knows anything about
+ the target account. In particular, the caller doesn't know whether
+ the account exists or not, nor whether the account is really a member
+ of the alias.
+
+ This routine removes the reference to the alias from the account's
+ membership list, removes the account key if there are no more aliases,
+ and removes the domain-sid key if this is the last account in the
+ domain.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ AliasRid - The relative ID of the alias.
+
+ AccountSid - The SID of the account.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_NO_SUCH_USER - The account does not exist.
+
+ STATUS_MEMBER_NOT_IN_ALIAS - The account is not a member of the
+ specified alias.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING DomainKeyName;
+ UNICODE_STRING AccountKeyName;
+ HANDLE TempHandle;
+ ULONG MembershipCount, KeyValueLength, i;
+ PULONG MembershipArray;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+ //
+ // Get the account membership
+ //
+
+ NtStatus = SampBuildAliasMembersKeyName(
+ AccountSid,
+ &DomainKeyName,
+ &AccountKeyName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &AccountKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND ||
+ NtStatus == STATUS_OBJECT_PATH_NOT_FOUND) {
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Retrieve the length of the membership buffer
+ //
+
+ KeyValueLength = 0;
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &MembershipCount,
+ NULL,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus)) {
+ ASSERT(MembershipCount == 0);
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+ }
+
+ if (NtStatus == STATUS_BUFFER_OVERFLOW) {
+
+ ASSERT(MembershipCount != 0);
+ ASSERT(KeyValueLength == (MembershipCount * sizeof(ULONG)));
+
+ MembershipArray = MIDL_user_allocate( KeyValueLength );
+
+ if (MembershipArray == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ NULL,
+ MembershipArray,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // See if the account is a member ...
+ //
+
+ NtStatus = STATUS_MEMBER_NOT_IN_ALIAS;
+
+ for (i = 0; i<MembershipCount ; i++ ) {
+ if ( MembershipArray[i] == AliasRid ) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Replace the removed alias information
+ // with the last entry's information.
+ // Then add it to the RXACT transaction
+ // to be written out.
+ //
+
+ MembershipCount -= 1;
+ KeyValueLength -= sizeof(ULONG);
+
+ if (MembershipCount > 0) {
+
+ MembershipArray[i] = MembershipArray[MembershipCount];
+
+ ASSERT(KeyValueLength == (MembershipCount * sizeof(ULONG)));
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &AccountKeyName,
+ MembershipCount,
+ MembershipArray,
+ KeyValueLength
+ );
+ } else {
+
+ //
+ // This is the last alias membership for
+ // this account. Delete the keys.
+ //
+
+ NtStatus = SampDeleteAliasMembershipKeysForAccount(
+ AccountSid);
+ }
+ }
+ }
+
+ MIDL_user_free( MembershipArray );
+ }
+
+ }
+
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+
+ SampFreeUnicodeString( &DomainKeyName );
+ SampFreeUnicodeString( &AccountKeyName );
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampRemoveAliasFromAllAccounts(
+ IN PSAMP_OBJECT AliasContext
+ )
+
+/*++
+
+Routine Description:
+
+ This service removes the specified alias from all account memberships
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+ This service leaves the alias membership list intact. It is assumed
+ that the caller will delete the alias member list as part of the
+ current transaction.
+
+Arguments:
+
+ AliasRid - The relative ID of the alias.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_NO_SUCH_ALIAS - The alias does not exist.
+
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG MemberCount, i;
+ PSID *MemberArray;
+
+ //
+ // Get the list of members in this alias
+ //
+
+ MemberArray = NULL;
+
+ NtStatus = SampRetrieveAliasMembers(
+ AliasContext,
+ &MemberCount,
+ &MemberArray);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT((MemberCount != 0) == (MemberArray != NULL));
+
+ //
+ // Remove this alias from each of our members in turn
+ //
+
+ for (i = 0; i < MemberCount ; i++ ) {
+
+ ULONG AliasRid = AliasContext->TypeBody.Alias.Rid;
+
+ NtStatus = SampRemoveAliasFromAccountMembership(AliasRid, MemberArray[i]);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+ }
+
+ if (MemberArray != NULL) {
+ MIDL_user_free( MemberArray );
+ }
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampRetrieveAliasMembers(
+ IN PSAMP_OBJECT AliasContext,
+ OUT PULONG MemberCount,
+ OUT PSID **Members OPTIONAL
+ )
+
+/*++
+Routine Description:
+
+ This service retrieves the number of members in a alias. If desired,
+ it will also retrieve an array of SIDs of the members of the alias.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ Context - Points to the account context whose alias members are to
+ to be retrieved.
+
+ MemberCount - Receives the number of members currently in the alias.
+
+ Members - (Otional) Receives a pointer to a buffer containing an array
+ of member PSIDs. If this value is NULL, then this information
+ is not returned. The returned buffer is allocated using
+ MIDL_user_allocate() and must be freed using MIDL_user_free() when
+ no longer needed.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ string to be returned in.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+
+ If this routine returns failure, *MemberCount will be zero and
+ *Members will be NULL.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSID MemberArray;
+ ULONG MemberArraySize;
+ ULONG i;
+
+
+ NtStatus = SampGetSidArrayAttribute(
+ AliasContext,
+ SAMP_ALIAS_MEMBERS,
+ FALSE, // Reference directly
+ &MemberArray,
+ &MemberArraySize,
+ MemberCount
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (ARGUMENT_PRESENT(Members)) {
+
+ //
+ // Allocate memory for the sid array and sid data
+ //
+
+ ULONG SidArraySize = *MemberCount * sizeof(PSID);
+ ULONG SidDataSize = MemberArraySize;
+
+ if ( *MemberCount == 0 ) {
+
+ //
+ // Nothing to copy, just return success.
+ //
+
+ *Members = NULL;
+ return( NtStatus );
+ }
+
+ (*Members) = (PSID *)MIDL_user_allocate(SidArraySize + SidDataSize);
+
+ if ((*Members) == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Copy the sid data into the last part of the block
+ //
+
+ PSID SidData = (PSID)(&((*Members)[*MemberCount]));
+
+ RtlCopyMemory(SidData, MemberArray, MemberArraySize);
+
+ //
+ // Fill in the sid pointer array
+ //
+
+ for (i = 0; i < *MemberCount ; i++) {
+
+ (*Members)[i] = SidData;
+
+ ((PCHAR)SidData) += RtlLengthSid(SidData);
+ }
+
+ ASSERT(SidData == ((PCHAR)(*Members)) + SidArraySize + SidDataSize);
+
+ }
+ }
+ }
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampDeleteAliasKeys(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+Routine Description:
+
+ This service deletes all registry keys related to a alias object.
+
+
+Arguments:
+
+ Context - Points to the alias context whose registry keys are
+ being deleted.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG Rid;
+ UNICODE_STRING AccountName, KeyName;
+
+
+ Rid = Context->TypeBody.Alias.Rid;
+
+
+ //
+ // Aliases are arranged as follows:
+ //
+ // +-- Aliases [Count]
+ // ---+--
+ // +-- Names
+ // | --+--
+ // | +-- (AliasName) [AliasRid,]
+ // |
+ // +-- (AliasRid) [Revision,SecurityDescriptor]
+ // ---+-----
+ // +-- V1_Fixed [,SAM_V1_FIXED_LENGTH_ALIAS]
+ // +-- Name [,Name]
+ // +-- AdminComment [,unicode string]
+ // +-- Members [Count,(Member0Sid, (...), MemberX-1Sid)]
+ //
+ // This all needs to be deleted from the bottom up.
+ //
+
+
+ //
+ // Decrement the alias count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampAliasObjectType, FALSE );
+
+
+
+
+ //
+ // Delete the registry key that has the alias's name to RID mapping.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the name
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ &AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ &AccountName
+ );
+
+ SampFreeUnicodeString( &AccountName );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+ }
+
+
+
+ //
+ // Delete the attribute keys
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampDeleteAttributeKeys(
+ Context
+ );
+ }
+
+
+ //
+ // Delete the RID key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ Rid,
+ NULL
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampDeleteAliasMembershipKeysForAccount(
+ IN PSID AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This service deletes the alias membership keys for the specified account.
+
+ This account rid key is deleted. If this was the last account-rid for
+ the domain then the domain keys is deleted also.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+ It is assumed we are in the middle of a registry transaction.
+
+Arguments:
+
+ AccountSid - The SID of the account.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The transactions have been added.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING DomainKeyName;
+ UNICODE_STRING AccountKeyName;
+ HANDLE TempHandle;
+ ULONG MembershipCount;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+ //
+ // Get the account membership key names
+ //
+
+ NtStatus = SampBuildAliasMembersKeyName(
+ AccountSid,
+ &DomainKeyName,
+ &AccountKeyName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Delete the account rid key
+ //
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &AccountKeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ //
+ // Adjust the rid count for the domain
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &DomainKeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ ASSERT(NT_SUCCESS(NtStatus)); // We just opened a sub-key successfully !
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &MembershipCount,
+ NULL,
+ NULL,
+ NULL);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Decrement the rid count, write out or delete key if 0
+ //
+
+ MembershipCount -= 1;
+ if (MembershipCount > 0) {
+
+ //
+ // Decrement the domain rid count
+ //
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &DomainKeyName,
+ MembershipCount,
+ NULL,
+ 0
+ );
+ } else {
+
+ //
+ // Delete the domain key
+ //
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &DomainKeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ //
+ // Adjust the count of domain keys
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampAdjustAliasDomainsCount(FALSE);
+ }
+ }
+
+ }
+
+ //
+ // Close the domain key handle
+ //
+
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+ }
+
+
+ SampFreeUnicodeString( &DomainKeyName );
+ SampFreeUnicodeString( &AccountKeyName );
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampAdjustAliasDomainsCount(
+ IN BOOLEAN Increment
+ )
+
+/*++
+Routine Description:
+
+ This service increments or decrements the number of domains that have
+ at least one account that is a member of one of our aliases.
+
+ This value is contained in the type of \(domain)\ALIASES\MEMBERS
+
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+Arguments:
+
+ Increment - TRUE to increment, FALSE to decrement
+
+Return Value:
+
+ STATUS_SUCCESS - The value has been adjusted and the new value added
+ to the current RXACT transaction.
+
+ STATUS_INSUFFICIENT_RESOURCES - Not enough memory could be allocated
+ to perform the requested operation.
+
+ Other values are unexpected errors. These may originate from
+ internal calls to:
+
+ NtOpenKey()
+ NtQueryInformationKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ //
+ // Don't maintain a count of domains for now
+ //
+
+
+ return(STATUS_SUCCESS);
+
+ DBG_UNREFERENCED_PARAMETER(Increment);
+}
+
+
+
+NTSTATUS
+SampValidateNewAliasMember(
+ IN PSID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This service checks the passed Sid is acceptable as a potential new
+ member of one of the aliases in the current domain.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+Arguments:
+
+ MemberId - the full Sid of the member to validate
+
+Return Value:
+
+ STATUS_SUCCESS - MemberId is a valid potential alias member
+
+ STATUS_INVALID_MEMBER - MemberId has the wrong account type.
+
+ STATUS_NO_SUCH_MEMBER - MemberId is not a valid account.
+
+ STATUS_INVALID_SID - MemberId is not a valid sid.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSID MemberDomainSid = NULL, CurrentDomainSid = NULL;
+ ULONG MemberRid;
+ SAMP_OBJECT_TYPE MemberType;
+
+ //
+ // Check the new member sid for structural soundness
+ //
+
+ if ((MemberId == NULL) || !RtlValidSid(MemberId)) {
+ return(STATUS_INVALID_SID);
+ }
+
+
+ //
+ // Get the current domain sid
+ //
+
+ ASSERT(SampTransactionWithinDomain);
+ CurrentDomainSid = SampDefinedDomains[SampTransactionDomainIndex].Sid;
+
+ //
+ // Break up the new member into domain and rid
+ //
+
+ NtStatus = SampSplitSid(MemberId, &MemberDomainSid, &MemberRid);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // If the member isn't from this domain, then they're OK.
+ //
+
+ if (!RtlEqualSid(CurrentDomainSid, MemberDomainSid)) {
+
+ NtStatus = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // The member is in our domain - check that the type of
+ // account is acceptable.
+ //
+
+ NtStatus = SampLookupAccountName(
+ MemberRid,
+ NULL,
+ &MemberType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ switch (MemberType) {
+ case SampUserObjectType:
+ case SampGroupObjectType:
+ NtStatus = STATUS_SUCCESS;
+ break;
+
+ case SampUnknownObjectType:
+ NtStatus = STATUS_NO_SUCH_MEMBER;
+ break;
+
+ default:
+ NtStatus = STATUS_INVALID_MEMBER;
+ break;
+ }
+ }
+
+ }
+
+
+ MIDL_user_free(MemberDomainSid);
+
+ return(NtStatus);
+}
+
+
+
+
+NTSTATUS
+SampChangeAliasAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ )
+
+/*++
+Routine Description:
+
+ This routine changes the account name of an alias account.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ Context - Points to the account context whose name is to be changed.
+
+ NewAccountName - New name to give this account
+
+ OldAccountName - old name is returned here. The buffer should be freed
+ by calling MIDL_user_free.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ SampGetUnicodeStringAttribute()
+ SampSetUnicodeStringAttribute()
+ SampValidateAccountNameChange()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+
+
+ /////////////////////////////////////////////////////////////
+ // There are two copies of the name of each account. //
+ // one is under the DOMAIN\(domainName)\ALIAS\NAMES key, //
+ // one is the value of the //
+ // DOMAIN\(DomainName)\ALIAS\(rid)\NAME key //
+ /////////////////////////////////////////////////////////////
+
+
+ //
+ // Get the current name so we can delete the old Name->Rid
+ // mapping key.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ OldAccountName
+ );
+
+ //
+ // Make sure the name is valid and not already in use
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampValidateAccountNameChange(
+ NewAccountName,
+ OldAccountName
+ );
+
+ //
+ // Delete the old name key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ OldAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+
+ }
+
+ //
+ //
+ // Create the new Name->Rid mapping key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ NewAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ULONG AliasRid = Context->TypeBody.Alias.Rid;
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ AliasRid,
+ (PVOID)NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+
+
+
+
+ //
+ // replace the account's name
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ SAMP_ALIAS_NAME,
+ NewAccountName
+ );
+ }
+
+ //
+ // Free up the old account name if we failed
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString(OldAccountName);
+ }
+
+ }
+
+
+ return(NtStatus);
+}
diff --git a/private/newsam/server/almember.c b/private/newsam/server/almember.c
new file mode 100644
index 000000000..7d5bc55eb
--- /dev/null
+++ b/private/newsam/server/almember.c
@@ -0,0 +1,3610 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ almember.c
+
+Abstract:
+
+ This file contains utilities related to membership of aliases.
+ Alternative design
+
+
+Author:
+
+ Scott Birrell 01-Apr-1993
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+#define SAMP_AL_FREE_OLD_LIST ((ULONG) 0x00000001L)
+#define SAMP_AL_ERROR_IF_MEMBER ((ULONG) 0x00000002L)
+#define SAMP_AL_ERROR_IF_NOT_MEMBER ((ULONG) 0x00000004L)
+#define SAMP_AL_ASSIGN_NEW_REFERENCES ((ULONG) 0x00000008L)
+#define SAMP_AL_LOOKUP_BY_SID ((ULONG) 0x00000010L)
+#define SAMP_AL_LOOKUP_BY_REFERENCE ((ULONG) 0x00000020L)
+#define SAMP_AL_VERIFY_NO_ALIASES_IN_ACCOUNT ((ULONG) 0x00000040L)
+#define SAMP_AL_VERIFY_ALL_ALIASES_IN_ACCOUNT ((ULONG) 0x00000080L)
+#define SAMP_AL_VERIFY_NO_MEMBERS_IN_ALIAS ((ULONG) 0x00000100L)
+#define SAMP_AL_VERIFY_ALL_MEMBERS_IN_ALIAS ((ULONG) 0x00000200L)
+
+#define SAMP_UNKNOWN_INDEX ((ULONG) 0xffffffffL)
+#define SAMP_AL_ALIAS_LIST_DELTA ((ULONG) 0x00000100L)
+#define SAMP_AL_ALIAS_DELTA ((ULONG) 0x00000040L)
+#define SAMP_AL_REFERENCED_DOMAIN_LIST_DELTA ((ULONG) 0x00000100L)
+#define SAMP_AL_INITIAL_MEMBER_ALIAS_LIST_LENGTH ((ULONG) 0x00001000L)
+#define SAMP_AL_MEMBER_ALIAS_LIST_DELTA ((ULONG) 0x00001000L)
+#define SAMP_AL_INITIAL_REFERENCED_DOMAIN_LIST_LENGTH ((ULONG) 0x00000400L)
+#define SAMP_AL_INITIAL_MEMBER_DOMAIN_LENGTH ((ULONG) 0x00000040L)
+#define SAMP_AL_INITIAL_MEMBER_ACCOUNT_ALIAS_CAPACITY ((ULONG) 0x00000004L)
+#define SAMP_AL_ENUM_PREFERRED_LENGTH ((ULONG) 0x00001000L)
+#define SAMP_AL_INITIAL_MEMBERSHIP_COUNT ((ULONG) 0x0000000aL)
+#define SAMP_AL_MEMBERSHIP_COUNT_DELTA ((ULONG) 0x0000000aL)
+#define SAMP_AL_MEMBER_ALIAS_LIST_SIGNATURE ((ULONG) 0x53494c41)
+#define SAMP_AL_MEMBER_DOMAIN_SIGNATURE ((ULONG) 0x4d4f444d)
+#define SAMP_AL_MEMBER_ACCOUNT_SIGNATURE ((ULONG) 0x4343414d)
+
+#define SAMP_AL_DR_ALIAS_LIST_KEY_NAME L"Aliases\\Members\\AliasList"
+#define SAMP_AL_DR_REFERENCED_DOMAIN_LIST_KEY_NAME \
+ L"Aliases\\Members\\ReferencedDomainList"
+
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Private macro functions //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+#define SampAlFirstMemberDomain( MemberAliasList ) \
+ (MemberAliasList->MemberDomains)
+
+#define SampAlOffsetFirstMemberDomain( MemberAliasList ) \
+ (((PUCHAR) SampAlFirstMemberDomain(MemberAliasList)) - ((PUCHAR) MemberAliasList))
+
+#define SampAlFirstMemberAccount( MemberDomain ) \
+ ((PSAMP_AL_MEMBER_ACCOUNT) \
+ (((PUCHAR) &((MemberDomain)->DomainSid)) + RtlLengthSid(&((MemberDomain)->DomainSid))))
+
+#define SampAlOffsetFirstMemberAccount( MemberDomain ) \
+ (((PUCHAR) SampAlFirstMemberAccount(MemberDomain)) - ((PUCHAR) MemberDomain))
+
+#define SampAlNextMemberAccount( MemberAccount ) \
+ ((PSAMP_AL_MEMBER_ACCOUNT)(((PUCHAR) MemberAccount) + (MemberAccount)->MaximumLength))
+
+#define SampAlOffsetFirstAlias( OutputMemberAccount ) \
+ ((ULONG) FIELD_OFFSET(SAMP_AL_MEMBER_ACCOUNT, AliasRids))
+
+#define SampAlNextMemberDomain( MemberDomain ) \
+ ((PSAMP_AL_MEMBER_DOMAIN)(((PUCHAR) MemberDomain) + (MemberDomain)->MaximumLength))
+
+#define SampAlNextNewAliasInMemberAccount( MemberAccount ) \
+ ((PULONG)(((PUCHAR) MemberAccount) + (MemberAccount)->UsedLength))
+
+#define SampAlNextNewMemberAccount( MemberDomain ) \
+ ((PSAMP_AL_MEMBER_ACCOUNT)(((PUCHAR) MemberDomain) + (MemberDomain)->UsedLength))
+
+#define SampAlNextNewMemberDomain( MemberAliasList ) \
+ ((PSAMP_AL_MEMBER_DOMAIN)(((PUCHAR) MemberAliasList) + (MemberAliasList)->UsedLength))
+
+#define SampAlInfoIsValid(DomainIndex) \
+ ((SampDefinedDomains[DomainIndex].AliasInformation.Valid) || \
+ (SampServiceState == SampServiceInitializing ))
+
+#define SampAlInfoMakeValid(DomainIndex) \
+ (SampDefinedDomains[DomainIndex].AliasInformation.Valid = TRUE)
+
+#define SampAlInfoMakeInvalid(DomainIndex) \
+ (SampDefinedDomains[DomainIndex].AliasInformation.Valid = FALSE)
+
+#define SampAlDomainIndexToMemberAliasList( DomainIndex ) \
+ ((PSAMP_AL_MEMBER_ALIAS_LIST) \
+ SampDefinedDomains[ DomainIndex].AliasInformation.MemberAliasList)
+
+#define SampAlDomainHandleToMemberAliasList( DomainHandle ) \
+ (SampAlDomainIndexToMemberAliasList(((PSAMP_OBJECT) DomainHandle)->DomainIndex))
+
+#define SampAlAliasHandleToMemberAliasList( AliasHandle ) \
+ (SampAlDomainIndexToMemberAliasList(((PSAMP_OBJECT) AliasHandle)->DomainIndex))
+
+#define SampAlMemberDomainToOffset( MemberAliasList, MemberDomain) \
+ (((PUCHAR) MemberDomain) - ((PUCHAR) MemberAliasList))
+
+#define SampAlMemberDomainFromOffset( MemberDomain, MemberDomainOffset) \
+ ((PSAMP_AL_MEMBER_DOMAIN)(((PUCHAR) MemberDomain) + MemberDomainOffset))
+
+#define SampAlMemberAccountToOffset( MemberDomain, MemberAccount) \
+ (((PUCHAR) MemberAccount) - ((PUCHAR) MemberDomain))
+
+#define SampAlMemberAccountFromOffset( MemberDomain, MemberAccountOffset) \
+ ((PSAMP_AL_MEMBER_ACCOUNT)(((PUCHAR) MemberDomain) + MemberAccountOffset))
+
+#define SampAlLengthRequiredMemberAccount( AliasCapacity ) \
+ (sizeof(SAMP_AL_MEMBER_ACCOUNT) + ((AliasCapacity - 1) * sizeof(ULONG)))
+
+#define SampAlUpdateMemberAliasList( AliasHandle, MemberAliasList ) \
+ { \
+ PSAMP_OBJECT InternalAliasHandle = (PSAMP_OBJECT) AliasHandle; \
+ SampDefinedDomains[InternalAliasHandle->DomainIndex].AliasInformation.MemberAliasList \
+ = MemberAliasList; \
+ }
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Private Datatypes //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+// This datatype is not currently used. It may be used if Alias information
+// is every stored to Registry Keys.
+//
+
+typedef enum _SAMP_AL_LIST_TYPE {
+
+ SampAlMemberAliasList = 1
+
+} SAMP_AL_LIST_TYPE, *PSAMP_AL_LIST_TYPE;
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Private Static Data //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+UNICODE_STRING SampAlDrMemberAliasListKeyName;
+BOOLEAN SampAlEnableBuildingOfList[SAMP_DEFINED_DOMAINS_COUNT] = { TRUE, TRUE };
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Prototypes of functions private to this module //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampAlCreateMemberAliasList(
+ IN LONG DomainIndex,
+ IN ULONG InitialMemberAliasListLength,
+ OUT OPTIONAL PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList
+ );
+
+NTSTATUS
+SampAlGrowMemberAliasList(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN ULONG ExtraSpaceRequired
+ );
+
+NTSTATUS
+SampAlBuildMemberAliasList(
+ IN LONG DomainIndex
+ );
+
+NTSTATUS
+SampAlCreateMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSID DomainSid,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ );
+
+NTSTATUS
+SampAlAllocateMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN ULONG MaximumLengthMemberDomain,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ );
+
+NTSTATUS
+SampAlGrowMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG ExtraSpaceRequired
+ );
+
+NTSTATUS
+SampAlDeleteMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN MemberDomain
+ );
+
+NTSTATUS
+SampAlLookupMemberDomain(
+ IN PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList,
+ IN PSID DomainSid,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ );
+
+NTSTATUS
+SampAlCreateMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG Rid,
+ IN ULONG AliasCapacity,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ );
+
+NTSTATUS
+SampAlAllocateMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG MaximumLengthMemberAccount,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ );
+
+NTSTATUS
+SampAlGrowMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG ExtraSpaceRequired
+ );
+
+NTSTATUS
+SampAlDeleteMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT MemberAccount,
+ OUT PBOOLEAN MemberDomainDeleted
+ );
+
+NTSTATUS
+SampAlLookupMemberAccount(
+ IN PSAMP_AL_MEMBER_DOMAIN MemberDomain,
+ IN ULONG MemberRid,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ );
+
+NTSTATUS
+SampAlAddAliasesToMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG Options,
+ IN PSAMPR_ULONG_ARRAY AliasRids
+ );
+
+NTSTATUS
+SampAlRemoveAliasesFromMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG Options,
+ IN PSAMPR_ULONG_ARRAY AliasRids,
+ OUT PBOOLEAN MemberDomainDeleted,
+ OUT PBOOLEAN MemberAccountDeleted
+ );
+
+NTSTATUS
+SampAlLookupAliasesInMemberAccount(
+ IN PSAMP_AL_MEMBER_ACCOUNT MemberAccount,
+ IN PSAMPR_ULONG_ARRAY AliasRids,
+ OUT PULONG ExistingAliasCount
+ );
+
+NTSTATUS
+SampAlSplitMemberSids(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN ULONG Options,
+ IN PSAMPR_PSID_ARRAY MemberSids,
+ OUT PSAMP_AL_SPLIT_MEMBER_SID_LIST SplitMemberSids
+ );
+
+BOOLEAN
+SampAlInfoIsValidForDomain(
+ IN SAMPR_HANDLE DomainHandle
+ );
+
+BOOLEAN
+SampAlInfoIsValidForAlias(
+ IN SAMPR_HANDLE AliasHandle
+ );
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// Code of Exported Routines //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SamrGetAliasMembership(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PSAMPR_PSID_ARRAY SidArray,
+ OUT PSAMPR_ULONG_ARRAY Membership
+ )
+
+/*++
+
+Routine Description:
+
+ This API searches the set of aliases in the specified domain to see
+ which aliases, if any, the passed SIDs are members of. Any aliases
+ that any of the SIDs are found to be members of are returned.
+
+ Note that any particular alias will appear only once in the returned list.
+
+Parameters:
+
+ DomainHandle - Handle from a SamOpenDomain call.
+
+ PassedCount - Specifies the number of Sids being passed.
+
+ Sids - Pointer to an arrray of Count pointers to Sids whose alias
+ memberships are to be looked up.
+
+ Membership - receives the array of rids rerpresenting the aliases
+ in this domain that any of the sid(s) are members of.
+
+Return Values:
+
+ STATUS_SUCCESS - The combined alias membership is in Membership
+
+ STATUS_INVALID_SID - One of the passed sids was invalid
+
+--*/
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG i;
+ ULONG SidCount;
+ PSID *Sids;
+ BOOLEAN ObjectReferenced = FALSE;
+
+ ASSERT(Membership != NULL);
+ ASSERT(Membership->Element == NULL);
+
+ SidCount = SidArray->Count;
+ Sids = (PSID *)(SidArray->Sids);
+
+ //
+ // Grab the lock
+ //
+
+ SampAcquireReadLock();
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LOOKUP,
+ SampDomainObjectType,
+ &FoundType
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto GetAliasMembershipError;
+ }
+
+ ObjectReferenced = TRUE;
+
+ //
+ // Validate the Sids. if any are invalid, return an error.
+ //
+
+ for (i=0; i < SidCount; i++) {
+
+ //
+ // Check for valid sid
+ //
+
+ if ( (Sids[i] == NULL) || !RtlValidSid(Sids[i]) ) {
+
+ NtStatus = STATUS_INVALID_SID;
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto GetAliasMembershipError;
+ }
+
+ //
+ // If the in-memory Alias Membership information for this domain is valid,
+ // use it to retrieve the Alias members.
+ //
+
+ if (SampAlInfoIsValidForDomain(DomainHandle)) {
+
+ NtStatus = SampAlQueryAliasMembership(
+ DomainHandle,
+ SidArray,
+ Membership
+ );
+ } else {
+
+ NtStatus = SampAlSlowQueryAliasMembership(
+ DomainHandle,
+ SidArray,
+ Membership
+ );
+ }
+
+GetAliasMembershipFinish:
+
+ //
+ // If necessary, dereference the SAM server object.
+ //
+
+ if (ObjectReferenced) {
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+ return(NtStatus);
+
+GetAliasMembershipError:
+
+ goto GetAliasMembershipFinish;
+}
+
+
+NTSTATUS
+SampAlQueryAliasMembership(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PSAMPR_PSID_ARRAY SidArray,
+ OUT PSAMPR_ULONG_ARRAY Membership
+ )
+
+/*++
+
+Routine Description:
+
+ This function is one of two worker routines for the SamrGetAliasMembership
+ API. This worker uses the Member Alias List to determine which aliases,
+ if any, the passed SIDs are members of. Any aliases that any of the SIDs
+ are found to be members of are returned.
+
+ Note that any particular alias will appear only once in the returned list.
+
+ See also SampAlSlowQueryAliasMembership()
+
+ WARNING: The SAM Read Lock must be held while this function executes.
+
+Parameters:
+
+ DomainHandle - Handle from a SamrOpenDomain call.
+
+ SidArray - Pointer to a counted array of pointers to Sids whose alias
+ memberships are to be looked up.
+
+ Membership - Receives the array of rids rerpresenting the aliases
+ in this domain that any of the sid(s) are members of.
+
+Return Values:
+
+ STATUS_SUCCESS - The combined alias membership is in Membership
+
+ STATUS_INVALID_SID - One of the passed sids was invalid
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ ULONG Rid, AliasRid;
+ ULONG AliasIndex, SidIndex;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ BOOLEAN AliasAlreadyFound;
+ ULONG AliasFoundIndex, MembershipMaximumCount;
+ PSID DomainSid = NULL;
+ PSID Sid = NULL;
+ PULONG NewMembership = NULL;
+
+ Membership->Count = 0;
+ Membership->Element = NULL;
+
+ //
+ // Obtain pointer to Alias Member List.
+ //
+
+ MemberAliasList = SampAlDomainHandleToMemberAliasList( DomainHandle );
+
+ //
+ // If there are no Member Domains in this Member Alias List, then just
+ // finish.
+ //
+
+ if (MemberAliasList->DomainCount == 0) {
+
+ goto QueryAliasMembershipFinish;
+ }
+
+ //
+ // Allocate Scratch Sid buffer. We will use this same buffer for splitting
+ // each Sid.
+ //
+
+ DomainSid = MIDL_user_allocate( RtlLengthRequiredSid( 256 ));
+
+ Status = STATUS_NO_MEMORY;
+
+ if (DomainSid == NULL) {
+
+ goto QueryAliasMembershipError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Allocate output array with a nominal initial size. Reallocate it
+ // as necessary
+ //
+
+ MembershipMaximumCount = SAMP_AL_INITIAL_MEMBERSHIP_COUNT;
+
+ Membership->Element = MIDL_user_allocate( MembershipMaximumCount * sizeof(ULONG));
+
+ Status = STATUS_NO_MEMORY;
+
+ if (Membership->Element == NULL) {
+
+ goto QueryAliasMembershipError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Now query the membership of the array of split Sids. For each
+ // Sid, we skip the Sid if it has an unknown MemberDomain, because
+ // it does not belong to any aliases. For each surviving Sid, we scan the
+ // Alias List, skipping entries for aliases we've already entered in the
+ // output list. We search for the Rid only in the section of the
+ // Alias List pertinent to the Sid's domain.
+ //
+
+ for (SidIndex = 0; SidIndex < SidArray->Count; SidIndex++) {
+
+ Sid = SidArray->Sids[ SidIndex ].SidPointer;
+
+ //
+ // Split this Sid into a DomainSid and a Rid. Note that we re-use
+ // the buffer containing the Domain Sid for the next Sid.
+ //
+
+ Status = SampSplitSid( Sid, &DomainSid, &Rid);
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Search the Member Alias List for the Sid's Member Domain
+ // (if any).
+ //
+
+ Status = SampAlLookupMemberDomain(
+ MemberAliasList,
+ DomainSid,
+ &MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // The only expected error is STATUS_NO_SUCH_DOMAIN. If we
+ // don't get this error, fail the request. Otherwise, the
+ // Sid is not a member of any aliases in the SAM local domain, so
+ // just skip to the next Sid.
+ //
+
+ if (Status != STATUS_NO_SUCH_DOMAIN) {
+
+ break;
+ }
+
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+
+ //
+ // We've found the Member Domain. Now find the Member Account.
+ //
+
+ Status = SampAlLookupMemberAccount(
+ MemberDomain,
+ Rid,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // The only expected error is STATUS_NO_SUCH_MEMBER. If we
+ // don't get this error, fail the request. Otherwise, the
+ // Sid is not a member of any aliases in the domain, so just
+ // skip to the next Sid.
+ //
+
+ if (Status != STATUS_NO_SUCH_MEMBER) {
+
+ break;
+ }
+
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+
+ //
+ // We've found the Member Account. For each of the aliases our Sid
+ // belongs to, add the alias to the output list if not already there.
+ //
+
+ for (AliasIndex = 0; AliasIndex < MemberAccount->AliasCount; AliasIndex++) {
+
+ AliasRid = MemberAccount->AliasRids[AliasIndex];
+
+ AliasAlreadyFound = FALSE;
+
+ for (AliasFoundIndex = 0;
+ AliasFoundIndex < Membership->Count;
+ AliasFoundIndex++) {
+
+ if (AliasRid == Membership->Element[AliasFoundIndex]) {
+
+ AliasAlreadyFound = TRUE;
+ break;
+ }
+ }
+
+ if (!AliasAlreadyFound) {
+
+ //
+ // If there isn't enough room in the output Membership
+ // array, reallocate it.
+ //
+
+ if (Membership->Count == MembershipMaximumCount) {
+
+ MembershipMaximumCount += SAMP_AL_MEMBERSHIP_COUNT_DELTA;
+
+ NewMembership = MIDL_user_allocate(
+ MembershipMaximumCount * sizeof(ULONG)
+ );
+
+ Status = STATUS_NO_MEMORY;
+
+ if (NewMembership == NULL) {
+
+ break;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ RtlMoveMemory(
+ NewMembership,
+ Membership->Element,
+ Membership->Count * sizeof(ULONG)
+ );
+
+ MIDL_user_free( Membership->Element);
+ Membership->Element = NewMembership;
+ }
+
+ Membership->Element[Membership->Count] = AliasRid;
+ Membership->Count++;
+ }
+ }
+ }
+
+ //
+ // If the buffer we've allocated turns out to be way overboard, allocate
+ // a smaller one for the output.
+ //
+
+ // TBS
+
+QueryAliasMembershipFinish:
+
+ //
+ // If we got as far as allocating a buffer for the DomainSids, free it.
+ //
+
+ if (DomainSid != NULL) {
+
+ MIDL_user_free(DomainSid);
+ DomainSid = NULL;
+ }
+
+ return(Status);
+
+QueryAliasMembershipError:
+
+ //
+ // If necessary, free the output membership array.
+ //
+
+ if (Membership->Element != NULL) {
+
+ MIDL_user_free( Membership->Element);
+ Membership->Element = NULL;
+ }
+
+ goto QueryAliasMembershipFinish;
+}
+
+
+NTSTATUS
+SampAlSlowQueryAliasMembership(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PSAMPR_PSID_ARRAY SidArray,
+ OUT PSAMPR_ULONG_ARRAY Membership
+ )
+
+/*++
+
+Routine Description:
+
+ This function is the slow version of the worker routine for the
+ SamrGetAliasMembership API searches. It is called when the in-memory
+ Alias Information is no longer valid.
+
+ WARNING! The caller of this function must hold the SAM Database Read
+ Lock.
+
+Parameters:
+
+ DomainHandle - Handle from a SamOpenDomain call.
+
+ SidArray - Pointer to a counted array of pointers to Sids whose alias
+ memberships are to be looked up.
+
+ Membership - Receives the array of rids rerpresenting the aliases
+ in this domain that any of the sid(s) are members of.
+
+Return Values:
+
+ STATUS_SUCCESS - The combined alias membership is in Membership
+
+ STATUS_INVALID_SID - One of the passed sids was invalid
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ ULONG i;
+ ULONG MembershipCount;
+ ULONG TotalMembershipCount;
+ ULONG MembershipIndex;
+ ULONG BufferSize;
+ ULONG TotalBufferSize;
+ ULONG SidCount = SidArray->Count;
+ PSID *Sids = (PSID *) &SidArray->Sids->SidPointer;
+
+ //
+ // For each Sid, retrieve its membership and size up how many membership
+ // entries we'll have in total.
+ //
+
+ TotalMembershipCount = 0;
+ TotalBufferSize = 0;
+
+ for (i=0; i < SidCount; i++) {
+
+ //
+ // Get the membership count for this account
+ //
+
+ BufferSize = 0;
+
+ NtStatus = SampRetrieveAliasMembership(
+ Sids[i],
+ &MembershipCount,
+ &BufferSize,
+ NULL
+ );
+
+ if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_BUFFER_OVERFLOW)) {
+
+ ASSERT(BufferSize == (MembershipCount * sizeof(ULONG)));
+
+ TotalMembershipCount += MembershipCount;
+ TotalBufferSize += BufferSize;
+
+ NtStatus = STATUS_SUCCESS;
+
+ } else {
+
+ break;
+ }
+ }
+
+ //
+ // Allocate and fill in the membership array
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Membership->Element = MIDL_user_allocate(TotalBufferSize);
+
+ if (Membership->Element == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Fill in the allocated membership list
+ //
+
+ MembershipIndex = 0;
+
+ for (i=0; i < SidCount; i++) {
+
+ //
+ // Get the membership list for this account
+ //
+
+ BufferSize = TotalBufferSize;
+
+ NtStatus = SampRetrieveAliasMembership(
+ Sids[i],
+ &MembershipCount,
+ &BufferSize,
+ &(Membership->Element[MembershipIndex])
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ ASSERT(BufferSize == (MembershipCount * sizeof(*(Membership->Element))));
+
+ //
+ // Remove duplicate aliases
+ //
+
+ if (MembershipCount > 0) {
+
+ ULONG ExistingIndex, NewIndex;
+
+ for (ExistingIndex = 0; ExistingIndex < MembershipIndex; ExistingIndex ++) {
+
+ for (NewIndex = MembershipIndex; NewIndex < MembershipIndex + MembershipCount; NewIndex ++) {
+
+ if (Membership->Element[ExistingIndex] ==
+ Membership->Element[NewIndex]) {
+
+ //
+ // This alias is already in the list - forget it
+ //
+
+ if (NewIndex < MembershipIndex + MembershipCount - 1) {
+
+ //
+ // Remove the duplicate alias
+ //
+
+ Membership->Element[NewIndex] =
+ Membership->Element[MembershipIndex + MembershipCount - 1];
+
+ NewIndex --; // So we come back to this alias again
+ }
+
+ MembershipCount --;
+ TotalMembershipCount --;
+ }
+ }
+ }
+ }
+
+ MembershipIndex += MembershipCount;
+
+ ASSERT(MembershipIndex <= TotalMembershipCount);
+ ASSERT(TotalBufferSize >= BufferSize);
+
+ TotalBufferSize -= BufferSize;
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ MIDL_user_free(Membership->Element);
+ Membership->Element = NULL;
+ } else {
+ Membership->Count = TotalMembershipCount;
+ }
+ }
+ }
+
+ return NtStatus;
+}
+
+
+NTSTATUS
+SampAlQueryMembersOfAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ OUT PSAMPR_PSID_ARRAY MemberSids
+ )
+
+/*++
+
+Routine Description:
+
+ This function returns an array of Sids of accounts that are members of
+ a specified alias.
+
+Arguments:
+
+ AliasHandle - Handle to an Alias object
+
+ MemberSids - Receives an array of Sids that belong to the Alias
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_ALIAS_MEMBER_LIST AliasMemberList = NULL;
+ PSID *Members = NULL;
+ ULONG AliasMemberCount;
+
+ Status = SampRetrieveAliasMembers(
+ AliasHandle,
+ &AliasMemberCount,
+ &Members
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto QueryMembersOfAliasError;
+ }
+
+QueryMembersOfAliasFinish:
+
+ MemberSids->Count = AliasMemberCount;
+ MemberSids->Sids = (PSAMPR_SID_INFORMATION) Members;
+ return(Status);
+
+QueryMembersOfAliasError:
+
+ AliasMemberCount = 0;
+ Members = NULL;
+ goto QueryMembersOfAliasFinish;
+}
+
+
+NTSTATUS
+SampAlAddMembersToAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG Options,
+ IN PSAMPR_PSID_ARRAY MemberSids
+ )
+
+/*++
+
+Routine Description:
+
+ This function adds one or more member to an alias. Any failure results
+ in the in-memory Alias Information being discarded.
+
+ WARNING: The calling function must perform all parameter validation and
+ the SAM Database Write Lock must be held.
+
+Parameters:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ Options - Specifies optional actions to be taken
+
+ SAMP_AL_VERIFY_NO_MEMBERS_IN_ALIAS - Verify that none of the
+ Members are already present in the Alias.
+
+ MemberSids - Array of member Sids to be added.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_MEMBER_IN_ALIAS - The member already belongs to the alias.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList = NULL;
+ PSAMP_AL_MEMBER_ALIAS_LIST OldMemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ ULONG AliasRid = ((PSAMP_OBJECT) AliasHandle)->TypeBody.Alias.Rid;
+ ULONG MemberRid, SidIndex, MembershipCount;
+ PSID DomainSid = NULL;
+ PSID MemberSid = NULL;
+ SAMPR_ULONG_ARRAY AliasRids;
+
+ AliasRids.Count = 0;
+ AliasRids.Element = NULL;
+
+ //
+ // Verify that the cached Alias Membership information is valid.
+ //
+
+ if (!SampAlInfoIsValidForAlias(AliasHandle)) {
+
+ goto AddMembersToAliasFinish;
+ }
+
+ //
+ // If requested, verify that none of members already belong to the alias
+ //
+
+ if (Options & SAMP_AL_VERIFY_NO_MEMBERS_IN_ALIAS) {
+
+ Status = SampAlLookupMembersInAlias(
+ AliasHandle,
+ AliasRid,
+ MemberSids,
+ &MembershipCount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AddMembersToAliasError;
+ }
+
+ Status = STATUS_MEMBER_NOT_IN_ALIAS;
+
+ if (MembershipCount > 0) {
+
+ goto AddMembersToAliasError;
+ }
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Allocate Scratch Sid buffer. We will use this same buffer for splitting
+ // each Sid.
+ //
+
+ DomainSid = MIDL_user_allocate( RtlLengthRequiredSid( 256 ));
+
+ Status = STATUS_NO_MEMORY;
+
+ if (DomainSid == NULL) {
+
+ goto AddMembersToAliasError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Obtain pointer to Member Alias List.
+ //
+
+ MemberAliasList = SampAlAliasHandleToMemberAliasList( AliasHandle );
+ OldMemberAliasList = MemberAliasList;
+
+ //
+ // For each Sid, obtain its DomainSid and Rid. Then lookup its
+ // DomainSid to obtain the MemberDomain, creating one if necessary.
+ // Then lookup its Rid to obtain its MemberAccount, creating one
+ // if necessary. Then add the Alias to the MemebrAccount.
+ //
+
+ for (SidIndex = 0; SidIndex < MemberSids->Count; SidIndex++ ) {
+
+ MemberSid = MemberSids->Sids[ SidIndex ].SidPointer;
+
+ Status = SampSplitSid( MemberSid, &DomainSid, &MemberRid );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Lookup the Member Domain for this DomainSid in the Member Alias
+ // List.
+ //
+
+ Status = SampAlLookupMemberDomain(
+ MemberAliasList,
+ DomainSid,
+ &MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_NO_SUCH_DOMAIN) {
+
+ break;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // The Member Domain was not found. Create a new Member Domain
+ //
+
+ Status = SampAlCreateMemberDomain(
+ &MemberAliasList,
+ DomainSid,
+ &MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Create a Member Account entry.
+ //
+
+ Status = SampAlCreateMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ MemberRid,
+ SAMP_AL_INITIAL_MEMBER_ACCOUNT_ALIAS_CAPACITY,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ } else {
+
+ //
+ // We found the domain. This means that we have to lookup
+ // each Member Account. If a Member Account does not exist,
+ // we'll create one. Note that we may already have one due
+ // to this account being a member of another Alias.
+ //
+
+ Status = SampAlLookupMemberAccount(
+ MemberDomain,
+ MemberRid,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_NO_SUCH_MEMBER) {
+
+ break;
+ }
+
+ //
+ // Create a Member Account for this Rid,
+ //
+
+ Status = SampAlCreateMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ MemberRid,
+ SAMP_AL_INITIAL_MEMBER_ACCOUNT_ALIAS_CAPACITY,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+ }
+ }
+
+ //
+ // We now have a MemberAccount. Now add the Alias to it.
+ //
+
+ AliasRids.Count = 1;
+ AliasRids.Element = &AliasRid;
+
+ Status = SampAlAddAliasesToMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ &MemberAccount,
+ 0,
+ &AliasRids
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Deal with next Member Sid for the Alias.
+ //
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AddMembersToAliasError;
+ }
+
+ //
+ // If the Member Alias List has been reallocated, store its new address.
+ //
+
+ if (MemberAliasList != OldMemberAliasList) {
+
+ SampAlUpdateMemberAliasList( AliasHandle, MemberAliasList );
+ }
+
+AddMembersToAliasFinish:
+
+ //
+ // If necessary, free the DomainSid.
+ //
+
+ if (DomainSid != NULL) {
+
+ MIDL_user_free( DomainSid );
+ DomainSid = NULL;
+ }
+
+ return(Status);
+
+AddMembersToAliasError:
+
+ goto AddMembersToAliasFinish;
+}
+
+
+NTSTATUS
+SampAlRemoveMembersFromAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG Options,
+ IN PSAMPR_PSID_ARRAY MemberSids
+ )
+
+/*++
+
+Routine Description:
+
+ This function removes a list of members from an Alias.
+
+Arguments:
+
+ AliasHandle - The handle of an opened alias to operate on.
+
+ Options - Specifies optional actions to be taken
+
+ SAMP_AL_VERIFY_ALL_MEMBERS_IN_ALIAS - Verify that all of the
+ Members belong to the Alias.
+
+ MemberSids - Array of member Sids to be removed.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList = NULL;
+ PSAMP_AL_MEMBER_ALIAS_LIST OldMemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ BOOLEAN MemberDomainDeleted;
+ BOOLEAN MemberAccountDeleted;
+ ULONG AliasRid = ((PSAMP_OBJECT) AliasHandle)->TypeBody.Alias.Rid;
+ ULONG MemberRid, SidIndex, MembershipCount;
+ PSID DomainSid = NULL;
+ PSID MemberSid = NULL;
+ SAMPR_ULONG_ARRAY AliasRids;
+
+ AliasRids.Count = 0;
+ AliasRids.Element = NULL;
+
+ //
+ // If requested, verify that all of members already belong to the alias
+ //
+
+ if (Options & SAMP_AL_VERIFY_ALL_MEMBERS_IN_ALIAS) {
+
+ Status = SampAlLookupMembersInAlias(
+ AliasHandle,
+ AliasRid,
+ MemberSids,
+ &MembershipCount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto RemoveMembersFromAliasError;
+ }
+
+ Status = STATUS_MEMBER_NOT_IN_ALIAS;
+
+ if (MembershipCount < MemberSids->Count) {
+
+ goto RemoveMembersFromAliasError;
+ }
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Obtain pointer to Member Alias List.
+ //
+
+ MemberAliasList = SampAlAliasHandleToMemberAliasList( AliasHandle );
+ OldMemberAliasList = MemberAliasList;
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto RemoveMembersFromAliasError;
+ }
+
+ //
+ // For each Sid, obtain its DomainSid and Rid. Then lookup its
+ // DomainSid to obtain the MemberDomain. Then lookup its Rid to obtain
+ // its MemberAccount. Then remove the Alias from the MemberAccount.
+ //
+
+ for (SidIndex = 0; SidIndex < MemberSids->Count; SidIndex++ ) {
+
+ MemberSid = MemberSids->Sids[ SidIndex ].SidPointer;
+
+ SampSplitSid( MemberSid, &DomainSid, &MemberRid );
+
+ //
+ // Lookup the Member Domain for this DomainSid in the Member Alias
+ // List.
+ //
+
+ Status = SampAlLookupMemberDomain(
+ MemberAliasList,
+ DomainSid,
+ &MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_MEMBER_NOT_IN_ALIAS) {
+
+ break;
+ }
+
+ if (Options & SAMP_AL_VERIFY_ALL_MEMBERS_IN_ALIAS) {
+
+ ASSERT( FALSE );
+ }
+
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+
+ //
+ // We found the domain. This means that we have to lookup
+ // each Member Account. If a Member Account does not exist,
+ // we'll just skip this account unless we already checked existence.
+ // If we checked existence and we can't find it now, its an
+ // internal error.
+ //
+
+ Status = SampAlLookupMemberAccount(
+ MemberDomain,
+ MemberRid,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_MEMBER_NOT_IN_ALIAS) {
+
+ break;
+ }
+
+ if (Options & SAMP_AL_VERIFY_ALL_MEMBERS_IN_ALIAS) {
+
+ ASSERT( FALSE);
+ }
+
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+
+ //
+ // We now have the MemberAccount. Now remove the Alias from it.
+ //
+
+ AliasRids.Count = 1;
+ AliasRids.Element = &AliasRid;
+
+ Status = SampAlRemoveAliasesFromMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ &MemberAccount,
+ 0,
+ &AliasRids,
+ &MemberDomainDeleted,
+ &MemberAccountDeleted
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Deal with next Member Sid for the Alias.
+ //
+
+ MIDL_user_free( DomainSid );
+ DomainSid = NULL;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto RemoveMembersFromAliasError;
+ }
+
+ //
+ // If the Member Alias List has been reallocated, store its new address.
+ //
+
+ if (MemberAliasList != OldMemberAliasList) {
+
+ SampAlUpdateMemberAliasList( AliasHandle, MemberAliasList );
+ }
+
+RemoveMembersFromAliasFinish:
+
+ return(Status);
+
+RemoveMembersFromAliasError:
+
+ goto RemoveMembersFromAliasFinish;
+}
+
+
+NTSTATUS
+SampAlLookupMembersInAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG AliasRid,
+ IN PSAMPR_PSID_ARRAY MemberSids,
+ OUT PULONG MembershipCount
+ )
+
+/*++
+
+Routine Description:
+
+ This function checks how many of a given list of Member Sids belong
+ to an Alias. It is called prior to updating Alias Memberships.
+
+Arguments:
+
+ AliasHandle - Handle to Alias Object
+
+ AliasRid - Specifies the Rid of the Alias
+
+ MemberSids - Pointer to counted array of pointers to Member Sids
+
+ MembershipCount - Receives count of member Sids in the given set
+ that belong to the alias.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ SAMPR_PSID_ARRAY AliasMemberSids;
+ ULONG OutputMembershipCount = 0;
+ ULONG SidIndex, AliasMemberSidIndex;
+ PSID Sid = NULL;
+ PSID AliasMemberSid = NULL;
+
+ //
+ // First, query the members of the Alias.
+ //
+
+ Status = SampAlQueryMembersOfAlias(
+ AliasHandle,
+ &AliasMemberSids
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto LookupMembersInAliasError;
+ }
+
+ //
+ // Now scan each of the given Member Sids and count it if it is a member
+ // of the Alias.
+ //
+
+ for (SidIndex = 0; SidIndex < MemberSids->Count; SidIndex++) {
+
+ Sid = MemberSids->Sids[ SidIndex].SidPointer;
+
+ for (AliasMemberSidIndex = 0;
+ AliasMemberSidIndex = AliasMemberSids.Count;
+ AliasMemberSidIndex++) {
+
+ AliasMemberSid = AliasMemberSids.Sids[ AliasMemberSidIndex].SidPointer;
+
+ if (RtlEqualSid( Sid, AliasMemberSid)) {
+
+ OutputMembershipCount++;
+ }
+ }
+ }
+
+ *MembershipCount = OutputMembershipCount;
+
+LookupMembersInAliasFinish:
+
+ return(Status);
+
+LookupMembersInAliasError:
+
+ *MembershipCount =0;
+ goto LookupMembersInAliasFinish;
+}
+
+
+NTSTATUS
+SampAlDeleteAlias(
+ IN SAMPR_HANDLE *AliasHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This function deletes an alias.
+
+Arguments:
+
+ AliasHandle - Pointer to Handle to Alias
+
+Return Values:
+
+ NTSTATUS - Standard Nt Result Code
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ BOOLEAN MemberDomainDeleted;
+ BOOLEAN MemberAccountDeleted;
+ ULONG AliasRid = ((PSAMP_OBJECT) *AliasHandle)->TypeBody.Alias.Rid;
+ LONG DomainIndex;
+ ULONG RidCount;
+ LONG DomainCount;
+ ULONG AccountIndex;
+ SAMPR_ULONG_ARRAY AliasRids;
+ AliasRids.Count = 1;
+ AliasRids.Element = &AliasRid;
+
+ //
+ // Obtain pointer to Member Alias List.
+ //
+
+ MemberAliasList = SampAlAliasHandleToMemberAliasList( *AliasHandle );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto DeleteAliasError;
+ }
+
+ //
+ // Traverse the Member Alias List. Look in every Member Account for the
+ // Alias and remove it if present. This is rather slow if there is a
+ // large number of alias relationships for diverse domains.
+ //
+ DomainCount = (LONG) MemberAliasList->DomainCount;
+ for (DomainIndex = 0,
+ MemberDomain = SampAlFirstMemberDomain( MemberAliasList );
+ DomainIndex < DomainCount;
+ DomainIndex++ ) {
+
+ RidCount = MemberDomain->RidCount;
+ for (AccountIndex = 0,
+ MemberAccount = SampAlFirstMemberAccount( MemberDomain );
+ AccountIndex < RidCount;
+ AccountIndex++ ) {
+
+ ASSERT(MemberAccount->Signature == SAMP_AL_MEMBER_ACCOUNT_SIGNATURE);
+ //
+ // We now have the MemberAccount. Now remove the Alias from it.
+ //
+
+ Status = SampAlRemoveAliasesFromMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ &MemberAccount,
+ 0,
+ &AliasRids,
+ &MemberDomainDeleted,
+ &MemberAccountDeleted
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status == STATUS_MEMBER_NOT_IN_ALIAS) {
+
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+
+ break;
+ }
+
+ //
+ // Move the the next member account unless the one we were pointing
+ // to was deleted (in which case the next one moved to us).
+ //
+
+ if (!MemberAccountDeleted) {
+ MemberAccount = SampAlNextMemberAccount( MemberAccount );
+ }
+
+ //
+ // If the member domain was deleted, then the count of members
+ // is off as is the member account pointer.
+ //
+
+ if (MemberDomainDeleted) {
+ break;
+ }
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Move the the next member domain unless the one we were pointing
+ // to was deleted (in which case the next one moved to us).
+ //
+
+ if (!MemberDomainDeleted) {
+ MemberDomain = SampAlNextMemberDomain( MemberDomain );
+ }
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto DeleteAliasError;
+ }
+
+DeleteAliasFinish:
+
+ return(Status);
+
+DeleteAliasError:
+
+ goto DeleteAliasFinish;
+
+}
+
+
+NTSTATUS
+SampAlRemoveAccountFromAllAliases(
+ IN PSID AccountSid,
+ IN BOOLEAN CheckAccess,
+ IN SAMPR_HANDLE DomainHandle OPTIONAL,
+ IN PULONG MembershipCount OPTIONAL,
+ IN PULONG *Membership OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes the specified account from the member list of all
+ aliases in this domain.
+
+Arguments:
+
+ AccountSid - The SID of the account being Removed.
+
+ CheckAccess - if TRUE, this routine will make sure that the caller
+ is allowed REMOVE_ALIAS_MEMBER access to this alias. If FALSE,
+ the caller is already known to have proper access.
+
+ DomainHandle - if CheckAccess is TRUE, this handle must be provided
+ to allow access to be checked.
+
+ MembershipCount - if CheckAccess is TRUE, this pointer must be
+ provided to receive the number of aliases the account was
+ deleted from.
+
+ Membership - if CheckAccess is TRUE, this pointer must be provided
+ to point to a list of aliases the account was removed from. The
+ caller must free this list with MIDL_user_free().
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user has been Removed from all aliases.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ BOOLEAN MemberDomainDeleted;
+ PSID DomainSid = NULL;
+ LONG DomainIndex;
+ ULONG MemberRid, AliasRid;
+ SAMPR_ULONG_ARRAY AliasRids;
+ AliasRids.Count = 1;
+ AliasRids.Element = &AliasRid;
+
+ //
+ // Obtain pointer to Member Alias List for the Current Transaction Domain.
+ //
+
+ DomainIndex = SampTransactionDomainIndex;
+ MemberAliasList = SampAlDomainIndexToMemberAliasList( DomainIndex );
+
+ //
+ // We remove the Account from all aliases by locating its Member Account
+ // structure and deleting it. First, find the Member Domain.
+ //
+
+ SampSplitSid( AccountSid, &DomainSid, &MemberRid );
+
+ //
+ // Lookup the Member Domain for this DomainSid in the Member Alias
+ // List.
+ //
+
+ Status = SampAlLookupMemberDomain(
+ MemberAliasList,
+ DomainSid,
+ &MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_NO_SUCH_DOMAIN) {
+
+ goto RemoveAccountFromAllAliasesError;
+ }
+
+ //
+ // There is no member Domain object for this account. This means
+ // the account does not belong to any aliases.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ goto RemoveAccountFromAllAliasesFinish;
+ }
+
+ //
+ // We found the Member Domain. Now find the Member Account.
+ //
+
+ Status = SampAlLookupMemberAccount(
+ MemberDomain,
+ MemberRid,
+ &MemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ if (Status != STATUS_NO_SUCH_MEMBER) {
+
+ goto RemoveAccountFromAllAliasesError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ goto RemoveAccountFromAllAliasesFinish;
+ }
+
+ //
+ // If CheckAccess = TRUE, return a list of Aliases that the account was
+ // a member of.
+ //
+
+ if (CheckAccess) {
+
+ *Membership = MIDL_user_allocate( MemberAccount->AliasCount * sizeof(ULONG));
+ *MembershipCount = MemberAccount->AliasCount;
+
+ Status = STATUS_NO_MEMORY;
+
+ if (*Membership == NULL) {
+
+ goto RemoveAccountFromAllAliasesError;
+ }
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // We now have the MemberAccount. Now delete it, thereby removing the
+ // account from all Aliases.
+ //
+
+ Status = SampAlDeleteMemberAccount(
+ &MemberAliasList,
+ &MemberDomain,
+ MemberAccount,
+ &MemberDomainDeleted
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto RemoveAccountFromAllAliasesError;
+ }
+
+RemoveAccountFromAllAliasesFinish:
+
+ //
+ // Free the Domain Sid buffer (if any)
+ //
+
+ if (DomainSid != NULL) {
+
+ MIDL_user_free( DomainSid );
+ DomainSid = NULL;
+ }
+
+ return(Status);
+
+RemoveAccountFromAllAliasesError:
+
+ if (CheckAccess) {
+
+ *Membership = NULL;
+ *MembershipCount = 0;
+ }
+
+ goto RemoveAccountFromAllAliasesFinish;
+}
+
+
+NTSTATUS
+SampAlBuildAliasInformation(
+ )
+
+/*++
+
+Routine Description:
+
+ This function builds the Alias Information for each of the SAM Local
+ Domains. For each Domain, this information consists of the Member Alias
+ List.
+
+Arguments:
+
+ None.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ LONG DomainIndex;
+
+ for (DomainIndex = 0; DomainIndex < (LONG) SampDefinedDomainsCount; DomainIndex++) {
+
+ if (SampAlEnableBuildingOfList[ DomainIndex]) {
+
+ Status = SampAlBuildMemberAliasList( DomainIndex);
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+ }
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto BuildAliasInformationError;
+ }
+
+BuildAliasInformationFinish:
+
+ return(Status);
+
+BuildAliasInformationError:
+
+ goto BuildAliasInformationFinish;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// //
+// Private functions //
+// //
+////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampAlCreateMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG Rid,
+ IN ULONG AliasCapacity,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates an empty Member Account in the specified Member Domain
+ for the specified Member Rid. There must not already be al account for this
+ Rid. The Member Account is appended to the end of any existing ones in the
+ Member Domain.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to Member Alias List.
+
+ MemberDomain - Pointer to Member Domain in which the Member Account is
+ to be created. The Member Domain must already exist.
+
+ Rid - Specifies the Account Rid.
+
+ AliasCapacity - Specifies the initial number of Alias Rids that the
+ MemberAccount can hold.
+
+ MemberAccount - Receives pointer to the newly created Member Account.
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG MaximumLengthMemberAccount;
+ PSAMP_AL_MEMBER_ACCOUNT OutputMemberAccount = NULL;
+
+ //
+ // Calculate the length of data needed for the new member Account entry.
+ //
+
+ MaximumLengthMemberAccount = SampAlLengthRequiredMemberAccount( AliasCapacity );
+
+ //
+ // Allocate space for the Member Account.
+ //
+
+ Status = SampAlAllocateMemberAccount(
+ MemberAliasList,
+ MemberDomain,
+ MaximumLengthMemberAccount,
+ &OutputMemberAccount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto CreateMemberAccountError;
+ }
+
+ //
+ // Scratch the new Member Account
+ //
+
+ OutputMemberAccount->Signature = SAMP_AL_MEMBER_ACCOUNT_SIGNATURE;
+ OutputMemberAccount->MaximumLength = MaximumLengthMemberAccount;
+ OutputMemberAccount->UsedLength =
+ SampAlOffsetFirstAlias( OutputMemberAccount );
+ ASSERT(OutputMemberAccount->MaximumLength >=
+ OutputMemberAccount->UsedLength);
+ OutputMemberAccount->Rid = Rid;
+ OutputMemberAccount->AliasCount = 0;
+
+ ((*MemberDomain)->RidCount)++;
+ *MemberAccount = OutputMemberAccount;
+
+CreateMemberAccountFinish:
+
+ return(Status);
+
+CreateMemberAccountError:
+
+ *MemberAccount = NULL;
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+
+ goto CreateMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlAllocateMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG MaximumLengthMemberAccount,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ )
+
+/*++
+
+Routine Description:
+
+ This function allocates the space for a new Member Account in a Member
+ Domain. If necessary, the Mmeber Domain and its associated Member Alias
+ List will be grown.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ MaximumLengthMemberAccount - Initial Maximum Length required for the
+ Member Account
+
+ MemberAccount - receives pointer to the newly allocated Member Account
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG SpaceAvailable;
+
+ //
+ // Calculate the space available in the Member Domain
+ //
+
+ SpaceAvailable = (*MemberDomain)->MaximumLength - (*MemberDomain)->UsedLength;
+
+ if (MaximumLengthMemberAccount > SpaceAvailable) {
+
+ Status = SampAlGrowMemberDomain(
+ MemberAliasList,
+ MemberDomain,
+ MaximumLengthMemberAccount - SpaceAvailable
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AllocateMemberAccountError;
+ }
+ }
+
+ //
+ // The Member Domain is now guaranteed to be large enough. Reserve the
+ // space for the new Member Account.
+ //
+
+ *MemberAccount = SampAlNextNewMemberAccount(*MemberDomain);
+ (*MemberDomain)->UsedLength += MaximumLengthMemberAccount;
+ ASSERT((*MemberDomain)->MaximumLength >=
+ (*MemberDomain)->UsedLength);
+
+AllocateMemberAccountFinish:
+
+ return(Status);
+
+AllocateMemberAccountError:
+
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+ *MemberAccount = NULL;
+
+ goto AllocateMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlGrowMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG ExtraSpaceRequired
+ )
+
+/*++
+
+Routine Description:
+
+ This function grows a Member Account by at least the requested amount. If
+ necessary, the containing Member Domain and Member Alias List will also be
+ grown.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ MemberAccount - Pointer to Pointer to the Member Account.
+
+ ExtraSpaceRequired - Extra space needed in the Member Account.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG SpaceAvailable, MemberAccountOffset, CopyLength;
+ PUCHAR Destination = NULL;
+ PUCHAR Source = NULL;
+
+ //
+ // Calculate the space available in the Member Domain
+ //
+
+ SpaceAvailable = (*MemberDomain)->MaximumLength - (*MemberDomain)->UsedLength;
+
+ if (ExtraSpaceRequired > SpaceAvailable) {
+
+ //
+ // We need to grow the Member Domain. Calculate the offset of the
+ // Member Account in the old Member Domain, grow the Member Domain
+ // and then calculate the new address of the Member Account.
+ //
+
+ MemberAccountOffset = SampAlMemberAccountToOffset(
+ *MemberDomain,
+ *MemberAccount
+ );
+
+ Status = SampAlGrowMemberDomain(
+ MemberAliasList,
+ MemberDomain,
+ ExtraSpaceRequired - SpaceAvailable
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto GrowMemberAccountError;
+ }
+
+ *MemberAccount = SampAlMemberAccountFromOffset(
+ *MemberDomain,
+ MemberAccountOffset
+ );
+
+ }
+
+ //
+ // The Member Domain is now guaranteed to be large enough.
+ // Now shift any Member Accounts that follow the one being grown
+ // up to make room for the expanded Member Account. The source address
+ // for the move is the address of the next Member Account (if any) based
+ // on the existing size of the Member Account. The destination address
+ // of the move is the address of the next Member Account (if any) based
+ // on the new size of the Member Account.
+ //
+
+ Source = (PUCHAR) SampAlNextMemberAccount( *MemberAccount );
+ (*MemberAccount)->MaximumLength += ExtraSpaceRequired;
+ Destination = (PUCHAR) SampAlNextMemberAccount( *MemberAccount );
+ CopyLength =
+ (((PUCHAR)(SampAlNextNewMemberAccount(*MemberDomain))) - Source);
+
+ //
+ // Reserve the space in the Member Domain. If all's well, the
+ // end of the destination buffer should match the updated end of the
+ // used area of the Member Domain.
+ //
+
+ (*MemberDomain)->UsedLength += ExtraSpaceRequired;
+ ASSERT((*MemberDomain)->MaximumLength >=
+ (*MemberDomain)->UsedLength);
+
+ ASSERT( Destination + CopyLength ==
+ (PUCHAR) SampAlNextNewMemberAccount( *MemberDomain ));
+ ASSERT( Destination + CopyLength <=
+ (PUCHAR)(*MemberAliasList) + (*MemberAliasList)->MaximumLength );
+ ASSERT( Destination + CopyLength <=
+ (PUCHAR)(*MemberDomain) + (*MemberDomain)->MaximumLength );
+
+ if (CopyLength > 0) {
+
+ RtlMoveMemory( Destination, Source, CopyLength );
+ }
+
+GrowMemberAccountFinish:
+
+ return(Status);
+
+GrowMemberAccountError:
+
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+
+ goto GrowMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlLookupMemberAccount(
+ IN PSAMP_AL_MEMBER_DOMAIN MemberDomain,
+ IN ULONG MemberRid,
+ OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount
+ )
+
+/*++
+
+Routine Description:
+
+ This function looks up an Account Rid in a Member Domain to see if there
+ is a Member Account structure for it.
+
+Arguments:
+
+ MemberDomain - Pointer to Member Domain
+
+ MemberRid - Specifies the Account Rid
+
+ MemberAccount - Receives pointer to Member Account if found.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_ACCOUNT NextMemberAccount = NULL;
+ ULONG RidIndex;
+ BOOLEAN AccountFound = FALSE;
+
+
+ for (RidIndex = 0,
+ NextMemberAccount = SampAlFirstMemberAccount( MemberDomain );
+ RidIndex < MemberDomain->RidCount;
+ RidIndex++, NextMemberAccount = SampAlNextMemberAccount( NextMemberAccount)) {
+
+ if (MemberRid == NextMemberAccount->Rid) {
+
+ AccountFound = TRUE;
+
+ break;
+ }
+ }
+
+ Status = STATUS_NO_SUCH_MEMBER;
+
+ if (!AccountFound) {
+
+ goto LookupMemberAccountError;
+ }
+
+ *MemberAccount = NextMemberAccount;
+ Status = STATUS_SUCCESS;
+
+LookupMemberAccountFinish:
+
+ return(Status);
+
+LookupMemberAccountError:
+
+ goto LookupMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlAddAliasesToMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG Options,
+ IN PSAMPR_ULONG_ARRAY AliasRids
+ )
+
+/*++
+
+Routine Description:
+
+ This function adds an array of aliases to a Member Account. An error
+ will be returned if any of the aliases exist in the Member Account.
+ If necessary, the containing Member Account, Member Domain and Member
+ Alias List will also be grown.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ MemberAccount - Pointer to Pointer to the Member Account.
+
+ Options - Specifies optional actions to be taken
+
+ SAMP_AL_VERIFY_NO_ALIASES_IN_ACCOUNT - Verify that none of the
+ Aliases presented belong to the various Member Accounts.
+
+ AliasRids - Pointer to counted array of Alias Rids.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG SpaceRequired, SpaceAvailable, CopyLength;
+ PUCHAR Source = NULL;
+ PUCHAR Destination = NULL;
+ ULONG ExistingAliasCount;
+
+ //
+ // If requested, verify that none of the Aliases are already
+ // in the Member Account
+ //
+
+ if (Options & SAMP_AL_VERIFY_NO_ALIASES_IN_ACCOUNT) {
+
+ Status = SampAlLookupAliasesInMemberAccount(
+ *MemberAccount,
+ AliasRids,
+ &ExistingAliasCount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AddAliasesToMemberAccountError;
+ }
+
+ Status = STATUS_MEMBER_IN_ALIAS;
+
+ if (ExistingAliasCount > 0) {
+
+ goto AddAliasesToMemberAccountError;
+ }
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Calculate the space required for the new Aliases.
+ //
+
+ SpaceRequired = AliasRids->Count * sizeof( ULONG );
+
+ //
+ // If there is not enough space available in the Member Account,
+ // grow it.
+ //
+
+ SpaceAvailable = (*MemberAccount)->MaximumLength - (*MemberAccount)->UsedLength;
+
+ if (SpaceRequired > SpaceAvailable) {
+
+ Status = SampAlGrowMemberAccount(
+ MemberAliasList,
+ MemberDomain,
+ MemberAccount,
+ SpaceRequired - SpaceAvailable
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AddAliasesToMemberAccountError;
+ }
+ }
+
+ //
+ // The Member Account is now large enough. Copy in the aliases.
+ //
+
+ Destination = (PUCHAR) SampAlNextNewAliasInMemberAccount( *MemberAccount );
+ Source = (PUCHAR) AliasRids->Element;
+ CopyLength = SpaceRequired;
+ (*MemberAccount)->UsedLength += SpaceRequired;
+ ASSERT((*MemberAccount)->MaximumLength >=
+ (*MemberAccount)->UsedLength);
+ RtlMoveMemory( Destination, Source, CopyLength );
+
+ //
+ // Update the count of Aliases both in this Member Account and in the
+ // Member Alias List.
+ //
+
+ (*MemberAccount)->AliasCount += AliasRids->Count;
+
+AddAliasesToMemberAccountFinish:
+
+ return(Status);
+
+AddAliasesToMemberAccountError:
+
+ goto AddAliasesToMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlLookupAliasesInMemberAccount(
+ IN PSAMP_AL_MEMBER_ACCOUNT MemberAccount,
+ IN PSAMPR_ULONG_ARRAY AliasRids,
+ OUT PULONG ExistingAliasCount
+ )
+
+/*++
+
+Routine Description:
+
+ This function checks a set of Alias Rids to see if any are present in a
+ Member Account.
+
+Arguments:
+
+ MemberAccount - Pointer to Member Account
+
+ AliasRids - Specifies counted array of Alias Rids.
+
+ ExistingAliasCount - Receives a count of the Alias Rids presented that
+ are already in the Member Account.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG AliasIndex, AliasInMemberAccountIndex;
+
+ //
+ // Scan the Alias Rids, looking each one up.
+ //
+
+ for (AliasIndex = 0; AliasIndex < AliasRids->Count; AliasRids++ ) {
+
+ for (AliasInMemberAccountIndex = 0;
+ AliasInMemberAccountIndex < MemberAccount->AliasCount;
+ AliasInMemberAccountIndex++) {
+
+ if (AliasRids->Element[ AliasIndex ] ==
+ MemberAccount->AliasRids[ AliasInMemberAccountIndex ] ) {
+
+ (*ExistingAliasCount)++;
+ }
+ }
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+SampAlRemoveAliasesFromMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT *MemberAccount,
+ IN ULONG Options,
+ IN PSAMPR_ULONG_ARRAY AliasRids,
+ OUT PBOOLEAN MemberDomainDeleted,
+ OUT PBOOLEAN MemberAccountDeleted
+ )
+
+/*++
+
+Routine Description:
+
+ This function removes aliases from a Member Account. The Aliases need
+ not already exist unless an option to check that they do exist is
+ specified. No down sizing of the Member Account occurs, but an
+ empty one will be deleted.
+
+ NOTE: I don't know why ScottBi made MemberAliasList, MemberDomain, and
+ MemberAccount parameters pointers to pointers. He never updates
+ the pointers so he could have passed them in directly. JK
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ MemberAccount - Pointer to Pointer to the Member Account.
+
+ Options - Specifies optional actions to be taken
+
+ SAMP_AL_VERIFY_ALL_ALIASES_IN_ACCOUNT - Verify that none of the
+ Aliases presented belong to the Member Account.
+
+ MemberDomainDeleted - Will be set to TRUE if the member domain
+ pointed to by MemberDomain was deleted. Otherwise FALSE is returned.
+
+ MemberAccountDeleted - Will be set to TRUE if the member account
+ pointed to by MemberAccount was deleted. Otherwise FALSE is returned.
+
+ AliasRids - Pointer to counted array of Alias Rids.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG ExistingAliasIndex, LastAliasIndex, RemoveAliasIndex, ExistingAlias;
+ ULONG ExistingAliasCount;
+
+ (*MemberDomainDeleted) = FALSE;
+ (*MemberAccountDeleted) = FALSE;
+
+ //
+ // If requested, verify that all of the Aliases are already
+ // in the Member Account
+ //
+
+ if (Options & SAMP_AL_VERIFY_ALL_ALIASES_IN_ACCOUNT) {
+
+ Status = SampAlLookupAliasesInMemberAccount(
+ *MemberAccount,
+ AliasRids,
+ &ExistingAliasCount
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto RemoveAliasesFromMemberAccountError;
+ }
+
+ Status = STATUS_MEMBER_IN_ALIAS;
+
+ if (ExistingAliasCount < AliasRids->Count) {
+
+ goto RemoveAliasesFromMemberAccountError;
+ }
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // If the Member Account is empty, then somebody forgot to delete it
+ //
+
+ ASSERT((*MemberAccount)->AliasCount != 0);
+
+
+ LastAliasIndex = (*MemberAccount)->AliasCount - 1;
+
+ for (ExistingAliasIndex = 0;
+ ExistingAliasIndex < (*MemberAccount)->AliasCount;
+ ExistingAliasIndex++) {
+
+ ExistingAlias = (*MemberAccount)->AliasRids[ ExistingAliasIndex ];
+
+ for (RemoveAliasIndex = 0;
+ RemoveAliasIndex < AliasRids->Count;
+ RemoveAliasIndex++) {
+
+ if (ExistingAlias == AliasRids->Element[ RemoveAliasIndex ]) {
+
+ //
+ // We're to delete this Alias. If this Alias Rid is not at the
+ // end of the list contained in the Member Account, overwrite
+ // it with the one at the end of the list.
+ //
+
+ if (ExistingAliasIndex < LastAliasIndex) {
+
+ (*MemberAccount)->AliasRids[ ExistingAliasIndex] =
+ (*MemberAccount)->AliasRids[ LastAliasIndex];
+ }
+
+ (*MemberAccount)->AliasCount--;
+ (*MemberAccount)->UsedLength -= sizeof(ULONG);
+ ASSERT((*MemberAccount)->MaximumLength >=
+ (*MemberAccount)->UsedLength);
+
+ //
+ // If the Member Account is now empty, quit.
+ //
+
+ if ((*MemberAccount)->AliasCount == 0) {
+
+ break;
+ }
+
+ LastAliasIndex--;
+ }
+ }
+
+ //
+ // If the Member Account is now empty, quit.
+ //
+
+ if ((*MemberAccount)->AliasCount == 0) {
+
+ break;
+ }
+ }
+
+ //
+ // If the Member Account is now empty, delete it.
+ //
+
+ if ((*MemberAccount)->AliasCount == 0) {
+
+ Status = SampAlDeleteMemberAccount(
+ MemberAliasList,
+ MemberDomain,
+ *MemberAccount,
+ MemberDomainDeleted
+ );
+ if (NT_SUCCESS(Status)) {
+ (*MemberAccountDeleted) = TRUE;
+ }
+ }
+
+RemoveAliasesFromMemberAccountFinish:
+
+ return(Status);
+
+RemoveAliasesFromMemberAccountError:
+
+ goto RemoveAliasesFromMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlDeleteMemberAccount(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN OUT PSAMP_AL_MEMBER_ACCOUNT MemberAccount,
+ OUT PBOOLEAN MemberDomainDeleted
+ )
+
+/*++
+
+Routine Description:
+
+ This function deletes a Member Account. Currently, the containing
+ Member Domain and Member Alias List are not shrunk, but the containing
+ Member Domain will be deleted if empty.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ MemberAccount - Pointer to the Member Account.
+
+ MemberDomainDeleted - Will be set to TRUE if the member domain
+ pointed to by MemberDomain was deleted. Otherwise FALSE is returned.
+
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PUCHAR Source = NULL;
+ PUCHAR Destination = NULL;
+ ULONG CopyLength;
+
+ (*MemberDomainDeleted) = FALSE;
+
+ //
+ // Calculate pointers for moving the residual portion of the Member
+ // Domain down to close the gap left by the extant Member Account.
+ // unused space. The start of the residual portion is the end of the
+ // Member Account being deleted. The length of the residual portion is
+ // the distance from the start to the end of the used portion of the
+ // Member Domain.
+ //
+
+ Source = (PUCHAR) SampAlNextMemberAccount( MemberAccount );
+ Destination = (PUCHAR) MemberAccount;
+ CopyLength = (PUCHAR) SampAlNextNewMemberAccount( *MemberDomain ) - Source;
+
+ (*MemberDomain)->UsedLength -= MemberAccount->MaximumLength;
+ ASSERT((*MemberDomain)->MaximumLength >=
+ (*MemberDomain)->UsedLength);
+ (*MemberDomain)->RidCount--;
+
+ if (CopyLength > 0) {
+
+ RtlMoveMemory( Destination, Source, CopyLength );
+#if DBG
+ {
+ PSAMP_AL_MEMBER_ACCOUNT Member = (PSAMP_AL_MEMBER_ACCOUNT) Destination;
+ ASSERT(Member->Signature == SAMP_AL_MEMBER_ACCOUNT_SIGNATURE);
+ }
+
+#endif
+ }
+
+ //
+ // If the Member Domain now has no Member Accounts, delete it.
+ //
+
+ if ((*MemberDomain)->RidCount == 0) {
+
+ Status = SampAlDeleteMemberDomain(
+ MemberAliasList,
+ *MemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+ goto DeleteMemberAccountError;
+ }
+ (*MemberDomainDeleted) = TRUE;
+ }
+
+DeleteMemberAccountFinish:
+
+ return(Status);
+
+DeleteMemberAccountError:
+
+ goto DeleteMemberAccountFinish;
+}
+
+
+NTSTATUS
+SampAlCreateMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN PSID DomainSid,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates a new Member Domain in the specified Alias Member
+ List.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to Alias Member List.
+
+ DomainSid - Pointer to Sid of Domain to which this MemberDomain
+ relates.
+
+ MemberDomain - Receives pointer to the newly created Member Domain.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_DOMAIN OutputMemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT OutputMemberAccount = NULL;
+ ULONG MaximumLengthMemberDomain;
+ ULONG DomainSidLength = RtlLengthSid(DomainSid);
+ ULONG AlternativeLength;
+
+
+ //
+ // Allocate the Member Domain.
+ //
+
+ MaximumLengthMemberDomain = SAMP_AL_INITIAL_MEMBER_DOMAIN_LENGTH;
+ AlternativeLength = FIELD_OFFSET(SAMP_AL_MEMBER_DOMAIN, DomainSid)
+ + DomainSidLength;
+ if (MaximumLengthMemberDomain < AlternativeLength) {
+ MaximumLengthMemberDomain = AlternativeLength;
+ }
+
+ Status = SampAlAllocateMemberDomain(
+ MemberAliasList,
+ MaximumLengthMemberDomain,
+ &OutputMemberDomain
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto CreateMemberDomainError;
+ }
+
+ //
+ // Setup the new Member Domain entry.
+ //
+
+ OutputMemberDomain->MaximumLength = MaximumLengthMemberDomain;
+ OutputMemberDomain->RidCount = 0;
+ OutputMemberDomain->Signature = SAMP_AL_MEMBER_DOMAIN_SIGNATURE;
+
+ RtlCopySid(
+ DomainSidLength,
+ &OutputMemberDomain->DomainSid,
+ DomainSid
+ );
+
+ OutputMemberDomain->UsedLength = SampAlOffsetFirstMemberAccount(
+ OutputMemberDomain
+ );
+ ASSERT(OutputMemberDomain->MaximumLength >=
+ OutputMemberDomain->UsedLength);
+
+ ((*MemberAliasList)->DomainCount)++;
+ *MemberDomain = OutputMemberDomain;
+
+CreateMemberDomainFinish:
+
+ return(Status);
+
+CreateMemberDomainError:
+
+ *MemberDomain = NULL;
+ goto CreateMemberDomainFinish;
+}
+
+
+NTSTATUS
+SampAlAllocateMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN ULONG MaximumLengthMemberDomain,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ )
+
+/*++
+
+Routine Description:
+
+ This function allocates the space for a new Member Domain in a Member
+ Alias List. If necessary, the Member Alias List will be grown.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MaximumLengthMemberDomain - Initial Maximum Length required for the
+ Member Domain
+
+ MemberDomain - Receives pointer to the Member Domain
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG SpaceAvailable;
+
+ //
+ // Calculate the space available in the Member Alias List
+ //
+
+ SpaceAvailable = (*MemberAliasList)->MaximumLength - (*MemberAliasList)->UsedLength;
+
+ if (MaximumLengthMemberDomain > SpaceAvailable) {
+
+ Status = SampAlGrowMemberAliasList(
+ MemberAliasList,
+ MaximumLengthMemberDomain - SpaceAvailable
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto AllocateMemberDomainError;
+ }
+ }
+
+ //
+ // The Member Alias List is now guaranteed to be large enough. Reserve the
+ // space for the new Member Domain.
+ //
+
+ *MemberDomain = SampAlNextNewMemberDomain(*MemberAliasList);
+ (*MemberAliasList)->UsedLength += MaximumLengthMemberDomain;
+ ASSERT((*MemberAliasList)->MaximumLength >=
+ (*MemberAliasList)->UsedLength);
+
+AllocateMemberDomainFinish:
+
+ return(Status);
+
+AllocateMemberDomainError:
+
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+ *MemberDomain = NULL;
+ goto AllocateMemberDomainFinish;
+}
+
+
+NTSTATUS
+SampAlGrowMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain,
+ IN ULONG ExtraSpaceRequired
+ )
+
+/*++
+
+Routine Description:
+
+ This function grows a Member Domain by at least the requested amount. If
+ necessary, the Member Alias List will also be grown.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to pointer to the Member Domain
+
+ ExtraSpaceRequired - Extra space needed in the Member Domain.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG SpaceAvailable, MemberDomainOffset, CopyLength;
+ PUCHAR Destination = NULL;
+ PUCHAR Source = NULL;
+
+ //
+ // Calculate the space available in the Member Alias List
+ //
+
+ SpaceAvailable = (*MemberAliasList)->MaximumLength - (*MemberAliasList)->UsedLength;
+
+ if (ExtraSpaceRequired > SpaceAvailable) {
+
+ //
+ // We need to grow the Member Alias List. Calculate the offset of the
+ // Member Domain in the old Member Alias List, grow the Member Alias
+ // List and then calculate the new address of the Member Domain.
+ //
+
+ MemberDomainOffset = SampAlMemberDomainToOffset(
+ *MemberAliasList,
+ *MemberDomain
+ );
+
+ Status = SampAlGrowMemberAliasList(
+ MemberAliasList,
+ ExtraSpaceRequired - SpaceAvailable
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto GrowMemberDomainError;
+ }
+
+ //
+ // Calculate the new address of the Member Domain
+ //
+
+ *MemberDomain = SampAlMemberDomainFromOffset(
+ *MemberAliasList,
+ MemberDomainOffset
+ );
+ }
+
+ //
+ // The Member Alias List is now guaranteed to be large enough.
+ // Now shift any Member Domains that follow the one being grown
+ // up to make room for the expanded Member Domain. The source address
+ // for the move is the address of the next Member Domain (if any) based
+ // on the existing size of the Member Domain. The destination address
+ // of the move is the address of the next Member Domain (if any) based
+ // on the new size of the Member Domain.
+ //
+
+ Source = (PUCHAR) SampAlNextMemberDomain( *MemberDomain );
+ (*MemberDomain)->MaximumLength += ExtraSpaceRequired;
+ Destination = (PUCHAR) SampAlNextMemberDomain( *MemberDomain );
+ CopyLength =
+ (((PUCHAR)(SampAlNextNewMemberDomain(*MemberAliasList))) - Source);
+
+ //
+ // Reserve the space in the Member Alias List. If all's well, the
+ // end of the destination buffer should match the updated end of the
+ // used area of the member Alias List.
+ //
+
+ (*MemberAliasList)->UsedLength += ExtraSpaceRequired;
+ ASSERT((*MemberAliasList)->MaximumLength >=
+ (*MemberAliasList)->UsedLength);
+
+ ASSERT( Destination + CopyLength ==
+ (PUCHAR) SampAlNextNewMemberDomain( *MemberAliasList ));
+ ASSERT( Destination + CopyLength <=
+ (PUCHAR)(*MemberAliasList) + (*MemberAliasList)->MaximumLength );
+
+ if (CopyLength > 0) {
+
+ RtlMoveMemory( Destination, Source, CopyLength );
+ }
+
+GrowMemberDomainFinish:
+
+ return(Status);
+
+GrowMemberDomainError:
+
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+
+ goto GrowMemberDomainFinish;
+}
+
+
+NTSTATUS
+SampAlLookupMemberDomain(
+ IN PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList,
+ IN PSID DomainSid,
+ OUT PSAMP_AL_MEMBER_DOMAIN *MemberDomain
+ )
+
+/*++
+
+Routine Description:
+
+This function looks up a Domain Sid in a Member Alias List to find its
+Member Domain structure (if any).
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to Member Alias List
+
+ DomainSid - Domain Sid whose Member Domain is to be found.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_DOMAIN NextMemberDomain = NULL;
+ LONG DomainIndex;
+ BOOLEAN DomainFound = FALSE;
+
+
+ for (DomainIndex = 0,
+ NextMemberDomain = SampAlFirstMemberDomain( MemberAliasList );
+ DomainIndex < (LONG) MemberAliasList->DomainCount;
+ DomainIndex++, NextMemberDomain = SampAlNextMemberDomain( NextMemberDomain )
+ ) {
+
+ if (RtlEqualSid( DomainSid, &NextMemberDomain->DomainSid)) {
+
+ DomainFound = TRUE;
+
+ break;
+ }
+ }
+
+ Status = STATUS_NO_SUCH_DOMAIN;
+
+ if (!DomainFound) {
+
+ goto LookupMemberDomainError;
+ }
+
+ *MemberDomain = NextMemberDomain;
+ Status = STATUS_SUCCESS;
+
+LookupMemberDomainFinish:
+
+ return(Status);
+
+LookupMemberDomainError:
+
+ *MemberDomain = NULL;
+ goto LookupMemberDomainFinish;
+}
+
+
+NTSTATUS
+SampAlDeleteMemberDomain(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN OUT PSAMP_AL_MEMBER_DOMAIN MemberDomain
+ )
+
+/*++
+
+Routine Description:
+
+ This function deletes a Member Domain. The Member Domain may contain
+ zero or more Member Accounts. The containing Member Alias List is shrunk.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ MemberDomain - Pointer to the Member Domain
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PUCHAR Source = NULL;
+ PUCHAR Destination = NULL;
+ ULONG CopyLength;
+
+ //
+ // Calculate pointers for moving the residual portion of the
+ // Member Alias List down to close the gap left by the extant Member
+ // Domain. The start of the residual portion is the next Member Domain.
+ // The size of the portion is the distance between the start and the
+ // used portion of the Member Alias List.
+ //
+
+ Source = (PUCHAR) SampAlNextMemberDomain( MemberDomain );
+ Destination = (PUCHAR) MemberDomain;
+ CopyLength = ((PUCHAR) SampAlNextNewMemberDomain( *MemberAliasList )) - Source;
+
+ (*MemberAliasList)->UsedLength -= MemberDomain->MaximumLength;
+ ASSERT((*MemberAliasList)->MaximumLength >=
+ (*MemberAliasList)->UsedLength);
+ (*MemberAliasList)->DomainCount--;
+
+ if (CopyLength > 0) {
+
+ RtlMoveMemory( Destination, Source, CopyLength );
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+SampAlCreateMemberAliasList(
+ IN LONG DomainIndex,
+ IN ULONG InitialMemberAliasListLength,
+ OUT OPTIONAL PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates an empty Member Alias List for the specified SAM Local
+ Domain. The Member Alias List will be marked invalid.
+
+Arguments:
+
+ DomainIndex - Specifies the Local SAM Domain
+
+ InitialMemberAliasListLength - Specifies the initial maximum length of the
+ Member Alias List in bytes
+
+ MemberAliasList - Optional pointer to location in which a pointer to the
+ Member Alias List will be returned. Note that the pointer can always
+ be retrieved given the DomainIndex.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSAMP_AL_MEMBER_ALIAS_LIST OutputMemberAliasList = NULL;
+ PSAMP_AL_ALIAS_INFORMATION AliasInformation = NULL;
+
+ //
+ // Allocate memory for the list.
+ //
+
+ OutputMemberAliasList = MIDL_user_allocate( InitialMemberAliasListLength );
+
+ Status = STATUS_NO_MEMORY;
+
+ if (OutputMemberAliasList == NULL) {
+
+ goto CreateMemberAliasListError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Scratch the List header
+ //
+
+ OutputMemberAliasList->Signature = SAMP_AL_MEMBER_ALIAS_LIST_SIGNATURE;
+ OutputMemberAliasList->MaximumLength = InitialMemberAliasListLength;
+ OutputMemberAliasList->UsedLength = SampAlOffsetFirstMemberDomain(
+ OutputMemberAliasList
+ );
+ ASSERT(OutputMemberAliasList->MaximumLength >=
+ OutputMemberAliasList->UsedLength);
+
+ OutputMemberAliasList->DomainIndex = DomainIndex;
+ OutputMemberAliasList->DomainCount = 0;
+
+ //
+ // Link the Member Alias List to the SAM Local Domain info
+ //
+
+ AliasInformation = &(SampDefinedDomains[ DomainIndex].AliasInformation);
+ AliasInformation->MemberAliasList = OutputMemberAliasList;
+
+ *MemberAliasList = OutputMemberAliasList;
+
+CreateMemberAliasListFinish:
+
+ return(Status);
+
+CreateMemberAliasListError:
+
+ *MemberAliasList = NULL;
+ goto CreateMemberAliasListFinish;
+}
+
+
+NTSTATUS
+SampAlGrowMemberAliasList(
+ IN OUT PSAMP_AL_MEMBER_ALIAS_LIST *MemberAliasList,
+ IN ULONG ExtraSpaceRequired
+ )
+
+/*++
+
+Routine Description:
+
+ This function grows a Member Alias List by at least the requested amount.
+
+Arguments:
+
+ MemberAliasList - Pointer to pointer to the Member Alias List.
+
+ ExtraSpaceRequired - Extra space needed in the Member Alias List.
+
+Return Values:
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG NewMaximumLengthMemberAliasList;
+ PSAMP_AL_MEMBER_ALIAS_LIST OutputMemberAliasList = NULL;
+
+ //
+ // Calculate the new size of the Member Alias List needed. Round up to
+ // a multiple of the granularity.
+ //
+
+ NewMaximumLengthMemberAliasList = (*MemberAliasList)->MaximumLength +
+ ExtraSpaceRequired;
+
+ NewMaximumLengthMemberAliasList +=
+ (SAMP_AL_MEMBER_ALIAS_LIST_DELTA - (ULONG) 1);
+
+ NewMaximumLengthMemberAliasList &=
+ ((ULONG)(~(SAMP_AL_MEMBER_ALIAS_LIST_DELTA - (ULONG) 1)));
+
+ //
+ // Allocate memory for the grown Member Alias List.
+ //
+
+ OutputMemberAliasList = MIDL_user_allocate(
+ NewMaximumLengthMemberAliasList
+ );
+
+ Status = STATUS_NO_MEMORY;
+
+ if (OutputMemberAliasList == NULL) {
+
+ goto GrowMemberAliasListError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Copy the old list to the new list and the the new maximum length.
+ // Return pointer to new list.
+ //
+
+ RtlMoveMemory(
+ OutputMemberAliasList,
+ *MemberAliasList,
+ (*MemberAliasList)->UsedLength
+ );
+
+ OutputMemberAliasList->MaximumLength = NewMaximumLengthMemberAliasList;
+ ASSERT(OutputMemberAliasList->MaximumLength >=
+ OutputMemberAliasList->UsedLength);
+ *MemberAliasList = OutputMemberAliasList;
+
+GrowMemberAliasListFinish:
+
+ return(Status);
+
+GrowMemberAliasListError:
+
+ SampAlInfoMakeInvalid( (*MemberAliasList)->DomainIndex );
+
+ goto GrowMemberAliasListFinish;
+}
+
+
+NTSTATUS
+SampAlBuildMemberAliasList(
+ IN LONG DomainIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This function builds the Member Alias List for the specified SAM Local
+ Domain. For each Alias, its list of member Sids is read from backing
+ storage and MemberDomain and MemberAccount blocks are created.
+
+Arguments:
+
+ DomainIndex - Specifies the SAM Local Domain
+
+--*/
+
+{
+ NTSTATUS Status, EnumerationStatus;
+ PSAMP_AL_MEMBER_ALIAS_LIST OutputMemberAliasList = NULL;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain = NULL;
+ PSAMP_AL_MEMBER_ACCOUNT MemberAccount = NULL;
+ SAMPR_ULONG_ARRAY AliasRids;
+ ULONG Rids[1], EnumerationContext, AliasCount, AliasRid;
+ ULONG AliasIndex;
+ PSAMP_OBJECT AccountContext = NULL;
+ PSAMP_OBJECT AliasContext = NULL;
+ SAMPR_PSID_ARRAY MemberSids;
+ ULONG DomainSidMaximumLength = RtlLengthRequiredSid( 256 );
+ PSAMPR_ENUMERATION_BUFFER EnumerationBuffer = NULL;
+ PSID DomainSid = NULL;
+ PSID MemberSid = NULL;
+
+ AliasRids.Element = Rids;
+
+ //
+ // Mark the Member Alias List invalid
+ //
+
+ SampAlInfoMakeInvalid( DomainIndex );
+
+
+ //
+ // Allocate a scratch Domain Sid for splitting Sids. This has length
+ // equal to maximum possible Sid length.
+ //
+
+ Status = STATUS_NO_MEMORY;
+
+ DomainSid = MIDL_user_allocate( DomainSidMaximumLength );
+
+ if (DomainSid == NULL) {
+
+ goto BuildMemberAliasListError;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Create an empty Member Alias List and connect it to the
+ // local SAM Domain.
+ //
+
+ Status = SampAlCreateMemberAliasList(
+ DomainIndex,
+ SAMP_AL_INITIAL_MEMBER_ALIAS_LIST_LENGTH,
+ &OutputMemberAliasList
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto BuildMemberAliasListError;
+ }
+
+ //
+ // For each Alias in the SAM local domain, add its members to the
+ // Alias List
+ //
+
+ EnumerationContext = 0;
+ EnumerationStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // It is currently necessary to set the Transaction Domain before
+ // calling SampEnumerateAccountNames even though we're not modifying
+ // anything. The is because called routine SampBuildAccountKeyName()
+ // uses this information.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+ SampSetTransactionDomain( DomainIndex );
+
+ while (EnumerationStatus == STATUS_MORE_ENTRIES) {
+
+ Status = SampEnumerateAccountNames(
+ SampAliasObjectType,
+ &EnumerationContext,
+ &EnumerationBuffer,
+ SAMP_AL_ENUM_PREFERRED_LENGTH,
+ 0,
+ &AliasCount,
+ TRUE
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ EnumerationStatus = Status;
+
+ for (AliasIndex = 0; AliasIndex < AliasCount; AliasIndex++) {
+
+ AliasRid = EnumerationBuffer->Buffer[ AliasIndex ].RelativeId;
+
+ //
+ // Create a context for the account.
+ //
+
+ Status = SampCreateAccountContext(
+ SampAliasObjectType,
+ AliasRid,
+ TRUE,
+ TRUE,
+ &AliasContext
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // There is a rather ugly feature of the way the DomainIndex
+ // field is used in context handles while initializing. This
+ // value is set to the count of SAM Local Domains! So, I am
+ // setting it to the DomainIndex for the SAM Local Domain we're
+ // initializing, since this AliasContext is used only by me.
+ //
+
+ AliasContext->DomainIndex = DomainIndex;
+
+ Status = SampAlQueryMembersOfAlias(
+ AliasContext,
+ &MemberSids
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // Add these members to the Alias. No need to verify that
+ // they are already present since we're loading the Member Alias
+ // List from scratch.
+ //
+
+ Status = SampAlAddMembersToAlias(
+ AliasContext,
+ 0,
+ &MemberSids
+ );
+ }
+
+
+ SampDeleteContext( AliasContext );
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+ }
+
+ //
+ // Enumerate next set of Aliases
+ //
+
+ if (!NT_SUCCESS(Status)) {
+
+ break;
+ }
+
+ //
+ // Dispose of the Enumeration Buffer returned by SampEnumerateAccountNames
+ //
+
+ SamIFree_SAMPR_ENUMERATION_BUFFER( EnumerationBuffer );
+ EnumerationBuffer = NULL;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto BuildMemberAliasListError;
+ }
+
+ //
+ // Mark the Member Alias List valid
+ //
+
+ SampAlInfoMakeValid( DomainIndex );
+
+BuildMemberAliasListFinish:
+
+ SampTransactionWithinDomain = FALSE;
+ return(Status);
+
+BuildMemberAliasListError:
+
+ goto BuildMemberAliasListFinish;
+}
+
+
+BOOLEAN
+SampAlInfoIsValidForDomain(
+ IN SAMPR_HANDLE DomainHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This function checks whether Alias Information is valid for a specific
+ SAM Local Domain
+
+Arguments:
+
+ DomainHandle - Handle to SAM Local Domain
+
+Return Values:
+
+ BOOLEAN - TRUE if Alias Information is valid. The Alias Information may
+ be used in place of the backing storage to determine Alias membership
+ FALSE if the Alias Information is not valid. The Alias Information
+ does not exist, or is not reliable.
+
+--*/
+
+{
+ LONG DomainIndex;
+
+ //
+ // Get the Domain Index for the SAM Local Domain specified by DomainHandle.
+
+ DomainIndex = ((PSAMP_OBJECT) DomainHandle)->DomainIndex;
+
+ return(SampAlInfoIsValid( DomainIndex ));
+}
+
+
+BOOLEAN
+SampAlInfoIsValidForAlias(
+ IN SAMPR_HANDLE AliasHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This function checks whether Alias Information is valid for a specific
+ Alias. The information is valid if it is valid for the SAM Local Domain
+ containing the Alias.
+
+Arguments:
+
+ AliasHandle - Handle to SAM Alias
+
+Return Values:
+
+ BOOLEAN - TRUE if Alias Information is valid. The Alias Information may
+ be used in place of the backing storage to determine Alias membership
+ FALSE if the Alias Information is not valid. The Alias Information
+ does not exist, or is not reliable.
+
+--*/
+
+{
+ LONG DomainIndex;
+
+ //
+ // Get the Domain Index for the SAM Local Domain specified by DomainHandle.
+
+ DomainIndex = ((PSAMP_OBJECT) AliasHandle)->DomainIndex;
+
+ return(SampAlInfoIsValid( DomainIndex ));
+}
diff --git a/private/newsam/server/attr.c b/private/newsam/server/attr.c
new file mode 100644
index 000000000..3ffc7d55b
--- /dev/null
+++ b/private/newsam/server/attr.c
@@ -0,0 +1,4273 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ attr.c
+
+Abstract:
+
+ This file contains services that manipulate SAM object attributes.
+
+
+ WARNING: Terminology can sometimes be confusing. SAM objects have
+ attributes (e.g., users have LogonHours, FullName, AcctName,
+ et cetera). These attributes are stored in the registry
+ in registry-key-attributes. There is NOT a one-to-one
+ correllation between object-attributes and registry-key-
+ attributes. For example, all the fixed-length attributes
+ of an object are stored in a single registry-key-attribute
+ (whose name is pointed to by SampFixedAttributeName).
+
+
+Author:
+
+ Jim Kelly (JimK) 26-June-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+
+/*
+
+ Each SAM object-type has an Object-type descriptor. This is in a
+ data structure called SAMP_OBJECT_INFORMATION. This structure
+ contains information that applies to all instances of that object
+ type. This includes things like a mask of write operations for
+ the object type, and a name for the object type to be used in
+ auditing.
+
+ Each instance of an open SAM object has another data structure
+ used to identify it (called SAMP_OBJECT). The header of this
+ structure contains information that is common to all object-types
+ and is there to allow unified object manipulation. This includes
+ things like the handle to the object's registry key.
+
+ There are fields in each of these structures that are there to
+ allow generic object-attribute support routines to operate. In
+ SAMP_OBJECT, there is a pointer to a block of allocated memory
+ housing a copy of the object's attributes as they are stored on-disk.
+ These attributes are arbitrarily divided into two groups: fixed-length
+ and variable-length.
+
+ One of the fields in SAMP_OBJECT_INFORMATION indicates whether the
+ fixed-length and variable-length attributes for that object-type
+ are stored together in a single registry-key-attribute or separately
+ in two registry-key-attributes.
+
+
+ The registry api for querying and setting registry-key attributes are
+ rather peculiar in that they require the I/O buffer to include a
+ description of the data. Even the simplest data structure for reading
+ attribute values (KEY_VALUE_PARTIAL_INFORMATION) includes 3 ULONGs
+ before the actual data (TitleIndex, value Type, data length,
+ and then, finally, the data). To efficiently perform registry i/o,
+ the in-memory copy of the on-disk object attributes includes room
+ for this information preceeding the fixed and variable-length attribute
+ sections of the data.
+
+
+ NOTE: For object classes that store fixed and variable-length
+ data together, only the KEY_VALUE_PARTIAL_INFORMATION
+ structure preceeding the fixed-length attributes is used.
+ The one preceeding the variable-length attributes is
+ #ifdef'd out.
+
+
+ The structures related to object-attributes look like:
+
+
+ On-Disk Image
+ +-------------+ SAMP_OBJECT_INFORMATION
+ +-->|KEY_VALUE_ | +-----------------------+
+ SAMP_OBJECT | |PARTIAL_ | | |
+ +-----------+ | |INFORMATION | | (header) |
+ | | | |-------------| | |
+ | (header) | | | Fixed-Length|<-----+ | |
+ | | | | Attributes | | |-----------------------|
+ |-----------| | | | +-------|-< FixedAttrsOffset |
+ | OnDisk >-|--+ |-------------+ |-----------------------|
+ |-----------| |KEY_VALUE_ |<-------------|-< VariableBuffOffset |
+ | OnDisk | |PARTIAL_ | |-----------------------|
+ | Control | |INFORMATION | +-------|-< VariableArrayOffset |
+ | Flags | |(Optional) | | |-----------------------|
+ |-----------| |-------------| | +----|-< VariableDataOffset |
+ | | | Variable- |<-----+ | |-----------------------|
+ | type- | | Length | | |VariableAttributeCount |
+ | specific | | Attributes | | |-----------------------|
+ | body | | Array | | |FixedStoredSeparately |
+ | | |-------------| | |-----------------------|
+ +-----------+ | Variable- |<--------+ | |
+ | Length | | |
+ | Attributes | | o |
+ | Data | | o |
+ | | +-----------------------+
+ | |
+ +-------------+
+
+
+
+
+ The KEY_VALUE_PARTIAL_INFORMATION preceeding the VariableLengthAttributes
+ array is marked optional because it is only present if fixed-length and
+ variable-length attribute information is stored separately. In this case,
+ the VariableBufferOffset field in the SAMP_OBJECT_INFORMATION structure
+ is set to be zero.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+*/
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <lmcons.h>
+#include <nturtl.h>
+
+
+
+//
+// This value indicates the minumum size block of memory to allocate
+// when retrieving object attributes from disk.
+
+#define SAMP_MINIMUM_ATTRIBUTE_ALLOC (1000)
+
+//
+// This value is used when growing the size of the buffer containing
+// object attributes. It represents the amount of free space that
+// should be left (approximately) for future growth in the buffer.
+//
+
+#define SAMP_MINIMUM_ATTRIBUTE_PAD (200)
+
+
+
+//
+// The following type is used to identify which grouping of attribute
+// (fixed or variable-length) are being refered to in a number of api.
+//
+
+#define SAMP_FIXED_ATTRIBUTES (0L)
+#define SAMP_VARIABLE_ATTRIBUTES (1L)
+
+
+//
+// The following line enables attribute debugging code
+//
+
+//#define SAM_DEBUG_ATTRIBUTES
+//#ifdef SAM_DEBUG_ATTRIBUTES
+//Boolean that allows us to turn off debugging output
+//BOOLEAN SampDebugAttributes = FALSE;
+//#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private macros //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+//
+// Macro to round up a ULONG value to be dword aligned
+// (i.e., be a multipleof 4).
+// Note, this does not handle the case where the Ulong is greater
+// than 0xfffffffc.
+//
+
+#define SampDwordAlignUlong( v ) (((v)+3) & 0xfffffffc)
+
+
+//
+// Make sure an object type and corresponding variable-length attribute
+// index are legitimate.
+//
+
+ //
+ // Make sure it is a legitimate object type
+ // And a legitimate attribute index for the object type
+ //
+
+#define SampValidateAttributeIndex( c, i ) { \
+ ASSERT( ((c)->ObjectType < SampUnknownObjectType) ); \
+ ASSERT(((i) < SampObjectInformation[(c)->ObjectType].VariableAttributeCount) ); \
+}
+
+
+
+//
+// Test to see if an object's fixed or variable-length attributes
+// are in memory.
+//
+
+#define SampFixedAttributesValid( c ) ((c)->FixedValid)
+
+#define SampVariableAttributesValid( c ) ((c)->VariableValid)
+
+
+//
+// Get the number of variable-length attributes defined for the
+// specified object
+//
+
+#define SampVariableAttributeCount( c ) \
+ (SampObjectInformation[(c)->ObjectType].VariableAttributeCount)
+
+
+
+//
+// Get the offset of the beginning of the attribute buffers
+//
+
+#define SampFixedBufferOffset( c ) \
+ ( \
+ SampObjectInformation[(c)->ObjectType].FixedAttributesOffset \
+ )
+
+#define SampVariableBufferOffset( c ) \
+ ( \
+ SampObjectInformation[(c)->ObjectType].VariableBufferOffset \
+ )
+
+
+
+//
+// Get the offset of the beginning of the variable data i/o buffer.
+// If the fixed and variable-length attributes are stored separately,
+// then this will be the lower half of the buffer.
+// Otherwise, there is only one buffer, so it is the entire allocated buffer.
+//
+
+#define SampFixedBufferAddress( c ) \
+ ( \
+ ((PUCHAR)((c)->OnDisk)) + SampFixedBufferOffset( c ) \
+ )
+
+#define SampVariableBufferAddress( c ) \
+ ( \
+ ((PUCHAR)((c)->OnDisk)) + SampVariableBufferOffset( c ) \
+ )
+
+
+
+//
+// Get the offset of the beginning of the variable-length
+// attributes discriptors array. This address is dword-aligned.
+//
+
+#define SampVariableArrayOffset( c ) \
+ ( \
+ SampObjectInformation[(c)->ObjectType].VariableArrayOffset \
+ )
+
+//
+// Calculate the address of the beginning of the variable-length
+// attributes array.
+//
+
+#define SampVariableArrayAddress( c ) \
+ ( \
+ (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)((PUCHAR)((c)->OnDisk) + \
+ SampVariableArrayOffset( c ) ) \
+ )
+
+
+//
+// Get the offset of the beginning of the variable-length
+// attributes data.
+//
+
+
+#define SampVariableDataOffset( c ) \
+ ( \
+ SampObjectInformation[(c)->ObjectType].VariableDataOffset \
+ )
+
+
+
+//
+// Get the length of the on-disk buffer for holding the variable-length
+// attribute array and data. If the fixed and variable-length attributes
+// are stored separately, then this will be the lower half of the buffer.
+// Otherwise, there is only one buffer, so it is the entire allocated buffer.
+//
+
+#define SampFixedBufferLength( c ) \
+ ( \
+ SampObjectInformation[(c)->ObjectType].FixedLengthSize \
+ )
+
+#define SampVariableBufferLength( c ) \
+ ( \
+ (c)->OnDiskAllocated - SampVariableBufferOffset( c ) \
+ )
+
+
+
+
+//
+// Return the address of a Qualifier field within the variable-length
+// attribute descriptor array.
+//
+
+#define SampVariableQualifier( c, i ) \
+ ( \
+ SampVariableArrayAddress( c ) + \
+ (sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE) * i) \
+ + FIELD_OFFSET(SAMP_VARIABLE_LENGTH_ATTRIBUTE, Qualifier) \
+ )
+
+
+
+//
+// Return the address of the first byte of free space
+// in an object's attribute data buffer.
+// This will be dword aligned.
+//
+
+#define SampFirstFreeVariableAddress( c ) \
+ (PUCHAR)(((PUCHAR)((c)->OnDisk)) + (c)->OnDiskUsed)
+
+
+
+//
+// Get the number of bytes needed to store the entire variable-length
+// attribute information on disk.
+//
+
+#define SampVariableBufferUsedLength( c ) \
+ ( \
+ (PUCHAR)SampFirstFreeVariableAddress(c) - \
+ (PUCHAR)SampVariableArrayAddress(c) \
+ )
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SampValidateAttributes(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup
+ );
+
+PUCHAR
+SampObjectAttributeAddress(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ );
+
+ULONG
+SampObjectAttributeLength(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ );
+
+PULONG
+SampObjectAttributeQualifier(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ );
+
+NTSTATUS
+SampGetAttributeBufferReadInfo(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup,
+ OUT PUCHAR *Buffer,
+ OUT PULONG BufferLength,
+ OUT PUNICODE_STRING *KeyAttributeName
+ );
+
+NTSTATUS
+SampExtendAttributeBuffer(
+ IN PSAMP_OBJECT Context,
+ IN ULONG NewSize
+ );
+
+NTSTATUS
+SampReadRegistryAttribute(
+ IN HANDLE Key,
+ IN PUCHAR Buffer,
+ IN ULONG BufferLength,
+ IN PUNICODE_STRING AttributeName,
+ OUT PULONG RequiredLength
+ );
+
+NTSTATUS
+SampSetVariableAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN ULONG Qualifier,
+ IN PUCHAR Buffer,
+ IN ULONG Length
+ );
+
+NTSTATUS
+SampUpgradeToCurrentRevision(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup,
+ IN PUCHAR Buffer,
+ IN ULONG LengthOfDataRead,
+ IN PULONG TotalRequiredLength
+ );
+
+
+
+#ifdef SAM_DEBUG_ATTRIBUTES
+VOID
+SampDumpAttributes(
+ IN PSAMP_OBJECT Context
+ );
+
+VOID
+SampDumpData(
+ IN PVOID Buffer,
+ IN ULONG Length
+ );
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Public Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+VOID
+SampInitObjectInfoAttributes(
+ )
+
+
+/*++
+
+ This API initializes the attribute field information
+ of the various object information structures.
+
+ Attribute information includes:
+
+ FixedStoredSeparately (BOOLEAN)
+
+ FixedAttributeOffset (ULONG)
+ VariableBufferOffset (ULONG)
+ VariableArrayOffset (ULONG)
+ VariableDataOffset (ULONG)
+
+ FixedLengthSize (ULONG)
+ VariableAttributeCount (ULONG)
+
+
+Parameters:
+
+ None.
+
+
+
+Return Values:
+
+ None.
+
+
+--*/
+{
+
+
+ //
+ // Define the size of the header that is in front of our data when
+ // we read it back out of the registry.
+ //
+
+#define KEY_VALUE_HEADER_SIZE (SampDwordAlignUlong( \
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)))
+
+
+ PSAMP_OBJECT_INFORMATION Object;
+
+
+ //
+ // SERVER object attribute information
+ //
+
+ Object = &SampObjectInformation[SampServerObjectType];
+
+ Object->FixedStoredSeparately = SAMP_SERVER_STORED_SEPARATELY;
+
+ Object->FixedAttributesOffset = KEY_VALUE_HEADER_SIZE;
+
+ Object->FixedLengthSize = sizeof(SAMP_V1_FIXED_LENGTH_SERVER);
+
+#if SAMP_SERVER_STORED_SEPARATELY
+
+ Object->VariableBufferOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+ Object->VariableArrayOffset =
+ Object->VariableBufferOffset + KEY_VALUE_HEADER_SIZE;
+#else
+
+ Object->VariableBufferOffset = 0;
+
+ Object->VariableArrayOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+#endif //SAMP_SERVER_STORED_SEPARATELY
+
+
+ Object->VariableAttributeCount = SAMP_SERVER_VARIABLE_ATTRIBUTES;
+
+ Object->VariableDataOffset =
+ SampDwordAlignUlong( Object->VariableArrayOffset +
+ (SAMP_SERVER_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE))
+ );
+
+
+
+
+
+ //
+ // DOMAIN object attribute information
+ //
+
+ Object = &SampObjectInformation[SampDomainObjectType];
+
+ Object->FixedStoredSeparately = SAMP_DOMAIN_STORED_SEPARATELY;
+
+ Object->FixedAttributesOffset = KEY_VALUE_HEADER_SIZE;
+
+ Object->FixedLengthSize = sizeof(SAMP_V1_0A_FIXED_LENGTH_DOMAIN);
+
+#if SAMP_DOMAIN_STORED_SEPARATELY
+
+ Object->VariableBufferOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+ Object->VariableArrayOffset =
+ Object->VariableBufferOffset + KEY_VALUE_HEADER_SIZE;
+#else
+
+ Object->VariableBufferOffset = 0;
+
+ Object->VariableArrayOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+#endif //SAMP_DOMAIN_STORED_SEPARATELY
+
+
+ Object->VariableAttributeCount = SAMP_DOMAIN_VARIABLE_ATTRIBUTES;
+
+ Object->VariableDataOffset =
+ SampDwordAlignUlong( Object->VariableArrayOffset +
+ (SAMP_DOMAIN_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE))
+ );
+
+
+
+
+
+ //
+ // USER object attribute information
+ //
+
+ Object = &SampObjectInformation[SampUserObjectType];
+
+ Object->FixedStoredSeparately = SAMP_USER_STORED_SEPARATELY;
+
+ Object->FixedAttributesOffset = KEY_VALUE_HEADER_SIZE;
+
+ Object->FixedLengthSize = sizeof(SAMP_V1_0A_FIXED_LENGTH_USER);
+
+#if SAMP_USER_STORED_SEPARATELY
+
+ Object->VariableBufferOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+ Object->VariableArrayOffset =
+ Object->VariableBufferOffset + KEY_VALUE_HEADER_SIZE;
+#else
+
+ Object->VariableBufferOffset = 0;
+
+ Object->VariableArrayOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+#endif //SAMP_USER_STORED_SEPARATELY
+
+
+ Object->VariableAttributeCount = SAMP_USER_VARIABLE_ATTRIBUTES;
+
+ Object->VariableDataOffset =
+ SampDwordAlignUlong( Object->VariableArrayOffset +
+ (SAMP_USER_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE))
+ );
+
+
+
+
+
+ //
+ // GROUP object attribute information
+ //
+
+ Object = &SampObjectInformation[SampGroupObjectType];
+
+ Object->FixedStoredSeparately = SAMP_GROUP_STORED_SEPARATELY;
+
+ Object->FixedAttributesOffset = KEY_VALUE_HEADER_SIZE;
+
+ Object->FixedLengthSize = sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP);
+
+#if SAMP_GROUP_STORED_SEPARATELY
+
+ Object->VariableBufferOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+ Object->VariableArrayOffset =
+ Object->VariableBufferOffset + KEY_VALUE_HEADER_SIZE;
+#else
+
+ Object->VariableBufferOffset = 0;
+
+ Object->VariableArrayOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+#endif //SAMP_GROUP_STORED_SEPARATELY
+
+
+ Object->VariableAttributeCount = SAMP_GROUP_VARIABLE_ATTRIBUTES;
+
+ Object->VariableDataOffset =
+ SampDwordAlignUlong( Object->VariableArrayOffset +
+ (SAMP_GROUP_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE))
+ );
+
+
+
+
+
+ //
+ // ALIAS object attribute information
+ //
+
+ Object = &SampObjectInformation[SampAliasObjectType];
+
+ Object->FixedStoredSeparately = SAMP_ALIAS_STORED_SEPARATELY;
+
+ Object->FixedAttributesOffset = KEY_VALUE_HEADER_SIZE;
+
+ Object->FixedLengthSize = sizeof(SAMP_V1_FIXED_LENGTH_ALIAS);
+
+#if SAMP_ALIAS_STORED_SEPARATELY
+
+ Object->VariableBufferOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+ Object->VariableArrayOffset =
+ Object->VariableBufferOffset + KEY_VALUE_HEADER_SIZE;
+#else
+
+ Object->VariableBufferOffset = 0;
+
+ Object->VariableArrayOffset =
+ Object->FixedAttributesOffset +
+ SampDwordAlignUlong(Object->FixedLengthSize);
+
+#endif //SAMP_ALIAS_STORED_SEPARATELY
+
+
+ Object->VariableAttributeCount = SAMP_ALIAS_VARIABLE_ATTRIBUTES;
+
+ Object->VariableDataOffset =
+ SampDwordAlignUlong( Object->VariableArrayOffset +
+ (SAMP_ALIAS_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE))
+ );
+
+
+ return;
+}
+
+
+
+NTSTATUS
+SampStoreObjectAttributes(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN UseKeyHandle
+ )
+
+/*++
+
+ This API is used to store an object's attributes onto
+ backing store.
+
+ The object attributes are not flushed to disk with this
+ routine. They are just added to the RXACT.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ UseKeyHandle - If TRUE, the RootKey in the context block is passed
+ to the transaction code - this assumes that the key
+ will still be open when the transaction is committed.
+ If FALSE, the RootKey will be ignored and the transaction code will
+ open a key for itself.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+
+ Other status values as may be returned by the RXACT services.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ BOOLEAN
+ FlushFixed = FALSE,
+ FlushVariable = FALSE;
+
+ HANDLE
+ RootKey;
+
+ //
+ // See if anything is dirty and needs to be stored
+ //
+
+ if (Context->FixedValid && Context->FixedDirty) {
+
+ FlushFixed = TRUE;
+ }
+
+ if (Context->VariableValid && Context->VariableDirty) {
+
+ FlushVariable = TRUE;
+ }
+
+
+ if (!(FlushFixed || FlushVariable)) {
+
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Calculate the RootKey to pass to the transaction code
+ //
+
+ if (UseKeyHandle) {
+ RootKey = Context->RootKey;
+ } else {
+ RootKey = INVALID_HANDLE_VALUE;
+ }
+
+ //
+ // We keep an open domain context that is used to modify the change
+ // count whenever a change is made. But if this is a domain change
+ // here, then that change will overwrite this one. Check for that
+ // case, and copy this fixed data to the open domain context. Note
+ // that the open domain's variable data never gets changed.
+ //
+
+ if ( ( Context->ObjectType == SampDomainObjectType ) &&
+ ( Context != SampDefinedDomains[Context->DomainIndex].Context ) ) {
+
+ PSAMP_OBJECT DefinedContext;
+
+ //
+ // Get a pointer to the corresponding open defined domain.
+ // No changes should have been made to its data.
+ //
+
+ DefinedContext = SampDefinedDomains[Context->DomainIndex].Context;
+
+ ASSERT( DefinedContext->FixedValid == TRUE );
+ ASSERT( DefinedContext->FixedDirty == FALSE );
+
+#if DBG
+ if ( DefinedContext->VariableValid ) {
+ ASSERT( DefinedContext->VariableDirty == FALSE );
+ }
+#endif
+ DefinedContext->VariableDirty = FALSE;
+
+ //
+ // Copy our fixed data over the defined domain's fixed data.
+ // Note that we're assuming that the fixed and variable data are
+ // stored separately.
+ //
+
+ ASSERT(SampObjectInformation[SampDomainObjectType].FixedStoredSeparately);
+
+ RtlCopyMemory(
+ SampFixedBufferAddress( DefinedContext ),
+ SampFixedBufferAddress( Context ),
+ SampFixedBufferLength( Context )
+ );
+
+ //
+ // No need to flush this context's fixed data, since the commit
+ // code will flush the same stuff (plus an altered modified count).
+ //
+
+ FlushFixed = FALSE;
+ Context->FixedDirty = FALSE;
+ }
+
+ //
+ // One or more of the attributes needs to be stored.
+ //
+
+ if (!SampObjectInformation[Context->ObjectType].FixedStoredSeparately) {
+
+ //
+ // fixed and variable-length attributes stored together.
+ // Note - strip off the partial key info struct from the start
+ //
+
+ NtStatus = RtlAddAttributeActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &(Context->RootName),
+ RootKey,
+ &SampCombinedAttributeName,
+ REG_BINARY,
+ SampFixedBufferAddress(Context),
+ Context->OnDiskUsed - SampFixedBufferOffset(Context)
+ );
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to add action to RXact (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ Context->FixedDirty = FALSE;
+ Context->VariableDirty = FALSE;
+ }
+
+ } else {
+
+ //
+ // fixed and variable-length attributes stored separately.
+ // Only update the one(s) we need to.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ if (FlushFixed) {
+
+ NtStatus = RtlAddAttributeActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &(Context->RootName),
+ RootKey,
+ &SampFixedAttributeName,
+ REG_BINARY,
+ SampFixedBufferAddress(Context),
+ SampVariableBufferOffset(Context) - SampFixedBufferOffset(Context)
+ );
+
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to add action to RXact (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ Context->FixedDirty = FALSE;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus) && FlushVariable) {
+
+ NtStatus = RtlAddAttributeActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &(Context->RootName),
+ RootKey,
+ &SampVariableAttributeName,
+ REG_BINARY,
+ SampVariableArrayAddress( Context ),
+ SampVariableBufferUsedLength(Context)
+ );
+
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to add action to RXact (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+ Context->VariableDirty = FALSE;
+ }
+ }
+ }
+
+ return(NtStatus);
+}
+
+
+
+
+NTSTATUS
+SampDeleteAttributeKeys(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+ This API is used to delete the attribute keys that are created in the
+ registry underneath a SAM object.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ Error status may be returned by registry calls.
+
+--*/
+{
+ UNICODE_STRING
+ KeyName;
+
+ NTSTATUS
+ NtStatus;
+
+ if (SampObjectInformation[Context->ObjectType].FixedStoredSeparately) {
+
+ //
+ // Must delete both fixed and variable attribute keys.
+ //
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampUserObjectType,
+ &KeyName,
+ Context->TypeBody.User.Rid,
+ &SampFixedAttributeName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampUserObjectType,
+ &KeyName,
+ Context->TypeBody.User.Rid,
+ &SampVariableAttributeName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+
+ } else {
+
+ //
+ // Must delete the combined attribute key.
+ //
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampUserObjectType,
+ &KeyName,
+ Context->TypeBody.User.Rid,
+ &SampCombinedAttributeName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampGetFixedAttributes(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN MakeCopy,
+ OUT PVOID *FixedData
+ )
+
+/*++
+
+ This API is used to get a pointer to the fixed-length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ FixedData - Receives a pointer to the fixed-length data.
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_FIXED_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Return a pointer to the fixed-length attributes.
+ //
+
+ if (MakeCopy == FALSE) {
+ *FixedData = (PVOID)SampFixedBufferAddress( Context );
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Need to make a copy of the fixed data
+ //
+
+ *FixedData = (PVOID)MIDL_user_allocate( SampFixedBufferLength( Context ) );
+ if ((*FixedData) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ RtlCopyMemory( *FixedData,
+ SampFixedBufferAddress( Context ),
+ SampFixedBufferLength( Context ) );
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+
+NTSTATUS
+SampSetFixedAttributes(
+ IN PSAMP_OBJECT Context,
+ IN PVOID FixedData
+ )
+
+/*++
+
+ This API is used to replace the fixed-length data attribute.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ //
+ // Make the fixed-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_FIXED_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ if ( FixedData != SampFixedBufferAddress( Context ) ) {
+
+ //
+ // The caller had a copy of the data, so we must copy the changes
+ // over our data buffer.
+ //
+
+ RtlCopyMemory( SampFixedBufferAddress( Context ),
+ FixedData,
+ SampFixedBufferLength( Context ) );
+ }
+
+ //
+ // Mark the buffer dirty now and it will get flushed when the
+ // changes are committed.
+ //
+
+ Context->FixedDirty = TRUE;
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampGetUnicodeStringAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PUNICODE_STRING UnicodeAttribute
+ )
+
+
+/*++
+
+ This API is used to get a copy of a UNICODE_STRING attribute or a
+ pointer to the attribute. If a pointer to the attribute is sought,
+ care must be taken to ensure the pointer is not used after it becomes
+ invalid. Actions that may cause an attribute pointer to become invalid
+ include setting an attribute value or dereferencing and then referencing the
+ object again.
+
+ If MakeCopy is FALSE, indicating the string is to be referenced rather
+ than copied, then only the body of the string is referenced. The lengths
+ and pointer are set in the provided argument.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved as a UNICODE_STRING.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ UnicodeAttribute - Receives a pointer to the UNICODE_STRING. If
+ MakeCopy was specified as TRUE, then this pointer points to a block
+ of memory allocated with MIDL_user_allocate() which the caller is
+ responsible for freeing (using MIDL_user_free()).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ ULONG Length;
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Get the length of the attribute
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+ ASSERT(Length <= 0xFFFF);
+
+ UnicodeAttribute->MaximumLength = (USHORT)Length;
+ UnicodeAttribute->Length = (USHORT)Length;
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ UnicodeAttribute->Buffer =
+ (PWSTR)SampObjectAttributeAddress( Context, AttributeIndex );
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Need to make a copy of the attribute
+ //
+ // NOTE: We should test for zero length here and return a NULL pointer
+ // in that case, but this change would require verification of all of the
+ // callers of this routine, so I'm leaving it as is.
+ //
+
+ UnicodeAttribute->Buffer = (PSID)MIDL_user_allocate( Length );
+ if ((UnicodeAttribute->Buffer) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ RtlCopyMemory(
+ UnicodeAttribute->Buffer,
+ SampObjectAttributeAddress( Context, AttributeIndex ),
+ Length
+ );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+SampSetUnicodeStringAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PUNICODE_STRING Attribute
+ )
+
+
+/*++
+
+ This API is used to replace a UNICODE_STRING attribute in an
+ object's variable length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set as a UNICODE_STRING.
+
+
+ Attribute - Points to the new UNICODE_STRING value.
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ 0, // Qualifier not used
+ (PUCHAR)Attribute->Buffer,
+ Attribute->Length
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetSidAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PSID *Sid
+ )
+
+
+/*++
+
+ This API is used to get a copy of a SID attribute or a pointer to
+ the attribute. If a pointer to the attribute is sought, care must
+ be taken to ensure the pointer is not used after it becomes invalid.
+ Actions that may cause an attribute pointer to become invalid include
+ setting an attribute value or dereferencing and then referencing the
+ object again.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved as a SID.
+
+ MakeCopy - If TRUE, indicates that a copy of the SID is to be made.
+ If FALSE, indicates a pointer to the SID is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the SID remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the SID to be moved, and previously returned pointers
+ invalidated.
+
+ Sid - Receives a pointer to the SID. If MakeCopy was specified, then
+ this pointer points to a block of memory allocated with
+ MIDL_user_allocate() which the caller is responsible for freeing
+ (using MIDL_user_free()).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Length;
+
+ PSID
+ SidAttribute;
+
+
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Get the address of the attribute in question
+ //
+
+ SidAttribute = (PSID)SampObjectAttributeAddress( Context, AttributeIndex );
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ (*Sid) = SidAttribute;
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Need to make a copy of the SID
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+ ASSERT(Length == RtlLengthSid( SidAttribute ) );
+
+ (*Sid) = (PSID)MIDL_user_allocate( Length );
+ if ((*Sid) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ NtStatus = RtlCopySid( Length, (*Sid), SidAttribute );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampSetSidAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSID Attribute
+ )
+
+
+/*++
+
+ This API is used to replace a SID attribute in an object's variable
+ length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set as a SID.
+
+
+ Attribute - Points to the new SID value.
+
+ Length - The length of the new attribute value (in bytes).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ //
+ // Validate the passed SID
+ //
+
+ ASSERT(RtlValidSid(Attribute));
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ 0, // Qualifier not used
+ (PUCHAR)Attribute,
+ RtlLengthSid(Attribute)
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetAccessAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG Revision,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor
+ )
+
+
+/*++
+
+ This API is used to get a copy of the object access information.
+ This includes the security descriptor and revision level of the
+ object.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ Revision - Receives the revision level from the access information.
+
+ SecurityDescriptor - Receives a pointer to the attribute. If MakeCopy
+ was specified as TRUE, then this pointer points to a block of memory
+ allocated with MIDL_user_allocate() which the caller is responsible
+ for freeing (using MIDL_user_free()).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Length;
+
+ PVOID
+ RawAttribute;
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Get the revision level from the qualifier field of the variable
+ // array entry.
+ //
+
+ (*Revision) = *(SampObjectAttributeQualifier( Context, AttributeIndex ));
+
+
+ //
+ // Get the address of the attribute in question
+ //
+
+ RawAttribute = (PVOID)SampObjectAttributeAddress( Context, AttributeIndex );
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ (*SecurityDescriptor) = (PSECURITY_DESCRIPTOR)RawAttribute;
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Need to make a copy of the attribute
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+
+ (*SecurityDescriptor) = (PSECURITY_DESCRIPTOR)MIDL_user_allocate( Length );
+ if ((*SecurityDescriptor) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ RtlCopyMemory( (*SecurityDescriptor), RawAttribute, Length );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+NTSTATUS
+SampSetAccessAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSECURITY_DESCRIPTOR Attribute,
+ IN ULONG Length
+ )
+
+
+/*++
+
+ This API is used to replace a SECURITY_DESCRIPTOR attribute in
+ an object's variable length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set as a SECURITY_DESCRIPTOR.
+
+
+ Attribute - Points to the new SECURITY_DESCRIPTOR value.
+
+ Length - The length of the new attribute value (in bytes).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ SAMP_REVISION,
+ (PUCHAR)Attribute,
+ Length
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetUlongArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG *UlongArray,
+ OUT PULONG UsedCount,
+ OUT PULONG LengthCount
+ )
+
+
+/*++
+
+ This API is used to get a copy of an array of ULONGs attribute or
+ a pointer to the attribute. If a pointer to the attribute is sought,
+ care must be taken to ensure the pointer is not used after it becomes
+ invalid. Actions that may cause an attribute pointer to become invalid
+ include setting an attribute value or dereferencing and then referencing
+ the object again.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved as a ULONG array.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ UlongArray - Receives a pointer to the array of ULONGS. If
+ MakeCopy was specified as TRUE, then this pointer points to a block
+ of memory allocated with MIDL_user_allocate() which the caller is
+ responsible for freeing (using MIDL_user_free()).
+
+ UsedCount - Receives the number of elements used in the array.
+
+ LengthCount - Receives the total number of elements in the array (some
+ at the end may be unused). If this value is zero, then
+ UlongArray will be returned as NULL.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Length;
+
+
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the count of array elements.
+ // If this is zero, then return will a null buffer pointer.
+ //
+
+ (*UsedCount) = *(SampObjectAttributeQualifier( Context, AttributeIndex));
+
+
+
+
+ //
+ // Get the length of the attribute
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+
+ (*LengthCount) = Length / sizeof(ULONG);
+
+ ASSERT( (*UsedCount) <= (*LengthCount) );
+
+ if ((*LengthCount) == 0) {
+ (*UlongArray) = NULL;
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ (*UlongArray) =
+ (PULONG)SampObjectAttributeAddress( Context, AttributeIndex );
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Need to make a copy of the attribute
+ //
+
+ (*UlongArray) = (PULONG)MIDL_user_allocate( Length );
+ if ((*UlongArray) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ RtlCopyMemory( (*UlongArray),
+ SampObjectAttributeAddress( Context, AttributeIndex ),
+ Length );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+SampSetUlongArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PULONG Attribute,
+ IN ULONG UsedCount,
+ IN ULONG LengthCount
+ )
+
+
+/*++
+
+ This API is used to replace a ULONG array attribute in an
+ object's variable length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set.
+
+
+ Attribute - Points to the new ULONG array value.
+
+ UsedCount - The number of used elements in the array.
+
+ LengthCount - the total number of elements in the array (some at the
+ end may be unused).
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ASSERT( LengthCount >= UsedCount );
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ UsedCount, // Qualifier contains used element count
+ (PUCHAR)Attribute,
+ (LengthCount * sizeof(ULONG))
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetLargeIntArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PLARGE_INTEGER *LargeIntArray,
+ OUT PULONG Count
+ )
+
+
+/*++
+
+ This API is used to get a copy of an array of LARGE_INTEGERs attribute or
+ a pointer to the attribute. If a pointer to the attribute is sought,
+ care must be taken to ensure the pointer is not used after it becomes
+ invalid. Actions that may cause an attribute pointer to become invalid
+ include setting an attribute value or dereferencing and then referencing
+ the object again.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ LargeIntArray - Receives a pointer to the array of ULONGS. If
+ MakeCopy was specified as TRUE, then this pointer points to a block
+ of memory allocated with MIDL_user_allocate() which the caller is
+ responsible for freeing (using MIDL_user_free()).
+
+
+ Count - Receives the number of elements in the array. If this value
+ is zero, then LargeIntArray will be returned as NULL.
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Length;
+
+
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the count of array elements.
+ // If this is zero, then return will a null buffer pointer.
+ //
+
+ (*Count) = *(SampObjectAttributeQualifier( Context, AttributeIndex));
+
+ if ((*Count) == 0) {
+ (*LargeIntArray) = NULL;
+ return(STATUS_SUCCESS);
+ }
+
+
+
+ //
+ // Get the length of the attribute
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+
+ ASSERT((*Count) == (Length / sizeof(LARGE_INTEGER)) );
+
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ (*LargeIntArray) =
+ (PLARGE_INTEGER)SampObjectAttributeAddress( Context, AttributeIndex );
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Need to make a copy of the attribute
+ //
+
+ (*LargeIntArray) = (PLARGE_INTEGER)MIDL_user_allocate( Length );
+ if ((*LargeIntArray) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ RtlCopyMemory( (*LargeIntArray),
+ SampObjectAttributeAddress( Context, AttributeIndex ),
+ Length );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+SampSetLargeIntArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PLARGE_INTEGER Attribute,
+ IN ULONG Count
+ )
+
+
+/*++
+
+ This API is used to replace a LARGE_INTEGER array attribute in an
+ object's variable length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set.
+
+
+ Attribute - Points to the new LARGE_INTEGER array value.
+
+ Count - The number of elements in the array.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ Count, // Qualifier contains element count
+ (PUCHAR)Attribute,
+ (Count * sizeof(LARGE_INTEGER))
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetSidArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PSID *SidArray,
+ OUT PULONG Length,
+ OUT PULONG Count
+ )
+
+
+/*++
+
+ This API is used to get a copy of an array of SIDs attribute or
+ a pointer to the attribute. If a pointer to the attribute is sought,
+ care must be taken to ensure the pointer is not used after it becomes
+ invalid. Actions that may cause an attribute pointer to become invalid
+ include setting an attribute value or dereferencing and then referencing
+ the object again.
+
+
+ NOTE: This routine does not define the structure of a SID array,
+ so this effectively is a GetRawDataAttribute routine.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ SidArray - Receives a pointer to the array of SIDs. If
+ MakeCopy was specified as TRUE, then this pointer points to a block
+ of memory allocated with MIDL_user_allocate() which the caller is
+ responsible for freeing (using MIDL_user_free()).
+
+
+ Count - Receives the number of elements in the array. If this value
+ is zero, then SidArray will be returned as NULL.
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the count of array elements.
+ // If this is zero, then return will a null buffer pointer.
+ //
+
+ (*Count) = *(SampObjectAttributeQualifier( Context, AttributeIndex));
+
+ if ((*Count) == 0) {
+ (*SidArray) = NULL;
+ (*Length) = 0;
+ return(STATUS_SUCCESS);
+ }
+
+
+
+ //
+ // Get the length of the attribute
+ //
+
+ (*Length) = SampObjectAttributeLength( Context, AttributeIndex );
+
+
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ (*SidArray) =
+ (PSID)SampObjectAttributeAddress( Context, AttributeIndex );
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Need to make a copy of the attribute
+ //
+
+ (*SidArray) = (PSID)MIDL_user_allocate( (*Length) );
+ if ((*SidArray) == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ RtlCopyMemory( (*SidArray),
+ SampObjectAttributeAddress( Context, AttributeIndex ),
+ (*Length) );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+SampSetSidArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSID Attribute,
+ IN ULONG Length,
+ IN ULONG Count
+ )
+
+
+/*++
+
+ This API is used to replace a SID array attribute in an
+ object's variable length attributes.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set.
+
+
+ Attribute - Points to the new SID array value.
+
+ Length - Number of byte in the attribute buffer.
+
+ Count - Number of SIDs in the array.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ Count, // Qualifier contains element count
+ (PUCHAR)Attribute,
+ Length
+ );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampGetLogonHoursAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PLOGON_HOURS LogonHours
+ )
+
+
+/*++
+
+ This API is used to get a copy of a logon hours attribute or
+ a pointer to the attribute. If a pointer to the attribute is sought,
+ care must be taken to ensure the pointer is not used after it becomes
+ invalid. Actions that may cause an attribute pointer to become invalid
+ include setting an attribute value or dereferencing and then referencing
+ the object again.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved.
+
+ MakeCopy - If TRUE, indicates that a copy of the attribute is to be made.
+ If FALSE, indicates a pointer to the attribute is desired without
+ making a copy. WARNING, if this is FALSE, the pointer is only
+ valid while the in-memory copy of the attribute remains in place.
+ Addition or replacement of any variable length attribute may
+ cause the attribute to be moved, and previously returned pointers
+ invalidated.
+
+ LogonHours - Receives the logon hours information. If MakeCopy is TRUE
+ then the bitmap pointed to from within this structure will be a copy
+ of the attribute and must be deallocated uing MIDL_user_free().
+ Otherwise, this same field will point to the bitmap in the on-disk
+ buffer.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Length,
+ Units;
+
+
+
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the time units.
+ // If this is zero, then return will a null buffer pointer.
+ //
+
+ Units = *(SampObjectAttributeQualifier( Context, AttributeIndex));
+ ASSERT(Units <= 0xFFFF);
+ LogonHours->UnitsPerWeek = (USHORT)Units;
+
+ if (Units == 0) {
+ LogonHours->LogonHours = NULL;
+ return(STATUS_SUCCESS);
+ }
+
+
+
+
+ //
+ // If we are not to allocate memory, then just return a pointer
+ // to the attribute.
+ //
+
+ if (MakeCopy == FALSE) {
+ LogonHours->LogonHours =
+ (PUCHAR)SampObjectAttributeAddress( Context, AttributeIndex );
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Get the length of the attribute
+ //
+
+ Length = SampObjectAttributeLength( Context, AttributeIndex );
+ ASSERT(Length <= 0xFFFF);
+
+
+ //
+ // Need to make a copy of the attribute
+ //
+
+ LogonHours->LogonHours =
+ (PUCHAR)MIDL_user_allocate( Length );
+ if (LogonHours->LogonHours == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ RtlCopyMemory( LogonHours->LogonHours,
+ SampObjectAttributeAddress( Context, AttributeIndex ),
+ Length );
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+NTSTATUS
+SampSetLogonHoursAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PLOGON_HOURS Attribute
+ )
+
+
+/*++
+
+ This API is used to replace a LOGON_HOURS attribute in an
+ object's variable length attributes.
+
+ UnitsPerWeek are stored in the Qualifier field of the attribute.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set.
+
+
+ Attribute - Points to the new LOGON_HOURS value.
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to receive a copy of the attribute could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PUCHAR LogonHours;
+ ULONG Length;
+ USHORT UnitsPerWeek;
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+ //
+ // Make the variable-length data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Grab the UnitsPerWeek value for the logon_hours structure.
+ // We use this to calculate the length of the data.
+ //
+
+ if ( Attribute == NULL ) {
+ UnitsPerWeek = 0;
+ LogonHours = NULL;
+ } else {
+ UnitsPerWeek = Attribute->UnitsPerWeek;
+ LogonHours = Attribute->LogonHours;
+ }
+
+ //
+ // Validate the data - make sure that if the units per week are non-zero
+ // then the logon hours buffer is non-NULL.
+ //
+
+ if ( (UnitsPerWeek != 0) && (LogonHours == NULL) ) {
+
+ return(STATUS_INVALID_PARAMETER);
+ }
+ //
+ // Calculate length of logon_hours structure
+ //
+
+ Length = (ULONG)((UnitsPerWeek + 7) / 8);
+
+ //
+ // Set the new attribute value...
+ //
+
+ NtStatus = SampSetVariableAttribute(
+ Context,
+ AttributeIndex,
+ (ULONG)UnitsPerWeek, // Qualifier contains units per week
+ LogonHours,
+ Length
+ );
+
+ return(NtStatus);
+
+}
+
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampValidateAttributes(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup
+ )
+
+/*++
+
+ Ensure specified attributes are in-memory.
+ If they are not, then read them from backing store.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeGroup - identifies which kind of attributes are being
+ validated (SAMP_FIXED_ATTRIBUTES or SAMP_VARIABLE_ATTRIBUTES).
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The attributes are in-memory.
+
+ STATUS_NO_MEMORY - Memory could not be allocated to retrieve the
+ attributes.
+
+ Other values as may be returned by registry API trying to retrieve
+ the attributes from backing store.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ RequiredLength,
+ TotalRequiredLength,
+ BufferLength;
+
+ PUCHAR
+ Buffer;
+
+ PUNICODE_STRING
+ KeyAttributeName;
+
+ BOOLEAN
+ CreatedObject = FALSE;
+
+
+ //
+ // The data might already be in memory.
+ //
+
+ if (AttributeGroup == SAMP_FIXED_ATTRIBUTES) {
+ if (SampFixedAttributesValid( Context )) {
+ ASSERT(Context->OnDisk != NULL);
+ return(STATUS_SUCCESS);
+ }
+
+ } else {
+
+ ASSERT( AttributeGroup == SAMP_VARIABLE_ATTRIBUTES );
+ if (SampVariableAttributesValid( Context )) {
+ ASSERT(Context->OnDisk != NULL);
+ return(STATUS_SUCCESS);
+ }
+ }
+
+
+
+ //
+ // Retrieve it from the registry, or allocate it if new.
+ //
+
+
+ NtStatus = SampGetAttributeBufferReadInfo(
+ Context,
+ AttributeGroup,
+ &Buffer,
+ &BufferLength,
+ &KeyAttributeName
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ if ( Context->RootKey != INVALID_HANDLE_VALUE ) {
+
+ //
+ // Account exists on disk, so read in the attributes.
+ //
+
+ NtStatus = SampReadRegistryAttribute( Context->RootKey,
+ Buffer,
+ BufferLength,
+ KeyAttributeName,
+ &RequiredLength
+ );
+
+ RequiredLength = SampDwordAlignUlong(RequiredLength);
+
+ if ( ( SampObjectInformation[Context->ObjectType].FixedStoredSeparately ) &&
+ ( AttributeGroup == SAMP_VARIABLE_ATTRIBUTES ) ) {
+
+ //
+ // RequiredLength was returned to us as the length of the
+ // variable attributes on the disk. However, we're going
+ // to be using it to determine the total buffer size as well
+ // as to set how much of the buffer is in use, so we must add
+ // the size of the fixed stuff that preceeds the variable
+ // buffer.
+ //
+
+ TotalRequiredLength = RequiredLength +
+ SampVariableBufferOffset( Context );
+
+ } else {
+
+ //
+ // Either the attribute groups are read together, or we're
+ // reading in the fixed attribute group. Either way, we
+ // already have the total size we need.
+ //
+
+ TotalRequiredLength = RequiredLength;
+ }
+
+ if ((NtStatus == STATUS_BUFFER_TOO_SMALL) ||
+ ( NtStatus == STATUS_BUFFER_OVERFLOW ) ) {
+
+ NtStatus = SampExtendAttributeBuffer( Context, TotalRequiredLength );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ NtStatus = SampGetAttributeBufferReadInfo(
+ Context,
+ AttributeGroup,
+ &Buffer,
+ &BufferLength,
+ &KeyAttributeName
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ NtStatus = SampReadRegistryAttribute( Context->RootKey,
+ Buffer,
+ BufferLength,
+ KeyAttributeName,
+ &RequiredLength
+ );
+
+ }
+
+ } else {
+
+ //
+ // We're creating a new object.
+ //
+ // Initialize the requiredlength to the amount of the buffer
+ // we have used when we created the empty attributes. This will
+ // be the value stored in OnDiskUsed.
+ //
+ // Note OnDiskUsed is only used by operations on the variable
+ // length attributes.
+ //
+
+ TotalRequiredLength = SampVariableDataOffset(Context);
+
+ ASSERT(TotalRequiredLength <= Context->OnDiskAllocated);
+
+ CreatedObject = TRUE;
+ }
+
+
+
+ //
+ // if we read something, indicate that the corresponding buffer
+ // (and maybe both) are now valid.
+ //
+ // Also set the used and free information for the buffer if necessary.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (SampObjectInformation[Context->ObjectType].FixedStoredSeparately) {
+
+ //
+ // only one attribute group was read in
+ //
+
+ if (AttributeGroup == SAMP_FIXED_ATTRIBUTES) {
+ Context->FixedValid = TRUE;
+ Context->FixedDirty = FALSE;
+ } else {
+
+ ASSERT(AttributeGroup == SAMP_VARIABLE_ATTRIBUTES);
+ Context->VariableValid = TRUE;
+ Context->VariableDirty = FALSE;
+
+ Context->OnDiskUsed = SampDwordAlignUlong(TotalRequiredLength);
+ Context->OnDiskFree = Context->OnDiskAllocated -
+ Context->OnDiskUsed;
+ }
+ } else {
+
+ //
+ // Both attribute groups read in.
+ //
+
+ Context->FixedValid = TRUE;
+ Context->FixedDirty = FALSE;
+
+ Context->VariableValid = TRUE;
+ Context->VariableDirty = FALSE;
+
+ Context->OnDiskUsed = SampDwordAlignUlong(TotalRequiredLength);
+ Context->OnDiskFree = Context->OnDiskAllocated -
+ Context->OnDiskUsed;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus) && !CreatedObject) {
+
+ //
+ // make any adjustments necessary to bring the data
+ // just read in up to current revision format.
+ //
+
+ NtStatus = SampUpgradeToCurrentRevision(
+ Context,
+ AttributeGroup,
+ Buffer,
+ RequiredLength,
+ &TotalRequiredLength
+ );
+ }
+
+#ifdef SAM_DEBUG_ATTRIBUTES
+ if (SampDebugAttributes) {
+ DbgPrint("SampValidateAttributes - initialized the context :\n\n");
+ SampDumpAttributes(Context);
+ }
+#endif
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampUpgradeToCurrentRevision(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup,
+ IN OUT PUCHAR Buffer,
+ IN ULONG LengthOfDataRead,
+ IN OUT PULONG TotalRequiredLength
+ )
+
+/*++
+
+ Make any changes necessary bring attributes just read in
+ from disk up to the current revision level format.
+
+ When we upgrade our attribute format, we don't bother changing
+ all data on disk. We take a lazy update approach, and only change
+ the data as it is changed for other operations. This means that
+ data we read from disk may be from revision 1. When this is
+ detected, the data is copied into a current revision structure,
+ and a pointer to that buffer is returned.
+
+
+
+
+ NOTE: For future reference, GROUP and ALIAS objects have
+ a revision level stored as a "Qualifier" value associated
+ with the security descriptor attribute. The SERVER object
+ stores the revision level in its fixed length attributes.
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeGroup - identifies which kind of attributes are being
+ validated (SAMP_FIXED_ATTRIBUTES or SAMP_VARIABLE_ATTRIBUTES).
+
+ Buffer - Pointer to the buffer containing the attributes.
+
+ LengthOfDataRead - This is an important value. It must be the value
+ returned from the registry on the read operation. This tells us
+ exactly how many bytes of data were retrieved from disk.
+
+ TotalRequiredLength - Will be left unchanged if no update was
+ was required. If an updated was made, this will be adjusted
+ to reflect the new length of the data.
+
+Return Values:
+
+ None.
+
+
+--*/
+{
+
+ LARGE_INTEGER
+ ZeroModifiedCount = {0,0};
+ PULONG
+ Pointer;
+ NTSTATUS
+ NtStatus = STATUS_SUCCESS;
+
+
+ //
+ // Note that Buffer points inside a buffer that is
+ // hung off the Context block. We don't need to re-allocate
+ // a new attributes buffer because we are only changing
+ // fixed-length attributes in this release (and the variable
+ // length attributes were placed beyond the end of the new
+ // format fixed-length data).
+ //
+ // The approach we take is to copy the current fixed-length
+ // contents into a temporary buffer, and then copy them back
+ // into the attribute buffer. This can be done with stack
+ // variables.
+ //
+
+ //
+ // Switch on the type of objects that have gone through revision
+ // changes.
+ //
+
+ switch (Context->ObjectType) {
+ case SampDomainObjectType:
+
+ //
+ // Domain FIXED_LENGTH attributes have had the following
+ // revisions:
+ //
+ // Revision 0x00010001 - NT1.0 (Revision NOT stored in )
+ // (record. )
+ // (Must ascertain revision )
+ // (by record length. )
+ //
+ // Revision 0x00010002 - NT1.0a (Revision is first ULONG )
+ // (in record. )
+
+ if (LengthOfDataRead ==
+ (sizeof(SAMP_V1_0_FIXED_LENGTH_DOMAIN) +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)) ) {
+
+ PSAMP_V1_0A_FIXED_LENGTH_DOMAIN
+ V1aFixed;
+
+ SAMP_V1_0_FIXED_LENGTH_DOMAIN
+ V1Fixed, *OldV1Fixed;
+
+
+ //
+ // Update from revision 0x00010001
+ //
+ // First, copy the current buffer contents into a temporary
+ // buffer.
+ //
+
+ OldV1Fixed = (PSAMP_V1_0_FIXED_LENGTH_DOMAIN)(Buffer +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
+
+ RtlMoveMemory(&V1Fixed, OldV1Fixed, sizeof(SAMP_V1_0_FIXED_LENGTH_DOMAIN));
+
+
+ //
+ // Now copy it back in the new format
+ //
+
+ V1aFixed = (PSAMP_V1_0A_FIXED_LENGTH_DOMAIN)OldV1Fixed;
+
+ V1aFixed->CreationTime = V1Fixed.CreationTime;
+ V1aFixed->ModifiedCount = V1Fixed.ModifiedCount;
+ V1aFixed->MaxPasswordAge = V1Fixed.MaxPasswordAge;
+ V1aFixed->MinPasswordAge = V1Fixed.MinPasswordAge;
+ V1aFixed->ForceLogoff = V1Fixed.ForceLogoff;
+ V1aFixed->NextRid = V1Fixed.NextRid;
+ V1aFixed->PasswordProperties = V1Fixed.PasswordProperties;
+ V1aFixed->MinPasswordLength = V1Fixed.MinPasswordLength;
+ V1aFixed->PasswordHistoryLength = V1Fixed.PasswordHistoryLength;
+ V1aFixed->ServerState = V1Fixed.ServerState;
+ V1aFixed->ServerRole = V1Fixed.ServerRole;
+ V1aFixed->UasCompatibilityRequired = V1Fixed.UasCompatibilityRequired;
+
+
+ //
+ // And initialize fields new for this revision
+ //
+
+ V1aFixed->Revision = SAMP_REVISION;
+ V1aFixed->LockoutDuration.LowPart = 0xCF1DCC00; // 30 minutes - low part
+ V1aFixed->LockoutDuration.HighPart = 0XFFFFFFFB; // 30 minutes - high part
+ V1aFixed->LockoutObservationWindow.LowPart = 0xCF1DCC00; // 30 minutes - low part
+ V1aFixed->LockoutObservationWindow.HighPart = 0XFFFFFFFB; // 30 minutes - high part
+ V1aFixed->LockoutThreshold = 0; // Disabled
+
+ if (V1aFixed->ServerRole == DomainServerRolePrimary) {
+ V1aFixed->ModifiedCountAtLastPromotion = V1Fixed.ModifiedCount;
+ } else {
+ V1aFixed->ModifiedCountAtLastPromotion = ZeroModifiedCount;
+ }
+ }
+
+ break; //out of switch
+
+
+
+ case SampUserObjectType:
+
+ //
+ // User FIXED_LENGTH attributes have had the following
+ // revisions:
+ //
+ // Revision 0x00010001 - NT1.0 (Revision NOT stored in )
+ // (record. )
+ // (Must ascertain revision )
+ // (by record length. )
+ //
+ // Revision 0x00010002 - NT1.0a (Revision is first ULONG )
+ // (in record. )
+ // Revision 0x00010002a - NT3.5 (Revision is first ULONG )
+ // (in record, still )
+ // (0x00010002. Must )
+ // (ascertain revison by )
+ // (by record length )
+
+ if (LengthOfDataRead ==
+ (sizeof(SAMP_V1_FIXED_LENGTH_USER) +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)) ) {
+
+ PSAMP_V1_0A_FIXED_LENGTH_USER
+ V1aFixed;
+
+ SAMP_V1_FIXED_LENGTH_USER
+ V1Fixed, *OldV1Fixed;
+
+
+ //
+ // Update from revision 0x00010001
+ //
+ // First, copy the current buffer contents into a temporary
+ // buffer.
+ //
+
+ OldV1Fixed = (PSAMP_V1_FIXED_LENGTH_USER)(Buffer +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
+ RtlMoveMemory(&V1Fixed, OldV1Fixed, sizeof(SAMP_V1_FIXED_LENGTH_USER));
+
+
+ //
+ // Now copy it back in the new format
+ //
+
+ V1aFixed = (PSAMP_V1_0A_FIXED_LENGTH_USER)OldV1Fixed;
+
+
+ V1aFixed->LastLogon = V1Fixed.LastLogon;
+ V1aFixed->LastLogoff = V1Fixed.LastLogoff;
+ V1aFixed->PasswordLastSet = V1Fixed.PasswordLastSet;
+ V1aFixed->AccountExpires = V1Fixed.AccountExpires;
+ V1aFixed->UserId = V1Fixed.UserId;
+ V1aFixed->PrimaryGroupId = V1Fixed.PrimaryGroupId;
+ V1aFixed->UserAccountControl = V1Fixed.UserAccountControl;
+ V1aFixed->CountryCode = V1Fixed.CountryCode;
+ V1aFixed->CodePage = V1Fixed.CodePage;
+ V1aFixed->BadPasswordCount = V1Fixed.BadPasswordCount;
+ V1aFixed->LogonCount = V1Fixed.LogonCount;
+ V1aFixed->AdminCount = V1Fixed.AdminCount;
+
+ //
+ // And initialize fields new for this revision
+ //
+
+ V1aFixed->Revision = SAMP_REVISION;
+ V1aFixed->LastBadPasswordTime = SampHasNeverTime;
+ V1aFixed->OperatorCount = 0;
+ V1aFixed->Unused2 = 0;
+
+ } else if ((LengthOfDataRead ==
+ (sizeof(SAMP_V1_0_FIXED_LENGTH_USER) +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data)) ) &&
+ (AttributeGroup == SAMP_FIXED_ATTRIBUTES)) {
+
+ PSAMP_V1_0A_FIXED_LENGTH_USER
+ V1aFixed;
+
+ //
+ // Update from revision 0x00010002
+ //
+ // Just set the added field at the end to 0.
+ //
+
+ V1aFixed = (PSAMP_V1_0A_FIXED_LENGTH_USER)(Buffer +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
+
+ V1aFixed->OperatorCount = 0;
+ V1aFixed->Unused2 = 0;
+ }
+
+ break; //out of switch
+
+ case SampGroupObjectType:
+ //
+ // Group FIXED_LENGTH attributes have had the following
+ // revisions:
+ //
+ // Revision 0x00010001 - NT1.0 (Revision NOT stored in )
+ // (record. )
+ // (Must ascertain revision )
+ // (by first few ULONGs. )
+ //
+ // Revision 0x00010002 - NT1.0a (Revision is first ULONG )
+ // (in record. )
+
+ Pointer = (PULONG) (Buffer + FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
+
+ //
+ // The old fixed length group had a RID in the first ULONG and
+ // an attributes field in the second. The attributes are in the
+ // first and last nibble of the field. Currently, the RID is in
+ // the second ULONG. Since all RIDs are more than one nibble,
+ // a rid will always have something set in the middle six nibbles.
+ //
+
+ if ( ( Pointer[0] != SAMP_REVISION ) &&
+ ( ( Pointer[1] & 0x0ffffff0 ) == 0 ) ) {
+
+ PSAMP_V1_0A_FIXED_LENGTH_GROUP
+ V1aFixed;
+
+ SAMP_V1_FIXED_LENGTH_GROUP
+ V1Fixed, *OldV1Fixed;
+
+ ULONG TotalLengthRequired;
+
+ //
+ // Calculate the length required for the new group information.
+ // It is the size of the old group plus enough space for the
+ // new fields in the new fixed attributes.
+ //
+
+ TotalLengthRequired = SampDwordAlignUlong(
+ LengthOfDataRead +
+ sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP) -
+ sizeof(SAMP_V1_FIXED_LENGTH_GROUP)
+ );
+
+
+ NtStatus = SampExtendAttributeBuffer(
+ Context,
+ TotalLengthRequired
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Get the new buffer pointer
+ //
+
+ Buffer = Context->OnDisk;
+
+ //
+ // Move the variable information up to make space for the
+ // fixed information
+ //
+
+ RtlMoveMemory(
+ Buffer + SampFixedBufferOffset( Context ) + sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP),
+ Buffer + SampFixedBufferOffset( Context) + sizeof(SAMP_V1_FIXED_LENGTH_GROUP),
+ LengthOfDataRead - SampFixedBufferOffset( Context) - sizeof(SAMP_V1_FIXED_LENGTH_GROUP)
+ );
+
+ //
+ // Update from revision 0x00010001
+ //
+ // First, copy the current buffer contents into a temporary
+ // buffer.
+ //
+
+ OldV1Fixed = (PSAMP_V1_FIXED_LENGTH_GROUP)(Buffer +
+ FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data));
+
+ RtlCopyMemory(&V1Fixed, OldV1Fixed, sizeof(SAMP_V1_FIXED_LENGTH_GROUP));
+
+ //
+ // Now copy it back in the new format
+ //
+
+ V1aFixed = (PSAMP_V1_0A_FIXED_LENGTH_GROUP)OldV1Fixed;
+
+ V1aFixed->Revision = SAMP_REVISION;
+ V1aFixed->Unused1 = 0;
+ V1aFixed->RelativeId = V1Fixed.RelativeId;
+ V1aFixed->Attributes = V1Fixed.Attributes;
+ V1aFixed->AdminCount = (V1Fixed.AdminGroup) ? TRUE : FALSE;
+ V1aFixed->OperatorCount = 0;
+
+ //
+ // Update the indicator of how long the on disk structure
+ // is.
+ //
+
+ Context->OnDiskUsed += (sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP) - sizeof(SAMP_V1_FIXED_LENGTH_GROUP));
+ Context->OnDiskFree = Context->OnDiskAllocated - Context->OnDiskUsed;
+ }
+
+ break;
+
+ default:
+
+ //
+ // The rest of the object types have not changed format
+ // and so need not be updated.
+ //
+
+ break; //out of switch
+
+ }
+
+ return(NtStatus);
+}
+
+
+PUCHAR
+SampObjectAttributeAddress(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ )
+
+/*++
+
+
+ Retrieve the address of a variable-length attribute.
+
+ The attributes are assumed to already be in-memory.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be retrieved.
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The attributes are in-memory.
+
+ STATUS_NO_MEMORY - Memory could not be allocated to retrieve the
+ attributes.
+
+ Other values as may be returned by registry API trying to retrieve
+ the attributes from backing store.
+
+--*/
+{
+
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE
+ AttributeArray;
+
+ PUCHAR
+ AttributeAddress;
+
+
+ ASSERT( SampVariableAttributesValid( Context ) );
+
+ AttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ SampVariableArrayAddress( Context );
+
+ AttributeAddress = (PUCHAR)Context->OnDisk +
+ ( SampVariableDataOffset( Context ) +
+ AttributeArray[AttributeIndex].Offset );
+
+ return( AttributeAddress );
+
+}
+
+
+
+ULONG
+SampObjectAttributeLength(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ )
+
+/*++
+
+ Retrieve the length of a variable-length attribute.
+
+
+ The attributes are assumed to already be in-memory.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute whose length is to be retrieved.
+
+
+
+
+Return Values:
+
+ The length of the attribute (in bytes).
+
+--*/
+{
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE
+ AttributeArray;
+
+
+ ASSERT( SampVariableAttributesValid( Context ) );
+
+ AttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ SampVariableArrayAddress( Context );
+
+ return( AttributeArray[AttributeIndex].Length );
+
+}
+
+
+PULONG
+SampObjectAttributeQualifier(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex
+ )
+
+/*++
+
+ Retrieve the address of the qualifier field of a variable-length
+ attribute.
+
+ The attributes are assumed to already be in-memory.
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute whose qualifier address is to be returned.
+
+
+
+
+Return Values:
+
+ The address of the specifed attribute's qualifier field.
+
+--*/
+{
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE
+ AttributeArray;
+
+
+ ASSERT( SampVariableAttributesValid( Context ) );
+
+ AttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ SampVariableArrayAddress( Context );
+
+ return( &(AttributeArray[AttributeIndex].Qualifier) );
+
+}
+
+
+NTSTATUS
+SampGetAttributeBufferReadInfo(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeGroup,
+ OUT PUCHAR *Buffer,
+ OUT PULONG BufferLength,
+ OUT PUNICODE_STRING *KeyAttributeName
+ )
+
+/*++
+
+ Get attribute buffer information needed to read data from
+ backing store.
+
+ If there is currently no attribute buffer, then allocate one.
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeGroup - Indicates which attribute grouping you are
+ interested in. This is only interesting if the fixed and
+ variable-length attributes are stored separately.
+
+ Buffer - Receives a pointer to the beginning of the appropriate
+ buffer (fixed or variable). This will be dword aligned.
+ If the attributes are stored together, this will point
+ to the beginning of the fixed-length attributes.
+
+ BufferLength - Receives the number of bytes in the buffer.
+
+ KeyAttributeName - Receives a pointer to the unicode name of the
+ attribute to read the attributes from.
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The attributes have been read.
+
+ STATUS_NO_MEMORY - Memory could not be allocated to receive the
+ data from disk.
+
+ Other values as may be returned reading from disk.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus = STATUS_SUCCESS;
+
+
+ //
+ // If the context block currently has no buffer info, then
+ // "extend" (create) it so we can return buffer information.
+ //
+
+ if (Context->OnDiskAllocated == 0) {
+
+ NtStatus = SampExtendAttributeBuffer(
+ Context,
+ SAMP_MINIMUM_ATTRIBUTE_ALLOC
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+
+
+ //
+ // Get the buffer address and length
+ //
+
+ if (SampObjectInformation[Context->ObjectType].FixedStoredSeparately) {
+
+ //
+ // stored separately. Address and length is dependent upon
+ // what is being asked for. Source registry attribute name
+ // is also.
+ //
+
+ if (AttributeGroup == SAMP_FIXED_ATTRIBUTES) {
+ (*Buffer) = Context->OnDisk;
+ (*BufferLength) = SampVariableBufferOffset( Context );
+ (*KeyAttributeName) = &SampFixedAttributeName;
+ } else {
+ (*Buffer) = SampVariableBufferAddress( Context );
+ (*BufferLength) = SampVariableBufferLength( Context );
+ (*KeyAttributeName) = &SampVariableAttributeName;
+ }
+
+ } else {
+
+ //
+ // Attributes stored together - doesn't matter which is being
+ // asked for.
+ //
+
+ (*Buffer) = Context->OnDisk;
+ (*BufferLength) = Context->OnDiskAllocated;
+ (*KeyAttributeName) = &SampCombinedAttributeName;
+ }
+
+
+ return(NtStatus);
+}
+
+
+
+
+
+NTSTATUS
+SampExtendAttributeBuffer(
+ IN PSAMP_OBJECT Context,
+ IN ULONG NewSize
+ )
+
+
+/*++
+
+ This routine extends (or creates) an attribute buffer by allocating
+ a larger one. It then copies the existing buffer's contents into
+ the new buffer, if there is an existing buffer.
+
+ If a new buffer can not be allocated, then the context block is
+ returned with the old buffer intact.
+
+ If this call succeeds, the buffer will be at least as large as
+ that asked for (and perhaps larger).
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ NewSize - The number of bytes to allocate for the new buffer.
+ This value can not be less than the number of bytes currently
+ in use.
+
+
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The attributes are in-memory.
+
+ STATUS_NO_MEMORY - Memory could not be allocated to retrieve the
+ attributes.
+
+ Other values as may be returned by registry API trying to retrieve
+ the attributes from backing store.
+
+--*/
+
+{
+
+ PUCHAR
+ OldBuffer;
+
+ ULONG
+ AllocationSize;
+
+
+#if DBG
+ if ( Context->VariableValid ) {
+ ASSERT(NewSize >= Context->OnDiskUsed);
+ }
+#endif
+
+
+ //
+ // Is an allocation necessary?
+ //
+
+ if (NewSize <= Context->OnDiskAllocated) {
+ return(STATUS_SUCCESS);
+ }
+
+
+
+ OldBuffer = Context->OnDisk;
+
+
+ //
+ // Pad the extend to allow for future edits efficiently.
+ //
+
+ AllocationSize = SampDwordAlignUlong(NewSize + SAMP_MINIMUM_ATTRIBUTE_PAD);
+ Context->OnDisk = RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ AllocationSize
+ );
+
+ if (Context->OnDisk == NULL) {
+ Context->OnDisk = OldBuffer;
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ //
+ // Set the new allocated size
+
+ Context->OnDiskAllocated = AllocationSize;
+
+ //
+ // If there was no buffer originally, then zero the new buffer, mark
+ // it as being invalid, and return.
+ //
+
+ if (OldBuffer == NULL) {
+
+ RtlZeroMemory( (PVOID)Context->OnDisk, AllocationSize );
+
+ Context->FixedDirty = TRUE;
+ Context->VariableDirty = TRUE;
+ Context->FixedValid = FALSE;
+ Context->VariableValid = FALSE;
+
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Set the free size. Note that this information is only set if
+ // the variable data is valid.
+ // Used size remains the same.
+ //
+
+ if (Context->VariableValid == TRUE) {
+ Context->OnDiskFree = AllocationSize - Context->OnDiskUsed;
+ ASSERT(Context->OnDiskUsed == SampDwordAlignUlong(Context->OnDiskUsed));
+ }
+
+
+ //
+ // There was an old buffer (or else we would have exited earlier).
+ // If any data in it was valid, copy it to the new buffer. Free the
+ // old buffer.
+ //
+
+ if ( Context->FixedValid ) {
+
+ RtlCopyMemory(
+ Context->OnDisk,
+ OldBuffer,
+ SampFixedBufferLength( Context ) + SampFixedBufferOffset( Context )
+ );
+ }
+
+ //
+ // Note: in thise case we may copy the fixed data twice, since if the
+ // variable data is not stored separately then SampVariableBufferOffset
+ // is zero.
+ //
+
+ if ( Context->VariableValid ) {
+
+ RtlCopyMemory(
+ SampVariableBufferAddress( Context ),
+ OldBuffer + SampVariableBufferOffset( Context ),
+ Context->OnDiskUsed - SampVariableBufferOffset( Context )
+ );
+ }
+
+ RtlFreeHeap( RtlProcessHeap(), 0, OldBuffer );
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampReadRegistryAttribute(
+ IN HANDLE Key,
+ IN PUCHAR Buffer,
+ IN ULONG BufferLength,
+ IN PUNICODE_STRING AttributeName,
+ OUT PULONG RequiredLength
+ )
+
+/*++
+
+
+ Retrieve the address of a variable-length attribute.
+
+ The attributes are assumed to already be in-memory.
+
+
+
+Parameters:
+
+ Key - Handle to the key whose attribute is to be read.
+
+ Buffer - Pointer to the buffer to receive the information.
+
+ BufferLength - Length of the buffer receiving the information.
+
+ AttributeName - The name of the attribute.
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - Successful completion.
+
+
+ STATUS_BUFFER_TOO_SMALL - The data could not be read because the
+ buffer was too small.
+
+ Other values as may be returned by registry API trying to retrieve
+ the attribute from backing store.
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+
+ //
+ // Try to read the attribute
+ //
+
+ NtStatus = NtQueryValueKey( Key,
+ AttributeName, //ValueName,
+ KeyValuePartialInformation, //KeyValueInformationClass
+ (PVOID)Buffer,
+ BufferLength,
+ RequiredLength
+ );
+
+ return(NtStatus);
+
+}
+
+
+
+
+NTSTATUS
+SampSetVariableAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN ULONG Qualifier,
+ IN PUCHAR Buffer,
+ IN ULONG Length
+ )
+
+
+/*++
+
+ This API is used to set a new attribute value. The new attribute
+ value may be longer, shorter, or the same size as the current
+ attribute. The data in the attribute buffer will be shifted to
+ make room for a larger attribute value or to fill in room left by
+ a smaller attribute value.
+
+ PERFORMANCE CONCERN: If you have a lot of attributes to set, it
+ is worthwhile to start with the smallest indexed attribute
+ and work up to the largest indexed attribute.
+
+
+
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+ AttributeIndex - Indicates the index (into the variable length attribute
+ array) of the attribute to be set. Typically, all attributes beyond
+ this one will have their data shifted.
+
+ Buffer - The address of the buffer containing the new attribute value.
+ May be NULL if Length is zero.
+
+ Length - The length (in bytes) of the new attribute value.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ STATUS_NO_MEMORY - Memory to expand the attribute buffer could not
+ be allocated.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ ULONG
+ OriginalAttributeLength,
+ AdditionalSpaceNeeded,
+ NewBufferLength,
+ MaximumAttributeIndex,
+ MoveLength,
+ i;
+
+ LONG
+ OffsetDelta;
+
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE
+ AttributeArray;
+
+
+ PUCHAR
+ NewStart,
+ OriginalStart;
+
+ //
+ // Make sure the requested attribute exists for the specified
+ // object type.
+ //
+
+ SampValidateAttributeIndex( Context, AttributeIndex );
+
+
+
+ //
+ // Make the data valid
+ //
+
+ NtStatus = SampValidateAttributes( Context, SAMP_VARIABLE_ATTRIBUTES );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Allocate a new buffer if necessary
+ //
+
+ OriginalAttributeLength = SampObjectAttributeLength(Context, AttributeIndex);
+
+ if (OriginalAttributeLength < Length) {
+
+ AdditionalSpaceNeeded = Length - OriginalAttributeLength;
+
+ if (Context->OnDiskFree < AdditionalSpaceNeeded) {
+
+ NewBufferLength = Context->OnDiskUsed + AdditionalSpaceNeeded;
+ ASSERT(NewBufferLength > Context->OnDiskAllocated);
+
+ NtStatus = SampExtendAttributeBuffer( Context, NewBufferLength );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+ }
+
+ //
+ // Get the address of the attribute array.
+ //
+
+ AttributeArray = SampVariableArrayAddress( Context );
+
+
+ //
+ // Now shift following attribute values
+ //
+
+ OffsetDelta = (LONG)(SampDwordAlignUlong(Length) -
+ SampDwordAlignUlong(OriginalAttributeLength));
+
+ MaximumAttributeIndex = SampVariableAttributeCount( Context );
+
+ if ((OffsetDelta != 0) && (AttributeIndex+1 < MaximumAttributeIndex)) {
+
+ //
+ // Shift all attributes above this one up or down by the OffsetDelta
+ //
+
+ MoveLength = Context->OnDiskUsed -
+ ( SampVariableDataOffset( Context ) +
+ AttributeArray[AttributeIndex+1].Offset );
+
+ //
+ // Shift the data (if there is any)
+ //
+
+ if (MoveLength != 0) {
+
+ OriginalStart = SampObjectAttributeAddress( Context, AttributeIndex+1);
+ NewStart = (PUCHAR)(OriginalStart + OffsetDelta);
+ RtlMoveMemory( NewStart, OriginalStart, MoveLength );
+ }
+
+
+ //
+ // Adjust the offset pointers
+ //
+
+ for ( i=AttributeIndex+1; i<MaximumAttributeIndex; i++) {
+ AttributeArray[i].Offset =
+ (ULONG)(OffsetDelta + (LONG)(AttributeArray[i].Offset));
+ }
+ }
+
+
+
+ //
+ // Now set the length and qualifier, and copy in the new attribute value
+ // (if it is non-zero length)
+ //
+
+ AttributeArray[AttributeIndex].Length = Length;
+ AttributeArray[AttributeIndex].Qualifier = Qualifier;
+
+ if (Length != 0) {
+
+ RtlCopyMemory( SampObjectAttributeAddress( Context, AttributeIndex ),
+ Buffer,
+ Length
+ );
+ }
+
+
+
+ //
+ // Adjust the Used and Free values
+ //
+
+ Context->OnDiskUsed += OffsetDelta;
+ Context->OnDiskFree -= OffsetDelta;
+
+ ASSERT(Context->OnDiskFree == Context->OnDiskAllocated - Context->OnDiskUsed);
+
+ //
+ // Mark the variable attributes dirty
+ //
+
+ Context->VariableDirty = TRUE;
+
+
+#ifdef SAM_DEBUG_ATTRIBUTES
+ if (SampDebugAttributes) {
+ DbgPrint("SampSetVariableAttribute %d to length %#x, qualifier %#x:\n", AttributeIndex, Length, Qualifier);
+ SampDumpAttributes(Context);
+ }
+#endif
+
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+VOID
+SampFreeAttributeBuffer(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+
+ Free the buffer used to keep in-memory copies of the on-disk
+ object attributes.
+
+
+Parameters:
+
+ Context - Pointer to the object context whose buffer is to
+ be freed.
+
+
+Return Values:
+
+ None.
+
+--*/
+
+{
+#if DBG
+ if ( Context->FixedValid ) { ASSERT(Context->FixedDirty == FALSE); }
+ if ( Context->VariableValid) { ASSERT(Context->VariableDirty == FALSE); }
+ ASSERT(Context->OnDisk != NULL);
+ ASSERT(Context->OnDiskAllocated != 0);
+#endif
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Context->OnDisk );
+
+ Context->OnDisk = NULL;
+ Context->OnDiskAllocated = 0;
+
+ //
+ // Mark all attributes as invalid
+ //
+
+ Context->FixedValid = FALSE;
+ Context->VariableValid = FALSE;
+
+
+ return;
+}
+
+
+
+#ifdef SAM_DEBUG_ATTRIBUTES
+VOID
+SampDumpAttributes(
+ IN PSAMP_OBJECT Context
+ )
+
+
+/*++
+
+ This is a debug-only API to dump out the attributes for a context
+ to the kernel debugger.
+
+Parameters:
+
+ Context - Pointer to an object context block.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ ULONG Index;
+ PSAMP_OBJECT_INFORMATION ObjectTypeInfo = &SampObjectInformation[Context->ObjectType];
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE AttributeArray;
+
+ AttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ SampVariableArrayAddress( Context );
+
+
+ DbgPrint("Dumping context 0x%x\n", Context);
+ DbgPrint("\n");
+ DbgPrint("TYPE INFO\n");
+ DbgPrint("Object type name = %wZ\n", &ObjectTypeInfo->ObjectTypeName);
+ DbgPrint("Fixed stored separately = %s\n", ObjectTypeInfo->FixedStoredSeparately ? "TRUE" : "FALSE");
+ DbgPrint("Fixed attributes offset = %#x\n", ObjectTypeInfo->FixedAttributesOffset);
+ DbgPrint("Fixed attributes size = %#x\n", ObjectTypeInfo->FixedLengthSize);
+ DbgPrint("Variable buffer offset = %#x\n", ObjectTypeInfo->VariableBufferOffset);
+ DbgPrint("Variable array offset = %#x\n", ObjectTypeInfo->VariableArrayOffset);
+ DbgPrint("Variable data offset = %#x\n", ObjectTypeInfo->VariableDataOffset);
+ DbgPrint("Variable attribute count = %d\n", ObjectTypeInfo->VariableAttributeCount);
+ DbgPrint("\n");
+ DbgPrint("INSTANCE INFO\n");
+ DbgPrint("RootName = %wZ\n", &Context->RootName);
+ DbgPrint("Fixed Valid = %s\n", Context->FixedValid ? "TRUE" : "FALSE");
+ DbgPrint("Variable Valid = %s\n", Context->VariableValid ? "TRUE" : "FALSE");
+ DbgPrint("Fixed Dirty = %s\n", Context->FixedDirty ? "TRUE" : "FALSE");
+ DbgPrint("Variable Dirty = %s\n", Context->VariableDirty ? "TRUE" : "FALSE");
+ DbgPrint("OnDiskAllocated = %#x\n", Context->OnDiskAllocated);
+ DbgPrint("OnDiskUsed = %#x\n", Context->OnDiskUsed);
+ DbgPrint("OnDiskFree = %#x\n", Context->OnDiskFree);
+ DbgPrint("\n");
+
+ if ( Context->VariableValid ) {
+
+ for (Index = 0; Index < ObjectTypeInfo->VariableAttributeCount; Index ++) {
+
+ DbgPrint("Attr %d: Qualifier = %#6x, Offset = %#6x, Length = %#6x\n",
+ Index,
+ AttributeArray[Index].Qualifier,
+ AttributeArray[Index].Offset,
+ AttributeArray[Index].Length
+ );
+ SampDumpData(SampObjectAttributeAddress(Context, Index),
+ SampObjectAttributeLength(Context, Index));
+ }
+ }
+
+ DbgPrint("\n\n");
+}
+
+
+VOID
+SampDumpData(
+ IN PVOID Buffer,
+ IN ULONG Length
+ )
+
+
+/*++
+
+ This is a debug-only API to dump out a buffer in hex
+
+Parameters:
+
+ Buffer - Pointer to data
+
+ Length - number of bytes in data
+
+Return Values:
+
+ None.
+
+--*/
+{
+ ULONG Index;
+
+ for (Index = 0; Index < Length; Index ++) {
+
+ ULONG Value = (ULONG)(((PBYTE)Buffer)[Index]);
+
+ if ((Index % 16) == 0) {
+ DbgPrint("\n ");
+ }
+
+ DbgPrint("%02x ", Value & 0xff);
+ }
+
+ if (Length > 0) {
+ DbgPrint("\n\n");
+ }
+}
+
+#endif
diff --git a/private/newsam/server/bldsam3.c b/private/newsam/server/bldsam3.c
new file mode 100644
index 000000000..93ee15c6c
--- /dev/null
+++ b/private/newsam/server/bldsam3.c
@@ -0,0 +1,5260 @@
+/*++
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ bldsam3.c
+
+Abstract:
+
+ This module provides an initialization capability to SAM.
+
+
+ Approach
+ --------
+
+ This code has gone through a number of migrations that make it
+ less than obvious what is going on. To leverage off existing
+ code and yet extend it to the initialization of two domains,
+ with aliases, the following aproach has been taken:
+
+ (1) Obtain the name and SID of the account domain.
+
+ (2) Build the various security descriptors needed
+ in the two domains. These are kept in an array
+ and the index is used to specify which applies
+ to each new account.
+
+ (3) Build up a list of alias memberships. These, too,
+ are selected by index, with one entry being the
+ empty set.
+
+
+Author:
+
+ Jim Kelly 3-May-1991.
+
+Revision History:
+
+--*/
+
+#include <nt.h>
+#include <ntsam.h>
+#include "ntlsa.h"
+#include <ntrtl.h>
+#include <nturtl.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "samsrvp.h"
+
+
+
+
+
+
+//
+// Macro to round up a ULONG value to be dword aligned
+// (i.e., be a multipleof 4).
+// Note, this does not handle the case where the Ulong is greater
+// than 0xfffffffc.
+//
+
+#define SampDwordAlignUlong( v ) (((v)+3) & 0xfffffffc)
+
+//
+// Constants used for Sam Global Data string buffers
+//
+
+#define SAMP_MAXIMUM_INTERNAL_NAME_LENGTH ((USHORT) 0x00000200L)
+
+
+
+
+
+//
+// domain selector
+//
+
+typedef enum _SAMP_DOMAIN_SELECTOR {
+
+ DomainBuiltin = 0,
+ DomainAccount
+
+} SAMP_DOMAIN_SELECTOR, *PSAMP_DOMAIN_SELECTOR;
+
+//
+// Types of protection that may be assigned to accounts
+//
+
+typedef ULONG SAMP_ACCOUNT_PROTECTION;
+
+#define SAMP_PROT_SAM_SERVER (0L)
+#define SAMP_PROT_BUILTIN_DOMAIN (1L)
+#define SAMP_PROT_ACCOUNT_DOMAIN (2L)
+#define SAMP_PROT_ADMIN_ALIAS (3L)
+#define SAMP_PROT_PWRUSER_ACCESSIBLE_ALIAS (4L)
+#define SAMP_PROT_NORMAL_ALIAS (5L)
+#define SAMP_PROT_ADMIN_GROUP (6L)
+#define SAMP_PROT_NORMAL_GROUP (7L)
+#define SAMP_PROT_ADMIN_USER (8L)
+#define SAMP_PROT_NORMAL_USER (9L)
+#define SAMP_PROT_GUEST_ACCOUNT (10L)
+#define SAMP_PROT_TYPES (11L)
+
+//
+// Protection information for SAM objects
+//
+
+typedef struct _SAMP_PROTECTION {
+
+ ULONG Length;
+ PSECURITY_DESCRIPTOR Descriptor;
+ PULONG RidToReplace;
+ BOOLEAN RidReplacementRequired;
+
+} SAMP_PROTECTION, *PSAMP_PROTECTION;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////
+// //
+// Global variables //
+// //
+///////////////////////////////////////////////////////////////////////
+
+static LSA_HANDLE SampBldPolicyHandle; //Handle to LSA policy object
+
+static NTSTATUS Status;
+
+static BOOLEAN SampRealSetupWasRun; //Indicates a real setup was run
+static BOOLEAN SampDeveloperSetup; //Indicates a developer setup is running
+
+NT_PRODUCT_TYPE SampProductType;
+static DOMAIN_SERVER_ROLE SampServerRole;
+static PPOLICY_PRIMARY_DOMAIN_INFO SampBldPrimaryDomain = NULL;
+
+
+
+static PSID WorldSid,
+ LocalSystemSid,
+ AdminsAliasSid,
+ UsersAliasSid,
+ PowerUsersAliasSid,
+ AccountAliasSid,
+ AnySidInAccountDomain;
+
+
+static PACL TokenDefaultDaclInformation;
+static ULONG TokenDefaultDaclInformationSize;
+
+//
+// Handle to the registry key in which the SAM database resides
+//
+
+static HANDLE SamParentKey = NULL;
+
+//
+// Handle to the root SAM key.
+// This is the key that has the RXACT applied to it.
+//
+
+static HANDLE SamKey = NULL;
+
+static PRTL_RXACT_CONTEXT SamRXactContext;
+
+//
+// Assorted names, buffers, and values used during registry key creation
+//
+
+static PSID DomainSid;
+static PUNICODE_STRING DomainNameU, FullDomainNameU;
+static UNICODE_STRING AccountInternalDomainNameU, BuiltinInternalDomainNameU;
+static UNICODE_STRING AccountExternalDomainNameU, BuiltinExternalDomainNameU;
+static UNICODE_STRING FullAccountInternalDomainNameU, FullBuiltinInternalDomainNameU;
+static UNICODE_STRING DomainNamePrefixU, TemporaryNamePrefixU, KeyNameU, TempStringU;
+
+static WCHAR KeyNameBuffer[2000];
+static WCHAR TempStringBuffer[2000];
+static SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+
+
+
+//
+// Values that get placed in registry keys...
+//
+
+static LARGE_INTEGER DomainMaxPasswordAge = { 0, - 6L * 7L * 24L * 60L / 7L }; // 6 weeks
+static LARGE_INTEGER ModifiedCount = {0,0};
+static UNICODE_STRING NullUnicodeString;
+
+
+
+//
+// Array of protection information for SAM objects
+//
+
+static SAMP_PROTECTION SampProtection[SAMP_PROT_TYPES];
+
+
+
+
+
+
+
+
+//
+// Internal routine definitions
+//
+
+
+
+VOID
+SampGetDomainPolicy( VOID );
+
+VOID
+SampGetServerRole( VOID );
+
+VOID
+SampGetPrimaryDomainInfo( VOID );
+
+
+VOID
+GetDomainSids( VOID );
+
+VOID
+SetDomainName(
+ IN BOOLEAN BuiltinDomain
+ );
+
+
+VOID
+Usage ( VOID );
+
+
+NTSTATUS
+Initialize( VOID );
+
+BOOLEAN
+InitializeSecurityDescriptors( VOID );
+
+NTSTATUS
+SampCreateDatabaseProtection(
+ PISECURITY_DESCRIPTOR SD
+ );
+
+NTSTATUS
+SampBuildNewProtection(
+ IN ULONG AceCount,
+ IN PSID *AceSid,
+ IN ACCESS_MASK *AceMask,
+ IN PGENERIC_MAPPING GenericMap,
+ IN BOOLEAN UserObject,
+ OUT PSAMP_PROTECTION Result
+ );
+
+NTSTATUS
+InitializeSam( VOID );
+
+NTSTATUS
+PrepDomain(
+ IN SAMP_DOMAIN_SELECTOR Domain
+ );
+
+
+VOID
+SetCurrentDomain(
+ IN SAMP_DOMAIN_SELECTOR Domain
+ );
+
+
+NTSTATUS
+CreateBuiltinDomain( VOID );
+
+NTSTATUS
+CreateAccountDomain( VOID );
+
+
+
+NTSTATUS
+CreateAlias(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG Rid,
+ IN ULONG ProtectionIndex
+ );
+
+
+NTSTATUS
+CreateGroup(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG Rid,
+ IN BOOLEAN Admin
+ );
+
+
+NTSTATUS
+CreateUser(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG UserRid,
+ IN ULONG PrimaryGroup,
+ IN BOOLEAN Admin,
+ IN ULONG UserControl,
+ IN ULONG ProtectionIndex
+ );
+
+
+
+NTSTATUS
+UpdateAliasXReference(
+ IN ULONG AliasRid,
+ IN PSID Sid
+ );
+
+
+NTSTATUS
+OpenAliasMember(
+ IN PSID Sid,
+ OUT PHANDLE KeyHandle
+ );
+
+
+PSID
+BuildPrimaryDomainSid(
+ ULONG Rid
+ );
+
+PSID
+BuildAccountSid(
+ SAMP_DOMAIN_SELECTOR Domain,
+ ULONG Rid
+ );
+
+
+NTSTATUS
+OpenOrCreateAccountRidKey(
+ IN PSID Sid,
+ IN HANDLE AliasDomainHandle,
+ OUT PHANDLE KeyHandle
+ );
+
+NTSTATUS
+OpenOrCreateAliasDomainKey(
+ IN PSID Sid,
+ OUT PHANDLE KeyHandle
+ );
+
+NTSTATUS
+AppendAliasDomainNameToUnicodeString(
+ IN OUT PUNICODE_STRING Destination,
+ IN PSID Sid
+ );
+
+
+
+NTSTATUS
+SampInitilializeRegistry ( VOID );
+
+
+NTSTATUS
+SampDetermineSetupEnvironment( VOID );
+
+
+
+NTSTATUS
+SampGetMessageStrings(
+ LPVOID Resource,
+ DWORD Index1,
+ PUNICODE_STRING String1,
+ DWORD Index2,
+ PUNICODE_STRING String2 OPTIONAL
+ );
+
+
+
+
+///////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////
+
+
+VOID
+Usage (
+ VOID
+ )
+/*++
+
+
+Routine Description:
+
+ This routine prints the "Usage:" message.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+#if DBG
+ BldPrint( "\n");
+ BldPrint( "\n");
+
+ BldPrint( "We offer no assistance in this suicide.\n");
+ BldPrint( "\n");
+ BldPrint( "\n");
+ BldPrint( "\n");
+#endif
+
+ return;
+}
+
+
+VOID
+UnexpectedProblem (
+ VOID
+ )
+/*++
+
+
+Routine Description:
+
+ This routine prints a message indicating that an unexpected
+ problem has occured.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+#if DBG
+ BldPrint( "\n");
+ BldPrint( "\n");
+ BldPrint( " An unexpected problem has prevented the command from\n");
+ BldPrint( " completing successfully. Please contact one of the\n");
+ BldPrint( " members of the security group for assistance.\n");
+ BldPrint( "\n");
+#endif
+
+ return;
+
+}
+
+NTSTATUS
+Initialize(
+ VOID
+ )
+/*++
+
+
+Routine Description:
+
+ This routine performs initialization operations before creating
+ each domain.
+
+ This includes:
+
+ - Setting the correct default owner and DACL for registry key
+ operations.
+
+ - opening the parent registry key for the SAM database.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE - Indicates initialization was successful.
+
+ FALSE - Indicates initialization was not successful.
+
+--*/
+
+{
+ OBJECT_ATTRIBUTES SamParentAttributes, PolicyObjectAttributes;
+ UNICODE_STRING SamParentNameU;
+ ULONG Disposition;
+ HANDLE Token;
+ TOKEN_OWNER LocalSystemOwner;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ SID_IDENTIFIER_AUTHORITY WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY;
+ PACL Dacl;
+ TOKEN_DEFAULT_DACL DefaultDacl;
+ BOOLEAN CompletionStatus;
+ BOOLEAN ProductTypeRetrieved;
+
+ //
+ // Set up some of the well known account SIDs for use...
+ //
+
+ WorldSid = (PSID)RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+ ASSERT(WorldSid != NULL);
+ RtlInitializeSid( WorldSid, &WorldSidAuthority, 1 );
+ *(RtlSubAuthoritySid( WorldSid, 0 )) = SECURITY_WORLD_RID;
+
+ AdminsAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(AdminsAliasSid != NULL);
+ RtlInitializeSid( AdminsAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AdminsAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AdminsAliasSid, 1 )) = DOMAIN_ALIAS_RID_ADMINS;
+
+ PowerUsersAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(PowerUsersAliasSid != NULL);
+ RtlInitializeSid( PowerUsersAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( PowerUsersAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( PowerUsersAliasSid, 1 )) = DOMAIN_ALIAS_RID_POWER_USERS;
+
+ UsersAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(UsersAliasSid != NULL);
+ RtlInitializeSid( UsersAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( UsersAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( UsersAliasSid, 1 )) = DOMAIN_ALIAS_RID_USERS;
+
+ AccountAliasSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 2 ));
+ ASSERT(AccountAliasSid != NULL);
+ RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS;
+
+ LocalSystemSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+ ASSERT(LocalSystemSid != NULL);
+ RtlInitializeSid( LocalSystemSid, &NtAuthority, 1 );
+ *(RtlSubAuthoritySid( LocalSystemSid, 0 )) = SECURITY_LOCAL_SYSTEM_RID;
+
+ //
+ // Setup a buffer to use for all our key-name constructions
+ //
+
+ KeyNameU.MaximumLength = 2000;
+ KeyNameU.Buffer = KeyNameBuffer;
+
+ //
+ // Setup temporary Unicode string buffer.
+ //
+
+ TempStringU.Buffer = TempStringBuffer;
+ TempStringU.MaximumLength = 2000;
+
+ //
+ // Get a handle to the LSA Policy object
+ //
+
+ InitializeObjectAttributes(
+ &PolicyObjectAttributes,
+ NULL, // Name
+ 0, // Attributes
+ NULL, // Root
+ NULL // Security Descriptor
+ );
+
+ Status = LsaIOpenPolicyTrusted( &SampBldPolicyHandle );
+
+ if (!NT_SUCCESS(Status)) {
+
+ KdPrint(("newsam\\server\\bldsam3: Couldn't open LSA Policy object.\n"
+ " Status: 0x%lx\n\n", Status));
+ return(Status);
+ }
+
+ //
+ // Get the product type.
+ //
+
+ ProductTypeRetrieved = RtlGetNtProductType( &SampProductType );
+
+ if (!ProductTypeRetrieved) {
+
+ KdPrint(("Couldn't retrieve product type\n"));
+ return(STATUS_UNSUCCESSFUL);
+ }
+
+ //
+ // Figure out if we are being initialized following a real
+ // setup, or it this is a developer setup.
+ //
+
+ SampDetermineSetupEnvironment();
+
+ //
+ // Domain name prefix is required by SampGetDomainPolicy() and
+ // so must be initialized before that call.
+ //
+
+ RtlInitUnicodeString( &DomainNamePrefixU, L"Domains");
+
+ //
+ // Set up domain names/Sids.
+ //
+
+ SampGetDomainPolicy();
+
+ if (!NT_SUCCESS(Status)) {
+
+ return(Status);
+ }
+
+ //
+ // Get the role of this machine.
+ //
+
+ SampGetServerRole();
+
+ if (!NT_SUCCESS(Status)) {
+
+ return(Status);
+ }
+
+ //
+ // Get the primary domain info.
+ //
+
+ SampGetPrimaryDomainInfo();
+
+ //
+ // Set the default owner to SYSTEM
+ //
+
+ Status = NtOpenProcessToken(
+ NtCurrentProcess(),
+ (TOKEN_ADJUST_DEFAULT | TOKEN_QUERY),
+ &Token
+ );
+ SUCCESS_ASSERT(Status, " Couldn't open process token.\n");
+
+ Status = NtQueryInformationToken(
+ Token,
+ TokenDefaultDacl,
+ NULL,
+ 0,
+ &TokenDefaultDaclInformationSize
+ );
+
+ if (Status != STATUS_BUFFER_TOO_SMALL) {
+
+ KdPrint(("Error: Unable to query default ACL information\n"));
+ return( Status );
+ }
+
+ TokenDefaultDaclInformation = RtlAllocateHeap(RtlProcessHeap(), 0, TokenDefaultDaclInformationSize );
+
+ Status = NtQueryInformationToken(
+ Token,
+ TokenDefaultDacl,
+ TokenDefaultDaclInformation,
+ TokenDefaultDaclInformationSize,
+ &TokenDefaultDaclInformationSize
+ );
+
+ SUCCESS_ASSERT(Status, " Couldn't query default ACL information.\n");
+
+ LocalSystemOwner.Owner = LocalSystemSid;
+
+ Status = NtSetInformationToken(
+ Token,
+ TokenOwner,
+ &LocalSystemOwner,
+ (ULONG)sizeof(TOKEN_OWNER)
+ );
+
+ if (Status == STATUS_INVALID_OWNER) {
+#if DBG
+ BldPrint(" This command must be issued from an account that may\n");
+ BldPrint(" assign the default SYSTEM id as the owner of objects.\n");
+ BldPrint(" \n");
+ BldPrint(" Try logging on to the system account and issuing this\n");
+ BldPrint(" command again.\n");
+ BldPrint(" \n");
+#endif
+ return(Status);
+ }
+
+ //
+ // Set our default protection so that only system and ADMINISTRATORS
+ // can get to the registry keys created.
+ // The protection established is
+ //
+ // Grant:LocalSystem:Read|Write|Execute|Delete
+ // Grant:Administrators:Read|Execute|Delete|write_dacl
+ //
+ //
+ //
+
+ Dacl = RtlAllocateHeap( RtlProcessHeap(), 0, 256 );
+ if (Dacl == NULL) {
+#if DBG
+ BldPrint(" Exhausted Heap Space Allocating Dacl\n");
+#endif
+ return(STATUS_NO_MEMORY);
+ }
+
+ Status = RtlCreateAcl( Dacl, 256, ACL_REVISION2);
+ SUCCESS_ASSERT(Status, " Failed to initialize Dacl\n");
+
+ Status = RtlAddAccessAllowedAce(
+ Dacl,
+ ACL_REVISION2,
+ (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | DELETE ),
+ LocalSystemSid
+ );
+ SUCCESS_ASSERT(Status, " Failed to add SYSTEM ACE to Dacl\n");
+ DefaultDacl.DefaultDacl = Dacl;
+
+ Status = RtlAddAccessAllowedAce(
+ Dacl,
+ ACL_REVISION2,
+ (GENERIC_READ |
+ GENERIC_EXECUTE |
+ DELETE |
+ WRITE_DAC),
+ AdminsAliasSid
+ );
+ SUCCESS_ASSERT(Status, " Failed to add ADMIN alias ACE to Dacl\n");
+ DefaultDacl.DefaultDacl = Dacl;
+
+ Status = NtSetInformationToken(
+ Token,
+ TokenDefaultDacl,
+ &DefaultDacl,
+ (ULONG)sizeof(TOKEN_DEFAULT_DACL)
+ );
+ SUCCESS_ASSERT(Status, " Couldn't assign default Dacl\n");
+ Status = NtClose( Token );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Dacl ); // No longer needed
+
+ //
+ // Open a handle to the parent of the SAM registry location.
+ // This parent must already exist.
+ //
+
+ RtlInitUnicodeString( &SamParentNameU, L"\\Registry\\Machine\\Security" );
+
+ InitializeObjectAttributes(
+ &SamParentAttributes,
+ &SamParentNameU,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL
+ );
+ Status = RtlpNtCreateKey(
+ &SamParentKey,
+ (KEY_READ | KEY_CREATE_SUB_KEY),
+ &SamParentAttributes,
+ 0,
+ NULL,
+ &Disposition
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+#if DBG
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+ BldPrint( " We seem to be having trouble opening the registry\n" );
+ BldPrint( " database key in which the Security Account Manager\n" );
+ BldPrint( " information resides. This registry key should have been\n" );
+ BldPrint( " created at system startup time. Please see one of the\n" );
+ BldPrint( " security group developers for assistance in analyzing the\n" );
+ BldPrint( " the problem.\n" );
+ BldPrint( " Indicate that the registry key creation status is 0x%lx \n", Status);
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+#endif
+
+ return(Status);
+ }
+
+ //
+ // Set up some values, names, and buffers for later use
+ //
+
+
+ NullUnicodeString.Buffer = NULL;
+ NullUnicodeString.Length = 0;
+ NullUnicodeString.MaximumLength = 0;
+
+
+
+ TemporaryNamePrefixU.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0, 256);
+ TemporaryNamePrefixU.Length = 0;
+ TemporaryNamePrefixU.MaximumLength = 256;
+
+ KeyNameU.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0, 256);
+ KeyNameU.Length = 0;
+ KeyNameU.MaximumLength = 256;
+
+ //
+ // Set up Security Descriptors needed for initialization...
+ //
+
+ CompletionStatus = InitializeSecurityDescriptors();
+
+ if (CompletionStatus) {
+ Status = STATUS_SUCCESS;
+ } else {
+ Status = STATUS_UNSUCCESSFUL;
+ }
+
+ return(Status);
+}
+
+
+BOOLEAN
+InitializeSecurityDescriptors(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes security descriptors needed to create
+ a SAM database.
+
+
+ This routine expects all SIDs to be previously initialized.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE - Indicates initialization was successful.
+
+ FALSE - Indicates initialization was not successful.
+
+
+ The security descriptors are pointed to by global variables.
+
+--*/
+
+{
+ PSID AceSid[10]; // Don't expect more than 10 ACEs in any of these.
+ ACCESS_MASK AceMask[10]; // Access masks corresponding to Sids
+
+ ACCESS_MASK NotForThisProductType; // Used to mask product-specific access restrictions
+
+ GENERIC_MAPPING SamServerMap = {SAM_SERVER_READ,
+ SAM_SERVER_WRITE,
+ SAM_SERVER_EXECUTE,
+ SAM_SERVER_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING DomainMap = {DOMAIN_READ,
+ DOMAIN_WRITE,
+ DOMAIN_EXECUTE,
+ DOMAIN_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING AliasMap = {ALIAS_READ,
+ ALIAS_WRITE,
+ ALIAS_EXECUTE,
+ ALIAS_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING GroupMap = {GROUP_READ,
+ GROUP_WRITE,
+ GROUP_EXECUTE,
+ GROUP_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING UserMap = {USER_READ,
+ USER_WRITE,
+ USER_EXECUTE,
+ USER_ALL_ACCESS
+ };
+
+ //
+ // We need a number of different security descriptors:
+ //
+
+ //
+ //
+ // The following security is assigned to
+ //
+ // - Builtin DOMAIN objects
+ //
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant
+ // WORLD Administrators
+ // (Execute | Read) GenericRead |
+ // GenericExecute |
+ // DOMAIN_READ_OTHER_PARAMETERS |
+ // DOMAIN_ADMINISTER_SERVER |
+ // DOMAIN_CREATE_ALIAS
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ //
+ //
+ // The following security is assigned to
+ //
+ // - SAM_SERVER object
+ // - Account DOMAIN objects
+ // - The Administrators alias.
+ // - All groups in the ACCOUNT or BUILTIN domain that are
+ // made a member of the Administrators alias.
+ //
+ // Note: on WinNt systems, the ACLs do not grant DOMAIN_CREATE_GROUP.
+ //
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant
+ // WORLD Administrators
+ // (Execute | Read) GenericAll
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ // All other aliases and groups must be assigned the following
+ // security:
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant Grant
+ // WORLD Administrators AccountOperators Alias
+ // (Execute | Read) GenericAll GenericAll
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ // - All users in the ACCOUNT or BUILTIN domain that are
+ // made a member of the Administratos alias. This includes
+ // direct inclusion or indirect inclusion through group
+ // membership.
+ //
+ //
+ // The following security is assigned to:
+ //
+ // - All users in the ACCOUNT or BUILTIN domain that are
+ // made a member of the Administrators alias. This includes
+ // direct inclusion or indirect inclusion through group
+ // membership.
+ //
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant Grant
+ // WORLD Administrators User's SID
+ // (Execute | Read) GenericAll GenericWrite
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ //
+ // All other users must be assigned the following
+ // security:
+ //
+ // Owner: AccountOperators Alias
+ // Group: AccountOperators Alias
+ //
+ // Dacl: Grant Grant Grant Grant
+ // WORLD Administrators Account Operators Alias User's SID
+ // (Execute | Read) GenericAll GenericAll GenericWrite
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ // except builtin GUEST, who can't change their own account info.
+ //
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //
+ // Note, however, that because we are going to cram these ACLs
+ // directly into the backing store, we must map the generic accesses
+ // beforehand.
+ //
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //
+
+
+
+
+
+
+ //
+ // Sam Server SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (SAM_SERVER_EXECUTE | SAM_SERVER_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (SAM_SERVER_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 2, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &SamServerMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_SAM_SERVER] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ //
+ // Builtin Domain SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (DOMAIN_EXECUTE | DOMAIN_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (DOMAIN_EXECUTE | DOMAIN_READ |
+ DOMAIN_READ_OTHER_PARAMETERS |
+ DOMAIN_ADMINISTER_SERVER |
+ DOMAIN_CREATE_ALIAS);
+
+
+ Status = SampBuildNewProtection(
+ 2, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &DomainMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_BUILTIN_DOMAIN] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ //
+ // Account Domain SD
+ //
+
+ if (SampProductType == NtProductLanManNt) {
+ NotForThisProductType = 0;
+ } else {
+ NotForThisProductType = DOMAIN_CREATE_GROUP;
+ }
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (DOMAIN_EXECUTE | DOMAIN_READ) & ~NotForThisProductType;
+
+ AceSid[1] = UsersAliasSid;
+ AceMask[1] = (DOMAIN_EXECUTE | DOMAIN_READ | DOMAIN_CREATE_ALIAS)
+ & ~NotForThisProductType;
+
+ AceSid[2] = AdminsAliasSid;
+ AceMask[2] = (DOMAIN_ALL_ACCESS) & ~NotForThisProductType;
+
+ AceSid[3] = PowerUsersAliasSid;
+ AceMask[3] = (DOMAIN_EXECUTE | DOMAIN_READ | DOMAIN_CREATE_USER |
+ DOMAIN_CREATE_ALIAS)
+ & ~NotForThisProductType;
+
+ AceSid[4] = AccountAliasSid;
+ AceMask[4] = (DOMAIN_EXECUTE | DOMAIN_READ | DOMAIN_CREATE_USER |
+ DOMAIN_CREATE_GROUP |
+ DOMAIN_CREATE_ALIAS)
+ & ~NotForThisProductType;
+
+
+ Status = SampBuildNewProtection(
+ 5, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &DomainMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_ACCOUNT_DOMAIN] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Admin Alias SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (ALIAS_EXECUTE | ALIAS_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (ALIAS_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 2, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &AliasMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_ADMIN_ALIAS] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Normal Alias SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (ALIAS_EXECUTE | ALIAS_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (ALIAS_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (ALIAS_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &AliasMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_NORMAL_ALIAS] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Power User accessible Alias SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (ALIAS_EXECUTE | ALIAS_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (ALIAS_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (ALIAS_ALL_ACCESS);
+
+ AceSid[3] = PowerUsersAliasSid;
+ AceMask[3] = (ALIAS_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 4, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &AliasMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_PWRUSER_ACCESSIBLE_ALIAS] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Admin Group SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (GROUP_EXECUTE | GROUP_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (GROUP_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 2, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &GroupMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_ADMIN_GROUP] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Normal GROUP SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (GROUP_EXECUTE | GROUP_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (GROUP_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (GROUP_ALL_ACCESS);
+
+
+ Status = SampBuildNewProtection(
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &GroupMap, // GenericMap
+ FALSE, // Not user object
+ &SampProtection[SAMP_PROT_NORMAL_GROUP] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Admin User SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (USER_EXECUTE | USER_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (USER_ALL_ACCESS);
+
+ AceSid[2] = AnySidInAccountDomain;
+ AceMask[2] = (USER_WRITE);
+
+
+ Status = SampBuildNewProtection(
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &UserMap, // GenericMap
+ TRUE, // user object (rid replacement)
+ &SampProtection[SAMP_PROT_ADMIN_USER] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ //
+ // Normal User SD
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (USER_EXECUTE | USER_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (USER_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (USER_ALL_ACCESS);
+
+ AceSid[3] = AnySidInAccountDomain;
+ AceMask[3] = (USER_WRITE);
+
+
+ Status = SampBuildNewProtection(
+ 4, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &UserMap, // GenericMap
+ TRUE, // user object (rid replacement)
+ &SampProtection[SAMP_PROT_NORMAL_USER] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Builtin Guest Account SD
+ // Can't change own password or other setable fields
+ //
+
+ AceSid[0] = WorldSid;
+ AceMask[0] = (USER_READ | USER_EXECUTE & ~(USER_CHANGE_PASSWORD));
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (USER_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (USER_ALL_ACCESS);
+
+
+
+
+ Status = SampBuildNewProtection(
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &UserMap, // GenericMap
+ FALSE, // no rid replacement
+ &SampProtection[SAMP_PROT_GUEST_ACCOUNT] // Result
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ return(TRUE);
+
+}
+
+
+NTSTATUS
+SampBuildNewProtection(
+ IN ULONG AceCount,
+ IN PSID *AceSid,
+ IN ACCESS_MASK *AceMask,
+ IN PGENERIC_MAPPING GenericMap,
+ IN BOOLEAN UserObject,
+ OUT PSAMP_PROTECTION Result
+ )
+
+/*++
+
+
+Routine Description:
+
+ This routine builds a self-relative security descriptor ready
+ to be applied to one of the SAM objects.
+
+ If so indicated, a pointer to the last RID of the SID in the last
+ ACE of the DACL is returned and a flag set indicating that the RID
+ must be replaced before the security descriptor is applied to an object.
+ This is to support USER object protection, which must grant some
+ access to the user represented by the object.
+
+ The owner and group of each security descriptor will be set
+ to:
+
+ Owner: Administrators Alias
+ Group: Administrators Alias
+
+
+ The SACL of each of these objects will be set to:
+
+
+ Audit
+ Success | Fail
+ WORLD
+ (Write | Delete | WriteDacl | AccessSystemSecurity) & !ReadControl
+
+
+
+Arguments:
+
+ AceCount - The number of ACEs to be included in the DACL.
+
+ AceSid - Points to an array of SIDs to be granted access by the DACL.
+ If the target SAM object is a User object, then the last entry
+ in this array is expected to be the SID of an account within the
+ domain with the last RID not yet set. The RID will be set during
+ actual account creation.
+
+ AceMask - Points to an array of accesses to be granted by the DACL.
+ The n'th entry of this array corresponds to the n'th entry of
+ the AceSid array. These masks should not include any generic
+ access types.
+
+ GenericMap - Points to a generic mapping for the target object type.
+
+
+ UserObject - Indicates whether the target SAM object is a User object
+ or not. If TRUE (it is a User object), then the resultant
+ protection will be set up indicating Rid replacement is necessary.
+
+ Result - Receives a pointer to the resultant protection information.
+ All access masks in ACLs in the result are mapped to standard and
+ specific accesses.
+
+
+Return Value:
+
+ TBS.
+
+--*/
+{
+
+
+
+ SECURITY_DESCRIPTOR Absolute;
+ PSECURITY_DESCRIPTOR Relative;
+ PACL TmpAcl;
+ PACCESS_ALLOWED_ACE TmpAce;
+ PSID TmpSid;
+ ULONG Length, i;
+ PULONG RidLocation;
+ BOOLEAN IgnoreBoolean;
+ ACCESS_MASK MappedMask;
+
+ //
+ // The approach is to set up an absolute security descriptor that
+ // looks like what we want and then copy it to make a self-relative
+ // security descriptor.
+ //
+
+
+ Status = RtlCreateSecurityDescriptor(
+ &Absolute,
+ SECURITY_DESCRIPTOR_REVISION1
+ );
+ ASSERT( NT_SUCCESS(Status) );
+
+
+
+ //
+ // Owner
+ //
+
+ Status = RtlSetOwnerSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Group
+ //
+
+ Status = RtlSetGroupSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Discretionary ACL
+ //
+ // Calculate its length,
+ // Allocate it,
+ // Initialize it,
+ // Add each ACE
+ // Add it to the security descriptor
+ //
+
+ Length = (ULONG)sizeof(ACL);
+ for (i=0; i<AceCount; i++) {
+
+ Length += RtlLengthSid( AceSid[i] ) +
+ (ULONG)sizeof(ACCESS_ALLOWED_ACE) -
+ (ULONG)sizeof(ULONG); //Subtract out SidStart field length
+ }
+
+ TmpAcl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(TmpAcl != NULL);
+
+
+ Status = RtlCreateAcl( TmpAcl, Length, ACL_REVISION2);
+ ASSERT( NT_SUCCESS(Status) );
+
+ for (i=0; i<AceCount; i++) {
+ MappedMask = AceMask[i];
+ RtlMapGenericMask( &MappedMask, GenericMap );
+ Status = RtlAddAccessAllowedAce (
+ TmpAcl,
+ ACL_REVISION2,
+ MappedMask,
+ AceSid[i]
+ );
+ ASSERT( NT_SUCCESS(Status) );
+ }
+
+ Status = RtlSetDaclSecurityDescriptor (&Absolute, TRUE, TmpAcl, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Sacl
+ //
+
+
+ Length = (ULONG)sizeof(ACL) +
+ RtlLengthSid( WorldSid ) +
+ (ULONG)sizeof(SYSTEM_AUDIT_ACE) -
+ (ULONG)sizeof(ULONG); //Subtract out SidStart field length
+ TmpAcl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(TmpAcl != NULL);
+
+ Status = RtlCreateAcl( TmpAcl, Length, ACL_REVISION2);
+ ASSERT( NT_SUCCESS(Status) );
+
+ Status = RtlAddAuditAccessAce (
+ TmpAcl,
+ ACL_REVISION2,
+ (GenericMap->GenericWrite | DELETE | WRITE_DAC | ACCESS_SYSTEM_SECURITY) & ~READ_CONTROL,
+ WorldSid,
+ TRUE, //AuditSuccess,
+ TRUE //AuditFailure
+ );
+ ASSERT( NT_SUCCESS(Status) );
+
+ Status = RtlSetSaclSecurityDescriptor (&Absolute, TRUE, TmpAcl, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+
+
+ //
+ // Convert the Security Descriptor to Self-Relative
+ //
+ // Get the length needed
+ // Allocate that much memory
+ // Copy it
+ // Free the generated absolute ACLs
+ //
+
+ Length = 0;
+ Status = RtlAbsoluteToSelfRelativeSD( &Absolute, NULL, &Length );
+ ASSERT(Status == STATUS_BUFFER_TOO_SMALL);
+
+ Relative = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(Relative != NULL);
+ Status = RtlAbsoluteToSelfRelativeSD(&Absolute, Relative, &Length );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Dacl );
+ RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Sacl );
+
+
+
+
+ //
+ // If the object is a user object, then get the address of the
+ // last RID of the SID in the last ACE in the DACL.
+ //
+
+ if (UserObject == TRUE) {
+
+ Status = RtlGetDaclSecurityDescriptor(
+ Relative,
+ &IgnoreBoolean,
+ &TmpAcl,
+ &IgnoreBoolean
+ );
+ ASSERT(NT_SUCCESS(Status));
+ Status = RtlGetAce ( TmpAcl, AceCount-1, (PVOID *)&TmpAce );
+ ASSERT(NT_SUCCESS(Status));
+ TmpSid = (PSID)(&TmpAce->SidStart),
+
+ RidLocation = RtlSubAuthoritySid(
+ TmpSid,
+ (ULONG)(*RtlSubAuthorityCountSid( TmpSid ) - 1)
+ );
+ }
+
+
+
+
+
+
+
+ //
+ // Set the result information
+ //
+
+ Result->Length = Length;
+ Result->Descriptor = Relative;
+ Result->RidToReplace = RidLocation;
+ Result->RidReplacementRequired = UserObject;
+
+
+
+ return(Status);
+
+}
+
+VOID
+SampGetDomainPolicy(
+ )
+/*++
+
+
+Routine Description:
+
+ This routine builds the name strings for domains.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ ULONG Size;
+ PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo;
+
+ //
+ // Builtin domain - Well-known External Name and Sid
+ // - Internal Name matches External Name
+
+ RtlInitUnicodeString( &BuiltinInternalDomainNameU, L"Builtin");
+ FullBuiltinInternalDomainNameU.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0, 256);
+ FullBuiltinInternalDomainNameU.Length = 0;
+ FullBuiltinInternalDomainNameU.MaximumLength = 256;
+ RtlCopyUnicodeString( &FullBuiltinInternalDomainNameU, &DomainNamePrefixU );
+ Status = RtlAppendUnicodeToString( &FullBuiltinInternalDomainNameU, L"\\" );
+ RtlAppendUnicodeStringToString( &FullBuiltinInternalDomainNameU, &BuiltinInternalDomainNameU );
+
+ BuiltinExternalDomainNameU = BuiltinInternalDomainNameU;
+
+ SampBuiltinDomainSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+ ASSERT( SampBuiltinDomainSid != NULL );
+ RtlInitializeSid( SampBuiltinDomainSid, &BuiltinAuthority, 1 );
+ *(RtlSubAuthoritySid( SampBuiltinDomainSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+
+ //
+ // Account domain - Configurable External Name and Sid.
+ //
+ // The External Name and Sid are obtained from the
+ // Lsa Policy Object (PolicyAccountDomainInformation
+ // information class). For a DC, the External Name
+ // is the Domain Name and for a Wksta, the External
+ // Name is the Computer Name as at the time of the
+ // system load.
+ //
+ // For DC's the Internal Name is the Domain Name
+ // - For Wksta's the Internal Name is the constant name
+ // "Account".
+ //
+ // NOTE: The reason for these choices of Internal Name
+ // is to avoid having to change the SAM Database.
+ //
+
+ Status = SampGetAccountDomainInfo( &PolicyAccountDomainInfo );
+
+ if (!NT_SUCCESS(Status)) {
+
+ KdPrint(( "BLDSAM3: Couldn't retrieve policy information from LSA.\n"
+ " Status = 0x%lx\n", Status));
+ return;
+ }
+
+ SampAccountDomainSid = PolicyAccountDomainInfo->DomainSid;
+
+ AccountExternalDomainNameU = PolicyAccountDomainInfo->DomainName;
+
+ RtlInitUnicodeString( &AccountInternalDomainNameU, L"Account");
+
+ FullAccountInternalDomainNameU.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0, 256);
+ FullAccountInternalDomainNameU.Length = 0;
+ FullAccountInternalDomainNameU.MaximumLength = SAMP_MAXIMUM_INTERNAL_NAME_LENGTH;
+ RtlCopyUnicodeString( &FullAccountInternalDomainNameU, &DomainNamePrefixU );
+ Status = RtlAppendUnicodeToString( &FullAccountInternalDomainNameU, L"\\" );
+ RtlAppendUnicodeStringToString( &FullAccountInternalDomainNameU, &AccountInternalDomainNameU );
+
+ //
+ // Now initialize a SID that can be used to represent accounts
+ // in this domain. Same as SampAccountDomainSid except with one
+ // extra sub-authority. It doesn't matter what the value of the
+ // last RID is because it is always replaced before use.
+ //
+
+ Size = RtlLengthSid( SampAccountDomainSid ) + sizeof(ULONG);
+ AnySidInAccountDomain = RtlAllocateHeap( RtlProcessHeap(), 0, Size);
+ ASSERT( AnySidInAccountDomain != NULL );
+ Status = RtlCopySid( Size, AnySidInAccountDomain, SampAccountDomainSid );
+ ASSERT(NT_SUCCESS(Status));
+ (*RtlSubAuthorityCountSid( AnySidInAccountDomain )) += 1;
+
+
+ //
+ // Set builtin as "current" domain
+ //
+
+ SetCurrentDomain( DomainBuiltin );
+
+ return;
+}
+
+
+VOID
+SetCurrentDomain(
+ IN SAMP_DOMAIN_SELECTOR Domain
+ )
+/*++
+
+
+Routine Description:
+
+ This routine sets the current domain to be
+ either the account or builtin domain.
+
+Arguments:
+
+ Domain - Specifies either builtin or account domain.
+ (DomainBuiltin or DomainAccount).
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+
+ if (Domain == DomainBuiltin) {
+
+ DomainNameU = &BuiltinInternalDomainNameU;
+ FullDomainNameU = &FullBuiltinInternalDomainNameU;
+ DomainSid = SampBuiltinDomainSid;
+
+ } else {
+
+ DomainNameU = &AccountInternalDomainNameU;
+ FullDomainNameU = &FullAccountInternalDomainNameU;
+ DomainSid = SampAccountDomainSid;
+
+ }
+
+
+
+ return;
+}
+
+NTSTATUS
+InitializeSam(
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the SAM-level registry information.
+ It does not initialize any domains in the SAM.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSAMP_V1_FIXED_LENGTH_SERVER ServerFixedAttributes;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE ServerVariableAttributeArray;
+ PVOID ServerVariableData;
+ OBJECT_ATTRIBUTES SamAttributes;
+ UNICODE_STRING SamNameU;
+ ULONG Disposition;
+ ULONG ServerAttributeLength;
+ SECURITY_DESCRIPTOR SecurityDescriptor;
+ BOOLEAN IgnoreBoolean;
+ PACL Dacl;
+
+ //
+ // Build a system default Dacl to protect the SAM database
+ // with.
+ //
+
+ Status = SampCreateDatabaseProtection( &SecurityDescriptor );
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+ //
+ // See if a remnant of a SAM database already exists
+ //
+
+ RtlInitUnicodeString( &SamNameU, L"SAM" );
+
+ InitializeObjectAttributes(
+ &SamAttributes,
+ &SamNameU,
+ OBJ_CASE_INSENSITIVE,
+ SamParentKey,
+ &SecurityDescriptor
+ );
+ Status = RtlpNtCreateKey(
+ &SamKey,
+ (KEY_READ | KEY_CREATE_SUB_KEY | KEY_WRITE),
+ &SamAttributes,
+ 0,
+ NULL,
+ &Disposition
+ );
+
+ Status = RtlGetDaclSecurityDescriptor(
+ &SecurityDescriptor,
+ &IgnoreBoolean,
+ &Dacl,
+ &IgnoreBoolean
+ );
+
+ if (Dacl != NULL) {
+ RtlFreeHeap( RtlProcessHeap(), 0, Dacl );
+ }
+ ASSERT(SecurityDescriptor.Sacl == NULL);
+ ASSERT(SecurityDescriptor.Owner == NULL);
+ ASSERT(SecurityDescriptor.Group == NULL);
+
+ if ( !NT_SUCCESS(Status) ) {
+
+#if DBG
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+ BldPrint( " We seem to be having trouble creating the registry\n" );
+ BldPrint( " database key in which the Security Account Manager\n" );
+ BldPrint( " information resides. Please see one of the security\n" );
+ BldPrint( " group developers for assistance in analyzing the\n" );
+ BldPrint( " the problem.\n" );
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+#endif
+
+ return(Status);
+ }
+
+ if ( Disposition != REG_CREATED_NEW_KEY ) {
+
+#if DBG
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+ BldPrint( " I'm terribly sorry, but you have specified that a SAM\n" );
+ BldPrint( " database be initialized and yet there is already a SAM\n" );
+ BldPrint( " database in existance. If the SAM database is corrupt\n" );
+ BldPrint( " or you would like to replace the existing domain anyway,\n" );
+ BldPrint( " please delnode the existing database and re-issue this \n");
+ BldPrint( " command. \n");
+ BldPrint( " The SAM database is in ...\\registry\\Machine\\security\\sam.\n" );
+ BldPrint( " Thank you.\n" );
+ BldPrint( "\n" );
+ BldPrint( "\n" );
+#endif
+
+ Usage();
+
+ Status = NtClose( SamKey );
+
+
+ return(Status);
+ }
+
+
+ //
+ // Initialize the registry transaction structure for SAM.
+ //
+
+ Status = RtlInitializeRXact( SamKey, FALSE, &SamRXactContext );
+
+ if ( Status != STATUS_RXACT_STATE_CREATED ) {
+#if DBG
+ BldPrint("\n");
+ BldPrint(" The SAM database already has a structure in place.\n");
+ BldPrint(" This indicates multiple initializations being performed\n");
+ BldPrint(" simultaneously. Please be sure no other initializations\n");
+ BldPrint(" are being performed and issue this command again.\n");
+ BldPrint("\n");
+ BldPrint("\n");
+#endif
+
+ if ( Status == STATUS_SUCCESS ) {
+
+ //
+ // Shouldn't happen, but let's program defensively.
+ //
+
+ Status = STATUS_RXACT_INVALID_STATE;
+ }
+
+ return(Status);
+ }
+
+ //
+ // Start an RXACT to do the rest in ...
+ //
+
+ Status = RtlStartRXact( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Starting transaction\n");
+
+ //
+ // Set the server's fixed and variable attributes
+ //
+
+ ServerAttributeLength = sizeof( SAMP_V1_FIXED_LENGTH_SERVER ) +
+ ( SAMP_SERVER_VARIABLE_ATTRIBUTES *
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) ) +
+ SampProtection[SAMP_PROT_SAM_SERVER].Length;
+
+ ServerFixedAttributes = (PSAMP_V1_FIXED_LENGTH_SERVER)RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ ServerAttributeLength
+ );
+
+ if ( ServerFixedAttributes == NULL ) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SUCCESS_ASSERT(Status, " Failed to create server attributes\n");
+
+ //
+ // The server revision on the a new SAM database may not be the same
+ // as the revision on the rest of SAM. This allows the server revision
+ // to indicate which bugs have been fixed in this SAM.
+ //
+
+ ServerFixedAttributes->RevisionLevel = SAMP_SERVER_REVISION;
+
+ ServerVariableAttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ ((PUCHAR)(ServerFixedAttributes) +
+ sizeof( SAMP_V1_FIXED_LENGTH_SERVER ) );
+
+ ServerVariableAttributeArray->Offset = 0;
+ ServerVariableAttributeArray->Length =
+ SampProtection[SAMP_PROT_SAM_SERVER].Length;
+ ServerVariableAttributeArray->Qualifier = SAMP_REVISION;
+
+ ServerVariableData = (PVOID)( (PUCHAR)(ServerVariableAttributeArray) +
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) );
+
+ RtlCopyMemory(
+ ServerVariableData,
+ SampProtection[SAMP_PROT_SAM_SERVER].Descriptor,
+ SampProtection[SAMP_PROT_SAM_SERVER].Length
+ );
+
+ //
+ // Now write out the attributes via the RXACT.
+ //
+
+ RtlInitUnicodeString( &SamNameU, NULL );
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &SamNameU,
+ INVALID_HANDLE_VALUE,
+ &SampCombinedAttributeName,
+ REG_BINARY,
+ (PVOID)ServerFixedAttributes,
+ ServerAttributeLength
+ );
+
+ SUCCESS_ASSERT(Status, " Failed to write out server attributes\n" );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, ServerFixedAttributes );
+
+ //
+ // Create SAM\Domains
+ //
+
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &DomainNamePrefixU,
+ 0,
+ NULL,
+ 0
+ );
+
+ SUCCESS_ASSERT(Status, " Failed to add domain key to log\n");
+
+ Status = RtlApplyRXactNoFlush( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Committing SAM INIT transaction\n");
+
+ return(Status);
+}
+
+
+NTSTATUS
+SampCreateDatabaseProtection(
+ PISECURITY_DESCRIPTOR Sd
+ )
+
+
+/*++
+
+Routine Description:
+
+ This function allocates and initializes protection to assign to
+ the SAM database.
+
+ Upon return, any non-zero pointers in the security descriptors
+ point to memory allocated from process heap. It is the caller's
+ responsibility to free this memory.
+
+
+ Protection is:
+
+ System: All Access
+ Admin: ReadControl | WriteDac
+
+Arguments:
+
+ Sd - Address of a security descriptor to initialize.
+
+Return Value:
+
+ STATUS_SUCCESS - The Security descriptor has been initialize.
+
+ STATUS_NO_MEMORY - couldn't allocate memory for the protection info.
+
+--*/
+
+
+{
+ NTSTATUS
+ Status;
+
+ ULONG
+ Length;
+
+ USHORT
+ i;
+
+ PACL
+ Dacl;
+
+ PACE_HEADER
+ Ace;
+
+
+ //
+ // Initialize the security descriptor.
+ // This call should not fail.
+ //
+
+ Status = RtlCreateSecurityDescriptor( Sd, SECURITY_DESCRIPTOR_REVISION1 );
+ ASSERT(NT_SUCCESS(Status));
+
+ Length = (ULONG)sizeof(ACL) +
+ (2*((ULONG)sizeof(ACCESS_ALLOWED_ACE))) +
+ RtlLengthSid( LocalSystemSid ) +
+ RtlLengthSid( AdminsAliasSid ) +
+ 8; // The 8 is just for good measure
+
+
+ Dacl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+
+ if (Dacl == NULL) {
+ return(STATUS_NO_MEMORY);
+ }
+
+
+ Status = RtlCreateAcl (Dacl, Length, ACL_REVISION2 );
+ ASSERT(NT_SUCCESS(Status));
+
+ //
+ // Add ACEs to the ACL...
+ // These calls should not be able to fail.
+ //
+
+ Status = RtlAddAccessAllowedAce(
+ Dacl,
+ ACL_REVISION2,
+ (GENERIC_ALL ),
+ LocalSystemSid
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ Status = RtlAddAccessAllowedAce(
+ Dacl,
+ ACL_REVISION2,
+ (READ_CONTROL | WRITE_DAC),
+ AdminsAliasSid
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ //
+ // Now mark the ACEs as inheritable...
+ //
+
+ for ( i=0; i<Dacl->AceCount; i++) {
+
+ //
+ // Get the address of the next ACE
+ // (Shouldn't fail)
+ //
+
+ Status = RtlGetAce( Dacl, (ULONG)i, &Ace );
+ ASSERT(NT_SUCCESS(Status));
+
+ Ace->AceFlags |= (CONTAINER_INHERIT_ACE);
+
+ }
+
+
+ //
+ // And add the ACL to the security descriptor.
+ // This call should not fail.
+ //
+
+ Status = RtlSetDaclSecurityDescriptor(
+ Sd,
+ TRUE, // DaclPresent
+ Dacl, // Dacl OPTIONAL
+ FALSE // DaclDefaulted OPTIONAL
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+CreateBuiltinDomain (
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates a new builtin domain.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ UNICODE_STRING Name, Comment;
+ HMODULE AccountNamesResource;
+
+ //
+ // Get the message resource we need to get the account names from
+ //
+
+ AccountNamesResource = (HMODULE) LoadLibrary( L"SAMSRV.DLL" );
+ if (AccountNamesResource == NULL) {
+ return(STATUS_RESOURCE_DATA_NOT_FOUND);
+ }
+
+
+
+
+ //
+ // Prep the standard domain registry structure for this domain
+ //
+
+ Status = PrepDomain(DomainBuiltin);
+
+ if (!NT_SUCCESS(Status)) {
+
+ return(Status);
+ }
+
+ //
+ // Create the alias accounts with no members
+ // (Common to LanManNT and WinNT products)
+ //
+
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_ADMINS,
+ &Name,
+ SAMP_ALIAS_COMMENT_ADMINS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_ADMINS, // Rid
+ SAMP_PROT_ADMIN_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_USERS,
+ &Name,
+ SAMP_ALIAS_COMMENT_USERS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_USERS, // Rid
+ SAMP_PROT_PWRUSER_ACCESSIBLE_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_GUESTS,
+ &Name,
+ SAMP_ALIAS_COMMENT_GUESTS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_GUESTS, // Rid
+ SAMP_PROT_PWRUSER_ACCESSIBLE_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_BACKUP_OPS,
+ &Name,
+ SAMP_ALIAS_COMMENT_BACKUP_OPS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_BACKUP_OPS, // Rid
+ SAMP_PROT_ADMIN_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_REPLICATOR,
+ &Name,
+ SAMP_ALIAS_COMMENT_REPLICATOR,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_REPLICATOR, // Rid
+ SAMP_PROT_NORMAL_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ if (SampProductType == NtProductLanManNt) {
+
+ //
+ // specific to LanManNT products
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_SERVER_OPS,
+ &Name,
+ SAMP_ALIAS_COMMENT_SERVER_OPS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_SYSTEM_OPS, // Rid
+ SAMP_PROT_ADMIN_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_ACCOUNT_OPS,
+ &Name,
+ SAMP_ALIAS_COMMENT_ACCOUNT_OPS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS, // Rid
+ SAMP_PROT_ADMIN_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_PRINT_OPS,
+ &Name,
+ SAMP_ALIAS_COMMENT_PRINT_OPS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_PRINT_OPS, // Rid
+ SAMP_PROT_ADMIN_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ } else {
+
+ //
+ // specific to WinNT products
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_ALIAS_NAME_POWER_USERS,
+ &Name,
+ SAMP_ALIAS_COMMENT_POWER_USERS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateAlias(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_ALIAS_RID_POWER_USERS, // Rid
+ SAMP_PROT_PWRUSER_ACCESSIBLE_ALIAS // Protection
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ }
+
+ return(Status);
+}
+
+
+
+NTSTATUS
+CreateAccountDomain (
+ )
+/*++
+
+
+Routine Description:
+
+ This routine creates a new account domain using information
+ from the configuration database and based upon the system's
+ product type.
+
+ If the product is a WinNt system, then the domain's name is
+ "Account". If the product is a LanManNT system, then the
+ domain's name is retrieved from the configuration information.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ NTSTATUS Status;
+ UNICODE_STRING Name, Comment;
+ HMODULE AccountNamesResource;
+ ULONG AccountControl;
+ ULONG PrimaryGroup;
+
+
+ //
+ // Get the message resource we need to get the account names from
+ //
+
+ AccountNamesResource = (HMODULE) LoadLibrary( L"SAMSRV.DLL" );
+ if (AccountNamesResource == NULL) {
+ DbgPrint("BLDSAM3: Error loading library - error is 0x%lx", GetLastError());
+ return(STATUS_RESOURCE_DATA_NOT_FOUND);
+ }
+
+
+
+ //
+ // Prep the standard domain registry structure for this domain
+ //
+
+ Status = PrepDomain(DomainAccount);
+
+ if (!NT_SUCCESS(Status)) {
+
+ return(Status);
+ }
+
+ //
+ // Create the group accounts with no members
+ //
+
+ if ((SampProductType == NtProductWinNt) ||
+ (SampProductType == NtProductServer)) {
+
+ //
+ // WinNt systems only have one group (called 'None').
+ // This group has the same RID as the 'Domain Users' group.
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_GROUP_NAME_NONE,
+ &Name,
+ SAMP_GROUP_COMMENT_NONE,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateGroup(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_GROUP_RID_USERS, // Rid
+ FALSE // Admin
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ } else {
+
+ //
+ // LanManNT
+ //
+
+ //
+ // USERS global group
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_GROUP_NAME_USERS,
+ &Name,
+ SAMP_GROUP_COMMENT_USERS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateGroup(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_GROUP_RID_USERS, // Rid
+ FALSE // Admin
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ //
+ // ADMINS global group
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_GROUP_NAME_ADMINS,
+ &Name,
+ SAMP_GROUP_COMMENT_ADMINS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateGroup(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_GROUP_RID_ADMINS, // Rid
+ TRUE // Admin
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ //
+ // GUESTS global group
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_GROUP_NAME_GUESTS,
+ &Name,
+ SAMP_GROUP_COMMENT_GUESTS,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateGroup(&Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_GROUP_RID_GUESTS, // Rid
+ FALSE // Admin
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ }
+
+ //
+ // create the user accounts ...
+ // These are automatically added to the "Domain Users" group
+ // (except for Guest).
+ //
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_USER_NAME_ADMIN,
+ &Name,
+ SAMP_USER_COMMENT_ADMIN,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ Status = CreateUser( &Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_USER_RID_ADMIN, // UserRid
+ DOMAIN_GROUP_RID_USERS, // PrimaryGroup
+ TRUE, // Admin flag
+ USER_NORMAL_ACCOUNT |
+ USER_DONT_EXPIRE_PASSWORD, // AccountControl
+ SAMP_PROT_ADMIN_USER // ProtectionIndex
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+
+ Status = SampGetMessageStrings(
+ AccountNamesResource,
+ SAMP_USER_NAME_GUEST,
+ &Name,
+ SAMP_USER_COMMENT_GUEST,
+ &Comment
+ ); ASSERT(NT_SUCCESS(Status));
+
+ //
+ // Disable user account on all NT systems
+ //
+
+ AccountControl = USER_NORMAL_ACCOUNT |
+ USER_DONT_EXPIRE_PASSWORD |
+ USER_ACCOUNT_DISABLED;
+
+ if (SampProductType == NtProductLanManNt) {
+
+ //
+ // Guest group is in GUESTS global group for LmNT systems.
+ //
+
+ PrimaryGroup = DOMAIN_GROUP_RID_GUESTS;
+
+ } else {
+
+ //
+ // There isn't a GUESTS global group on WinNt systems.
+ // Put the guest in the NONE group (same as USERS group).
+ //
+
+ PrimaryGroup = DOMAIN_GROUP_RID_USERS;
+
+ }
+
+
+ Status = CreateUser( &Name, // AccountName
+ &Comment, // AccountComment
+ TRUE, // SpecialAccount
+ DOMAIN_USER_RID_GUEST, // UserRid
+ PrimaryGroup, // PrimaryGroup
+ FALSE, // Admin flag
+ AccountControl, // AccountControl
+ SAMP_PROT_GUEST_ACCOUNT // ProtectionIndex
+ ); ASSERT(NT_SUCCESS(Status));
+
+ LocalFree( Name.Buffer );
+ LocalFree( Comment.Buffer );
+
+ return(Status);
+}
+
+
+NTSTATUS
+PrepDomain(
+ IN SAMP_DOMAIN_SELECTOR Domain
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds the domain level definitions to the operation log.
+
+Arguments:
+
+ Domain - Indicates which domain is being prep'd
+
+Return Value:
+
+ TBS
+
+--*/
+
+{
+ PSAMP_V1_0A_FIXED_LENGTH_DOMAIN DomainFixedAttributes;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE DomainVariableAttributeArray;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE DomainVariableAttributeArrayStart;
+ PVOID DomainVariableData;
+ ULONG DomainAttributeLength;
+ ULONG ProtectionIndex;
+ ULONG UserCount, GroupCount, AliasCount;
+
+ //
+ // Set current domain
+ //
+
+ SetCurrentDomain( Domain );
+
+ //
+ // Select correct protection, and the number of accounts we're going
+ // to create
+ //
+
+ if (Domain == DomainBuiltin) {
+
+ ProtectionIndex = SAMP_PROT_BUILTIN_DOMAIN;
+
+ UserCount = 0;
+ GroupCount = 0;
+
+ if (SampProductType == NtProductLanManNt) {
+
+ //
+ // Admins, BackupOps, Guests, Replicator, Users, SysOps,
+ // AcctOps, PrintOps
+ //
+
+ AliasCount = 8;
+
+ } else {
+ //
+ // Admins, BackupOps, Guests, Replicator, Users, Power Users
+ //
+
+ AliasCount = 6;
+
+ }
+
+ } else {
+
+ ProtectionIndex = SAMP_PROT_ACCOUNT_DOMAIN;
+
+ AliasCount = 0;
+ UserCount = 2; // Administrator, Guest
+
+ if (SampProductType == NtProductLanManNt) {
+
+ GroupCount = 3; // Users, Administrators, Guests
+
+ } else {
+
+ GroupCount = 1; // "None"
+ }
+ }
+
+ //
+ // Use a transaction.
+ //
+
+ Status = RtlStartRXact( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Starting transaction\n");
+
+ //
+ // Create SAM\Domains\(DomainName) (KeyValueType is revision level)
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+
+ //
+ // Set the domain's fixed and variable attributes.
+ //
+
+ DomainFixedAttributes = (PSAMP_V1_0A_FIXED_LENGTH_DOMAIN)RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_DOMAIN )
+ );
+
+ if ( DomainFixedAttributes == NULL ) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SUCCESS_ASSERT(Status, " Failed to create domain fixed attributes\n");
+
+ DomainFixedAttributes->Revision = SAMP_REVISION;
+ DomainFixedAttributes->MinPasswordLength = 0;
+ DomainFixedAttributes->PasswordHistoryLength = 0;
+ DomainFixedAttributes->PasswordProperties = 0L;
+ DomainFixedAttributes->NextRid = 1000;
+ DomainFixedAttributes->ServerState = DomainServerEnabled;
+ DomainFixedAttributes->ServerRole = SampServerRole;
+ NtQuerySystemTime( &(DomainFixedAttributes->CreationTime) );
+ DomainFixedAttributes->ModifiedCount = ModifiedCount;
+ DomainFixedAttributes->MaxPasswordAge = DomainMaxPasswordAge;
+ DomainFixedAttributes->MinPasswordAge = SampImmediatelyDeltaTime;
+ DomainFixedAttributes->ForceLogoff = SampNeverDeltaTime;
+ DomainFixedAttributes->UasCompatibilityRequired = TRUE;
+ DomainFixedAttributes->LockoutDuration.LowPart = 0xCF1DCC00; // 30 minutes - low part
+ DomainFixedAttributes->LockoutDuration.HighPart = 0XFFFFFFFB; // 30 minutes - high part
+ DomainFixedAttributes->LockoutObservationWindow.LowPart = 0xCF1DCC00; // 30 minutes - low part
+ DomainFixedAttributes->LockoutObservationWindow.HighPart = 0XFFFFFFFB; // 30 minutes - high part
+ DomainFixedAttributes->LockoutThreshold = 0; // Disabled
+ DomainFixedAttributes->ModifiedCountAtLastPromotion = ModifiedCount;
+
+ DomainAttributeLength = SampDwordAlignUlong(RtlLengthSid( DomainSid ) ) +
+ ( SAMP_DOMAIN_VARIABLE_ATTRIBUTES *
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) ) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+
+ DomainVariableAttributeArrayStart = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ DomainAttributeLength
+ );
+
+ if ( DomainVariableAttributeArrayStart == NULL ) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SUCCESS_ASSERT(Status, " Failed to create domain variable attributes\n");
+
+ DomainVariableAttributeArray = DomainVariableAttributeArrayStart;
+
+ DomainVariableAttributeArray->Offset = 0;
+ DomainVariableAttributeArray->Length =
+ SampProtection[ProtectionIndex].Length;
+ DomainVariableAttributeArray->Qualifier = SAMP_REVISION;
+
+ DomainVariableAttributeArray++;
+
+ DomainVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+ DomainVariableAttributeArray->Length =
+ RtlLengthSid( DomainSid );
+ DomainVariableAttributeArray->Qualifier = 0;
+
+ DomainVariableAttributeArray++;
+
+ DomainVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(RtlLengthSid( DomainSid ));
+ DomainVariableAttributeArray->Length = 0;
+ DomainVariableAttributeArray->Qualifier = 0;
+
+ DomainVariableAttributeArray++;
+
+ DomainVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(RtlLengthSid( DomainSid ));
+ DomainVariableAttributeArray->Length = 0;
+ DomainVariableAttributeArray->Qualifier = 0;
+
+ DomainVariableData = (PVOID)( (PUCHAR)(DomainVariableAttributeArray) +
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) );
+
+ RtlCopyMemory(
+ DomainVariableData,
+ SampProtection[ProtectionIndex].Descriptor,
+ SampProtection[ProtectionIndex].Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(DomainVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length)),
+ DomainSid,
+ RtlLengthSid( DomainSid )
+ );
+
+ //
+ // Now write out the attributes via the RXACT.
+ //
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampFixedAttributeName,
+ REG_BINARY,
+ (PVOID)DomainFixedAttributes,
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_DOMAIN )
+ );
+
+ SUCCESS_ASSERT(Status, " Failed to write out domain fixed attributes\n" );
+ RtlFreeHeap( RtlProcessHeap(), 0, DomainFixedAttributes );
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampVariableAttributeName,
+ REG_BINARY,
+ (PVOID)DomainVariableAttributeArrayStart,
+ DomainAttributeLength
+ );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, DomainVariableAttributeArrayStart );
+ SUCCESS_ASSERT(Status, " Failed to write out domain variable attributes\n" );
+
+ //
+ // Create SAM\Domains\(DomainName)\Users
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Users" );
+ SUCCESS_ASSERT(Status, " Failed to append to unicode: \\Users\n" );
+
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ UserCount,
+ NULL,
+ 0
+ );
+
+ SUCCESS_ASSERT(Status, " Failed to add Users key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Users\Names
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Users\\Names" );
+ SUCCESS_ASSERT(Status, " Failed to append to unicode: \\Users\\Names\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ 0,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Users/Names key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Groups
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Groups" );
+ SUCCESS_ASSERT(Status, " Failed to append Groups key name to unicode\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ GroupCount,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Groups key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Groups\Names
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Groups\\Names" );
+ SUCCESS_ASSERT(Status, " Failed to append Groups key name to unicode\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ 0,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Groups key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Aliases
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases" );
+ SUCCESS_ASSERT(Status, " Failed to append Aliases key name to unicode\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ AliasCount,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add aliases key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Aliases\Names
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases\\Names" );
+ SUCCESS_ASSERT(Status, " Failed to append Aliases key name to unicode\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ 0,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Aliases\\Names key to log\n");
+
+ //
+ // Create SAM\Domains\(DomainName)\Aliases\Members
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases\\Members" );
+ SUCCESS_ASSERT(Status, " Failed to append Aliases\\Members key name to unicode\n" );
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ 0, // Domain Count
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Aliases\\Members key to log\n");
+
+ //
+ // Commit these additions...
+ //
+
+ Status = RtlApplyRXactNoFlush( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to commit domain initialization.\n");
+
+ return Status;
+}
+
+
+NTSTATUS
+CreateAlias(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG Rid,
+ IN ULONG ProtectionIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds the keys necessary to create an alias. It also applies
+ the appropriate protection to the alias.
+
+Arguments:
+
+ AccountNameU - The Unicode name of the Alias.
+
+ AccountCommentU - A Unicode comment to put in the object's variable data.
+
+ SpecialAccount - A boolean indicating whether or not the account
+ is special. Special accounts are marked as such and can not
+ be deleted.
+
+ Rid - The RID of the account.
+
+
+ Admin - Indicates whether the account is in the Administrators alias
+ or not. TRUE means it is, FALSE means it isn't.
+
+Return Value:
+
+ TBS
+
+--*/
+
+{
+ PSAMP_V1_FIXED_LENGTH_ALIAS AliasFixedAttributes;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE AliasVariableAttributeArray;
+ PVOID AliasVariableData;
+ PSID Sid1, Sid2;
+ PSID AliasMembers = NULL;
+ ULONG MemberCount, TotalLength, AliasAttributeLength;
+ UNICODE_STRING AliasNameU, AliasCommentU;
+
+ AliasNameU = *AccountNameU;
+ AliasCommentU = *AccountCommentU;
+
+ //
+ // Set the account specific RID in the DACL's if necessary
+ //
+
+ if ( SampProtection[ProtectionIndex].RidReplacementRequired == TRUE ) {
+
+ (*SampProtection[ProtectionIndex].RidToReplace) = Rid;
+ }
+
+ //
+ // Use a transaction.
+ //
+
+ Status = RtlStartRXact( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to start Alias addition transaction\n");
+
+ //
+ // Add Aliases\Names\(AccountName) [ Rid, ]
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases\\Names\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\aliases\\names to keyname\n");
+ Status = RtlAppendUnicodeStringToString( &KeyNameU, &AliasNameU);
+ SUCCESS_ASSERT(Status, " Failed to append Alias account name to\n");
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ Rid,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Aliases\\Names\\(AliasName) to log\n");
+
+
+ //
+ // Set the Members attribute. We know which accounts are supposed
+ // to be members of which aliases, so we'll build the memberships in
+ // automatically.
+ //
+ // Each domain has a list of SIDs that are members of its aliases.
+ // We'll update these values at the same time that we set the alias
+ // members by calling UpdateAliasXReference(). Currently, that only
+ // happens in the builtin domain, where things look like this:
+ //
+ // BuiltinDomainSid
+ // AdminUserRid - Admins alias (WinNt + primary domain)
+ // UserUserRid - Users alias (WinNt + developer setup),
+ // Power users alias (WinNt + developer setup)
+ // AccountDomainSid
+ // AdminUserRid - Admins alias (always)
+ // GuestUserRid - Guests alias (always)
+ // UserGroupRid - Users alias, (always)
+ // Power users alias (WinNt + developer setup)
+ // AdminGroupRid - Admins alias (LanManNt only)
+ //
+
+ MemberCount = 0;
+ TotalLength = 0;
+
+ switch ( Rid ) {
+
+ case DOMAIN_ALIAS_RID_ADMINS: {
+
+ MemberCount = 1;
+
+ Sid1 = BuildAccountSid( DomainAccount, DOMAIN_USER_RID_ADMIN );
+ if ( Sid1 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+
+ if ( SampProductType == NtProductLanManNt ) {
+
+ MemberCount = 2;
+
+ Sid2 = BuildAccountSid( DomainAccount, DOMAIN_GROUP_RID_ADMINS );
+ if ( Sid2 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+
+ if ( ( SampProductType != NtProductLanManNt ) &&
+ ( SampBldPrimaryDomain != NULL ) ) {
+
+ MemberCount = 2;
+
+ Sid2 = BuildPrimaryDomainSid( DOMAIN_GROUP_RID_ADMINS );
+ if ( Sid2 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+
+ break;
+ }
+
+ case DOMAIN_ALIAS_RID_USERS: {
+
+ MemberCount = 0;
+
+ if ( (SampProductType == NtProductWinNt)
+ || (SampProductType == NtProductServer) ) {
+
+ if ( SampBldPrimaryDomain != NULL ) {
+
+ MemberCount = 1;
+ Sid1 = BuildPrimaryDomainSid( DOMAIN_GROUP_RID_USERS );
+
+ if ( Sid1 == NULL ) {
+
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+
+ } else {
+
+ //
+ //
+ if (SampProductType == NtProductLanManNt ) {
+
+ //
+ // NTAS systems have the USERS global group in
+ // the USERS alias.
+ //
+
+ MemberCount = 1;
+ Sid1 = BuildAccountSid(
+ DomainAccount,
+ DOMAIN_GROUP_RID_USERS
+ );
+
+ if ( Sid1 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ } else {
+
+ //
+ // WinNT systems have the ADMINISTRATOR user account
+ // in the USERS alias. The None group is NOT in this
+ // alias because even guests are in the None group.
+ //
+
+ MemberCount = 1;
+ Sid1 = BuildAccountSid(
+ DomainAccount,
+ DOMAIN_USER_RID_ADMIN
+ );
+
+ if ( Sid1 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+ }
+
+ break;
+ }
+
+ case DOMAIN_ALIAS_RID_GUESTS: {
+
+
+ if ( (SampProductType == NtProductWinNt)
+ || (SampProductType == NtProductServer) ) {
+
+ //
+ // WinNT system - make our GUEST user account a member of
+ // the GUESTS alias.
+ //
+
+ MemberCount = 1;
+ Sid1 = BuildAccountSid( DomainAccount, DOMAIN_USER_RID_GUEST );
+
+
+ //
+ // If we are in a primary domain, then add that domain's
+ // GUESTS global group to the alias as well.
+ //
+
+ if ( SampBldPrimaryDomain != NULL ) {
+
+ MemberCount += 1;
+ Sid2 = BuildPrimaryDomainSid( DOMAIN_GROUP_RID_GUESTS );
+ if ( Sid2 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+ } else {
+
+ //
+ // NTAS System - Just make the GUESTS global group
+ // a member of the GUESTS alias.
+ //
+
+ MemberCount = 1;
+ Sid1 = BuildPrimaryDomainSid( DOMAIN_GROUP_RID_GUESTS );
+ if ( Sid1 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+
+
+ break;
+ }
+
+ case DOMAIN_ALIAS_RID_POWER_USERS: {
+
+ if ( ( SampProductType != NtProductLanManNt ) &&
+ ( SampDeveloperSetup ) ) {
+
+ MemberCount = 1;
+
+ Sid1 = BuildAccountSid( DomainAccount, DOMAIN_GROUP_RID_USERS );
+
+ if ( Sid1 == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+
+ if ( SampBldPrimaryDomain != NULL ) {
+
+ MemberCount = 2;
+
+ Sid2 = BuildPrimaryDomainSid( DOMAIN_GROUP_RID_USERS );
+
+ if ( Sid2 == NULL ) {
+
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate Sid\n" );
+ }
+ }
+ }
+
+ break;
+ }
+
+ case DOMAIN_ALIAS_RID_ACCOUNT_OPS:
+ case DOMAIN_ALIAS_RID_SYSTEM_OPS:
+ case DOMAIN_ALIAS_RID_PRINT_OPS:
+ case DOMAIN_ALIAS_RID_BACKUP_OPS:
+ case DOMAIN_ALIAS_RID_REPLICATOR: {
+
+ break;
+ }
+
+ default: {
+
+ SUCCESS_ASSERT(STATUS_UNSUCCESSFUL, " Bad Alias RID\n");
+ break;
+ }
+ };
+
+ if ( MemberCount > 0 ) {
+
+ TotalLength = RtlLengthSid( Sid1 );
+ if ( MemberCount == 2 ) {
+
+ TotalLength += RtlLengthSid( Sid2 );
+ }
+
+ AliasMembers = RtlAllocateHeap( RtlProcessHeap(), 0, TotalLength );
+ if ( AliasMembers == NULL ) {
+ SUCCESS_ASSERT( STATUS_INSUFFICIENT_RESOURCES, " Could not allocate AliasMembers\n" );
+ }
+
+ Status = RtlCopySid( RtlLengthSid( Sid1 ), AliasMembers, Sid1 );
+ SUCCESS_ASSERT( Status, " Couldn't copy Sid1\n" );
+
+ Status = UpdateAliasXReference( Rid, Sid1 );
+ SUCCESS_ASSERT( Status, " Couldn't update alias xref\n" );
+
+ if ( MemberCount == 2 ) {
+
+ Status = RtlCopySid(
+ RtlLengthSid( Sid2 ),
+ (PSID)((PUCHAR)AliasMembers + RtlLengthSid( Sid1 ) ),
+ Sid2 );
+ SUCCESS_ASSERT( Status, " Couldn't copy Sid2\n" );
+
+ Status = UpdateAliasXReference( Rid, Sid2 );
+ RtlFreeHeap( RtlProcessHeap(), 0, Sid2 );
+ SUCCESS_ASSERT( Status, " Couldn't update alias xref\n" );
+ }
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Sid1 );
+ }
+
+
+ //
+ // Set the alias's fixed and variable attributes
+ //
+
+ AliasAttributeLength = sizeof( SAMP_V1_FIXED_LENGTH_ALIAS ) +
+ ( SAMP_ALIAS_VARIABLE_ATTRIBUTES *
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) ) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(AccountNameU->Length) +
+ SampDwordAlignUlong(AccountCommentU->Length) +
+ TotalLength;
+
+ AliasFixedAttributes = (PSAMP_V1_FIXED_LENGTH_ALIAS)RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ AliasAttributeLength
+ );
+
+ if ( AliasFixedAttributes == NULL ) {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ SUCCESS_ASSERT(Status, " Failed to create alias attributes\n");
+
+ AliasFixedAttributes->RelativeId = Rid;
+
+ AliasVariableAttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ ((PUCHAR)(AliasFixedAttributes) +
+ sizeof( SAMP_V1_FIXED_LENGTH_ALIAS ) );
+
+ AliasVariableAttributeArray->Offset = 0;
+ AliasVariableAttributeArray->Length =
+ SampProtection[ProtectionIndex].Length;
+ AliasVariableAttributeArray->Qualifier = SAMP_REVISION;
+
+ AliasVariableAttributeArray++;
+
+ AliasVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+ AliasVariableAttributeArray->Length = AliasNameU.Length;
+ AliasVariableAttributeArray->Qualifier = 0;
+
+ AliasVariableAttributeArray++;
+
+ AliasVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(AccountNameU->Length);
+ AliasVariableAttributeArray->Length = AliasCommentU.Length;
+ AliasVariableAttributeArray->Qualifier = 0;
+
+ AliasVariableAttributeArray++;
+
+ AliasVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(AliasNameU.Length) +
+ SampDwordAlignUlong(AliasCommentU.Length);
+ AliasVariableAttributeArray->Length = TotalLength;
+ AliasVariableAttributeArray->Qualifier = MemberCount;
+
+ AliasVariableData = (PVOID)( (PUCHAR)(AliasVariableAttributeArray) +
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) );
+
+ RtlCopyMemory(
+ AliasVariableData,
+ SampProtection[ProtectionIndex].Descriptor,
+ SampProtection[ProtectionIndex].Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(AliasVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length)),
+ AccountNameU->Buffer,
+ AccountNameU->Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(AliasVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(AliasNameU.Length)),
+ AccountCommentU->Buffer,
+ AccountCommentU->Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(AliasVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(AliasNameU.Length) +
+ SampDwordAlignUlong(AliasCommentU.Length)),
+ AliasMembers,
+ TotalLength
+ );
+
+ if ( AliasMembers != NULL ) {
+
+ RtlFreeHeap( RtlProcessHeap(), 0, AliasMembers );
+ }
+
+ //
+ // Create Aliases\(AliasRid) [Revision,] key
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\aliases\\ to keyname\n");
+
+ //
+ // Convert the Rid to a Unicode String with leading zero's
+ //
+
+ Status = SampRtlConvertUlongToUnicodeString(
+ Rid,
+ 16,
+ 8,
+ FALSE,
+ &KeyNameU
+ );
+
+ SUCCESS_ASSERT(Status, " CreateAlias' SampRtlConvertUlongToUnicodeString failed\n");
+
+ //
+ // Now write out the attributes via the RXACT.
+ //
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampCombinedAttributeName,
+ REG_BINARY,
+ (PVOID)AliasFixedAttributes,
+ AliasAttributeLength
+ );
+ SUCCESS_ASSERT(Status, " Failed to write out alias attributes\n" );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, AliasFixedAttributes );
+
+ //
+ // Commit these additions...
+ //
+
+ Status = RtlApplyRXactNoFlush( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to commit Alias addition.\n");
+
+ return Status;
+ DBG_UNREFERENCED_PARAMETER(SpecialAccount);
+
+}
+
+
+NTSTATUS
+CreateGroup(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG Rid,
+ IN BOOLEAN Admin
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds the keys necessary to create a group. It also applies
+ the appropriate protection to the group.
+
+Arguments:
+
+ AccountNameU - The Unicode name of the group.
+
+ AccountCommentU - A Unicode comment to put in the object's variable data.
+
+ SpecialAccount - A boolean indicating whether or not the account
+ is special. Special accounts are marked as such and can not
+ be deleted.
+
+ Rid - The RID of the account.
+
+ Admin - Indicates whether the account is in the Administrators alias
+ or not. TRUE means it is, FALSE means it isn't.
+
+Return Value:
+
+ TBS
+
+--*/
+
+{
+ PSAMP_V1_0A_FIXED_LENGTH_GROUP GroupFixedAttributes;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE GroupVariableAttributeArray;
+ PVOID GroupVariableData;
+
+ ULONG Attributes, ProtectionIndex, GroupCount, GroupAttributeLength;
+ ULONG GroupMembers[2];
+
+ UNICODE_STRING GroupNameU, GroupCommentU;
+
+ GroupNameU = *AccountNameU;
+ GroupCommentU = *AccountCommentU;
+
+ Attributes = (SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED);
+
+ //
+ // Set the correct protection.
+ //
+
+ if (Admin == TRUE) {
+
+ ProtectionIndex = SAMP_PROT_ADMIN_GROUP;
+
+ } else {
+
+ ProtectionIndex = SAMP_PROT_NORMAL_GROUP;
+ }
+
+ //
+ // Set the account specific RID in the DACL's if necessary
+ //
+
+ if ( SampProtection[ProtectionIndex].RidReplacementRequired == TRUE ) {
+
+ (*SampProtection[ProtectionIndex].RidToReplace) = Rid;
+ }
+
+ //
+ // Use a transaction
+ //
+
+ Status = RtlStartRXact( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to start group addition transaction\n");
+
+ //
+ // Add Groups\Names\(GroupName) [ Rid, ]
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Groups\\Names\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\Groups\\Names\n");
+ Status = RtlAppendUnicodeStringToString( &KeyNameU, AccountNameU);
+ SUCCESS_ASSERT(Status, " Failed to append AccountName\n");
+
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ Rid,
+ NULL,
+ 0
+ );
+
+ SUCCESS_ASSERT(Status, " Failed to add Groups\\Names\\(GroupName) to log\n");
+
+ //
+ // Create Groups\(GroupRid) [Revision,] key
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Groups\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\Groups\\\n");
+
+ Status = SampRtlConvertUlongToUnicodeString(
+ Rid,
+ 16,
+ 8,
+ FALSE,
+ &KeyNameU
+ );
+
+ SUCCESS_ASSERT(Status, "CreateGroup: Failed to append Rid Name\n");
+
+ //
+ // Set the Members attribute. The Admin and Guest users are always
+ // members of the Users group. If there is an Admins group, then the
+ // Admin user is a member of it.
+ //
+
+ GroupCount = 0;
+ if ( (Rid == DOMAIN_GROUP_RID_USERS) ||
+ (Rid == DOMAIN_GROUP_RID_ADMINS) ) {
+
+ GroupMembers[GroupCount] = DOMAIN_USER_RID_ADMIN;
+ GroupCount++;
+
+ }
+
+ //
+ // Guests are only members of the Guest group on NTAS systems.
+ // On WinNT systems they are members of NONE (which is the sam
+ // as USERS
+ //
+
+ if ( (Rid == DOMAIN_GROUP_RID_GUESTS) &&
+ (SampProductType == NtProductLanManNt) ) {
+
+ GroupMembers[GroupCount] = DOMAIN_USER_RID_GUEST;
+ GroupCount++;
+ }
+
+ if ( (Rid == DOMAIN_GROUP_RID_USERS) &&
+ ((SampProductType == NtProductWinNt)
+ || (SampProductType == NtProductServer)) ) {
+
+ GroupMembers[GroupCount] = DOMAIN_USER_RID_GUEST;
+ GroupCount++;
+ }
+
+ //
+ // Set the group's fixed and variable attributes
+ //
+
+ GroupAttributeLength = sizeof( SAMP_V1_0A_FIXED_LENGTH_GROUP ) +
+ ( SAMP_GROUP_VARIABLE_ATTRIBUTES *
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) ) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length)
+ + SampDwordAlignUlong(GroupNameU.Length) +
+ SampDwordAlignUlong(GroupCommentU.Length) +
+ ( GroupCount * sizeof( ULONG ) );
+
+ GroupFixedAttributes = (PSAMP_V1_0A_FIXED_LENGTH_GROUP)RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ GroupAttributeLength
+ );
+
+ if ( GroupFixedAttributes == NULL ) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SUCCESS_ASSERT(Status, " Failed to create group attributes\n");
+
+ GroupFixedAttributes->RelativeId = Rid;
+ GroupFixedAttributes->Attributes = Attributes;
+ GroupFixedAttributes->AdminCount = Admin ? 1 : 0;
+ GroupFixedAttributes->OperatorCount = 0;
+ GroupFixedAttributes->Revision = SAMP_REVISION;
+
+ GroupVariableAttributeArray = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ ((PUCHAR)(GroupFixedAttributes) +
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_GROUP ) );
+
+ GroupVariableAttributeArray->Offset = 0;
+ GroupVariableAttributeArray->Length =
+ SampProtection[ProtectionIndex].Length;
+ GroupVariableAttributeArray->Qualifier = SAMP_REVISION;
+
+ GroupVariableAttributeArray++;
+
+ GroupVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+ GroupVariableAttributeArray->Length = GroupNameU.Length;
+ GroupVariableAttributeArray->Qualifier = 0;
+
+ GroupVariableAttributeArray++;
+
+ GroupVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(GroupNameU.Length);
+ GroupVariableAttributeArray->Length = GroupCommentU.Length;
+ GroupVariableAttributeArray->Qualifier = 0;
+
+ GroupVariableAttributeArray++;
+
+ GroupVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(GroupNameU.Length) +
+ SampDwordAlignUlong(GroupCommentU.Length);
+ GroupVariableAttributeArray->Length = GroupCount * sizeof( ULONG );
+ GroupVariableAttributeArray->Qualifier = GroupCount;
+
+ GroupVariableData = (PVOID)( (PUCHAR)(GroupVariableAttributeArray) +
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) );
+
+ RtlCopyMemory(
+ GroupVariableData,
+ SampProtection[ProtectionIndex].Descriptor,
+ SampProtection[ProtectionIndex].Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(GroupVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length)),
+ GroupNameU.Buffer,
+ GroupNameU.Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(GroupVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(GroupNameU.Length)),
+ GroupCommentU.Buffer,
+ GroupCommentU.Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(GroupVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(GroupNameU.Length) +
+ SampDwordAlignUlong(GroupCommentU.Length)),
+ GroupMembers,
+ GroupCount * sizeof( ULONG )
+ );
+
+ //
+ // Now write out the attributes via the RXACT.
+ //
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampCombinedAttributeName,
+ REG_BINARY,
+ (PVOID)GroupFixedAttributes,
+ GroupAttributeLength
+ );
+ SUCCESS_ASSERT(Status, " Failed to write out group attributes\n" );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, GroupFixedAttributes );
+
+ //
+ // Commit these additions...
+ //
+
+ Status = RtlApplyRXactNoFlush( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to commit group addition.\n");
+
+ return Status;
+ DBG_UNREFERENCED_PARAMETER(SpecialAccount);
+}
+
+
+NTSTATUS
+CreateUser(
+ IN PUNICODE_STRING AccountNameU,
+ IN PUNICODE_STRING AccountCommentU,
+ IN BOOLEAN SpecialAccount,
+ IN ULONG UserRid,
+ IN ULONG PrimaryGroup,
+ IN BOOLEAN Admin,
+ IN ULONG UserControl,
+ IN ULONG ProtectionIndex
+ )
+
+/*++
+
+
+Routine Description:
+
+ This routine adds keys for a single user.
+ This routine adds the keys necessary to create a user. It also applies
+ the appropriate protection to the user (protection differs for some
+ standard users).
+
+
+Arguments:
+
+ AccountNameU - The Unicode name of the user.
+
+ AccountCommentU - A Unicode comment to put in the object's variable data.
+
+ SpecialAccount - A boolean indicating whether or not the account
+ is special. Special accounts are marked as such and can not
+ be deleted.
+
+ UserRid - The RID of the user account.
+
+ PrimaryGroup - The RID of the account's primary group. The user
+ does not have to be a member of the group. In fact, it doesn't
+ have to be a group. In fact, no checking is done to see if it
+ is even a valid account.
+
+ Admin - Indicates whether the account is in the Administrators alias
+ or not. TRUE means it is, FALSE means it isn't.
+
+ ProtectionIndex - Indicates which security descriptor to use to protect
+ this object.
+
+
+Return Value:
+
+ TBS
+
+--*/
+
+{
+ PSAMP_V1_0A_FIXED_LENGTH_USER UserFixedAttributes;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE UserVariableAttributeArray;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE UserVariableAttributeArrayStart;
+ PVOID UserVariableData;
+ GROUP_MEMBERSHIP GroupMembership[2];
+
+ WCHAR RidNameBuffer[9], GroupIndexNameBuffer[9];
+ ULONG GroupCount, UserAttributeLength;
+ BOOLEAN DomainAdminMember = FALSE;
+
+ UNICODE_STRING UserNameU, UserCommentU;
+
+ UserNameU = *AccountNameU;
+ UserCommentU = *AccountCommentU;
+
+
+ //
+ // Set the account specific RID in the DACL's if necessary
+ //
+
+ if ( SampProtection[ProtectionIndex].RidReplacementRequired == TRUE ) {
+
+ (*SampProtection[ProtectionIndex].RidToReplace) = UserRid;
+ }
+
+ //
+ // Use a transaction.
+ //
+
+ Status = RtlStartRXact( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to start user addition transaction\n");
+
+ RidNameBuffer[8] = 0;
+ GroupIndexNameBuffer[8] = 0;
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Users\\Names\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\Users\\Names\\\n");
+ Status = RtlAppendUnicodeStringToString( &KeyNameU, &UserNameU);
+ SUCCESS_ASSERT(Status, " Failed to append User Account Name\n");
+ Status = RtlAddActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ UserRid,
+ NULL,
+ 0
+ );
+ SUCCESS_ASSERT(Status, " Failed to add Users\\Names\\(Name) to log\n");
+
+ //
+ // Create Users\(UserRid) key
+ // (KeyValueType is revision, KeyValue is SecurityDescriptor)
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Users\\" );
+ SUCCESS_ASSERT(Status, " Failed to append \\Users\\\n");
+
+ Status = SampRtlConvertUlongToUnicodeString(
+ UserRid,
+ 16,
+ 8,
+ FALSE,
+ &KeyNameU
+ );
+
+ SUCCESS_ASSERT(Status, " CreateUser: Failed to append UserRid Name\n");
+
+ //
+ // Set the Groups attribute.
+ // Everybody except GUEST is a member of the Users group.
+ // On WindowsNT systems (as opposed to NTAS systems) even GUEST
+ // is a member of the Users group.
+ // On LanManNt systems, the Admin is a member of the Admins group.
+ //
+
+ GroupCount = 0;
+
+ if ( (UserRid != DOMAIN_USER_RID_GUEST) ||
+ (SampProductType != NtProductLanManNt) ) {
+
+ GroupMembership[GroupCount].RelativeId = DOMAIN_GROUP_RID_USERS;
+ GroupMembership[GroupCount].Attributes = SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED;
+ GroupCount++;
+ }
+
+ if ( (UserRid == DOMAIN_USER_RID_GUEST) &&
+ (SampProductType == NtProductLanManNt) ) {
+
+ GroupMembership[GroupCount].RelativeId = DOMAIN_GROUP_RID_GUESTS;
+ GroupMembership[GroupCount].Attributes = SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED;
+ GroupCount++;
+ }
+
+
+ if ( ( UserRid == DOMAIN_USER_RID_ADMIN ) &&
+ ( SampProductType == NtProductLanManNt ) ) {
+
+ GroupMembership[GroupCount].RelativeId = DOMAIN_GROUP_RID_ADMINS;
+ GroupMembership[GroupCount].Attributes = SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED;
+ GroupCount++;
+ DomainAdminMember = TRUE;
+ }
+
+ //
+ // Set the user's fixed and variable attributes
+ //
+
+ UserFixedAttributes = (PSAMP_V1_0A_FIXED_LENGTH_USER)RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_USER )
+ );
+
+ if ( UserFixedAttributes == NULL ) {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SUCCESS_ASSERT(Status, " Failed to create user fixed attributes\n");
+
+ UserFixedAttributes->Revision = SAMP_REVISION;
+
+ UserFixedAttributes->CountryCode = 0;
+ UserFixedAttributes->CodePage = 0;
+ UserFixedAttributes->BadPasswordCount = 0;
+ UserFixedAttributes->LogonCount = 0;
+ UserFixedAttributes->OperatorCount = 0;
+ UserFixedAttributes->Unused1 = 0;
+ UserFixedAttributes->Unused2 = 0;
+
+ if ( Admin ) {
+
+ //
+ // If the user is an admin and a member of Domain Admins, set the count
+ // to two.
+ //
+
+ if ( DomainAdminMember ) {
+ UserFixedAttributes->AdminCount = 2;
+ } else {
+ UserFixedAttributes->AdminCount = 1;
+ }
+
+
+ } else {
+
+ UserFixedAttributes->AdminCount = 0;
+ }
+
+ UserFixedAttributes->UserAccountControl = UserControl;
+ UserFixedAttributes->UserId = UserRid;
+ UserFixedAttributes->PrimaryGroupId = PrimaryGroup;
+ UserFixedAttributes->LastLogon = SampHasNeverTime;
+ UserFixedAttributes->LastLogoff = SampHasNeverTime;
+ UserFixedAttributes->PasswordLastSet = SampHasNeverTime;
+ UserFixedAttributes->AccountExpires = SampWillNeverTime;
+ UserFixedAttributes->LastBadPasswordTime = SampHasNeverTime;
+
+
+ UserAttributeLength = SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length) +
+ SampDwordAlignUlong( GroupCount *
+ sizeof( GROUP_MEMBERSHIP ) ) +
+ ( SAMP_USER_VARIABLE_ATTRIBUTES *
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) ) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+
+ UserVariableAttributeArrayStart = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE)
+ RtlAllocateHeap(
+ RtlProcessHeap(), 0,
+ UserAttributeLength
+ );
+
+ if ( UserVariableAttributeArrayStart == NULL ) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ SUCCESS_ASSERT(Status, " Failed to create user variable attributes\n");
+
+ UserVariableAttributeArray = UserVariableAttributeArrayStart;
+
+ UserVariableAttributeArray->Offset = 0;
+ UserVariableAttributeArray->Length =
+ SampProtection[ProtectionIndex].Length;
+ UserVariableAttributeArray->Qualifier = SAMP_REVISION;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length);
+ UserVariableAttributeArray->Length = UserNameU.Length;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length);
+ UserVariableAttributeArray->Length = UserCommentU.Length;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length);
+ UserVariableAttributeArray->Length = GroupCount * sizeof( GROUP_MEMBERSHIP );
+ UserVariableAttributeArray->Qualifier = GroupCount;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length) +
+ SampDwordAlignUlong((GroupCount * sizeof( GROUP_MEMBERSHIP )));
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length) +
+ SampDwordAlignUlong((GroupCount * sizeof( GROUP_MEMBERSHIP )));
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length) +
+ SampDwordAlignUlong((GroupCount * sizeof( GROUP_MEMBERSHIP )));
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableAttributeArray++;
+
+ UserVariableAttributeArray->Offset =
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length) +
+ SampDwordAlignUlong((GroupCount * sizeof( GROUP_MEMBERSHIP )));
+ UserVariableAttributeArray->Length = 0;
+ UserVariableAttributeArray->Qualifier = 0;
+
+ UserVariableData = (PVOID)( (PUCHAR)(UserVariableAttributeArray) +
+ sizeof( SAMP_VARIABLE_LENGTH_ATTRIBUTE ) );
+
+ RtlCopyMemory(
+ UserVariableData,
+ SampProtection[ProtectionIndex].Descriptor,
+ SampProtection[ProtectionIndex].Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(UserVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length)),
+ UserNameU.Buffer,
+ UserNameU.Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(UserVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length)),
+ UserCommentU.Buffer,
+ UserCommentU.Length
+ );
+
+ RtlCopyMemory(
+ (PVOID)((PUCHAR)(UserVariableData) +
+ SampDwordAlignUlong(SampProtection[ProtectionIndex].Length) +
+ SampDwordAlignUlong(UserNameU.Length) +
+ SampDwordAlignUlong(UserCommentU.Length)),
+ &GroupMembership,
+ GroupCount * sizeof( GROUP_MEMBERSHIP )
+ );
+
+ //
+ // Now write out the attributes via the RXACT.
+ //
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampFixedAttributeName,
+ REG_BINARY,
+ (PVOID)UserFixedAttributes,
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_USER )
+ );
+ SUCCESS_ASSERT(Status, " Failed to write out user fixed attributes\n" );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, UserFixedAttributes );
+
+ Status = RtlAddAttributeActionToRXact(
+ SamRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameU,
+ INVALID_HANDLE_VALUE,
+ &SampVariableAttributeName,
+ REG_BINARY,
+ (PVOID)UserVariableAttributeArrayStart,
+ UserAttributeLength
+ );
+ SUCCESS_ASSERT(Status, " Failed to write out user variable attributes\n" );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, UserVariableAttributeArrayStart );
+
+ //
+ // Commit these additions...
+ //
+
+ Status = RtlApplyRXactNoFlush( SamRXactContext );
+ SUCCESS_ASSERT(Status, " Failed to commit user addition.\n");
+
+ return Status;
+
+ DBG_UNREFERENCED_PARAMETER(SpecialAccount);
+ DBG_UNREFERENCED_PARAMETER(UserControl);
+}
+
+
+
+PSID
+BuildPrimaryDomainSid(
+ ULONG Rid
+ )
+{
+ NTSTATUS Status;
+ PSID SourceDomainSid, NewSid;
+ ULONG SidLength, SubAuthorityCount;
+
+ SourceDomainSid = SampBldPrimaryDomain->Sid;
+
+ SidLength = RtlLengthSid( SourceDomainSid ) + sizeof(ULONG);
+ NewSid = RtlAllocateHeap( RtlProcessHeap(), 0, SidLength );
+ if (NewSid != NULL) {
+
+ Status = RtlCopySid (SidLength, NewSid, SourceDomainSid );
+ ASSERT(NT_SUCCESS(Status));
+
+ (*RtlSubAuthorityCountSid( NewSid )) += 1;
+ SubAuthorityCount = (ULONG)(*RtlSubAuthorityCountSid( NewSid ));
+ (*RtlSubAuthoritySid( NewSid, SubAuthorityCount-1)) = Rid;
+
+ }
+
+
+ return(NewSid);
+
+
+}
+
+
+
+
+PSID
+BuildAccountSid(
+ SAMP_DOMAIN_SELECTOR Domain,
+ ULONG Rid
+ )
+{
+ NTSTATUS Status;
+ PSID SourceDomainSid, NewSid;
+ ULONG SidLength, SubAuthorityCount;
+
+
+ if (Domain == DomainBuiltin) {
+ SourceDomainSid = SampBuiltinDomainSid;
+ } else {
+ SourceDomainSid = SampAccountDomainSid;
+ }
+
+ SidLength = RtlLengthSid( SourceDomainSid ) + sizeof(ULONG);
+ NewSid = RtlAllocateHeap( RtlProcessHeap(), 0, SidLength );
+ if (NewSid != NULL) {
+
+ Status = RtlCopySid (SidLength, NewSid, SourceDomainSid );
+ ASSERT(NT_SUCCESS(Status));
+
+ (*RtlSubAuthorityCountSid( NewSid )) += 1;
+ SubAuthorityCount = (ULONG)(*RtlSubAuthorityCountSid( NewSid ));
+ (*RtlSubAuthoritySid( NewSid, SubAuthorityCount-1)) = Rid;
+
+ }
+
+
+ return(NewSid);
+
+
+}
+
+
+
+NTSTATUS
+UpdateAliasXReference(
+ IN ULONG AliasRid,
+ IN PSID Sid
+ )
+
+/*++
+
+
+Routine Description:
+
+ This routine updates the set of alias member SIDs either by adding
+ specified SID (if it isn't already an alias member) or incrementing
+ its count (if it is already an alias member).
+
+
+ The BUILTIN domain is updated.
+
+
+
+Arguments:
+
+
+ Sid - member Sid to update.
+
+
+
+
+Return Value:
+
+ TBS
+
+--*/
+
+{
+ NTSTATUS IgnoreStatus;
+
+ HANDLE KeyHandle;
+
+
+
+ if (RtlSubAuthorityCountSid( Sid ) == 0) {
+ return(STATUS_INVALID_SID);
+ }
+
+
+ //
+ // Open the domain key for this alias member.
+ //
+
+ SetCurrentDomain( DomainBuiltin );
+ Status = OpenAliasMember( Sid, &KeyHandle );
+
+
+ if (NT_SUCCESS(Status)) {
+
+ ULONG MembershipCount,
+ KeyValueLength,
+ OldKeyValueLength,
+ i;
+ PULONG MembershipArray;
+
+ //
+ // Retrieve the length of the current membership buffer
+ // and allocate one large enough for that plus another member.
+ //
+
+ KeyValueLength = 0;
+ Status = RtlpNtQueryValueKey( KeyHandle,
+ &MembershipCount,
+ NULL,
+ &KeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW)) {
+
+ KeyValueLength += sizeof(ULONG);
+ MembershipArray = RtlAllocateHeap( RtlProcessHeap(), 0, KeyValueLength );
+
+
+ if (MembershipArray == NULL) {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ OldKeyValueLength = KeyValueLength;
+ Status = RtlpNtQueryValueKey(
+ KeyHandle,
+ NULL,
+ MembershipArray,
+ &OldKeyValueLength,
+ NULL);
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // See if the account is already a member ...
+ //
+
+ for (i = 0; i<MembershipCount ; i++ ) {
+ if ( MembershipArray[i] == AliasRid )
+ {
+ Status = STATUS_MEMBER_IN_ALIAS;
+ }
+ }
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // Add the Aliasrid to the end
+ //
+
+ MembershipCount += 1;
+ MembershipArray[MembershipCount-1] = AliasRid;
+
+ //
+ // And write it out.
+ //
+
+ Status = RtlpNtSetValueKey(
+ KeyHandle,
+ MembershipCount,
+ MembershipArray,
+ KeyValueLength
+ );
+ }
+ }
+
+ RtlFreeHeap(RtlProcessHeap(), 0, MembershipArray);
+ }
+
+ }
+
+ IgnoreStatus = NtClose( KeyHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ }
+
+
+
+ return( Status );
+
+}
+
+
+NTSTATUS
+OpenAliasMember(
+ IN PSID Sid,
+ OUT PHANDLE KeyHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens the registry key containing the alias
+ xreference for the specified SID. If either this key, or
+ its corresponding parent key doesn't exist, it (they) will
+ be created.
+
+ If a new domain-level key is created, the DomainCount in the
+ ALIASES\MEMBERS key is incremented as well.
+
+
+Arguments:
+
+ Sid - The SID that is an alias member.
+
+ KeyHandle - Receives a handle to the registry key for this alias
+ member account xreference.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ NTSTATUS IgnoreStatus;
+ HANDLE AliasDomainHandle;
+
+ //
+ // Open or create the domain-level key.
+ //
+
+
+ Status = OpenOrCreateAliasDomainKey( Sid, &AliasDomainHandle );
+
+ if (NT_SUCCESS(Status)) {
+
+
+ //
+ // Open or create the account-rid key
+ //
+
+ Status = OpenOrCreateAccountRidKey( Sid,
+ AliasDomainHandle,
+ KeyHandle
+ );
+
+ IgnoreStatus = NtClose( AliasDomainHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(Status);
+
+
+}
+
+
+
+NTSTATUS
+OpenOrCreateAccountRidKey(
+ IN PSID Sid,
+ IN HANDLE AliasDomainHandle,
+ OUT PHANDLE KeyHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens an account xreference key for an alias
+ member SID.
+
+ If this key doesn't exist, it will be created.
+
+ If a new key is created, the RidCount in the AliasDomainHandle
+ key is incremented as well.
+
+
+Arguments:
+
+ Sid - The SID that is an alias member.
+
+ AliasDomainHandle
+
+ KeyHandle - Receives a handle to the registry key for this alias
+ member domain xreference.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ ULONG Disposition;
+ ULONG Rid;
+
+ if (RtlSubAuthorityCountSid( Sid ) == 0) {
+ return(STATUS_INVALID_SID);
+ }
+
+ Rid = (*RtlSubAuthoritySid(Sid, (ULONG)(*RtlSubAuthorityCountSid(Sid))-1));
+
+ //
+ // Build the Unicode Key for this Rid.
+ //
+
+ KeyNameU.Length = (USHORT) 0;
+
+ Status = SampRtlConvertUlongToUnicodeString(
+ Rid,
+ 16,
+ 8,
+ FALSE,
+ &KeyNameU
+ );
+
+ //
+ // Open this key relative to the alias domain key
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyNameU,
+ OBJ_CASE_INSENSITIVE,
+ AliasDomainHandle,
+ NULL
+ );
+ Status = RtlpNtCreateKey(
+ KeyHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0, //Options
+ NULL, //Provider
+ &Disposition
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ if (Disposition == REG_CREATED_NEW_KEY) {
+
+ //
+ // Update the AccountRid count in the alias domain key
+ //
+
+ ULONG MembershipCount;
+
+
+ //
+ // Retrieve the current domain count and increment it by 1.
+ //
+
+ Status = RtlpNtQueryValueKey( AliasDomainHandle,
+ &MembershipCount,
+ NULL,
+ NULL,
+ NULL);
+
+ if (NT_SUCCESS(Status)) {
+
+ MembershipCount += 1;
+
+ //
+ // Write it back out.
+ //
+
+ Status = RtlpNtSetValueKey(
+ AliasDomainHandle,
+ MembershipCount,
+ NULL,
+ 0
+ );
+ }
+
+ //
+ // Now write out the AccountRid key info
+ //
+
+ Status = RtlpNtSetValueKey(
+ *KeyHandle,
+ 0, //Not yet a member of any aliases
+ NULL,
+ 0
+ );
+
+ }
+ }
+
+ return(Status);
+}
+
+
+
+NTSTATUS
+OpenOrCreateAliasDomainKey(
+ IN PSID Sid,
+ OUT PHANDLE KeyHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens a domain xreference key for an alias
+ member SID.
+
+ If this key doesn't exist, it will be created.
+
+ If a new key is created, the DomainCount in the
+ ALIASES\MEMBERS key is incremented as well.
+
+
+Arguments:
+
+ Sid - The SID that is an alias member.
+
+ KeyHandle - Receives a handle to the registry key for this alias
+ member domain xreference.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS IgnoreStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ ULONG Disposition;
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases" );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Members\\" );
+ Status = AppendAliasDomainNameToUnicodeString( &KeyNameU, Sid );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyNameU,
+ OBJ_CASE_INSENSITIVE,
+ SamKey,
+ NULL
+ );
+ Status = RtlpNtCreateKey(
+ KeyHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0, //Options
+ NULL, //Provider
+ &Disposition
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ if (Disposition == REG_CREATED_NEW_KEY) {
+
+ HANDLE TmpHandle;
+
+ //
+ // Update the Domain count
+ //
+
+ RtlCopyUnicodeString( &KeyNameU, FullDomainNameU );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Aliases" );
+ Status = RtlAppendUnicodeToString( &KeyNameU, L"\\Members\\" );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyNameU,
+ OBJ_CASE_INSENSITIVE,
+ SamKey,
+ NULL
+ );
+ Status = RtlpNtOpenKey(
+ &TmpHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ if (NT_SUCCESS(Status)) {
+
+ ULONG MembershipCount;
+
+
+ //
+ // Retrieve the current domain count and increment it by 1.
+ //
+
+ Status = RtlpNtQueryValueKey( TmpHandle,
+ &MembershipCount,
+ NULL,
+ NULL,
+ NULL);
+
+ if (NT_SUCCESS(Status)) {
+
+ MembershipCount += 1;
+
+ //
+ // Write it back out.
+ //
+
+ Status = RtlpNtSetValueKey(
+ TmpHandle,
+ MembershipCount,
+ NULL,
+ 0
+ );
+ }
+
+ IgnoreStatus = NtClose( TmpHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ }
+ }
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+AppendAliasDomainNameToUnicodeString(
+ IN OUT PUNICODE_STRING Destination,
+ IN PSID Sid
+ )
+
+{
+ UCHAR OriginalCount;
+
+ //
+ // Save the current sub-authority count and decrement it by one.
+ //
+
+ OriginalCount = (*RtlSubAuthorityCountSid(Sid));
+ (*RtlSubAuthorityCountSid(Sid)) = OriginalCount -1;
+
+ //
+ // Convert the Sid to a Unicode String and place it in the global
+ // temporary Unicode String buffer.
+ //
+
+ Status = RtlConvertSidToUnicodeString( &TempStringU, Sid, TRUE);
+
+ (*RtlSubAuthorityCountSid(Sid)) = OriginalCount;
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = RtlAppendUnicodeStringToString( Destination, &TempStringU );
+ }
+
+ return(Status);
+}
+
+
+
+VOID
+SampGetServerRole(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves the server role from the LSA policy database
+ and places it in the global variable SampServerRole.
+
+
+Arguments:
+
+ None.
+
+
+Return Value:
+
+ (placed in the global variable (Status) )
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsarQueryInformationPolicy()
+--*/
+
+{
+ NTSTATUS IgnoreStatus;
+ PPOLICY_LSA_SERVER_ROLE_INFO ServerRoleInfo = NULL;
+
+ //
+ // Query the server role information
+ //
+
+ Status = LsarQueryInformationPolicy(
+ SampBldPolicyHandle,
+ PolicyLsaServerRoleInformation,
+ (PLSAPR_POLICY_INFORMATION *)&ServerRoleInfo
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ if (ServerRoleInfo->LsaServerRole == PolicyServerRolePrimary) {
+
+ SampServerRole = DomainServerRolePrimary;
+
+ } else {
+
+ SampServerRole = DomainServerRoleBackup;
+ }
+
+ IgnoreStatus = LsaFreeMemory( ServerRoleInfo );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return;
+}
+
+
+
+
+VOID
+SampGetPrimaryDomainInfo(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves the primary domain name/sid from the
+ LSA policy database and places it in the global variable
+ SampBldPrimaryDomain.
+
+
+Arguments:
+
+ None.
+
+
+Return Value:
+
+ (placed in the global variable (Status) )
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsarQueryInformationPolicy()
+
+ NOTE: The Rdr and Bowser components of the LanmanWorkstation
+ service rely on there always being a primary domain name.
+ For this reason Network SETUP always supplies a default
+ "workgroup" name, which is set as the primary domain name.
+ In this case, the name is present but the SID is NULL;
+ this is equivalent to NOT having a primary domain at all.
+
+--*/
+
+{
+ SampBldPrimaryDomain = NULL;
+
+ Status = LsarQueryInformationPolicy(
+ SampBldPolicyHandle,
+ PolicyPrimaryDomainInformation,
+ (PLSAPR_POLICY_INFORMATION *) &SampBldPrimaryDomain
+ );
+
+ if (NT_SUCCESS(Status) && ( SampBldPrimaryDomain->Sid == NULL )) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ (PLSAPR_POLICY_INFORMATION) SampBldPrimaryDomain
+ );
+
+ SampBldPrimaryDomain = NULL;
+ }
+
+ return;
+}
+
+
+
+
+VOID
+SampRestoreDefaultDacl(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Restores the saved default DACL to the current token.
+
+Arguments:
+
+ None.
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ HANDLE Token;
+
+ Status = NtOpenProcessToken(
+ NtCurrentProcess(),
+ (TOKEN_ADJUST_DEFAULT),
+ &Token
+ );
+
+
+ ASSERT(NT_SUCCESS( Status ));
+
+ Status = NtSetInformationToken (
+ Token,
+ TokenDefaultDacl,
+ TokenDefaultDaclInformation,
+ TokenDefaultDaclInformationSize
+ );
+
+ ASSERT(NT_SUCCESS( Status ));
+
+ NtClose( Token );
+
+}
+
+
+NTSTATUS
+SampDetermineSetupEnvironment( VOID )
+
+
+/*++
+
+Routine Description:
+
+ This function checks to see whether we are running folloing
+ a formal SETUP. If not, it is assumed we are running to
+ perform a developer's setup.
+
+ Global variables are set to indicate our setup environment.
+
+
+ BOOLEAN SampRealSetupWasRun; //Indicates a real setup was run
+ BOOLEAN SampDeveloperSetup; //Indicates a developer setup is running
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+--*/
+
+{
+ NTSTATUS NtStatus, TmpStatus;
+ HANDLE InstallationEvent;
+ OBJECT_ATTRIBUTES EventAttributes;
+ UNICODE_STRING EventName;
+
+ SampRealSetupWasRun = FALSE;
+ SampDeveloperSetup = FALSE;
+
+ //
+ // If the following event exists, it is an indication that
+ // a real setup was run.
+ //
+
+ RtlInitUnicodeString( &EventName, L"\\INSTALLATION_SECURITY_HOLD");
+ InitializeObjectAttributes( &EventAttributes, &EventName, 0, 0, NULL );
+
+ NtStatus = NtOpenEvent(
+ &InstallationEvent,
+ SYNCHRONIZE,
+ &EventAttributes
+ );
+
+ if ( NT_SUCCESS(NtStatus)) {
+
+ //
+ // The event exists - installation created it and will signal it
+ // when it is ok to proceed with security initialization.
+ //
+
+ SampRealSetupWasRun = TRUE;
+
+ TmpStatus = NtClose( InstallationEvent );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ } else {
+ SampDeveloperSetup = TRUE;
+ }
+
+
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampInitializeRegistry (
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine initializes a SAM database in the registry.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ STATUS_SUCCESS or an error received along the way.
+
+--*/
+
+{
+ NTSTATUS IgnoreStatus;
+
+ Status = Initialize();
+ if (!NT_SUCCESS(Status)) {
+ return( Status );
+ }
+
+ //
+ // Initialize SAM-level registry structures
+ //
+
+ Status = InitializeSam( );
+ if (!NT_SUCCESS(Status)) {return(Status);}
+
+ //
+ // OK, we have a SAM key.
+ // Create each of the domains.
+ //
+
+ Status = CreateBuiltinDomain( ); if (!NT_SUCCESS(Status)) {return(Status);}
+ Status = CreateAccountDomain( ); if (!NT_SUCCESS(Status)) {return(Status);}
+
+ //
+ // all done
+ //
+
+ //
+ // Close our handle to LSA. Ignore any errors.
+ //
+
+ IgnoreStatus = LsarClose( (PLSAPR_HANDLE) &SampBldPolicyHandle );
+ SampBldPolicyHandle = NULL;
+
+ //
+ // We modified the default DACL in the token, put it back to what
+ // it was here.
+ //
+
+ SampRestoreDefaultDacl();
+
+ //
+ // Free up the transaction context we created
+ //
+
+ RtlFreeHeap( RtlProcessHeap(), 0, SamRXactContext );
+ SamRXactContext = NULL;
+
+ //
+ // Close the database root key after flushing all the changes we made.
+ //
+
+ Status = NtFlushKey( SamKey );
+
+ if (NT_SUCCESS(Status)) {
+
+ IgnoreStatus = NtClose( SamKey );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+
+ KdPrint(("SAMSRV: FlushKey failed, database not built. Status: 0x%lx\n", Status));
+ IgnoreStatus = NtClose( SamKey );
+ }
+
+ SamKey = NULL;
+
+ //
+ // Close the database root parent key
+ //
+
+ if (SamParentKey != NULL) {
+ IgnoreStatus = NtClose( SamParentKey );
+ }
+
+ return( Status );
+}
+
+
+NTSTATUS
+SampGetMessageStrings(
+ LPVOID Resource,
+ DWORD Index1,
+ PUNICODE_STRING String1,
+ DWORD Index2,
+ PUNICODE_STRING String2 OPTIONAL
+ )
+
+
+/*++
+
+Routine Description:
+
+ This gets 1 or 2 message strings values from a resource message table.
+ The string buffers are allocated and the strings initialized properly.
+
+ The string buffers must be freed using LocalFree() when no longer needed.
+
+Arguments:
+
+ Resource - points to the resource table.
+
+ Index1 - Index of first message to retrieve.
+
+ String1 - Points to a UNICODE_STRING structure to receive the first
+ message string.
+
+ Index2 - Index of second message to retrieve.
+
+ String2 - Points to a UNICODE_STRING structure to receive the first
+ message string. If this parameter is NULL, then only one message
+ string is retrieved.
+
+Return Value:
+
+ None.
+
+--*/
+
+
+{
+
+ String1->Buffer = NULL;
+
+ String1->MaximumLength = (USHORT) FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
+ FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ Resource,
+ Index1,
+ 0, // Use caller's language
+ (LPWSTR)&(String1->Buffer),
+ 0,
+ NULL
+ );
+
+ if (String1->Buffer == NULL) {
+ return(STATUS_RESOURCE_DATA_NOT_FOUND);
+ } else {
+
+ //
+ // Note that we are retrieving a message from a message file.
+ // This message will have a cr/lf tacked on the end of it
+ // (0x0d 0x0a) that we don't want to be part of our returned
+ // strings. Also note that FormatMessage() returns a character
+ // count, not a byte count. So, we have to do some adjusting
+ // to make the string lengths correct.
+ //
+
+ String1->MaximumLength -= 2; // For the cr/lf we don't want.
+ String1->MaximumLength *= sizeof(WCHAR); // to make it a byte count
+ String1->Length = String1->MaximumLength;
+ }
+
+
+ if (!ARGUMENT_PRESENT(String2)) {
+ return(STATUS_SUCCESS);
+ }
+
+ String2->Buffer = NULL;
+ String2->MaximumLength = (USHORT) FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
+ FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ Resource,
+ Index2,
+ 0, // Use caller's language
+ (LPWSTR)&(String2->Buffer),
+ 0,
+ NULL
+ );
+
+ if (String2->Buffer == NULL) {
+ LocalFree( String1->Buffer );
+ return(STATUS_RESOURCE_DATA_NOT_FOUND);
+ } else {
+
+ //
+ // Note that we are retrieving a message from a message file.
+ // This message will have a cr/lf tacked on the end of it
+ // (0x0d 0x0a) that we don't want to be part of our returned
+ // strings. Also note that FormatMessage() returns a character
+ // count, not a byte count. So, we have to do some adjusting
+ // to make the string lengths correct.
+ //
+
+ String2->MaximumLength -= 2; // For the cr/lf we don't want.
+ String2->MaximumLength *= sizeof(WCHAR); // to make it a byte count
+ String2->Length = String2->MaximumLength;
+ }
+
+
+
+ return(STATUS_SUCCESS);
+
+}
diff --git a/private/newsam/server/close.c b/private/newsam/server/close.c
new file mode 100644
index 000000000..1e99cfe54
--- /dev/null
+++ b/private/newsam/server/close.c
@@ -0,0 +1,179 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ close.c
+
+Abstract:
+
+ This file contains the object close routine for SAM objects.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+NTSTATUS
+SamrCloseHandle(
+ IN OUT SAMPR_HANDLE * SamHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service closes a handle for any type of SAM object.
+
+ Any race conditions that may occur with respect to attempts to
+ close a handle that is just becoming invalid by other means are
+ expected to be handled by the RPC runtime. That is, this service
+ will never be called by the RPC runtime when the handle value is
+ no longer valid. It will also never call this routine when there
+ is another call outstanding with this same context handle.
+
+Arguments:
+
+ SamHandle - A valid handle to a SAM object.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The handle has successfully been closed.
+
+ Others that might be returned by:
+
+ SampLookupcontext()
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+
+ Context = (PSAMP_OBJECT)(* SamHandle);
+
+ //
+ // Grab a read lock
+ //
+
+ SampAcquireReadLock();
+
+ NtStatus = SampLookupContext(
+ Context, //Context
+ 0, //DesiredAccess
+ SampUnknownObjectType, //ExpectedType
+ &FoundType //FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Mark it for delete and remove the reference caused by
+ // context creation (representing the handle reference).
+ //
+
+ SampDeleteContext( Context );
+
+ //
+ // And drop our reference from the lookup operation
+ //
+
+ SampDeReferenceContext( Context, FALSE );
+
+ //
+ // Tell RPC that the handle is no longer valid...
+ //
+
+ (*SamHandle) = NULL;
+ }
+
+ //
+ // Free read lock
+ //
+
+ SampReleaseReadLock();
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( FoundType == SampServerObjectType ) &&
+ ( !(LastUnflushedChange.QuadPart == SampHasNeverTime.QuadPart) ) ) {
+
+ //
+ // Some app is closing the server object after having made
+ // changes. We should make sure that the changes get
+ // flushed to disk before the app exits. We need to get
+ // the write lock for this.
+ //
+
+ FlushImmediately = TRUE;
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( !(LastUnflushedChange.QuadPart ==SampHasNeverTime.QuadPart) ) {
+
+ //
+ // Nobody flushed while we were waiting for the
+ // write lock. So flush the changes now.
+ //
+
+ NtStatus = NtFlushKey( SampKey );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ FlushImmediately = FALSE;
+ LastUnflushedChange = SampHasNeverTime;
+ }
+ }
+
+ SampReleaseWriteLock( FALSE );
+ }
+ }
+
+ return(NtStatus);
+}
diff --git a/private/newsam/server/context.c b/private/newsam/server/context.c
new file mode 100644
index 000000000..56d5a494c
--- /dev/null
+++ b/private/newsam/server/context.c
@@ -0,0 +1,1634 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ context.c
+
+Abstract:
+
+ This file contains services for operating on internal context blocks.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Context validation services.
+// The service to invalidate a context is visible outside this file and so
+// its prototype is in samsrvp.h.
+//
+
+VOID
+SampAddNewValidContextAddress(
+ IN PSAMP_OBJECT NewContext
+ );
+
+NTSTATUS
+SampValidateContextAddress(
+ IN PSAMP_OBJECT Context
+ );
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+
+
+
+PSAMP_OBJECT
+SampCreateContext(
+ IN SAMP_OBJECT_TYPE Type,
+ IN BOOLEAN TrustedClient
+ )
+
+/*++
+
+Routine Description:
+
+ This service creates a new object context block of the specified type.
+
+ If the context block is for either a user or group object type, then
+ it will be added to the list of contexts for the transaction domain.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+ Upon return:
+
+ - The ObjectType field will be set to the passed value.
+
+ - The Reference count field will be set to 1,
+
+ - The GrantedAccess field will be zero.
+
+ - The TrustedClient field will be set according to the passed
+ value.
+
+ - The Valid flag will be TRUE.
+
+ All other fields must be filled in by the creator.
+
+
+Arguments:
+
+ Type - Specifies the type of context block being created.
+
+ TrustedClient - Indicates whether the client is a trusted component
+ of the operating syste. If so, than all access checks are
+ circumvented.
+
+
+
+Return Value:
+
+
+ Non-Null - Pointer to a context block.
+
+ NULL - Insufficient resources. No context block allocated.
+
+
+--*/
+{
+
+ PSAMP_OBJECT Context;
+
+ if (!TrustedClient) {
+ if (SampActiveContextCount >= SAMP_MAXIMUM_ACTIVE_CONTEXTS) {
+ return(NULL);
+ }
+
+ SampActiveContextCount += 1;
+ }
+
+
+ Context = MIDL_user_allocate( sizeof(SAMP_OBJECT) );
+ if (Context != NULL) {
+
+#if SAMP_DIAGNOSTICS
+ IF_SAMP_GLOBAL( CONTEXT_TRACKING ) {
+ SampDiagPrint( CONTEXT_TRACKING, ("Creating ") );
+ if (Type == SampServerObjectType) SampDiagPrint(CONTEXT_TRACKING, ("Server "));
+ if (Type == SampDomainObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Domain "));
+ if (Type == SampGroupObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Group "));
+ if (Type == SampAliasObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Alias "));
+ if (Type == SampUserObjectType) SampDiagPrint(CONTEXT_TRACKING, (" User "));
+ SampDiagPrint(CONTEXT_TRACKING, ("context : 0x%lx\n", Context ));
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+ Context->ObjectType = Type;
+ Context->ReferenceCount = 1; // Represents RPCs held context handle value
+ Context->GrantedAccess = 0;
+
+ Context->RootKey = INVALID_HANDLE_VALUE;
+ RtlInitUnicodeString(&Context->RootName, NULL);
+
+ Context->TrustedClient = TrustedClient;
+ Context->MarkedForDelete = FALSE;
+ Context->AuditOnClose = FALSE;
+
+ Context->OnDisk = NULL;
+ Context->OnDiskAllocated = 0;
+ Context->FixedValid = FALSE;
+ Context->VariableValid = FALSE;
+
+ //
+ // The following are meaningless at this point because of the
+ // values of the variables above, but we'll set them just to be
+ // neat.
+ //
+
+ Context->FixedDirty = FALSE;
+ Context->VariableDirty = FALSE;
+
+ Context->OnDiskUsed = 0;
+ Context->OnDiskFree = 0;
+
+
+ //
+ // Add this new context to the set of valid contexts ...
+ //
+
+ SampAddNewValidContextAddress( Context );
+
+
+ //
+ // User and group context blocks are kept on linked lists
+ // from the domain's in-memory structure.
+ //
+ //
+
+ Context->DomainIndex = SampTransactionDomainIndex;
+
+ switch (Type) {
+
+ case SampServerObjectType:
+ case SampDomainObjectType:
+
+ InsertTailList(
+ &SampContextListHead,
+ &Context->ContextListEntry
+ );
+ break;
+
+ case SampUserObjectType:
+
+ InsertTailList(
+ &SampDefinedDomains[SampTransactionDomainIndex].UserContextHead,
+ &Context->ContextListEntry
+ );
+ break;
+
+ case SampGroupObjectType:
+
+ InsertTailList(
+ &SampDefinedDomains[SampTransactionDomainIndex].GroupContextHead,
+ &Context->ContextListEntry
+ );
+ break;
+
+ case SampAliasObjectType:
+
+ InsertTailList(
+ &SampDefinedDomains[SampTransactionDomainIndex].AliasContextHead,
+ &Context->ContextListEntry
+ );
+ break;
+ }
+ }
+
+ return(Context);
+}
+
+
+VOID
+SampDeleteContext(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This service marks a context object for delete and dereferences it.
+ If this causes the reference count to go to zero, then the context
+ block will be immediately deleted (deallocated). Otherwise, the
+ context block will be deleted when the reference count finally does
+ go to zero.
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to delete.
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+ NTSTATUS IgnoreStatus;
+
+ Context->MarkedForDelete = TRUE;
+
+ //
+ // Audit the close of this context.
+ //
+
+ (VOID) NtCloseObjectAuditAlarm (
+ &SampSamSubsystem,
+ (PVOID)Context,
+ Context->AuditOnClose
+ );
+
+ //
+ // Remove this context from the valid context set.
+ // Note that the context may have already been removed. This is
+ // not an error.
+ //
+
+ SampInvalidateContextAddress( Context );
+
+
+ //
+ // User and group context blocks are kept on linked lists
+ // from the domain's in-memory structure. Domain and
+ // server context blocks are kept on a global in-memory list.
+ // They are removed when they are marked for delete.
+ //
+
+ RemoveEntryList(&Context->ContextListEntry);
+
+ //
+ // We have to call dereference to counter the initial count of 1
+ // put on by create.
+ //
+
+ IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+
+ return;
+
+}
+
+
+NTSTATUS
+SampLookupContext(
+ IN PSAMP_OBJECT Context,
+ IN ACCESS_MASK DesiredAccess,
+ IN SAMP_OBJECT_TYPE ExpectedType,
+ OUT PSAMP_OBJECT_TYPE FoundType
+ )
+
+/*++
+
+Routine Description:
+
+ This service:
+
+ - Checks to make sure the Service state is one in which an
+ object can be looked up (i.e., not Initializing or Terminating).
+
+ - Makes sure the Service state is compatible with the lookup.
+ Non-trusted clients can only perform lookups when the Service
+ state is Enabled. If the client isn't trusted and the context
+ is for a group or user, then the state of that object's domain
+ must also be enabled
+
+ - Checks to make sure the context block represents the
+ type of object expected, and, if so:
+
+ - Checks to see that the caller has the requested (desired)
+ access, and, if so:
+
+ - Makes sure the object still exists, and opens it if it
+ does. Servers and domains can't be deleted, and so
+ their handle is left open.
+
+ - References the context block
+
+
+ Note that if the block is marked as TrustedClient, then access will
+ always be granted unless service state prevents it.
+
+ Also, if the ExpectedType is specified to be unknown, then any type
+ of context will be accepted.
+
+
+
+ If the type of object is found to be , Domain, Group or User, then the
+ this service will set the transaction domain.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to look-up.
+
+ DesiredAccess - The type of access the client is requesting to this
+ object. A zero-valued access mask may be specified. In this case,
+ the calling routine must do access validation.
+
+ ExpectedType - The type of object expected. This may be unknown. In
+ this case, the DesiredAccess should only include access types that
+ apply to any type of object (e.g., Delete, WriteDacl, et cetera).
+
+ FoundType - Receives the type of context actually found.
+
+Return Value:
+
+ STATUS_SUCCESS - The context was found to be the type expected (or any
+ type if ExpectedType was unknown) and the DesiredAccesses are all
+ granted.
+
+ STATUS_OBJECT_TYPE_MISMATCH - Indicates the context was not the expected
+ type.
+
+ STATUS_ACCESS_DENIED - The desired access is not granted by this context.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG Rid;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+
+ //
+ // Make sure we are in a legitimate state to at least access
+ // a context block. If we are initializing we have somehow allowed
+ // a connect through. This should never happen.
+ // If we are terminating, clients may still have handles (since we
+ // have no way to tell RPC they are no longer valid without the client
+ // calling us, Argh!). However, since we are terminating, the blocks
+ // are being cleaned up and may no longer be allocated.
+ //
+
+ ASSERT( SampServiceState != SampServiceInitializing );
+ if ( SampServiceState == SampServiceTerminating ) {
+ return(STATUS_INVALID_SERVER_STATE);
+ }
+
+
+ //
+ // Make sure the passed context address is (still) valid.
+ //
+
+ NtStatus = SampValidateContextAddress( Context );
+ if ( !NT_SUCCESS(NtStatus) ) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Check type
+ //
+
+ (*FoundType) = Context->ObjectType;
+ if (ExpectedType != SampUnknownObjectType) {
+ if (ExpectedType != (*FoundType)) {
+ return(STATUS_OBJECT_TYPE_MISMATCH);
+ }
+ }
+
+ //
+ // if the object is either user or group, then we need to set the
+ // transaction domain.
+
+ if ((Context->ObjectType == SampDomainObjectType) ||
+ (Context->ObjectType == SampGroupObjectType) ||
+ (Context->ObjectType == SampAliasObjectType) ||
+ (Context->ObjectType == SampUserObjectType) ) {
+
+ SampSetTransactionDomain( Context->DomainIndex );
+
+ }
+
+
+
+
+ //
+ // If the client isn't trusted, then there are a number of things
+ // that will prevent them from continuing...
+ //
+
+ // If the service isn't enabled, we allow trusted clients to continue,
+ // but reject non-trusted client lookups.
+ //
+
+ if ( !Context->TrustedClient ) {
+
+ //
+ // The SAM service must be enabled
+ //
+
+ if (SampServiceState != SampServiceEnabled) {
+ return(STATUS_INVALID_SERVER_STATE);
+ }
+
+
+ //
+ // If the access is to a USER or GROUP and the client isn't trusted
+ // then the domain must be enabled or the operation is rejected.
+ //
+
+ if ( (Context->ObjectType == SampUserObjectType) ||
+ (Context->ObjectType == SampAliasObjectType) ||
+ (Context->ObjectType == SampGroupObjectType) ) {
+ if (SampDefinedDomains[Context->DomainIndex].CurrentFixed.ServerState
+ != DomainServerEnabled) {
+ return(STATUS_INVALID_DOMAIN_STATE);
+ }
+ }
+
+ }
+
+ //
+ // Check the desired access ...
+ //
+ // There are several special cases:
+ //
+ // 1) The client is trusted. This is granted with no access check
+ // or role consistency check.
+ //
+ // 2) The caller specified 0 for desired access. This is used
+ // to close handles and is granted with no access check.
+ //
+ // 3) The role of the domain (for domain object operations) is
+ // inconsistent with the desired access.
+ //
+ //
+
+ if ( (!Context->TrustedClient) ) {
+
+ if (DesiredAccess != 0) {
+
+ if (!RtlAreAllAccessesGranted( Context->GrantedAccess, DesiredAccess)) {
+ return(STATUS_ACCESS_DENIED);
+ }
+ }
+
+ if ( (Context->ObjectType == SampDomainObjectType) ||
+ (Context->ObjectType == SampGroupObjectType) ||
+ (Context->ObjectType == SampAliasObjectType) ||
+ (Context->ObjectType == SampUserObjectType)
+ ) {
+ //
+ // The state of the domain may have changed while the caller had
+ // the object open. In this case, the granted access mask may
+ // provide a write operation, but the role of the domain no longer
+ // allows un-trusted clients to perform write operations.
+ //
+ // Yuch.
+ //
+
+ if (SampDefinedDomains[Context->DomainIndex].CurrentFixed.ServerRole
+ != DomainServerRolePrimary) {
+
+ if (RtlAreAnyAccessesGranted(
+ SampObjectInformation[ Context->ObjectType ].WriteOperations,
+ DesiredAccess) ) {
+ return(STATUS_INVALID_DOMAIN_ROLE);
+ }
+ }
+ }
+ }
+
+
+
+ //
+ // Make sure the object is still around (that is, somebody didn't delete
+ // it right out from under us).
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ if (Context->RootKey == INVALID_HANDLE_VALUE) {
+
+ switch (Context->ObjectType) {
+
+ case SampGroupObjectType:
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Reopened group handle <%wZ>,", &Context->RootName));
+ Rid = Context->TypeBody.Group.Rid;
+ break;
+
+ case SampAliasObjectType:
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Reopened alias handle <%wZ>,", &Context->RootName));
+ Rid = Context->TypeBody.Alias.Rid;
+ break;
+
+ case SampUserObjectType:
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Reopened user handle <%wZ>,", &Context->RootName));
+ Rid = Context->TypeBody.User.Rid;
+ break;
+
+ case SampDomainObjectType:
+
+ //
+ // Domain objects share the root key we keep around in the
+ // in-memory domain context for each domain
+ //
+
+
+ ASSERT(Context != SampDefinedDomains[Context->DomainIndex].Context);
+ Context->RootKey = SampDefinedDomains[Context->DomainIndex].Context->RootKey;
+ ASSERT(Context->RootKey != INVALID_HANDLE_VALUE);
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Recopied domain context handle <%wZ>, 0x%lx\n", &Context->RootName, Context->RootKey));
+ break;
+
+ case SampServerObjectType:
+
+ //
+ // Server objects share our global root key
+ //
+
+
+ Context->RootKey = SampKey;
+ ASSERT(Context->RootKey != INVALID_HANDLE_VALUE);
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Recopied server context handle <%wZ>, 0x%lx\n", &Context->RootName, Context->RootKey));
+ break;
+ }
+
+ //
+ // Go open the appropriate account key
+ //
+
+ if (Context->RootKey == INVALID_HANDLE_VALUE) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &Context->RootName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+
+ NtStatus = RtlpNtOpenKey(
+ &Context->RootKey,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ Context->RootKey = INVALID_HANDLE_VALUE;
+ }
+
+ SampDiagPrint( CONTEXT_TRACKING, (" 0x%lx, status = 0x%lx\n", Context->RootKey, NtStatus));
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Reference the context
+ //
+
+ Context->ReferenceCount ++;
+ }
+
+
+ return(NtStatus);
+
+}
+
+
+VOID
+SampReferenceContext(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This service increments a context block's reference count.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to dreference.
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+ Context->ReferenceCount++;
+
+ return;
+}
+
+
+
+NTSTATUS
+SampDeReferenceContext(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN Commit
+ )
+
+/*++
+
+Routine Description:
+
+ This service decrements a context block's reference count.
+ If the reference count drops to zero, then the MarkedForDelete
+ flag is checked. If it is true, then the context block is
+ deallocated.
+
+ The attribute buffers are always deleted.
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to de-reference.
+
+ Commit - if TRUE, the attribute buffers will be added to the RXACT.
+ Otherwise, they will just be ignored.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The service completed successfully.
+
+ Errors may be returned from SampStoreObjectAttributes().
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ BOOLEAN TrustedClient;
+
+ ASSERT( Context->ReferenceCount != 0 );
+ Context->ReferenceCount --;
+
+ TrustedClient = Context->TrustedClient;
+
+ NtStatus = STATUS_SUCCESS;
+
+ if ( Context->OnDisk != NULL ) {
+
+ //
+ // There are attribute buffers for this context. Flush them if
+ // asked to do so.
+ // Use existing open keys
+ //
+
+ if ( Commit ) {
+
+ NtStatus = SampStoreObjectAttributes(Context, TRUE);
+
+#if DBG
+ //
+ // SampFreeAttributeBuffer will assert if we try to free dirty
+ // buffers. This is generally useful, but if we're aborting
+ // (which is this case, Commit = FALSE) we don't want the assert
+ // so avoid it by pretending the buffers aren't dirty.
+ //
+
+ } else {
+
+ Context->FixedDirty = FALSE;
+ Context->VariableDirty = FALSE;
+#endif
+
+ }
+
+ //
+ // Free the buffer that was being used to hold attributes.
+ //
+
+ SampFreeAttributeBuffer( Context );
+ }
+
+ if (Context->ReferenceCount == 0) {
+
+ //
+ // ReferenceCount has dropped to 0, see if we should delete this
+ // context.
+ //
+
+ if (Context->MarkedForDelete == TRUE) {
+
+ //
+ // Close the context block's root key.
+ // Domain and server contexts contain root key
+ // handles that are shared - so don't clean-up these
+ // if they match the ones in memory.
+ //
+
+ switch (Context->ObjectType) {
+
+ case SampServerObjectType:
+
+ if ((Context->RootKey != SampKey) &&
+ (Context->RootKey != INVALID_HANDLE_VALUE)) {
+
+ IgnoreStatus = NtClose( Context->RootKey );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ break;
+
+ case SampDomainObjectType:
+
+ if ((Context->RootKey != SampDefinedDomains[Context->DomainIndex].Context->RootKey) &&
+ (Context->RootKey != INVALID_HANDLE_VALUE)) {
+
+ IgnoreStatus = NtClose( Context->RootKey );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ break;
+
+ default:
+
+ //
+ // Close the root key handle
+ //
+
+ if (Context->RootKey != INVALID_HANDLE_VALUE) {
+
+ IgnoreStatus = NtClose( Context->RootKey );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the root key name
+ //
+
+ SampFreeUnicodeString( &(Context->RootName) );
+ }
+
+
+#if SAMP_DIAGNOSTICS
+ IF_SAMP_GLOBAL( CONTEXT_TRACKING ) {
+ SampDiagPrint( CONTEXT_TRACKING, ("Deallocating ") );
+ if (Context->ObjectType == SampServerObjectType) SampDiagPrint(CONTEXT_TRACKING, ("Server "));
+ if (Context->ObjectType == SampDomainObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Domain "));
+ if (Context->ObjectType == SampGroupObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Group "));
+ if (Context->ObjectType == SampAliasObjectType) SampDiagPrint(CONTEXT_TRACKING, (" Alias "));
+ if (Context->ObjectType == SampUserObjectType) SampDiagPrint(CONTEXT_TRACKING, (" User "));
+ SampDiagPrint(CONTEXT_TRACKING, ("context : 0x%lx\n", Context ));
+ }
+#endif //SAMP_DIAGNOSTICS
+
+ MIDL_user_free( Context );
+
+
+
+ //
+ // Decrement the number of active opens
+ //
+
+ if (!TrustedClient) {
+ ASSERT( SampActiveContextCount >= 1 );
+ SampActiveContextCount -= 1;
+ }
+
+ }
+ }
+
+#if DBG
+ //
+ // Make sure a commit worked.
+ //
+
+ if (Commit) {
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_STORAGE_FAIL,
+ ("SAM: Commit failure, status: 0x%lx\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+ }
+#endif //DBG
+
+
+ return( NtStatus );
+}
+
+
+VOID
+SampInvalidateContextAddress(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This service removes a context from the set of valid contexts.
+
+ Note that we may have already removed the context. This is not an
+ error is expected to happen in the case where an object (like a user
+ or group) is deleted out from under an open handle.
+
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to be removed from the set
+ of valid contexts. The ObjectType field of this context must
+ be valid.
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+
+
+ ASSERT( (Context->ObjectType == SampUserObjectType) ||
+ (Context->ObjectType == SampGroupObjectType) ||
+ (Context->ObjectType == SampAliasObjectType) ||
+ (Context->ObjectType == SampDomainObjectType) ||
+ (Context->ObjectType == SampServerObjectType)
+ );
+
+ Context->Valid = FALSE;
+
+}
+
+
+
+VOID
+SampInvalidateGroupContexts(
+ IN ULONG Rid
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service marks all group contexts open to the specified
+ group as being invalid. This is typically done because the
+ object has been deleted while there were open handles. All
+ registry keys related to this context are closed.
+
+ This is done by walking the list of group contexts hung off
+ the permanent in-memory domain structure.
+
+ THIS IS AN IRRIVERSIBLE OPERATION.
+
+
+
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Rid - The RID of the group being invalidated.
+
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+NTSTATUS IgnoreStatus;
+PLIST_ENTRY Head, NextEntry;
+PSAMP_OBJECT NextContext;
+
+
+
+ Head = &SampDefinedDomains[SampTransactionDomainIndex].GroupContextHead;
+
+ //
+ // Walk the list of active contexts checking for RID matches
+ //
+
+ NextEntry = Head->Flink;
+
+ while (NextEntry != Head) {
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ if ( (Rid == NextContext->TypeBody.Group.Rid) &&
+ (NextContext->Valid == TRUE)
+ ) {
+ NextContext->Valid = FALSE;
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Invalidating group context 0x%lx : <%wZ>, handle = 0x%lx\n", NextContext, &NextContext->RootName, NextContext->RootKey));
+
+ if (NextContext->RootKey != INVALID_HANDLE_VALUE) {
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Closing handle 0x%lx\n", NextContext->RootKey));
+
+ IgnoreStatus = NtClose(NextContext->RootKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ NextContext->RootKey = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ return;
+}
+
+
+
+VOID
+SampInvalidateAliasContexts(
+ IN ULONG Rid
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service marks all alias contexts open to the specified
+ alias as being invalid. This is typically done because the
+ object has been deleted while there were open handles. All
+ registry keys related to this context are closed.
+
+ This is done by walking the list of alias contexts hung off
+ the permanent in-memory domain structure.
+
+ THIS IS AN IRRIVERSIBLE OPERATION.
+
+
+
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Rid - The RID of the alias being invalidated.
+
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+NTSTATUS IgnoreStatus;
+PLIST_ENTRY Head, NextEntry;
+PSAMP_OBJECT NextContext;
+
+
+
+ Head = &SampDefinedDomains[SampTransactionDomainIndex].AliasContextHead;
+
+ //
+ // Walk the list of active contexts checking for RID matches
+ //
+
+ NextEntry = Head->Flink;
+
+ while (NextEntry != Head) {
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ if ( (Rid == NextContext->TypeBody.Alias.Rid) &&
+ (NextContext->Valid == TRUE)
+ ) {
+ NextContext->Valid = FALSE;
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Invalidating alias context 0x%lx : <%wZ>, handle = 0x%lx\n", NextContext, &NextContext->RootName, NextContext->RootKey));
+
+ if (NextContext->RootKey != INVALID_HANDLE_VALUE) {
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Closing handle 0x%lx\n", NextContext->RootKey));
+
+ IgnoreStatus = NtClose(NextContext->RootKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ NextContext->RootKey = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ return;
+}
+
+
+VOID
+SampInvalidateUserContexts(
+ IN ULONG Rid
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service marks all group contexts open to the specified
+ group as being invalid. This is typically done because the
+ object has been deleted while there were open handles. All
+ registry keys related to this context are closed.
+
+
+ This is done by walking the list of group contexts hung off
+ the permanent in-memory domain structure.
+
+
+ THIS IS AN IRRIVERSIBLE OPERATION.
+
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Rid - The RID of the group being invalidated.
+
+
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+
+NTSTATUS IgnoreStatus;
+PLIST_ENTRY Head, NextEntry;
+PSAMP_OBJECT NextContext;
+
+
+
+ Head = &SampDefinedDomains[SampTransactionDomainIndex].UserContextHead;
+
+ //
+ // Walk the list of active contexts checking for RID matches
+ //
+
+ NextEntry = Head->Flink;
+
+ while (NextEntry != Head) {
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ if ( (Rid == NextContext->TypeBody.User.Rid) &&
+ (NextContext->Valid == TRUE)
+ ) {
+ NextContext->Valid = FALSE;
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Invalidating user context 0x%lx : <%wZ>, handle = 0x%lx\n", NextContext, &NextContext->RootName, NextContext->RootKey));
+
+ if (NextContext->RootKey != INVALID_HANDLE_VALUE) {
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Closing handle 0x%lx\n", NextContext->RootKey));
+
+ IgnoreStatus = NtClose(NextContext->RootKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ NextContext->RootKey = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ NextEntry = NextEntry->Flink;
+ }
+}
+
+
+VOID
+SampInvalidateContextListKeys(
+ IN PLIST_ENTRY Head,
+ IN BOOLEAN Close
+ )
+
+/*++
+
+Routine Description:
+
+ Marks all registry handles invalid in the contexts in the passed list.
+ Used after a registry hive refresh.
+
+Arguments:
+
+ Close : If TRUE the registry handles are closed before invalidation
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS IgnoreStatus;
+ PLIST_ENTRY NextEntry;
+
+ NextEntry = Head->Flink;
+
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Invalidating key for context 0x%lx : <%wZ>, handle = 0x%lx\n", NextContext, &NextContext->RootName, NextContext->RootKey));
+
+ if (Close && (NextContext->RootKey != INVALID_HANDLE_VALUE)) {
+
+ SampDiagPrint( CONTEXT_TRACKING, ("SAM: Closing handle 0x%lx\n", NextContext->RootKey));
+
+ IgnoreStatus = NtClose( NextContext->RootKey );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ NextContext->RootKey = INVALID_HANDLE_VALUE;
+
+ NextEntry = NextEntry->Flink;
+ }
+}
+
+
+
+
+#ifdef SAMP_DIAGNOSTICS
+VOID
+SampDumpContext(
+ IN PSAMP_OBJECT Context
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service prints out info on a context to debugger
+
+Arguments:
+
+ Context - a context
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+ PSTR Type;
+
+ switch (Context->ObjectType) {
+ case SampServerObjectType:
+ Type = "S";
+ break;
+ case SampDomainObjectType:
+ if (Context == SampDefinedDomains[Context->DomainIndex].Context) {
+ Type = "d";
+ } else {
+ Type = "D";
+ }
+ break;
+ case SampUserObjectType:
+ Type = "U";
+ break;
+ case SampAliasObjectType:
+ Type = "A";
+ break;
+ case SampGroupObjectType:
+ Type = "G";
+ break;
+ }
+
+ KdPrint(("%s 0x%8x %2d 0x%8x %s %s %s %wZ\n",
+ Type,
+ Context,
+ Context->ReferenceCount,
+ Context->RootKey,
+ Context->MarkedForDelete ? "D": " ",
+ Context->Valid ? " ": "NV",
+ Context->TrustedClient ? "TC": " ",
+ &Context->RootName
+ ));
+}
+
+
+VOID
+SampDumpContexts(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Prints out info on all contexts
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY NextEntry;
+ PLIST_ENTRY Head;
+ ULONG Servers;
+ ULONG Domains;
+ ULONG Domain0Users;
+ ULONG Domain0Aliases;
+ ULONG Domain0Groups;
+ ULONG Domain1Users;
+ ULONG Domain1Aliases;
+ ULONG Domain1Groups;
+
+ Domains = 0;
+ Servers = 0;
+
+ Head = &SampContextListHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ switch (NextContext->ObjectType) {
+ case SampServerObjectType:
+ (Servers)++;
+ break;
+ case SampDomainObjectType:
+ (Domains)++;
+ break;
+ default:
+ ASSERT(FALSE);
+ break;
+ }
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+
+
+ Domain0Users = 0;
+ Head = &SampDefinedDomains[0].UserContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain0Users) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampUserObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ Domain1Users = 0;
+ Head = &SampDefinedDomains[1].UserContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain1Users) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampUserObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ Domain0Groups = 0;
+ Head = &SampDefinedDomains[0].GroupContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain0Groups) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampGroupObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ Domain1Groups = 0;
+ Head = &SampDefinedDomains[1].GroupContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain1Groups) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampGroupObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ Domain0Aliases = 0;
+ Head = &SampDefinedDomains[0].AliasContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain0Aliases) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampAliasObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ Domain1Aliases = 0;
+ Head = &SampDefinedDomains[1].AliasContextHead;
+ NextEntry = Head->Flink;
+ while (NextEntry != Head) {
+
+ PSAMP_OBJECT NextContext;
+
+ (Domain1Aliases) ++;
+
+ NextContext = CONTAINING_RECORD(
+ NextEntry,
+ SAMP_OBJECT,
+ ContextListEntry
+ );
+
+ ASSERT (NextContext->ObjectType == SampAliasObjectType);
+
+ SampDumpContext(NextContext);
+
+ NextEntry = NextEntry->Flink;
+ }
+
+
+ KdPrint(("SAM: Active untrusted contexts = %d\n", SampActiveContextCount));
+ KdPrint((" Server = %4d Domain = %4d\n", Servers, Domains));
+ KdPrint((" Users0 = %4d Groups0 = %4d Aliases0 = %4d\n", Domain0Users, Domain0Aliases, Domain0Groups));
+ KdPrint((" Users1 = %4d Groups1 = %4d Aliases1 = %4d\n", Domain1Users, Domain1Aliases, Domain1Groups));
+
+
+
+}
+#endif //SAMP_DIAGNOSTICS
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service Implementations //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+VOID
+SampAddNewValidContextAddress(
+ IN PSAMP_OBJECT NewContext
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service adds the new context to the set of valid contexts.
+
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ NewContext - Pointer to the context block to be added to the set
+ of valid contexts. The ObjectType field of this context must
+ be set.
+
+
+Return Value:
+
+ None.
+
+
+
+--*/
+{
+
+ ASSERT( (NewContext->ObjectType == SampUserObjectType) ||
+ (NewContext->ObjectType == SampGroupObjectType) ||
+ (NewContext->ObjectType == SampAliasObjectType) ||
+ (NewContext->ObjectType == SampDomainObjectType) ||
+ (NewContext->ObjectType == SampServerObjectType)
+ );
+
+
+ NewContext->Valid = TRUE;
+
+
+}
+
+
+NTSTATUS
+SampValidateContextAddress(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This service checks to make sure a context is still valid.
+
+ Note that even though RPC still thinks we have a context related
+ to a SAM_HANDLE, we may, in fact, have deleted it out from under
+ the user. Since there is no way to inform RPC of this, we must
+ suffer, and wait until RPC calls us (either with a call by the client
+ or to rundown the context handle). This sucks, but there apparently
+ isn't any other way around it.
+
+
+
+ WARNING - IT IS ASSUMED THE CONTEXT WAS ONCE VALID. IT MAY HAVE
+ BEEN INVALIDATED, BUT IF YOU ARE CALLING THIS ROUTINE
+ IT BETTER STILL HAVE A NON-ZERO REFERENCE COUNT. THIS
+ COULD BE CHANGED IN THE FUTURE, BUT IT WOULD REQUIRE
+ KEEPING A LIST OF VALID DOMAINS AND PERFORMING THE BULK
+ OF THIS ROUTINE INSIDE A TRY-EXCEPT CLAUSE. YOU COULD
+ LOCATE THE CONTEXT'S DOMAIN (WHICH MIGHT ACCESS VIOLATE)
+ AND THEN MAKE SURE THAT DOMAIN IS VALID. THEN WALK THAT
+ DOMAIN'S LIST TO ENSURE THE USER OR GROUP IS VALID.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to the context block to be validated as still being
+ a valid context. The ObjectType field of this context must
+ be valid.
+
+Return Value:
+
+ STATUS_SUCCESS - The context is still valid.
+
+ STATUS_INVALID_HANDLE - The context is no longer valid and the handle
+ that caused the reference should be invalidated as well. When the
+ handle is invalidated, the context should be closed (deleted).
+
+ STATUS_NO_SUCH_CONTEXT - This value is not yet returned by this routine.
+ It may be added in the future to distinguish between an attempt to
+ use a context that has been invalidated and an attempt to use a
+ context that doesn't exist. The prior being a legitimate condition,
+ the later representing a bug-check condition.
+
+
+
+--*/
+{
+
+ ASSERT( (Context->ObjectType == SampUserObjectType) ||
+ (Context->ObjectType == SampGroupObjectType) ||
+ (Context->ObjectType == SampAliasObjectType) ||
+ (Context->ObjectType == SampDomainObjectType) ||
+ (Context->ObjectType == SampServerObjectType)
+ );
+
+
+ if (Context->Valid != TRUE) {
+ return(STATUS_INVALID_HANDLE);
+
+ }
+
+ return(STATUS_SUCCESS);
+
+}
diff --git a/private/newsam/server/display.c b/private/newsam/server/display.c
new file mode 100644
index 000000000..22bd3a655
--- /dev/null
+++ b/private/newsam/server/display.c
@@ -0,0 +1,5840 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ display.c
+
+Abstract:
+
+ This file contains services for maintaining the cached display
+ information.
+
+ The information is stored in multiple tables because there are
+ multiple formats it must be returned in. The tables maintained
+ include:
+
+ AccountsByRid - includes all user and global group accounts
+ by RID. Aliases may be added to this list at some time
+ in the future.
+
+ NormalUsersByName - Normal user accounts, sorted by name.
+
+ MachinesByName - Machine user accounts, sorted by name.
+
+ InterDomainByName - Interdomain trust accounts, sorted by
+ name.
+
+ GroupsByName - Global group accounts, sorted by name.
+
+
+ Any time an entry is placed in or removed from one of "ByName"
+ tables, it is also placed in or removed from the "ByRid" table.
+
+ User and machine accounts are added to the display cache in one
+ operation. So, there is a single boolean flag indicating whether
+ or not these tables are valid. The groups are maintained in a
+ separate table, and so there is another flag indicating whether
+ or not that table is valid.
+
+ The Rid table is only valid if both the group table and the
+ user/machine tables are valid.
+
+
+
+Author:
+
+ Dave Chalmers (Davidc) 1-April-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampCreateDisplayInformation (
+ DOMAIN_DISPLAY_INFORMATION DisplayType
+ );
+
+
+VOID
+SampDeleteDisplayInformation (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ );
+
+NTSTATUS
+SampRetrieveDisplayInfoFromDisk(
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ );
+
+NTSTATUS
+SampAddDisplayAccount (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ );
+
+NTSTATUS
+SampDeleteDisplayAccount (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ );
+
+NTSTATUS
+SampUpdateDisplayAccount(
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ );
+
+NTSTATUS
+SampTallyTableStatistics (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ );
+
+NTSTATUS
+SampEmptyGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ BOOLEAN FreeElements
+ );
+
+PVOID
+SampGenericTable2Allocate (
+ CLONG BufferSize
+ );
+
+VOID
+SampGenericTable2Free (
+ PVOID Buffer
+ );
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareUserNodeByName (
+ PVOID Node1,
+ PVOID Node2
+ );
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareMachineNodeByName ( // Also used for Interdomain trust accounts
+ PVOID Node1,
+ PVOID Node2
+ );
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareGroupNodeByName (
+ PVOID Node1,
+ PVOID Node2
+ );
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareNodeByRid (
+ PVOID Node1,
+ PVOID Node2
+ );
+
+NTSTATUS
+SampInitializeUserInfo(
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_USER *UserInfo,
+ BOOLEAN CopyData
+ );
+
+NTSTATUS
+SampInitializeMachineInfo( // Also used for Interdomain trust accounts
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_MACHINE *MachineInfo,
+ BOOLEAN CopyData
+ );
+
+NTSTATUS
+SampInitializeGroupInfo(
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_GROUP *GroupInfo,
+ BOOLEAN CopyData
+ );
+
+NTSTATUS
+SampDuplicateUserInfo(
+ PDOMAIN_DISPLAY_USER Destination,
+ PDOMAIN_DISPLAY_USER Source,
+ ULONG Index
+ );
+
+NTSTATUS
+SampDuplicateMachineInfo( // Also used for Interdomain trust accounts
+ PDOMAIN_DISPLAY_MACHINE Destination,
+ PDOMAIN_DISPLAY_MACHINE Source,
+ ULONG Index
+
+ );
+
+NTSTATUS
+SampDuplicateGroupInfo(
+ PDOMAIN_DISPLAY_GROUP Destination,
+ PDOMAIN_DISPLAY_GROUP Source,
+ ULONG Index
+ );
+
+NTSTATUS
+SampDuplicateOemUserInfo(
+ PDOMAIN_DISPLAY_OEM_USER Destination,
+ PDOMAIN_DISPLAY_USER Source,
+ ULONG Index
+ );
+
+NTSTATUS
+SampDuplicateOemGroupInfo(
+ PDOMAIN_DISPLAY_OEM_GROUP Destination,
+ PDOMAIN_DISPLAY_GROUP Source,
+ ULONG Index
+ );
+
+
+VOID
+SampFreeUserInfo(
+ PDOMAIN_DISPLAY_USER UserInfo
+ );
+
+VOID
+SampFreeMachineInfo(
+ PDOMAIN_DISPLAY_MACHINE MachineInfo
+ );
+
+VOID
+SampFreeGroupInfo(
+ PDOMAIN_DISPLAY_GROUP GroupInfo
+ );
+
+VOID
+SampFreeOemUserInfo(
+ PDOMAIN_DISPLAY_OEM_USER UserInfo
+ );
+
+VOID
+SampFreeOemGroupInfo(
+ PDOMAIN_DISPLAY_OEM_GROUP GroupInfo
+ );
+
+VOID
+SampSwapUserInfo(
+ PDOMAIN_DISPLAY_USER Info1,
+ PDOMAIN_DISPLAY_USER Info2
+ );
+
+VOID
+SampSwapMachineInfo( // Also used for Interdomain trust accounts
+ PDOMAIN_DISPLAY_MACHINE Info1,
+ PDOMAIN_DISPLAY_MACHINE Info2
+ );
+
+VOID
+SampSwapGroupInfo(
+ PDOMAIN_DISPLAY_GROUP Info1,
+ PDOMAIN_DISPLAY_GROUP Info2
+ );
+
+ULONG
+SampBytesRequiredUserNode (
+ PDOMAIN_DISPLAY_USER Node
+ );
+
+ULONG
+SampBytesRequiredMachineNode ( // Also used for Interdomain trust accounts
+ PDOMAIN_DISPLAY_MACHINE Node
+ );
+
+ULONG
+SampBytesRequiredGroupNode (
+ PDOMAIN_DISPLAY_GROUP Node
+ );
+
+ULONG
+SampBytesRequiredOemUserNode (
+ PDOMAIN_DISPLAY_OEM_USER Node
+ );
+
+ULONG
+SampBytesRequiredOemGroupNode (
+ PDOMAIN_DISPLAY_OEM_GROUP Node
+ );
+
+
+VOID
+SampDisplayDiagnostic( VOID );
+
+VOID
+SampDisplayDiagEnumRids( VOID );
+
+
+
+LONG
+SampCompareDisplayStrings(
+ IN PUNICODE_STRING String1,
+ IN PUNICODE_STRING String2,
+ IN BOOLEAN IgnoreCase
+ );
+
+
+//
+// Macros for deciding whether an account is:
+//
+// A normal user account
+//
+// A machine account
+//
+// An Interdomain trust account
+//
+// Included in the display cache
+//
+//
+
+#define USER_ACCOUNT(AccountControl) ((AccountControl & \
+ (USER_NORMAL_ACCOUNT | \
+ USER_TEMP_DUPLICATE_ACCOUNT)) != 0)
+
+#define MACHINE_ACCOUNT(AccountControl) ((AccountControl & \
+ (USER_WORKSTATION_TRUST_ACCOUNT | \
+ USER_SERVER_TRUST_ACCOUNT)) != 0)
+
+
+#define INTERDOMAIN_ACCOUNT(AccountControl) (((AccountControl) & \
+ (USER_INTERDOMAIN_TRUST_ACCOUNT)) != 0)
+
+
+#define DISPLAY_ACCOUNT(AccountControl) (USER_ACCOUNT(AccountControl) || \
+ MACHINE_ACCOUNT(AccountControl) || \
+ INTERDOMAIN_ACCOUNT(AccountControl))
+
+
+
+//
+// Test to see if Rid table is valid
+//
+// BOOLEAN
+// SampRidTableValid( IN ULONG DomainIndex )
+//
+
+#define SampRidTableValid(DI) ( \
+ (SampDefinedDomains[DI].DisplayInformation.UserAndMachineTablesValid) && \
+ (SampDefinedDomains[DI].DisplayInformation.GroupTableValid) \
+ )
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Private data types //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+//
+// All entries in the display cache are expected to start with this
+// data structure.
+//
+
+typedef struct _SAMP_DISPLAY_ENTRY_HEADER {
+
+ //
+ // The index field plays two roles. Within the generic table,
+ // it is used to indicate which type of account this is. The
+ // valid types are: SAM_USER_ACCOUNT, SAM_GLOBAL_GROUP_ACCOUNT,
+ // or SAM_LOCAL_GROUP_ACCOUNT.
+ //
+ // Otherwise, this field is filled in just before being returned
+ // to query and other client calls.
+ //
+
+
+ ULONG Index;
+
+
+ //
+ // The RID of the account
+ //
+
+ ULONG Rid;
+
+} SAMP_DISPLAY_ENTRY_HEADER, *PSAMP_DISPLAY_ENTRY_HEADER;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Module-wide variables //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+LCID SampSystemDefaultLCID;
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// RPC exported routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamrQueryDisplayInformation (
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ IN ULONG EntriesRequested,
+ IN ULONG PreferredMaximumLength,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Thin wrapper around SamrQueryDisplayInformation3().
+
+ Provided for compatibility with down-level clients.
+
+--*/
+{
+ return( SamrQueryDisplayInformation3(
+ DomainHandle,
+ DisplayInformation,
+ Index,
+ EntriesRequested,
+ PreferredMaximumLength,
+ TotalAvailable,
+ TotalReturned,
+ Buffer
+ ) );
+}
+
+NTSTATUS
+SamrQueryDisplayInformation2 (
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ IN ULONG EntriesRequested,
+ IN ULONG PreferredMaximumLength,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Thin wrapper around SamrQueryDisplayInformation3().
+
+ Provided for compatibility with down-level clients.
+
+--*/
+{
+ return( SamrQueryDisplayInformation3(
+ DomainHandle,
+ DisplayInformation,
+ Index,
+ EntriesRequested,
+ PreferredMaximumLength,
+ TotalAvailable,
+ TotalReturned,
+ Buffer
+ ) );
+}
+
+NTSTATUS
+SamrQueryDisplayInformation3 (
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN ULONG Index,
+ IN ULONG EntriesRequested,
+ IN ULONG PreferredMaximumLength,
+ OUT PULONG TotalAvailable,
+ OUT PULONG TotalReturned,
+ OUT PSAMPR_DISPLAY_INFO_BUFFER Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine provides fast return of information commonly
+ needed to be displayed in user interfaces.
+
+ NT User Interface has a requirement for quick enumeration of SAM
+ accounts for display in list boxes. (Replication has similar but
+ broader requirements.)
+
+ The netui listboxes all contain similar information. e.g:
+
+ o AccountControl, the bits that identify the account type,
+ eg, HOME, REMOTE, SERVER, WORKSTATION, etc.
+
+ o Logon name (machine name for computers)
+
+ o Full name (not used for computers)
+
+ o Comment (admin comment for users)
+
+ SAM maintains this data locally in two sorted indexed cached
+ lists identified by infolevels.
+
+ o DomainDisplayUser: HOME and REMOTE user accounts only
+
+ o DomainDisplayMachine: SERVER and WORKSTATION accounts only
+
+ Note that trust accounts, groups, and aliases are not in either of
+ these lists.
+
+
+ Added for NT1.0A -
+
+ o Group enumeration has been added in NT1.0A
+ with the following characteristic:
+
+ We did not change the RPC interface ID. This allows
+ callers to continue to call down-level servers. However,
+ down-level servers will return an error if they passed
+ this information level.
+
+ o OEM string info levels were added for jimh (Chicago). These
+ info levels dramatically reduce the memory needed to query
+ the limited information that Chicago is interested in.
+
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which information is to be enumerated.
+
+ Index - The index of the first entry to be retrieved.
+
+ PreferedMaximumLength - A recommended upper limit to the number of
+ bytes to be returned. The returned information is allocated by
+ this routine.
+
+ TotalAvailable - Total number of bytes availabe in the specified info
+ class.
+
+ TotalReturned - Number of bytes actually returned for this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+ ReturnedEntryCount - Number of entries returned by this call. Zero
+ indicates there are no entries with an index as large as that
+ specified.
+
+
+ Buffer - Receives a pointer to a buffer containing a (possibly)
+ sorted list of the requested information. This buffer is
+ allocated by this routine and contains the following
+ structure:
+
+
+ DomainDisplayMachine --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_USER. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_USER structures.
+
+ DomainDisplayMachine --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_MACHINE. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_MACHINE structures.
+
+ DomainDisplayGroup --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_GROUP. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_GROUP structures.
+
+ DomainDisplayOemUser --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_OEM_USER. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_OEM_user structures.
+
+ DomainDisplayOemGroup --> An array of ReturnedEntryCount elements
+ of type DOMAIN_DISPLAY_OEM_GROUP. This is
+ followed by the bodies of the various
+ strings pointed to from within the
+ DOMAIN_DISPLAY_OEM_GROUP structures.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+ STATUS_INVALID_INFO_CLASS - The requested class of information
+ is not legitimate for this service.
+
+
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ PSAMP_OBJECT
+ DomainContext;
+
+ SAMP_OBJECT_TYPE
+ FoundType;
+
+ PSAMP_DEFINED_DOMAINS
+ Domain;
+
+ PSAMPR_DOMAIN_DISPLAY_USER
+ UserElement;
+
+ PSAMPR_DOMAIN_DISPLAY_MACHINE
+ MachineElement;
+
+ PSAMPR_DOMAIN_DISPLAY_GROUP
+ GroupElement;
+
+
+ ULONG
+ ReturnedBytes = 0,
+ ReturnedItems = 0;
+
+ PVOID
+ RestartKey;
+
+ //
+ // Prepare for failure
+ //
+
+ *TotalAvailable = 0;
+ *TotalReturned = 0;
+
+ switch (DisplayInformation) {
+ case DomainDisplayUser:
+ Buffer->UserInformation.EntriesRead = 0;
+ Buffer->UserInformation.Buffer = NULL;
+ break;
+
+ case DomainDisplayMachine:
+ Buffer->MachineInformation.EntriesRead = 0;
+ Buffer->MachineInformation.Buffer = NULL;
+ break;
+
+ case DomainDisplayGroup:
+ Buffer->GroupInformation.EntriesRead = 0;
+ Buffer->GroupInformation.Buffer = NULL;
+ break;
+
+ case DomainDisplayOemUser:
+ Buffer->OemUserInformation.EntriesRead = 0;
+ Buffer->OemUserInformation.Buffer = NULL;
+ break;
+
+ case DomainDisplayOemGroup:
+ Buffer->OemGroupInformation.EntriesRead = 0;
+ Buffer->OemGroupInformation.Buffer = NULL;
+ break;
+
+ default:
+ return(STATUS_INVALID_INFO_CLASS);
+ }
+
+ //
+ // If they don't want anything, that's what they'll get
+ //
+
+ if (EntriesRequested == 0) {
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Make sure we don't try to allocate too much memory on
+ // the user's behalf
+ //
+
+ if (EntriesRequested > 5000) {
+ EntriesRequested = 5000;
+ }
+
+ //
+ // Grab the read lock
+ //
+
+ SampAcquireReadLock();
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LIST_ACCOUNTS,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+
+
+ //
+ // Set up the common loop statistics
+ //
+
+ ReturnedBytes = 0;
+ ReturnedItems = 0;
+
+
+ switch (DisplayInformation) {
+
+ case DomainDisplayUser:
+
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DomainDisplayUser);
+
+ //
+ // Set the Restart Key from the passed in index
+ //
+
+ UserElement = RtlRestartKeyByIndexGenericTable2(
+ &Domain->DisplayInformation.UserTable,
+ Index,
+ &RestartKey
+ );
+
+ if (UserElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ Buffer->GroupInformation.EntriesRead = 0;
+ *TotalReturned = 0;
+ *TotalAvailable = 0; // Not supported for this info level
+ break; // out of switch
+ }
+
+
+ //
+ // Allocate space for array of elements
+ //
+
+ Buffer->UserInformation.Buffer = MIDL_user_allocate(
+ EntriesRequested * sizeof(SAMPR_DOMAIN_DISPLAY_USER));
+
+ if (Buffer->UserInformation.Buffer == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ break; // out of switch
+ }
+
+ //
+ // Prepare default return value
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // Increment the index value for assignment in our return
+ // buffer
+ //
+
+ Index++;
+
+ do {
+ NTSTATUS TempStatus;
+
+ //
+ // Store a copy of this element in the return buffer.
+ //
+
+ TempStatus = SampDuplicateUserInfo(
+ (PDOMAIN_DISPLAY_USER)
+ &(Buffer->UserInformation.Buffer[ReturnedItems]),
+ (PDOMAIN_DISPLAY_USER)UserElement,
+ Index);
+ Index++;
+
+ if (!NT_SUCCESS(TempStatus)) {
+
+ //
+ // Free up everything we've allocated so far
+ //
+
+ while(ReturnedItems > 0) {
+ ReturnedItems --;
+ SampFreeUserInfo((PDOMAIN_DISPLAY_USER)
+ &(Buffer->UserInformation.Buffer[ReturnedItems]));
+ }
+
+ MIDL_user_free(Buffer->UserInformation.Buffer);
+ Buffer->UserInformation.Buffer = NULL;
+
+ NtStatus = TempStatus;
+ break; // out of do loop
+ }
+
+ //
+ // Update loop statistics
+ //
+
+ ReturnedBytes += SampBytesRequiredUserNode(
+ (PDOMAIN_DISPLAY_USER)UserElement);
+ ReturnedItems ++;
+
+ //
+ // Go find the next element
+ //
+
+ UserElement = RtlEnumerateGenericTable2(
+ &Domain->DisplayInformation.UserTable,
+ &RestartKey
+ );
+
+ if (UserElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ break; // out of do loop
+ }
+
+
+ } while ( (ReturnedBytes < PreferredMaximumLength) &&
+ (ReturnedItems < EntriesRequested) );
+
+ //
+ // Update output parameters
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ Buffer->UserInformation.EntriesRead = ReturnedItems;
+ *TotalReturned = ReturnedBytes;
+ *TotalAvailable = Domain->DisplayInformation.TotalBytesInUserTable;
+ }
+
+ break; // out of switch
+
+
+ case DomainDisplayMachine:
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DomainDisplayMachine);
+
+ //
+ // Set the Restart Key from the passed in index
+ //
+
+ MachineElement = RtlRestartKeyByIndexGenericTable2(
+ &Domain->DisplayInformation.MachineTable,
+ Index,
+ &RestartKey
+ );
+
+ if (MachineElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ Buffer->GroupInformation.EntriesRead = 0;
+ *TotalReturned = 0;
+ *TotalAvailable = 0; // Not supported for this info level
+ break; // out of switch
+ }
+
+ //
+ // Allocate space for array of elements
+ //
+
+ Buffer->MachineInformation.Buffer = MIDL_user_allocate(
+ EntriesRequested * sizeof(SAMPR_DOMAIN_DISPLAY_MACHINE));
+
+ if (Buffer->MachineInformation.Buffer == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ break; // out of switch
+ }
+
+ //
+ // Prepare default return value
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // Increment the index value for assignment in our return
+ // buffer
+ //
+
+ Index++;
+
+ do {
+ NTSTATUS TempStatus;
+
+ //
+ // Store a copy of this element in the return buffer.
+ //
+
+ TempStatus = SampDuplicateMachineInfo(
+ (PDOMAIN_DISPLAY_MACHINE)
+ &(Buffer->MachineInformation.Buffer[ReturnedItems]),
+ (PDOMAIN_DISPLAY_MACHINE)MachineElement,
+ Index);
+ Index++;
+
+ if (!NT_SUCCESS(TempStatus)) {
+
+ //
+ // Free up everything we've allocated so far
+ //
+
+ while(ReturnedItems > 0) {
+ ReturnedItems--;
+ SampFreeMachineInfo((PDOMAIN_DISPLAY_MACHINE)
+ &(Buffer->MachineInformation.Buffer[ReturnedItems]));
+ }
+
+ MIDL_user_free(Buffer->MachineInformation.Buffer);
+ Buffer->MachineInformation.Buffer = NULL;
+
+ NtStatus = TempStatus;
+ break; // out of do loop
+ }
+
+ //
+ // Update loop statistics
+ //
+
+ ReturnedBytes += SampBytesRequiredMachineNode(
+ (PDOMAIN_DISPLAY_MACHINE)MachineElement);
+ ReturnedItems ++;
+
+ //
+ // Go find the next element
+ //
+
+ MachineElement = RtlEnumerateGenericTable2(
+ &Domain->DisplayInformation.MachineTable,
+ &RestartKey
+ );
+
+ if (MachineElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ break; // out of do loop
+ }
+
+
+ } while ( (ReturnedBytes < PreferredMaximumLength) &&
+ (ReturnedItems < EntriesRequested) );
+
+ //
+ // Update output parameters
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ Buffer->MachineInformation.EntriesRead = ReturnedItems;
+ *TotalReturned = ReturnedBytes;
+ *TotalAvailable = Domain->DisplayInformation.TotalBytesInMachineTable;
+ }
+
+ break; // out of switch
+
+
+ case DomainDisplayGroup:
+
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DomainDisplayGroup);
+
+ //
+ // Set the Restart Key from the passed in index
+ //
+
+ GroupElement = RtlRestartKeyByIndexGenericTable2(
+ &Domain->DisplayInformation.GroupTable,
+ Index,
+ &RestartKey
+ );
+
+ if (GroupElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ Buffer->GroupInformation.EntriesRead = 0;
+ *TotalReturned = 0;
+ *TotalAvailable = 0; // Not supported for this info level
+ break; // out of switch
+ }
+
+ //
+ // Allocate space for array of elements
+ //
+
+ Buffer->GroupInformation.Buffer = MIDL_user_allocate(
+ EntriesRequested * sizeof(SAMPR_DOMAIN_DISPLAY_GROUP));
+
+ if (Buffer->GroupInformation.Buffer == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ break; // out of switch
+ }
+
+ //
+ // Prepare default return value
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // Increment the index value for assignment in our return
+ // buffer
+ //
+
+ Index++;
+
+ do {
+ NTSTATUS TempStatus;
+
+ //
+ // Store a copy of this element in the return buffer.
+ //
+
+ TempStatus = SampDuplicateGroupInfo(
+ (PDOMAIN_DISPLAY_GROUP)
+ &(Buffer->GroupInformation.Buffer[ReturnedItems]),
+ (PDOMAIN_DISPLAY_GROUP)GroupElement,
+ Index);
+ Index++;
+
+ if (!NT_SUCCESS(TempStatus)) {
+
+ //
+ // Free up everything we've allocated so far
+ //
+
+ while(ReturnedItems > 0) {
+ ReturnedItems--;
+ SampFreeGroupInfo((PDOMAIN_DISPLAY_GROUP)
+ &(Buffer->GroupInformation.Buffer[ReturnedItems]));
+ }
+
+ MIDL_user_free(Buffer->GroupInformation.Buffer);
+ Buffer->GroupInformation.Buffer = NULL;
+
+ NtStatus = TempStatus;
+ break; // out of do loop
+ }
+
+ //
+ // Update loop statistics
+ //
+
+ ReturnedBytes += SampBytesRequiredGroupNode(
+ (PDOMAIN_DISPLAY_GROUP)GroupElement);
+ ReturnedItems ++;
+
+ //
+ // Go find the next element
+ //
+
+ GroupElement = RtlEnumerateGenericTable2(
+ &Domain->DisplayInformation.GroupTable,
+ &RestartKey
+ );
+
+ if (GroupElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ break; // out of do loop
+ }
+
+
+ } while ( (ReturnedBytes < PreferredMaximumLength) &&
+ (ReturnedItems < EntriesRequested) );
+
+ //
+ // Update output parameters
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ Buffer->GroupInformation.EntriesRead = ReturnedItems;
+ *TotalReturned = ReturnedBytes;
+ *TotalAvailable = Domain->DisplayInformation.TotalBytesInGroupTable;
+ }
+
+ break; // out of switch
+
+ case DomainDisplayOemUser:
+
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DomainDisplayUser);
+
+ //
+ // Set the Restart Key from the passed in index
+ //
+
+ UserElement = RtlRestartKeyByIndexGenericTable2(
+ &Domain->DisplayInformation.UserTable,
+ Index,
+ &RestartKey
+ );
+
+ if (UserElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ Buffer->GroupInformation.EntriesRead = 0;
+ *TotalReturned = 0;
+ *TotalAvailable = 0; // Not supported for this info level
+ break; // out of switch
+ }
+
+
+ //
+ // Allocate space for array of elements
+ //
+
+ Buffer->UserInformation.Buffer = MIDL_user_allocate(
+ EntriesRequested * sizeof(SAMPR_DOMAIN_DISPLAY_OEM_USER));
+
+ if (Buffer->OemUserInformation.Buffer == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ break; // out of switch
+ }
+
+ //
+ // Prepare default return value
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // Increment the index value for assignment in our return
+ // buffer
+ //
+
+ Index++;
+
+ do {
+ NTSTATUS TempStatus;
+
+ //
+ // Store a copy of this element in the return buffer.
+ //
+
+ TempStatus = SampDuplicateOemUserInfo(
+ (PDOMAIN_DISPLAY_OEM_USER)
+ &(Buffer->OemUserInformation.Buffer[ReturnedItems]),
+ (PDOMAIN_DISPLAY_USER)UserElement,
+ Index);
+ Index++;
+
+ if (!NT_SUCCESS(TempStatus)) {
+
+ //
+ // Free up everything we've allocated so far
+ //
+
+ while(ReturnedItems > 0) {
+ ReturnedItems --;
+ SampFreeOemUserInfo((PDOMAIN_DISPLAY_OEM_USER)
+ &(Buffer->UserInformation.Buffer[ReturnedItems]));
+ }
+
+ MIDL_user_free(Buffer->OemUserInformation.Buffer);
+ Buffer->OemUserInformation.Buffer = NULL;
+
+ NtStatus = TempStatus;
+ break; // out of do loop
+ }
+
+ //
+ // Update loop statistics
+ //
+
+ ReturnedBytes +=
+ SampBytesRequiredOemUserNode(
+ (PDOMAIN_DISPLAY_OEM_USER)
+ &(Buffer->OemUserInformation.Buffer[ReturnedItems]));
+ ReturnedItems ++;
+
+ //
+ // Go find the next element
+ //
+
+ UserElement = RtlEnumerateGenericTable2(
+ &Domain->DisplayInformation.UserTable,
+ &RestartKey
+ );
+
+ if (UserElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ break; // out of do loop
+ }
+
+
+ } while ( (ReturnedBytes < PreferredMaximumLength) &&
+ (ReturnedItems < EntriesRequested) );
+
+ //
+ // Update output parameters
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ Buffer->UserInformation.EntriesRead = ReturnedItems;
+ *TotalReturned = ReturnedBytes;
+ *TotalAvailable = 0; // Not supported for this info level
+ }
+
+ break; // out of switch
+
+
+ case DomainDisplayOemGroup:
+
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DomainDisplayGroup);
+
+ //
+ // Set the Restart Key from the passed in index
+ //
+
+ GroupElement = RtlRestartKeyByIndexGenericTable2(
+ &Domain->DisplayInformation.GroupTable,
+ Index,
+ &RestartKey
+ );
+
+ if (GroupElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ Buffer->GroupInformation.EntriesRead = 0;
+ *TotalReturned = 0;
+ *TotalAvailable = 0; // Not supported for this info level
+ break; // out of switch
+ }
+
+
+ //
+ // Allocate space for array of elements
+ //
+
+ Buffer->GroupInformation.Buffer = MIDL_user_allocate(
+ EntriesRequested * sizeof(SAMPR_DOMAIN_DISPLAY_OEM_GROUP));
+
+ if (Buffer->OemGroupInformation.Buffer == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ break; // out of switch
+ }
+
+ //
+ // Prepare default return value
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+ //
+ // Increment the index value for assignment in our return
+ // buffer
+ //
+
+ Index++;
+
+ do {
+ NTSTATUS TempStatus;
+
+ //
+ // Store a copy of this element in the return buffer.
+ //
+
+ TempStatus = SampDuplicateOemGroupInfo(
+ (PDOMAIN_DISPLAY_OEM_GROUP)
+ &(Buffer->OemGroupInformation.Buffer[ReturnedItems]),
+ (PDOMAIN_DISPLAY_GROUP)GroupElement,
+ Index);
+ Index++;
+
+ if (!NT_SUCCESS(TempStatus)) {
+
+ //
+ // Free up everything we've allocated so far
+ //
+
+ while(ReturnedItems > 0) {
+ ReturnedItems --;
+ SampFreeOemGroupInfo((PDOMAIN_DISPLAY_OEM_GROUP)
+ &(Buffer->GroupInformation.Buffer[ReturnedItems]));
+ }
+
+ MIDL_user_free(Buffer->OemGroupInformation.Buffer);
+ Buffer->OemGroupInformation.Buffer = NULL;
+
+ NtStatus = TempStatus;
+ break; // out of do loop
+ }
+
+ //
+ // Update loop statistics
+ //
+
+ ReturnedBytes +=
+ SampBytesRequiredOemGroupNode(
+ (PDOMAIN_DISPLAY_OEM_GROUP)
+ &(Buffer->OemGroupInformation.Buffer[ReturnedItems]));
+ ReturnedItems ++;
+
+ //
+ // Go find the next element
+ //
+
+ GroupElement = RtlEnumerateGenericTable2(
+ &Domain->DisplayInformation.GroupTable,
+ &RestartKey
+ );
+
+ if (GroupElement == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ break; // out of do loop
+ }
+
+
+ } while ( (ReturnedBytes < PreferredMaximumLength) &&
+ (ReturnedItems < EntriesRequested) );
+
+ //
+ // Update output parameters
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ Buffer->GroupInformation.EntriesRead = ReturnedItems;
+ *TotalReturned = ReturnedBytes;
+ *TotalAvailable = 0; // Not supported for this info level
+ }
+
+ break; // out of switch
+
+ }
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrGetDisplayEnumerationIndex (
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN PRPC_UNICODE_STRING Prefix,
+ OUT PULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This wrapper around SamrGetDisplayEnumerationIndex2().
+
+ Provided for compatibility with down-level clients.
+
+
+--*/
+{
+
+ return(SamrGetDisplayEnumerationIndex2( DomainHandle,
+ DisplayInformation,
+ Prefix,
+ Index
+ ) );
+}
+
+NTSTATUS
+SamrGetDisplayEnumerationIndex2 (
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ IN PRPC_UNICODE_STRING Prefix,
+ OUT PULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the index of the entry which alphabetically
+ immediatly preceeds a specified prefix. If no such entry exists,
+ then zero is returned as the index.
+
+Parameters:
+
+ DomainHandle - A handle to an open domain for DOMAIN_LIST_ACCOUNTS.
+
+ DisplayInformation - Indicates which sorted information class is
+ to be searched.
+
+ Prefix - The prefix to compare.
+
+ Index - Receives the index of the entry of the information class
+ with a LogonName (or MachineName) which immediatly preceeds the
+ provided prefix string. If there are no elements which preceed
+ the prefix, then zero is returned.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ the necessary access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened Domain object.
+
+ STATUS_NO_MORE_ENTRIES - There are no entries for this information class.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ PSAMP_OBJECT
+ DomainContext;
+
+ SAMP_OBJECT_TYPE
+ FoundType;
+
+ PSAMP_DEFINED_DOMAINS
+ Domain;
+
+ PRTL_GENERIC_TABLE2
+ Table;
+
+ DOMAIN_DISPLAY_USER
+ UserElement;
+
+ DOMAIN_DISPLAY_MACHINE
+ MachineElement;
+
+ DOMAIN_DISPLAY_GROUP
+ GroupElement;
+
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ PRTL_GENERIC_2_COMPARE_ROUTINE
+ CompareRoutine;
+
+ PVOID
+ Element,
+ NextElement,
+ RestartKey;
+
+ ULONG
+ CurrentIndex;
+
+
+ //
+ // Check the information class
+ //
+
+ if ((DisplayInformation != DomainDisplayUser) &&
+ (DisplayInformation != DomainDisplayMachine) &&
+ (DisplayInformation != DomainDisplayGroup)
+ ) {
+
+ return(STATUS_INVALID_INFO_CLASS);
+ }
+
+
+ //
+ // Grab the read lock
+ //
+
+ SampAcquireReadLock();
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LIST_ACCOUNTS,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ //
+ // Set default return value
+ //
+
+ (*Index) = 0;
+
+ //
+ // Recreate our cached data if necessary
+ //
+
+ NtStatus = SampCreateDisplayInformation(DisplayInformation);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Set up
+ // The table to search,
+ // The comparison routine to use,
+ // An appropriate element for the search.
+ //
+
+ switch (DisplayInformation) {
+
+ case DomainDisplayUser:
+
+ Table = &Domain->DisplayInformation.UserTable;
+ CompareRoutine = SampCompareUserNodeByName;
+
+ Element = (PVOID)&UserElement;
+ UserElement.LogonName = *(PUNICODE_STRING)Prefix;
+
+ break; // out of switch
+
+ case DomainDisplayMachine:
+
+ Table = &Domain->DisplayInformation.MachineTable;
+ CompareRoutine = SampCompareMachineNodeByName;
+
+ Element = (PVOID)&MachineElement;
+ MachineElement.Machine = *(PUNICODE_STRING)Prefix;
+
+ break; // out of switch
+
+
+ case DomainDisplayGroup:
+
+ Table = &Domain->DisplayInformation.GroupTable;
+ CompareRoutine = SampCompareGroupNodeByName;
+
+ Element = (PVOID)&GroupElement;
+ GroupElement.Group = *(PUNICODE_STRING)Prefix;
+
+ break; // out of switch
+ }
+
+
+ if (RtlIsGenericTable2Empty(Table)) {
+
+ NtStatus = STATUS_NO_MORE_ENTRIES;
+
+ } else {
+
+ //
+ // Now compare each entry until we find the one asked
+ // for.
+ //
+
+ CurrentIndex = 0;
+
+ RestartKey = NULL;
+ for (NextElement = RtlEnumerateGenericTable2(Table, &RestartKey);
+ NextElement != NULL;
+ NextElement = RtlEnumerateGenericTable2(Table, &RestartKey)) {
+
+ //
+ // Compare with passed in element
+ //
+
+ CompareResult = (*CompareRoutine)( NextElement, Element );
+ if (CompareResult != GenericLessThan) {
+ break; // break out of for loop
+ }
+
+ CurrentIndex++;
+ }
+
+ //
+ // CurrentIndex has the return value in it.
+ //
+
+ ASSERT( CurrentIndex <= RtlNumberElementsGenericTable2(Table) );
+
+ (*Index) = CurrentIndex;
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ return(NtStatus);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines available to trusted clients in SAM's process //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamIEnumerateAccountRids(
+ IN SAMPR_HANDLE DomainHandle,
+ IN ULONG AccountTypesMask,
+ IN ULONG StartingRid,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG ReturnCount,
+ OUT PULONG *AccountRids
+ )
+
+/*++
+
+Routine Description:
+
+ Provide a list of account RIDs. The caller may ask for one or
+ more types of account rids in a single call.
+
+ The returned rids are in ascending value order.
+
+ WARNING - This routine is only callable by trusted clients.
+ Therefore, parameter checking is only performed
+ in checked-build systems.
+
+Parameters:
+
+ DomainHandle - handle to the domain whose accounts are to be
+ enumerated.
+
+ AccountTypesMask - Mask indicating which types of accounts
+ the caller wants enumerated. These included:
+
+ SAM_USER_ACCOUNT
+ SAM_GLOBAL_GROUP_ACCOUNT
+ SAM_LOCAL_GROUP_ACCOUNT (not yet supported)
+
+ StartingRid - A rid that is less than the lowest value rid to be
+ included in the enumeration.
+
+
+ PreferedMaximumLength - Provides a restriction on how much memory
+ may be returned in this call. This is not a hard upper limit,
+ but serves as a guideline.
+
+ ReturnCount - Receives a count of the number of rids returned.
+
+ AccountRids - Receives a pointer to an array of rids. If
+ ReturnCount is zero, then this will be returned as NULL.
+ Otherwise, it will point to an array containing ReturnCount
+ rids.
+
+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_INVALID_INFO_CLASS - The specified AccountTypesMask contained
+ unknown or unsupported account types.
+
+ STATUS_NO_MEMORY - Could not allocate pool to complete the call.
+
+--*/
+{
+
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ PSAMP_OBJECT
+ DomainContext;
+
+ SAMP_OBJECT_TYPE
+ FoundType;
+
+ PSAMP_DEFINED_DOMAINS
+ Domain;
+
+ PRTL_GENERIC_TABLE2
+ Table;
+
+ ULONG
+ MaxEntries,
+ Count,
+ AccountType;
+
+ PVOID
+ RestartKey;
+
+ PSAMP_DISPLAY_ENTRY_HEADER
+ Element;
+
+ SAMP_DISPLAY_ENTRY_HEADER
+ RestartValue;
+
+ //
+ // Prepare for failure
+ //
+
+ (*ReturnCount) = 0;
+ (*AccountRids) = NULL;
+
+#if DBG
+
+ if ( (AccountTypesMask & ~( SAM_USER_ACCOUNT | SAM_GLOBAL_GROUP_ACCOUNT))
+ != 0 ) {
+ return(STATUS_INVALID_INFO_CLASS);
+ }
+
+
+#endif //DBG
+
+ //
+ // Grab the read lock
+ //
+
+ SampAcquireReadLock();
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0, // Trusted clients only
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+ Table = &Domain->DisplayInformation.RidTable;
+
+ //
+ // If the RID table isn't valid, force it to be made valid.
+ //
+
+ if (!SampRidTableValid(DomainContext->DomainIndex)) {
+ NtStatus = SampCreateDisplayInformation ( DomainDisplayUser ); //User and machine
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampCreateDisplayInformation ( DomainDisplayGroup );
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Allocate a return buffer.
+ // Only allocate as much as we can use.
+ // This is limited either by PreferedMaximumLength
+ // or the number of entries in the table.
+ //
+
+ MaxEntries =
+ ( PreferedMaximumLength / sizeof(ULONG) );
+
+ if (MaxEntries == 0) {
+ MaxEntries = 1; // Always return at least one
+ }
+
+ if (MaxEntries > RtlNumberElementsGenericTable2(Table) ) {
+ MaxEntries = RtlNumberElementsGenericTable2(Table);
+ }
+
+ PreferedMaximumLength = MaxEntries *
+ sizeof(SAMP_DISPLAY_ENTRY_HEADER);
+
+ (*AccountRids) = MIDL_user_allocate( PreferedMaximumLength );
+ if ((*AccountRids) == NULL) {
+ STATUS_NO_MEMORY;
+ }
+
+ //
+ // Get the restart key based upon the passed in RID.
+ //
+
+ Table = &Domain->DisplayInformation.RidTable;
+ RestartValue.Rid = StartingRid;
+
+ Element = RtlRestartKeyByValueGenericTable2(
+ Table,
+ &RestartValue,
+ &RestartKey
+ );
+
+ //
+ // Now we may loop obtaining entries until we reach
+ // either MaxEntries or the end of the table.
+ //
+ // WARNING - there is one special case that we have to
+ // take care of. If the returned Element is not null,
+ // but the RestartKey is null, then the caller has
+ // asked for an enumeration and passed in the last rid
+ // defined. If we aren't careful, this will cause an
+ // enumeration to be started from the beginning of the
+ // list again. Instead, return status indicating we have
+ // no more entries.
+ //
+
+ Count = 0;
+ if (((Element != NULL) && (RestartKey == NULL))) {
+
+ Element = NULL; // Used to signify no more entries found
+
+ } else {
+
+ for (Element = RtlEnumerateGenericTable2(Table, &RestartKey);
+ ( (Element != NULL) && (Count < MaxEntries) );
+ Element = RtlEnumerateGenericTable2(Table, &RestartKey)) {
+
+ //
+ // Make sure this is an account that was asked for
+ //
+
+ AccountType = Element->Index;
+ if ((AccountType & AccountTypesMask) != 0) {
+ (*AccountRids)[Count] = Element->Rid;
+ Count++;
+ }
+ }
+ }
+
+ //
+ // Now figure out what we have done:
+ //
+ // Returned all entries in table => STATUS_SUCCESS
+ // More entries to return => STATUS_MORE_ENTRIES
+ //
+ // Count == 0 => free AccountRid array.
+ //
+
+ if (Element == NULL) {
+ NtStatus = STATUS_SUCCESS;
+ } else {
+ NtStatus = STATUS_MORE_ENTRIES;
+ }
+
+ if (Count == 0) {
+ MIDL_user_free( (*AccountRids) );
+ (*AccountRids) = NULL;
+ }
+
+ (*ReturnCount) = Count;
+
+ }
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+ return(NtStatus);
+
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines available to other SAM modules //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampInitializeDisplayInformation (
+ ULONG DomainIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This routines initializes the display information structure.
+ This involves initializing the User, Machine and Group trees (empty),
+ and setting the Valid flag to FALSE.
+
+ If this is the account domain, we also create the display information.
+
+Parameters:
+
+ DomainIndex - An index into the DefinedDomains array. This array
+ contains information about the domain being openned,
+ including its name.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation;
+
+ //
+ // This must be initialized before we use SampCompareDisplayStrings().
+ //
+
+ SampSystemDefaultLCID = GetSystemDefaultLCID();
+
+ DisplayInformation = &SampDefinedDomains[DomainIndex].DisplayInformation;
+
+ RtlInitializeGenericTable2(
+ &DisplayInformation->UserTable,
+ SampCompareUserNodeByName,
+ SampGenericTable2Allocate,
+ SampGenericTable2Free);
+
+ RtlInitializeGenericTable2(
+ &DisplayInformation->MachineTable,
+ SampCompareMachineNodeByName,
+ SampGenericTable2Allocate,
+ SampGenericTable2Free);
+
+ RtlInitializeGenericTable2(
+ &DisplayInformation->InterdomainTable,
+ SampCompareMachineNodeByName,
+ SampGenericTable2Allocate,
+ SampGenericTable2Free);
+
+ RtlInitializeGenericTable2(
+ &DisplayInformation->GroupTable,
+ SampCompareGroupNodeByName,
+ SampGenericTable2Allocate,
+ SampGenericTable2Free);
+
+ RtlInitializeGenericTable2(
+ &DisplayInformation->RidTable,
+ SampCompareNodeByRid,
+ SampGenericTable2Allocate,
+ SampGenericTable2Free);
+
+ DisplayInformation->UserAndMachineTablesValid = FALSE;
+ DisplayInformation->GroupTableValid = FALSE;
+
+
+ if ( SampProductType == NtProductLanManNt &&
+ DomainIndex == SampDefinedDomainsCount - 1 ) {
+ //
+ // Grab the read lock and indicate which domain the transaction is in
+ //
+
+ SampAcquireReadLock();
+ SampSetTransactionDomain( DomainIndex );
+
+ //
+ // Populate the Display Cache
+ //
+
+ (VOID) SampCreateDisplayInformation(DomainDisplayUser);
+ (VOID) SampCreateDisplayInformation(DomainDisplayGroup);
+
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+ }
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+VOID
+SampDeleteDisplayInformation (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ This routines frees up any resources used by the display information.
+
+
+ Note: It use to be that we could selectively invalidate
+ portions of the display cache (e.g., users, or groups).
+ With the addition of the RID table, this becomes
+ problematic. So, now the approach is to flush all tables
+ for a domain if any the tables in that domain are flushed.
+
+
+Parameters:
+
+ DisplayInformation - The display information structure to delete.
+
+ ObjectType - Indicates which table to delete the information from.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+
+
+ //
+ // Empty the user table and check it really is empty
+ //
+
+ NtStatus = SampEmptyGenericTable2(&DisplayInformation->UserTable, FALSE);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->UserTable));
+
+ DisplayInformation->TotalBytesInUserTable = 0;
+
+
+
+ //
+ // Empty the machine table and check it really is empty
+ //
+
+ NtStatus = SampEmptyGenericTable2(&DisplayInformation->MachineTable, FALSE);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->MachineTable));
+
+ DisplayInformation->TotalBytesInMachineTable = 0;
+
+
+
+ //
+ // Empty the Interdomain table and check it really is empty
+ //
+
+ NtStatus = SampEmptyGenericTable2(&DisplayInformation->InterdomainTable, FALSE);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->InterdomainTable));
+
+ DisplayInformation->TotalBytesInInterdomainTable = 0;
+
+
+
+ //
+ // Empty the Group table and check it really is empty
+ //
+
+ NtStatus = SampEmptyGenericTable2(&DisplayInformation->GroupTable, FALSE);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->GroupTable));
+
+ DisplayInformation->TotalBytesInGroupTable = 0;
+
+
+
+
+ //
+ // Empty the Rid table and check it really is empty.
+ //
+
+
+ NtStatus = SampEmptyGenericTable2(&DisplayInformation->RidTable, FALSE);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->RidTable));
+
+ DisplayInformation->TotalBytesInRidTable = 0;
+
+}
+
+
+
+NTSTATUS
+SampMarkDisplayInformationInvalid (
+ SAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine invalidates any cached display information. This
+ causes it to be recreated the next time a client queries it.
+ Later we will probably start/restart a thread here and have it
+ re-create the display information in the background.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+ Another Note: It use to be that we could selectively invalidate
+ portions of the display cache (e.g., users, or groups).
+ With the addition of the RID table, this becomes
+ problematic. So, now the approach is to flush all tables
+ for a domain if any the tables in that domain are flushed.
+
+
+Parameters:
+
+ ObjectType - SampUserObjectType or SampGroupObjectType. Only the
+ appropriate tables will be marked Invalid. For User type, the
+ user and machine tables will be marked Invalid. For Group type,
+ the group table will be marked Invalid.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: MarkDisplayInformationInvalid : Emptying cache\n"));
+
+ //
+ // Get pointer to the current domain structure
+ //
+
+ Domain = &SampDefinedDomains[SampTransactionDomainIndex];
+
+ //
+ // Delete any cached data
+ //
+
+ SampDeleteDisplayInformation(&Domain->DisplayInformation, ObjectType);
+
+ //
+ // Set the Valid flag to FALSE
+ //
+
+ Domain->DisplayInformation.UserAndMachineTablesValid = FALSE;
+ Domain->DisplayInformation.GroupTableValid = FALSE;
+
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+NTSTATUS
+SampCreateDisplayInformation (
+ DOMAIN_DISPLAY_INFORMATION DisplayType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds the cached display information for the current
+ domain.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseReadLock().
+
+Parameters:
+
+ DisplayType - Indicates which type of display information is
+ being created. This leads us to the appropriate table(s).
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation;
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+
+ Domain = &SampDefinedDomains[SampTransactionDomainIndex];
+
+
+ DisplayInformation = &Domain->DisplayInformation;
+
+ switch (DisplayType) {
+ case DomainDisplayUser:
+ case DomainDisplayMachine:
+
+ //
+ // If the cache is valid, nothing to do
+ //
+
+ if (DisplayInformation->UserAndMachineTablesValid) {
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation : User/Machine Cache is valid, nothing to do\n"));
+ return(STATUS_SUCCESS);
+ };
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation : Creating user/machine cache...\n"));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->UserTable));
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->MachineTable));
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->InterdomainTable));
+
+
+ NtStatus = SampRetrieveDisplayInfoFromDisk( DisplayInformation, SampUserObjectType );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampTallyTableStatistics(DisplayInformation, SampUserObjectType);
+ }
+
+ //
+ // Clean up on error
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation FAILED: 0x%lx\n", NtStatus));
+
+ SampDeleteDisplayInformation(&Domain->DisplayInformation, SampUserObjectType);
+ } else {
+ Domain->DisplayInformation.UserAndMachineTablesValid = TRUE;
+ }
+
+ break; // out of switch
+
+
+ case DomainDisplayGroup:
+
+ //
+ // If the cache is valid, nothing to do
+ //
+
+ if (DisplayInformation->GroupTableValid) {
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation : Group Cache is valid, nothing to do\n"));
+
+ return(STATUS_SUCCESS);
+ };
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation : Creating group cache...\n"));
+
+ ASSERT(RtlIsGenericTable2Empty(&DisplayInformation->GroupTable));
+
+
+ NtStatus = SampRetrieveDisplayInfoFromDisk( DisplayInformation, SampGroupObjectType );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampTallyTableStatistics(DisplayInformation, SampGroupObjectType);
+ }
+
+ //
+ // Clean up on error
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: CreateDisplayInformation FAILED: 0x%lx\n", NtStatus));
+ SampDeleteDisplayInformation(&Domain->DisplayInformation, SampGroupObjectType);
+ } else {
+ Domain->DisplayInformation.GroupTableValid = TRUE;
+ }
+
+ break; // out of switch
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampRetrieveDisplayInfoFromDisk(
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ )
+
+{
+ NTSTATUS NtStatus;
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ PSAMPR_ENUMERATION_BUFFER EnumerationBuffer;
+ ULONG i;
+ ULONG CountReturned;
+ BOOLEAN MoreEntries;
+
+
+ //
+ // Enumerate the accounts.
+ // For each account, get the relevant information on it,
+ // and add to either the UserTable, MachineTable, or GroupTable.
+ //
+
+ EnumerationContext = 0;
+
+ do {
+
+ NtStatus = SampEnumerateAccountNames(
+ ObjectType,
+ &EnumerationContext,
+ &EnumerationBuffer,
+ 200000, // PreferedMaximumLength
+ 0L, // no filter
+ &CountReturned,
+ FALSE // trusted client
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve Info From Disk - "
+ "Error enumerating account names (0x%lx)\n",
+ NtStatus) );
+ break;
+ }
+
+ //
+ // Make a note if there are more entries
+ //
+
+ MoreEntries = (NtStatus == STATUS_MORE_ENTRIES);
+
+
+ //
+ // For each account, get the necessary information for it
+ // and add to the appropriate display information table
+ //
+
+ for (i = 0; i < EnumerationBuffer->EntriesRead; i++) {
+
+ ULONG AccountRid =
+ EnumerationBuffer->Buffer[i].RelativeId;
+ PUNICODE_STRING AccountName =
+ (PUNICODE_STRING)&(EnumerationBuffer->Buffer[i].Name);
+ SAMP_V1_0A_FIXED_LENGTH_USER UserV1aFixed; // Contains account control
+ SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed; // Contains attributes
+ SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
+ PSAMP_OBJECT AccountContext;
+
+
+ //
+ // Open a context to the account
+ //
+
+ NtStatus = SampCreateAccountContext(
+ ObjectType,
+ AccountRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AccountContext
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve Info From Disk - "
+ "Error Creating account context (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+
+ //
+ // Get the account control information
+ //
+
+ switch (ObjectType) {
+ case SampUserObjectType:
+
+ NtStatus = SampRetrieveUserV1aFixed(AccountContext, &UserV1aFixed);
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( AccountContext );
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve USER From Disk - "
+ "Error getting V1a Fixed (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+
+ //
+ // If this is not an account we're interested in skip it
+ //
+
+ if (!DISPLAY_ACCOUNT(UserV1aFixed.UserAccountControl)) {
+ SampDeleteContext( AccountContext );
+ continue; // next account
+ }
+
+
+
+ //
+ // Get the admin comment
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ FALSE, // Don't make copy
+ &AccountInfo.Comment
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( AccountContext );
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve USER From Disk - "
+ "Error getting admin comment (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+
+ //
+ // Get the full name
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ FALSE, // Don't make copy
+ &AccountInfo.FullName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( AccountContext );
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve USER From Disk - "
+ "Error getting full name (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+ //
+ // Set the account control
+ //
+
+ AccountInfo.AccountControl = UserV1aFixed.UserAccountControl;
+
+ break; // out of switch
+
+ case SampGroupObjectType:
+
+ NtStatus = SampRetrieveGroupV1Fixed(AccountContext, &GroupV1Fixed);
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( AccountContext );
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve GROUP From Disk - "
+ "Error getting V1 fixed (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+ //
+ // Get the admin comment
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ FALSE, // Don't make copy
+ &AccountInfo.Comment
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( AccountContext );
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: Retrieve GROUP From Disk - "
+ "Error getting admin comment (0x%lx)\n",
+ NtStatus) );
+ break; // out of for loop
+ }
+
+ //
+ // Set the attributes
+ //
+
+ AccountInfo.AccountControl = GroupV1Fixed.Attributes;
+
+ break; // out of switch
+ }
+
+
+ //
+ // Now add this account to the cached data
+ //
+
+ AccountInfo.Rid = AccountRid;
+ AccountInfo.Name = *((PUNICODE_STRING)(&EnumerationBuffer->Buffer[i].Name));
+
+ NtStatus = SampAddDisplayAccount(DisplayInformation,
+ ObjectType,
+ &AccountInfo);
+
+ //
+ // We're finished with the account context
+ //
+
+ SampDeleteContext( AccountContext );
+
+ //
+ // Check the result of adding the account to the cache
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break; // out of for loop
+ }
+
+
+ } // end_for
+
+
+ //
+ // Free up the enumeration buffer returned
+ //
+
+ SamIFree_SAMPR_ENUMERATION_BUFFER(EnumerationBuffer);
+
+
+ } while ( MoreEntries );
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampUpdateDisplayInformation (
+ PSAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo OPTIONAL,
+ PSAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo OPTIONAL,
+ SAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ This routines updates the cached display information to reflect
+ changes to a single account.
+
+ If any error occurs, this routine marks the cached information
+ Invalid so it will get fixed during re-creation.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+Parameters:
+
+ OldAccountInfo - The old information for this account. If this is NULL
+ then the account is being added.
+ The only fields required in the OldAccountInfo are
+ Name
+ AccountControl
+ Rid
+
+ NewAccountInfo - The new information for this account. If this is NULL
+ then the account is being deleted.
+
+
+ ObjectType - Indicates whether the account is a user account or
+ group account.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation;
+ BOOLEAN DoUpdate;
+
+ ASSERT( ARGUMENT_PRESENT(OldAccountInfo) ||
+ ARGUMENT_PRESENT(NewAccountInfo)
+ );
+
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+ Domain = &SampDefinedDomains[SampTransactionDomainIndex];
+ DisplayInformation = &Domain->DisplayInformation;
+
+
+ IF_SAMP_GLOBAL( DISPLAY_CACHE ) {
+
+ if (ARGUMENT_PRESENT(OldAccountInfo) && ARGUMENT_PRESENT(NewAccountInfo)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayInformation : Updating cache for old account <%wZ>, new <%wZ>\n",
+ &OldAccountInfo->Name, &NewAccountInfo->Name));
+ }
+ if (!ARGUMENT_PRESENT(OldAccountInfo) && ARGUMENT_PRESENT(NewAccountInfo)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayInformation : Adding account <%wZ> to cache\n",
+ &NewAccountInfo->Name));
+ }
+ if (ARGUMENT_PRESENT(OldAccountInfo) && !ARGUMENT_PRESENT(NewAccountInfo)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayInformation : Deleting account <%wZ> from cache\n",
+ &OldAccountInfo->Name));
+ }
+ } //end_IF_SAMP_GLOBAL
+
+
+ switch (ObjectType) {
+
+ case SampUserObjectType:
+
+ //
+ // If the cache is Invalid there's nothing to do
+ //
+
+ if (!DisplayInformation->UserAndMachineTablesValid) {
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayInformation : User Cache is Invalid, nothing to do\n"));
+
+ return(STATUS_SUCCESS);
+ };
+
+
+ //
+ // If this is an update to an existing account then try
+ // to do an inplace update of the cache.
+ // If this fails because it's too complex etc, then revert to
+ // the less efficient method of deleting the old, then adding the new.
+ //
+
+ DoUpdate = FALSE;
+ if (ARGUMENT_PRESENT(OldAccountInfo) && ARGUMENT_PRESENT(NewAccountInfo)) {
+
+ //
+ // We can only do an update if both old and new accounts
+ // are types that we keep in the display cache.
+ //
+
+ if ( DISPLAY_ACCOUNT(OldAccountInfo->AccountControl) &&
+ DISPLAY_ACCOUNT(NewAccountInfo->AccountControl) ) {
+
+ //
+ // We can only do an update if the account is still of
+ // the same type. i.e. it hasn't jumped cache table.
+ //
+
+ if ( (USER_ACCOUNT(OldAccountInfo->AccountControl) ==
+ USER_ACCOUNT(NewAccountInfo->AccountControl)) &&
+ (MACHINE_ACCOUNT(OldAccountInfo->AccountControl) ==
+ MACHINE_ACCOUNT(NewAccountInfo->AccountControl)) ) {
+
+ //
+ // We can only do an update if the account name hasn't changed
+ //
+
+ if (RtlEqualUnicodeString( &OldAccountInfo->Name,
+ &NewAccountInfo->Name,
+ FALSE // Case sensitive
+ )) {
+ //
+ // Everything has been checked out - we can do an update
+ //
+
+ DoUpdate = TRUE;
+ }
+ }
+ }
+ }
+
+ break; // out of switch
+
+ case SampGroupObjectType:
+
+ //
+ // If the cache is already Invalid there's nothing to do
+ //
+
+ if (!DisplayInformation->GroupTableValid) {
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayInformation : Group Cache is Invalid, nothing to do\n"));
+
+ return(STATUS_SUCCESS);
+ };
+
+
+ //
+ // If this is an update to an existing account then try
+ // and do an inplace update of the cache.
+ // If this fails because it's too complex etc, then revert to
+ // the less efficient method of deleting the old, then adding the new.
+ //
+
+ DoUpdate = FALSE;
+ if (ARGUMENT_PRESENT(OldAccountInfo) && ARGUMENT_PRESENT(NewAccountInfo)) {
+
+ //
+ // We can only do an update if the account name hasn't changed
+ //
+
+ if (RtlEqualUnicodeString( &OldAccountInfo->Name,
+ &NewAccountInfo->Name,
+ FALSE // Case sensitive
+ )) {
+ DoUpdate = TRUE;
+ }
+ }
+
+ break; // out of switch
+ }
+
+
+ //
+ // Do an update if possible, otherwise do delete then insert
+ //
+
+ if (DoUpdate) {
+
+ NtStatus = SampUpdateDisplayAccount(DisplayInformation,
+ ObjectType,
+ NewAccountInfo);
+
+ } else {
+
+ NtStatus = STATUS_SUCCESS;
+
+ //
+ // Delete the old account
+ //
+
+ if (ARGUMENT_PRESENT(OldAccountInfo)) {
+ NtStatus = SampDeleteDisplayAccount(DisplayInformation,
+ ObjectType,
+ OldAccountInfo);
+ }
+
+ //
+ // Add the new account
+ //
+
+ if (NT_SUCCESS(NtStatus) && ARGUMENT_PRESENT(NewAccountInfo)) {
+ NtStatus = SampAddDisplayAccount(DisplayInformation,
+ ObjectType,
+ NewAccountInfo);
+ }
+
+ //
+ // Re-tally the cache
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampTallyTableStatistics(DisplayInformation, ObjectType);
+ }
+ }
+
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ //
+ // Something screwed up.
+ // Mark the cache Invalid - it will get rebuilt from scratch
+ // at the next query.
+ //
+
+ KdPrint(("SAM: The display cache is inconsistent, forcing rebuild\n"));
+ ASSERT(FALSE);
+
+ NtStatus = SampMarkDisplayInformationInvalid(ObjectType);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+
+ return(NtStatus);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines available within this module only //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SampDeleteDisplayAccount (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routines deletes the specified account from the cached display
+ information. It is asummed that if this account is a cached type it
+ will appear in the appropriate cache table.
+
+Parameters:
+
+ DisplayInformation - Pointer to cached display information
+
+ ObjectType - Indicates which table(s) to look for the account in.
+
+ AccountInfo - The account to be deleted.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_INTERNAL_ERROR - the account is a cached type yet could not be
+ found in the cached data.
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG Control = AccountInfo->AccountControl;
+ BOOLEAN Success;
+
+ //
+ // We expect the cache to be valid
+ //
+#if DBG
+ switch (ObjectType) {
+ case SampUserObjectType:
+ ASSERT(DisplayInformation->UserAndMachineTablesValid);
+ break; //out of switch
+
+ case SampGroupObjectType:
+ ASSERT(DisplayInformation->GroupTableValid);
+ break; //out of switch
+ }
+#endif //DBG
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Deleting account <%wZ>\n", &AccountInfo->Name));
+
+
+
+ switch (ObjectType) {
+ case SampUserObjectType:
+
+ if (USER_ACCOUNT(Control)) {
+
+ DOMAIN_DISPLAY_USER LocalUserInfo;
+ PDOMAIN_DISPLAY_USER UserInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Deleting account from user table\n"));
+
+ UserInfo = &LocalUserInfo;
+ NtStatus = SampInitializeUserInfo(AccountInfo, &UserInfo, FALSE);
+ if (NT_SUCCESS(NtStatus)) {
+ //
+ // Delete the account from the user table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->UserTable,
+ (PVOID)UserInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from user table\n"));
+ ASSERT(FALSE);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now remove it to the RID table
+ //
+
+ (VOID)RtlDeleteElementGenericTable2(
+ &DisplayInformation->RidTable,
+ (PVOID)UserInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from RID table\n"));
+ NtStatus = STATUS_INTERNAL_ERROR;
+ ASSERT(Success);
+ }
+ }
+ }
+
+
+ } else if (MACHINE_ACCOUNT(Control)) {
+
+ DOMAIN_DISPLAY_MACHINE LocalMachineInfo;
+ PDOMAIN_DISPLAY_MACHINE MachineInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Deleting account from machine table\n"));
+
+ MachineInfo = &LocalMachineInfo;
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &MachineInfo, FALSE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Delete the account from the machine table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->MachineTable,
+ (PVOID)MachineInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from machine table\n"));
+ ASSERT(FALSE);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now remove it to the RID table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->RidTable,
+ (PVOID)MachineInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from RID table\n"));
+ NtStatus = STATUS_INTERNAL_ERROR;
+ ASSERT(Success);
+ }
+ }
+ }
+
+ } else if (INTERDOMAIN_ACCOUNT(Control)) {
+
+ //
+ // Interdomain account
+ //
+
+ DOMAIN_DISPLAY_MACHINE LocalInterdomainInfo;
+ PDOMAIN_DISPLAY_MACHINE InterdomainInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Deleting account from Interdomain table\n"));
+
+ InterdomainInfo = &LocalInterdomainInfo;
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &InterdomainInfo, FALSE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Delete the account from the Interdomain table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->InterdomainTable,
+ (PVOID)InterdomainInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from Interdomain table\n"));
+ ASSERT(FALSE);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now remove it to the RID table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->RidTable,
+ (PVOID)InterdomainInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from RID table\n"));
+ NtStatus = STATUS_INTERNAL_ERROR;
+ ASSERT(Success);
+ }
+ }
+ }
+
+ } else {
+
+ //
+ // This account is not one that we cache - nothing to do
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Account is not one that we cache, account control = 0x%lx\n", Control));
+ }
+
+
+ break; //out of switch
+
+
+
+
+
+ case SampGroupObjectType:
+
+ {
+
+ DOMAIN_DISPLAY_GROUP LocalGroupInfo;
+ PDOMAIN_DISPLAY_GROUP GroupInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Deleting account from Group table\n"));
+
+ GroupInfo = &LocalGroupInfo;
+ NtStatus = SampInitializeGroupInfo(AccountInfo, &GroupInfo, FALSE);
+ if (NT_SUCCESS(NtStatus)) {
+ //
+ // Delete the account from the Group table
+ //
+
+ Success = RtlDeleteElementGenericTable2(
+ &DisplayInformation->GroupTable,
+ (PVOID)GroupInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from Group table\n"));
+ ASSERT(FALSE);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now remove it to the RID table
+ //
+
+ (VOID)RtlDeleteElementGenericTable2(
+ &DisplayInformation->RidTable,
+ (PVOID)GroupInfo);
+ if (!Success) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: DeleteDisplayAccount : Failed to delete element from RID table\n"));
+ NtStatus = STATUS_INTERNAL_ERROR;
+ ASSERT(Success);
+ }
+ }
+ }
+
+ break; //out of switch
+ }
+
+ }
+
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampAddDisplayAccount (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routines adds the specified account to the cached display
+ information as appropriate to its type.
+
+Parameters:
+
+ DisplayInformation - Pointer to cached display information
+
+ ObjectType - SampUserObjectType or SampGroupObjectType. Helps
+ determine which table it goes into.
+
+ AccountInfo - The account to be added.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_INTERNAL_ERROR - the account already existed in the cache
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ Control = AccountInfo->AccountControl;
+
+ BOOLEAN
+ NewElement;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Adding account <%wZ>\n", &AccountInfo->Name));
+
+
+ if (ObjectType == SampGroupObjectType) {
+
+ PDOMAIN_DISPLAY_GROUP GroupInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Adding account to group table\n"));
+
+ NtStatus = SampInitializeGroupInfo(AccountInfo, &GroupInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the account to the Group table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->GroupTable,
+ GroupInfo,
+ &NewElement);
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: AddDisplayAccount : Account already exists in GROUP table\n"));
+ ASSERT(FALSE);
+ SampFreeGroupInfo(GroupInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now add it to the RID table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->RidTable,
+ GroupInfo,
+ &NewElement);
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: AddDisplayAccount : Account already exists in RID table\n"));
+ NtStatus = STATUS_INTERNAL_ERROR;
+ ASSERT(NewElement);
+ }
+
+ }
+ }
+
+ } else {
+
+ ASSERT(ObjectType == SampUserObjectType);
+
+ if (USER_ACCOUNT(Control)) {
+
+ PDOMAIN_DISPLAY_USER UserInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Adding account to user table\n"));
+
+ NtStatus = SampInitializeUserInfo(AccountInfo, &UserInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the account to the normal user table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->UserTable,
+ UserInfo,
+ &NewElement);
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: AddDisplayAccount : Account already exists in USER table\n"));
+ ASSERT(FALSE);
+ SampFreeUserInfo(UserInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now add it to the RID table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->RidTable,
+ UserInfo,
+ &NewElement);
+
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: AddDisplayAccount : Account already exists in RID table\n"));
+ ASSERT(NewElement);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+
+ }
+ }
+
+ } else if (MACHINE_ACCOUNT(Control)) {
+
+ PDOMAIN_DISPLAY_MACHINE MachineInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Adding account to machine table\n"));
+
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &MachineInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the account to the machine table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->MachineTable,
+ MachineInfo,
+ &NewElement);
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Account already exists in MACHINE table\n"));
+ ASSERT(FALSE);
+ SampFreeMachineInfo(MachineInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now add it to the RID table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->RidTable,
+ MachineInfo,
+ &NewElement);
+
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Account already exists in RID table\n"));
+ ASSERT(NewElement);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+
+ }
+ }
+ } else if (INTERDOMAIN_ACCOUNT(Control)) {
+
+ PDOMAIN_DISPLAY_MACHINE InterdomainInfo;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Adding account to Interdomain table\n"));
+
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &InterdomainInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the account to the Interdomain table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->InterdomainTable,
+ InterdomainInfo,
+ &NewElement);
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Account already exists in Interdomain table\n"));
+ ASSERT(FALSE);
+ SampFreeMachineInfo(InterdomainInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ } else {
+
+ //
+ // Now add it to the RID table
+ //
+
+ (VOID)RtlInsertElementGenericTable2(
+ &DisplayInformation->RidTable,
+ InterdomainInfo,
+ &NewElement);
+
+ if (!NewElement) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Account already exists in RID table\n"));
+ ASSERT(NewElement);
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+
+ }
+ }
+
+ } else {
+
+ //
+ // This account is not one that we cache - nothing to do
+ //
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: AddDisplayAccount : Account is not one that we cache, account control = 0x%lx\n", Control));
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampUpdateDisplayAccount(
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType,
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routines attempts to update an account in the display cache.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+Parameters:
+
+ DisplayInformation - Pointer to cached display information
+
+ ObjectType - Indicates whether the account is a user account or
+ group account.
+
+ AccountInfo - The new information for this account.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+
+Notes:
+
+ The account must be a cached type (MACHINE/USER/GROUP)
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayAccount : Updating cached account <%wZ>\n",
+ &AccountInfo->Name));
+
+#if SAMP_DIAGNOSTICS
+ {
+ UNICODE_STRING
+ SampDiagAccountName;
+
+ RtlInitUnicodeString( &SampDiagAccountName, L"SAMP_DIAG" );
+
+ if (RtlEqualUnicodeString(&AccountInfo->Name, &SampDiagAccountName, FALSE)) {
+ SampDisplayDiagnostic();
+ }
+
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+ //
+ // We should only be called when the cache is valid.
+ //
+
+ switch (ObjectType) {
+ case SampUserObjectType:
+
+ ASSERT(DisplayInformation->UserAndMachineTablesValid);
+
+ //
+ // The account must be one that we cache
+ //
+
+ ASSERT( DISPLAY_ACCOUNT(AccountInfo->AccountControl) );
+
+ //
+ // Go find the account in the appropriate table and update it's fields.
+ //
+
+ if (USER_ACCOUNT(AccountInfo->AccountControl)) {
+
+ PDOMAIN_DISPLAY_USER UserInfo;
+
+ //
+ // Allocate space for and initialize the new data
+ //
+
+ NtStatus = SampInitializeUserInfo(AccountInfo, &UserInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ PDOMAIN_DISPLAY_USER FoundElement;
+
+ //
+ // Search for the account in the user table
+ //
+
+ FoundElement = RtlLookupElementGenericTable2(
+ &DisplayInformation->UserTable,
+ UserInfo);
+
+ if (FoundElement == NULL) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayAccount : Account <%wZ> not found in user table\n", &AccountInfo->Name));
+ ASSERT(FALSE);
+ SampFreeUserInfo(UserInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+
+ } else {
+
+ //
+ // We found it. Check the old and new match where we expect.
+ // Can't change either the logon name or RID by this routine.
+ //
+
+ ASSERT(RtlEqualUnicodeString(&FoundElement->LogonName, &UserInfo->LogonName, FALSE));
+ ASSERT(FoundElement->Rid == UserInfo->Rid);
+
+ //
+ // Free up the existing data in the account element
+ // (all the strings) and replace it with the new data.
+ // Don't worry about the index value. It isn't
+ // valid in the table.
+ //
+
+ SampSwapUserInfo(FoundElement, UserInfo);
+ SampFreeUserInfo(UserInfo);
+ }
+ }
+
+ } else if (MACHINE_ACCOUNT(AccountInfo->AccountControl)) {
+
+ PDOMAIN_DISPLAY_MACHINE MachineInfo;
+
+ //
+ // Allocate space for and initialize the new data
+ //
+
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &MachineInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ PDOMAIN_DISPLAY_MACHINE FoundElement;
+
+ //
+ // Search for the account in the user table
+ //
+
+ FoundElement = RtlLookupElementGenericTable2(
+ &DisplayInformation->MachineTable,
+ MachineInfo);
+
+ if (FoundElement == NULL) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayAccount : Account <%wZ> not found in machine table\n", &AccountInfo->Name));
+ ASSERT(FALSE);
+ SampFreeMachineInfo(MachineInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+
+ } else {
+
+ //
+ // We found it. Check the old and new match where we expect.
+ // Can't change either the account name or RID by this routine.
+ //
+
+ ASSERT(RtlEqualUnicodeString(&FoundElement->Machine, &MachineInfo->Machine, FALSE));
+ ASSERT(FoundElement->Rid == MachineInfo->Rid);
+
+ //
+ // Free up the existing data in the account element
+ // (all the strings) and replace it with the new data.
+ // Don't worry about the index value. It isn't
+ // valid in the table.
+ //
+
+ SampSwapMachineInfo(FoundElement, MachineInfo);
+ SampFreeMachineInfo(MachineInfo);
+ }
+ }
+
+ } else if (INTERDOMAIN_ACCOUNT(AccountInfo->AccountControl)) {
+
+ PDOMAIN_DISPLAY_MACHINE InterdomainInfo;
+
+ //
+ // Allocate space for and initialize the new data
+ //
+
+ NtStatus = SampInitializeMachineInfo(AccountInfo, &InterdomainInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ PDOMAIN_DISPLAY_MACHINE FoundElement;
+
+ //
+ // Search for the account in the user table
+ //
+
+ FoundElement = RtlLookupElementGenericTable2(
+ &DisplayInformation->InterdomainTable,
+ InterdomainInfo);
+
+ if (FoundElement == NULL) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayAccount : Account <%wZ> not found in Interdomain table\n", &AccountInfo->Name));
+ ASSERT(FALSE);
+ SampFreeMachineInfo(InterdomainInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+
+ } else {
+
+ //
+ // We found it. Check the old and new match where we expect.
+ // Can't change either the account name or RID by this routine.
+ //
+
+ ASSERT(RtlEqualUnicodeString(&FoundElement->Machine, &InterdomainInfo->Machine, FALSE));
+ ASSERT(FoundElement->Rid == InterdomainInfo->Rid);
+
+ //
+ // Free up the existing data in the account element
+ // (all the strings) and replace it with the new data.
+ // Don't worry about the index value. It isn't
+ // valid in the table.
+ //
+
+ SampSwapMachineInfo(FoundElement, InterdomainInfo);
+ SampFreeMachineInfo(InterdomainInfo);
+ }
+ }
+ }
+
+
+ break; // out of switch
+
+ case SampGroupObjectType:
+ {
+ PDOMAIN_DISPLAY_GROUP GroupInfo;
+
+ ASSERT(DisplayInformation->GroupTableValid);
+
+ //
+ // Allocate space for and initialize the new data
+ //
+
+ NtStatus = SampInitializeGroupInfo(AccountInfo, &GroupInfo, TRUE);
+ if (NT_SUCCESS(NtStatus)) {
+
+ PDOMAIN_DISPLAY_GROUP FoundElement;
+
+ //
+ // Search for the account in the group table
+ //
+
+ FoundElement = RtlLookupElementGenericTable2(
+ &DisplayInformation->GroupTable,
+ GroupInfo);
+
+ if (FoundElement == NULL) {
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: UpdateDisplayAccount : Account <%wZ> not found in group table\n", &AccountInfo->Name));
+ ASSERT(FALSE);
+ SampFreeGroupInfo(GroupInfo);
+ NtStatus = STATUS_INTERNAL_ERROR;
+
+ } else {
+
+ //
+ // We found it. Check the old and new match where we expect.
+ // Can't change either the account name or RID by this routine.
+ //
+
+ ASSERT(RtlEqualUnicodeString(&FoundElement->Group, &GroupInfo->Group, FALSE));
+ ASSERT(FoundElement->Rid == GroupInfo->Rid);
+
+ //
+ // Free up the existing data in the account element
+ // (all the strings) and replace it with the new data.
+ // Don't worry about the index value. It isn't
+ // valid in the table.
+ //
+
+ SampSwapGroupInfo(FoundElement, GroupInfo);
+ SampFreeGroupInfo(GroupInfo);
+ }
+ }
+ }
+
+ break; // out of switch
+
+ } // end_switch
+
+
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampTallyTableStatistics (
+ PSAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation,
+ SAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ This routines runs through the cached data tables and totals
+ up the number of bytes in all elements of each table and stores
+ in the displayinfo.
+
+Parameters:
+
+ DisplayInformation - The display information structure to tally.
+
+ ObjectType - Indicates which table(s) to tally.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ PVOID Node;
+ PVOID RestartKey;
+
+
+ switch (ObjectType) {
+ case SampUserObjectType:
+
+ DisplayInformation->TotalBytesInUserTable = 0;
+ RestartKey = NULL;
+
+ for (Node = RtlEnumerateGenericTable2( &DisplayInformation->UserTable,
+ &RestartKey);
+ Node != NULL;
+ Node = RtlEnumerateGenericTable2( &DisplayInformation->UserTable,
+ &RestartKey)
+ ) {
+
+ DisplayInformation->TotalBytesInUserTable +=
+ SampBytesRequiredUserNode((PDOMAIN_DISPLAY_USER)Node);
+ }
+
+ DisplayInformation->TotalBytesInMachineTable = 0;
+ RestartKey = NULL;
+
+ for (Node = RtlEnumerateGenericTable2( &DisplayInformation->MachineTable,
+ &RestartKey);
+ Node != NULL;
+ Node = RtlEnumerateGenericTable2( &DisplayInformation->MachineTable,
+ &RestartKey)
+ ) {
+
+
+ DisplayInformation->TotalBytesInMachineTable +=
+ SampBytesRequiredMachineNode((PDOMAIN_DISPLAY_MACHINE)Node);
+ }
+
+ break; // out of switch
+
+
+ case SampGroupObjectType:
+
+ DisplayInformation->TotalBytesInGroupTable = 0;
+ RestartKey = NULL;
+
+ for (Node = RtlEnumerateGenericTable2( &DisplayInformation->GroupTable,
+ &RestartKey);
+ Node != NULL;
+ Node = RtlEnumerateGenericTable2( &DisplayInformation->GroupTable,
+ &RestartKey)
+ ) {
+
+
+ DisplayInformation->TotalBytesInGroupTable +=
+ SampBytesRequiredGroupNode((PDOMAIN_DISPLAY_GROUP)Node);
+ }
+
+ break; // out of switch
+
+ } // end_switch
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampEmptyGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ BOOLEAN FreeElements
+ )
+
+/*++
+
+Routine Description:
+
+ This routines deletes all elements in the specified table.
+
+Parameters:
+
+ Table - The table whose elements are to be deleted.
+
+ FreeElements - Indicates whether or not the element bodies
+ should also be freed.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+--*/
+{
+ BOOLEAN Deleted;
+ PVOID Element;
+ ULONG RestartKey;
+
+ RestartKey = 0; // Always get the first element
+ while ((Element = RtlEnumerateGenericTable2( Table, (PVOID *)&RestartKey)) != NULL) {
+
+ Deleted = RtlDeleteElementGenericTable2(Table, Element);
+ ASSERT(Deleted);
+
+ if (FreeElements) {
+ MIDL_user_free( Element );
+ }
+
+ RestartKey = 0;
+ }
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampInitializeUserInfo(
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_USER *UserInfo,
+ BOOLEAN CopyData
+ )
+
+/*++
+
+Routine Description:
+
+ This routines initializes the passed user info structure from the
+ AccountInfo parameter.
+
+Parameters:
+
+ AccountInfo - The account information
+
+ UserInfo - Pointer to the pointer to the user structure to initialize.
+ If CopyData is TRUE, then a pointer to the user structure will be
+ returned to this argument.
+
+ CopyData - FALSE - the UserInfo structure points to the same data as
+ the AccountInfo structure
+ TRUE - space for the data is allocated and all data copied
+ out of the AccountInfo structure into it.
+
+Return Values:
+
+ STATUS_SUCCESS - UserInfo initialized successfully.
+
+ STATUS_NO_MEMORY - Heap could not be allocated to copy the data.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ PDOMAIN_DISPLAY_USER
+ UI;
+
+ if (CopyData) {
+ (*UserInfo) = MIDL_user_allocate( sizeof(DOMAIN_DISPLAY_USER) );
+ if ((*UserInfo) == NULL) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: Init User Info: failed to allocate %d bytes\n",
+ sizeof(DOMAIN_DISPLAY_USER)) );
+ return(STATUS_NO_MEMORY);
+ }
+ }
+
+ UI = (*UserInfo);
+
+
+ UI->Rid = AccountInfo->Rid;
+ UI->AccountControl = AccountInfo->AccountControl;
+
+ if (CopyData) {
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&UI->LogonName, NULL);
+ RtlInitUnicodeString(&UI->AdminComment, NULL);
+ RtlInitUnicodeString(&UI->FullName, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&UI->LogonName,
+ &AccountInfo->Name);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&UI->AdminComment,
+ &AccountInfo->Comment);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&UI->FullName,
+ &AccountInfo->FullName);
+ }
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampInitializeUserInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeUserInfo(UI);
+ }
+
+ } else {
+
+ //
+ // Refer to source data directly
+ //
+
+ UI->LogonName = AccountInfo->Name;
+ UI->AdminComment = AccountInfo->Comment;
+ UI->FullName = AccountInfo->FullName;
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+
+ //
+ // In the Generic Table, the Index field is used to tag the type
+ // of account so we can filter enumerations.
+ //
+
+ UI->Index = SAM_USER_ACCOUNT;
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampInitializeMachineInfo(
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_MACHINE *MachineInfo,
+ BOOLEAN CopyData
+ )
+
+/*++
+
+Routine Description:
+
+ This routines initializes the passed machine info structure from the
+ AccountInfo parameter.
+
+Parameters:
+
+ AccountInfo - The account information
+
+ MachineInfo - Pointer to the pointer to the Machine structure to initialize.
+ If CopyData is TRUE, then a pointer to the structure will be
+ returned to this argument.
+
+ CopyData - FALSE - the MachineInfo structure points to the same data as
+ the AccountInfo structure
+ TRUE - space for the data is allocated and all data copied
+ out of the AccountInfo structure into it.
+
+Return Values:
+
+ STATUS_SUCCESS - UserInfo initialized successfully.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ PDOMAIN_DISPLAY_MACHINE
+ MI;
+
+ if (CopyData) {
+ (*MachineInfo) = MIDL_user_allocate( sizeof(DOMAIN_DISPLAY_MACHINE) );
+ if ((*MachineInfo) == NULL) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: Init Mach Info: failed to allocate %d bytes\n",
+ sizeof(DOMAIN_DISPLAY_MACHINE)) );
+ return(STATUS_NO_MEMORY);
+ }
+ }
+
+ MI = (*MachineInfo);
+
+ MI->Rid = AccountInfo->Rid;
+ MI->AccountControl = AccountInfo->AccountControl;
+
+ if (CopyData) {
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&MI->Machine, NULL);
+ RtlInitUnicodeString(&MI->Comment, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&MI->Machine,
+ &AccountInfo->Name);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&MI->Comment,
+ &AccountInfo->Comment);
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampInitializeMachineInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeMachineInfo(MI);
+ }
+
+ } else {
+
+ //
+ // Refer to source data directly
+ //
+
+ MI->Machine = AccountInfo->Name;
+ MI->Comment = AccountInfo->Comment;
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ //
+ // In the Generic Table, the Index field is used to tag the type
+ // of account so we can filter enumerations.
+ //
+
+ MI->Index = SAM_USER_ACCOUNT;
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampInitializeGroupInfo(
+ PSAMP_ACCOUNT_DISPLAY_INFO AccountInfo,
+ PDOMAIN_DISPLAY_GROUP *GroupInfo,
+ BOOLEAN CopyData
+ )
+
+/*++
+
+Routine Description:
+
+ This routines initializes the passed Group info structure from the
+ AccountInfo parameter.
+
+Parameters:
+
+ AccountInfo - The account information
+
+ GroupInfo - Pointer to the pointer to the Group structure to initialize.
+ If CopyData is TRUE, then a pointer to the structure will be
+ returned to this argument.
+
+ CopyData - FALSE - the GroupInfo structure points to the same data as
+ the AccountInfo structure
+ TRUE - space for the data is allocated and all data copied
+ out of the AccountInfo structure into it.
+
+Return Values:
+
+ STATUS_SUCCESS - GroupInfo initialized successfully.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ PDOMAIN_DISPLAY_GROUP
+ GI;
+
+ if (CopyData) {
+ (*GroupInfo) = MIDL_user_allocate( sizeof(DOMAIN_DISPLAY_GROUP) );
+ if ((*GroupInfo) == NULL) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: Init Group Info: failed to allocate %d bytes\n",
+ sizeof(DOMAIN_DISPLAY_GROUP)) );
+ return(STATUS_NO_MEMORY);
+ }
+ }
+
+ GI = (*GroupInfo);
+
+
+ GI->Rid = AccountInfo->Rid;
+ GI->Attributes = AccountInfo->AccountControl;
+
+ if (CopyData) {
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&GI->Group, NULL);
+ RtlInitUnicodeString(&GI->Comment, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&GI->Group,
+ &AccountInfo->Name);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&GI->Comment,
+ &AccountInfo->Comment);
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampInitializeGroupInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeGroupInfo(GI);
+ }
+
+ } else {
+
+ //
+ // Refer to source data directly
+ //
+
+ GI->Group = AccountInfo->Name;
+ GI->Comment = AccountInfo->Comment;
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ //
+ // In the Generic Table, the Index field is used to tag the type
+ // of account so we can filter enumerations.
+ //
+
+ GI->Index = SAM_GLOBAL_GROUP_ACCOUNT;
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampDuplicateUserInfo(
+ PDOMAIN_DISPLAY_USER Destination,
+ PDOMAIN_DISPLAY_USER Source,
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates space in the destination and copies over the
+ data from the source into it.
+
+Parameters:
+
+ Destination - The structure to copy data into
+
+ Source - The structure containing the data to copy
+
+ Index - This value will be placed in the destination's Index
+ field.
+
+Return Values:
+
+ STATUS_SUCCESS - Destination contains a duplicate of the source data.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ Destination->Index = Index;
+ Destination->Rid = Source->Rid;
+ Destination->AccountControl = Source->AccountControl;
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&Destination->LogonName, NULL);
+ RtlInitUnicodeString(&Destination->AdminComment, NULL);
+ RtlInitUnicodeString(&Destination->FullName, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&Destination->LogonName,
+ &Source->LogonName);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&Destination->AdminComment,
+ &Source->AdminComment);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&Destination->FullName,
+ &Source->FullName);
+ }
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampDuplicateUserInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeUserInfo(Destination);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampDuplicateMachineInfo(
+ PDOMAIN_DISPLAY_MACHINE Destination,
+ PDOMAIN_DISPLAY_MACHINE Source,
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates space in the destination and copies over the
+ data from the source into it.
+
+Parameters:
+
+ Destination - The structure to copy data into
+
+ Source - The structure containing the data to copy
+
+ Index - This value will be placed in the destination's Index
+ field.
+
+Return Values:
+
+ STATUS_SUCCESS - Destination contains a duplicate of the source data.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ Destination->Index = Index;
+ Destination->Rid = Source->Rid;
+ Destination->AccountControl = Source->AccountControl;
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&Destination->Machine, NULL);
+ RtlInitUnicodeString(&Destination->Comment, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&Destination->Machine,
+ &Source->Machine);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&Destination->Comment,
+ &Source->Comment);
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampDuplicateMachineInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeMachineInfo(Destination);
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampDuplicateGroupInfo(
+ PDOMAIN_DISPLAY_GROUP Destination,
+ PDOMAIN_DISPLAY_GROUP Source,
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates space in the destination and copies over the
+ data from the source into it.
+
+Parameters:
+
+ Destination - The structure to copy data into
+
+ Source - The structure containing the data to copy
+
+ Index - This value will be placed in the destination's Index
+ field.
+
+Return Values:
+
+ STATUS_SUCCESS - Destination contains a duplicate of the source data.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ Destination->Index = Index;
+ Destination->Rid = Source->Rid;
+ Destination->Attributes = Source->Attributes;
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitUnicodeString(&Destination->Group, NULL);
+ RtlInitUnicodeString(&Destination->Comment, NULL);
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampDuplicateUnicodeString(&Destination->Group,
+ &Source->Group);
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampDuplicateUnicodeString(&Destination->Comment,
+ &Source->Comment);
+ }
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampDuplicateGroupInfo failed, status = 0x%lx\n", NtStatus));
+ SampFreeGroupInfo(Destination);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampDuplicateOemUserInfo(
+ PDOMAIN_DISPLAY_OEM_USER Destination,
+ PDOMAIN_DISPLAY_USER Source,
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates space in the destination and copies over the
+ data from the source into it.
+
+Parameters:
+
+ Destination - The structure to copy data into
+
+ Source - The structure containing the data to copy
+
+ Index - This value will be placed in the destination's Index
+ field.
+
+Return Values:
+
+ STATUS_SUCCESS - Destination contains a duplicate of a subset of
+ the source data.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ Destination->Index = Index;
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitString(&Destination->User, NULL);
+
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampUnicodeToOemString(&Destination->User,
+ &Source->LogonName);
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampDuplicateOemUser failed, status = 0x%lx\n", NtStatus));
+ RtlInitString(&Destination->User, NULL);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampDuplicateOemGroupInfo(
+ PDOMAIN_DISPLAY_OEM_GROUP Destination,
+ PDOMAIN_DISPLAY_GROUP Source,
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates space in the destination and copies over the
+ data from the source into it.
+
+Parameters:
+
+ Destination - The structure to copy data into
+
+ Source - The structure containing the data to copy
+
+ Index - This value will be placed in the destination's Index
+ field.
+
+Return Values:
+
+ STATUS_SUCCESS - Destination contains a duplicate of a subset of
+ the source data.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ Destination->Index = Index;
+
+ //
+ // Set all strings to NULL initially
+ //
+
+ RtlInitString(&Destination->Group, NULL);
+
+
+ //
+ // Copy source data into destination
+ //
+
+ NtStatus = SampUnicodeToOemString(&Destination->Group,
+ &Source->Group);
+
+ //
+ // Clean up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE_ERRORS,
+ ("SAM: SampDuplicateOemGroup failed, status = 0x%lx\n", NtStatus));
+ RtlInitString(&Destination->Group, NULL);
+ }
+
+ return(NtStatus);
+}
+
+
+
+VOID
+SampSwapUserInfo(
+ PDOMAIN_DISPLAY_USER Info1,
+ PDOMAIN_DISPLAY_USER Info2
+ )
+
+/*++
+
+Routine Description:
+
+ Swap the field contents of Info1 and Info2.
+
+Parameters:
+
+ Info1 & Info2 - The structures whose contents are to be swapped.
+
+
+Return Values:
+
+ None
+
+--*/
+{
+
+ DOMAIN_DISPLAY_USER
+ Tmp;
+
+ Tmp.LogonName = Info1->LogonName;
+ Tmp.AdminComment = Info1->AdminComment;
+ Tmp.FullName = Info1->FullName;
+ Tmp.AccountControl = Info1->AccountControl;
+
+ Info1->LogonName = Info2->LogonName;
+ Info1->AdminComment = Info2->AdminComment;
+ Info1->FullName = Info2->FullName;
+ Info1->AccountControl = Info2->AccountControl;
+
+ Info2->LogonName = Tmp.LogonName;
+ Info2->AdminComment = Tmp.AdminComment;
+ Info2->FullName = Tmp.FullName;
+ Info2->AccountControl = Tmp.AccountControl;
+
+ return;
+}
+
+
+VOID
+SampSwapMachineInfo(
+ PDOMAIN_DISPLAY_MACHINE Info1,
+ PDOMAIN_DISPLAY_MACHINE Info2
+ )
+
+/*++
+
+Routine Description:
+
+ Swap the field contents of Info1 and Info2.
+
+Parameters:
+
+ Info1 & Info2 - The structures whose contents are to be swapped.
+
+
+Return Values:
+
+ None
+
+--*/
+{
+
+ DOMAIN_DISPLAY_MACHINE
+ Tmp;
+
+ Tmp.Machine = Info1->Machine;
+ Tmp.Comment = Info1->Comment;
+ Tmp.AccountControl = Info1->AccountControl;
+
+ Info1->Machine = Info2->Machine;
+ Info1->Comment = Info2->Comment;
+ Info1->AccountControl = Info2->AccountControl;
+
+ Info2->Machine = Tmp.Machine;
+ Info2->Comment = Tmp.Comment;
+ Info2->AccountControl = Tmp.AccountControl;
+
+ return;
+}
+
+
+VOID
+SampSwapGroupInfo(
+ PDOMAIN_DISPLAY_GROUP Info1,
+ PDOMAIN_DISPLAY_GROUP Info2
+ )
+
+/*++
+
+Routine Description:
+
+ Swap the field contents of Info1 and Info2.
+
+Parameters:
+
+ Info1 & Info2 - The structures whose contents are to be swapped.
+
+
+Return Values:
+
+ None
+
+--*/
+{
+
+ DOMAIN_DISPLAY_GROUP
+ Tmp;
+
+ Tmp.Group = Info1->Group;
+ Tmp.Comment = Info1->Comment;
+ Tmp.Attributes = Info1->Attributes;
+
+ Info1->Group = Info2->Group;
+ Info1->Comment = Info2->Comment;
+ Info1->Attributes = Info2->Attributes;
+
+ Info2->Group = Tmp.Group;
+ Info2->Comment = Tmp.Comment;
+ Info2->Attributes = Tmp.Attributes;
+
+ return;
+}
+
+
+VOID
+SampFreeUserInfo(
+ PDOMAIN_DISPLAY_USER UserInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Frees data associated with a userinfo structure.
+
+Parameters:
+
+ UserInfo - User structure to free
+
+
+Return Values:
+
+ None
+
+--*/
+{
+ SampFreeUnicodeString(&UserInfo->LogonName);
+ SampFreeUnicodeString(&UserInfo->AdminComment);
+ SampFreeUnicodeString(&UserInfo->FullName);
+
+ MIDL_user_free( UserInfo );
+ return;
+}
+
+
+
+VOID
+SampFreeMachineInfo(
+ PDOMAIN_DISPLAY_MACHINE MachineInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Frees data associated with a machineinfo structure.
+
+Parameters:
+
+ UserInfo - User structure to free
+
+Return Values:
+
+ None
+
+--*/
+{
+ SampFreeUnicodeString(&MachineInfo->Machine);
+ SampFreeUnicodeString(&MachineInfo->Comment);
+
+ MIDL_user_free( MachineInfo );
+ return;
+}
+
+
+VOID
+SampFreeGroupInfo(
+ PDOMAIN_DISPLAY_GROUP GroupInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Frees data associated with a Groupinfo structure.
+
+Parameters:
+
+ UserInfo - User structure to free
+
+Return Values:
+
+ None
+
+--*/
+{
+ SampFreeUnicodeString(&GroupInfo->Group);
+ SampFreeUnicodeString(&GroupInfo->Comment);
+
+ MIDL_user_free( GroupInfo );
+ return;
+}
+
+
+
+VOID
+SampFreeOemUserInfo(
+ PDOMAIN_DISPLAY_OEM_USER UserInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Frees data associated with a UserInfo structure.
+
+Parameters:
+
+ UserInfo - User structure to free
+
+
+Return Values:
+
+ None
+
+--*/
+{
+ SampFreeOemString(&UserInfo->User);
+
+ MIDL_user_free( UserInfo );
+ return;
+}
+
+
+
+VOID
+SampFreeOemGroupInfo(
+ PDOMAIN_DISPLAY_OEM_GROUP GroupInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Frees data associated with a GroupInfo structure.
+
+Parameters:
+
+ GroupInfo - Group structure to free
+
+
+Return Values:
+
+ None
+
+--*/
+{
+ SampFreeOemString(&GroupInfo->Group);
+
+ MIDL_user_free( GroupInfo );
+ return;
+}
+
+
+
+ULONG
+SampBytesRequiredUserNode (
+ PDOMAIN_DISPLAY_USER Node
+ )
+
+/*++
+
+Routine Description:
+
+ This routines returns the total number of bytes required to store all
+ the elements of the the specified node.
+
+Parameters:
+
+ Node - The node whose size we will return.
+
+Return Values:
+
+ Bytes required by node
+
+--*/
+{
+ return( sizeof(*Node) +
+ Node->LogonName.Length +
+ Node->AdminComment.Length +
+ Node->FullName.Length
+ );
+}
+
+
+
+ULONG
+SampBytesRequiredMachineNode (
+ PDOMAIN_DISPLAY_MACHINE Node
+ )
+
+/*++
+
+Routine Description:
+
+ This routines returns the total number of bytes required to store all
+ the elements of the the specified node.
+
+Parameters:
+
+ Node - The node whose size we will return.
+
+Return Values:
+
+ Bytes required by node
+
+--*/
+{
+ return( sizeof(*Node) +
+ Node->Machine.Length +
+ Node->Comment.Length
+ );
+}
+
+
+ULONG
+SampBytesRequiredGroupNode (
+ PDOMAIN_DISPLAY_GROUP Node
+ )
+
+/*++
+
+Routine Description:
+
+ This routines returns the total number of bytes required to store all
+ the elements of the the specified node.
+
+Parameters:
+
+ Node - The node whose size we will return.
+
+Return Values:
+
+ Bytes required by node
+
+--*/
+{
+ return( sizeof(*Node) + Node->Group.Length + Node->Comment.Length );
+}
+
+
+ULONG
+SampBytesRequiredOemUserNode (
+ PDOMAIN_DISPLAY_OEM_USER Node
+ )
+
+/*++
+
+Routine Description:
+
+ This routines returns the total number of bytes required to store all
+ the elements of the the specified node.
+
+Parameters:
+
+ Node - The node whose size we will return.
+
+Return Values:
+
+ Bytes required by node
+
+--*/
+{
+ return( sizeof(*Node) + Node->User.Length );
+}
+
+
+ULONG
+SampBytesRequiredOemGroupNode (
+ PDOMAIN_DISPLAY_OEM_GROUP Node
+ )
+
+/*++
+
+Routine Description:
+
+ This routines returns the total number of bytes required to store all
+ the elements of the the specified node.
+
+Parameters:
+
+ Node - The node whose size we will return.
+
+Return Values:
+
+ Bytes required by node
+
+--*/
+{
+ return( sizeof(*Node) + Node->Group.Length );
+}
+
+
+
+PVOID
+SampGenericTable2Allocate (
+ CLONG BufferSize
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used by the generic table2 package to allocate
+ memory.
+
+Parameters:
+
+ BufferSize - the number of bytes needed.
+
+Return Values:
+
+ Pointer to the allocated memory
+
+--*/
+{
+ PVOID
+ Buffer;
+
+ Buffer = MIDL_user_allocate(BufferSize);
+#if DBG
+ if (Buffer == NULL) {
+ SampDiagPrint( DISPLAY_CACHE_ERRORS,
+ ("SAM: GenTab alloc of %d bytes failed.\n",
+ BufferSize) );
+ }
+#endif //DBG
+ return(Buffer);
+}
+
+
+
+VOID
+SampGenericTable2Free (
+ PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routines frees memory previously allocated using
+ SampGenericTable2Allocate().
+
+Parameters:
+
+ Node - the memory to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ //
+ // Free up the base structure
+ //
+
+ MIDL_user_free(Buffer);
+
+ return;
+}
+
+
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareUserNodeByName (
+ PVOID Node1,
+ PVOID Node2
+ )
+
+/*++
+
+Routine Description:
+
+ This routines compares account name fields of two user nodes.
+
+Parameters:
+
+ Node1, Node2, the nodes to compare
+
+Return Values:
+
+ GenericLessThan - Node1 < Node2
+ GenericGreaterThan - Node1 > Node2
+ GenericEqual - Node1 == Node2
+
+--*/
+{
+ PUNICODE_STRING
+ NodeName1,
+ NodeName2;
+
+ LONG
+ NameComparison;
+
+ NodeName1 = &((PDOMAIN_DISPLAY_USER)Node1)->LogonName;
+ NodeName2 = &((PDOMAIN_DISPLAY_USER)Node2)->LogonName;
+
+ //
+ // Do a case-insensitive comparison of the node names
+ //
+
+ NameComparison = SampCompareDisplayStrings(NodeName1, NodeName2, TRUE);
+
+ if (NameComparison > 0) {
+ return(GenericGreaterThan);
+ }
+
+ if (NameComparison < 0) {
+ return(GenericLessThan);
+ }
+
+ return(GenericEqual);
+}
+
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareMachineNodeByName (
+ PVOID Node1,
+ PVOID Node2
+ )
+
+/*++
+
+Routine Description:
+
+ This routines compares account name fields of two machine nodes.
+
+Parameters:
+
+ Node1, Node2, the nodes to compare
+
+Return Values:
+
+ GenericLessThan - Node1 < Node2
+ GenericGreaterThan - Node1 > Node2
+ GenericEqual - Node1 == Node2
+
+--*/
+{
+ PUNICODE_STRING
+ NodeName1,
+ NodeName2;
+
+ LONG
+ NameComparison;
+
+
+
+ NodeName1 = &((PDOMAIN_DISPLAY_MACHINE)Node1)->Machine;
+ NodeName2 = &((PDOMAIN_DISPLAY_MACHINE)Node2)->Machine;
+
+
+ //
+ // Do a case-insensitive comparison of the node names
+ //
+
+ NameComparison = SampCompareDisplayStrings(NodeName1, NodeName2, TRUE);
+
+ if (NameComparison > 0) {
+ return(GenericGreaterThan);
+ }
+
+ if (NameComparison < 0) {
+ return(GenericLessThan);
+ }
+
+ return(GenericEqual);
+}
+
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareGroupNodeByName (
+ PVOID Node1,
+ PVOID Node2
+ )
+
+/*++
+
+Routine Description:
+
+ This routines compares account name fields of two group nodes.
+
+Parameters:
+
+ Node1, Node2, the nodes to compare
+
+Return Values:
+
+ GenericLessThan - Node1 < Node2
+ GenericGreaterThan - Node1 > Node2
+ GenericEqual - Node1 == Node2
+
+--*/
+{
+ PUNICODE_STRING
+ NodeName1,
+ NodeName2;
+
+ LONG
+ NameComparison;
+
+
+
+ NodeName1 = &((PDOMAIN_DISPLAY_GROUP)Node1)->Group;
+ NodeName2 = &((PDOMAIN_DISPLAY_GROUP)Node2)->Group;
+
+ //
+ // Do a case-insensitive comparison of the node names
+ //
+
+ NameComparison = SampCompareDisplayStrings(NodeName1, NodeName2, TRUE);
+
+ if (NameComparison > 0) {
+ return(GenericGreaterThan);
+ }
+
+ if (NameComparison < 0) {
+ return(GenericLessThan);
+ }
+
+ return(GenericEqual);
+}
+
+
+RTL_GENERIC_COMPARE_RESULTS
+SampCompareNodeByRid (
+ PVOID Node1,
+ PVOID Node2
+ )
+
+/*++
+
+Routine Description:
+
+ This routines compares the RID of two nodes.
+
+Parameters:
+
+ Node1, Node2, the nodes to compare
+
+Return Values:
+
+ GenericLessThan - Node1 < Node2
+ GenericGreaterThan - Node1 > Node2
+ GenericEqual - Node1 == Node2
+
+--*/
+{
+
+ PDOMAIN_DISPLAY_USER
+ N1,
+ N2;
+
+ //
+ // This routine assumes that all nodes have RIDs in the same
+ // place, regardless of node type.
+ //
+
+ ASSERT(FIELD_OFFSET(DOMAIN_DISPLAY_USER, Rid) ==
+ FIELD_OFFSET(DOMAIN_DISPLAY_MACHINE, Rid));
+ ASSERT(FIELD_OFFSET(DOMAIN_DISPLAY_USER, Rid) ==
+ FIELD_OFFSET(DOMAIN_DISPLAY_GROUP, Rid));
+
+ N1 = Node1;
+ N2 = Node2;
+
+
+ if (N1->Rid < N2->Rid) {
+ return(GenericLessThan);
+ }
+
+ if (N1->Rid > N2->Rid) {
+ return(GenericGreaterThan);
+ }
+
+ return(GenericEqual);
+}
+
+
+LONG
+SampCompareDisplayStrings(
+ IN PUNICODE_STRING String1,
+ IN PUNICODE_STRING String2,
+ IN BOOLEAN IgnoreCase
+ )
+/*++
+
+Routine Description:
+
+ This routine is a replacement for RtlCompareUnicodeString().
+ The difference between RtlCompareUnicodeString() and this routine
+ is that this routine takes into account various customer selected
+ sort criteria (like, how is "A-MarilF" sorted in comparison to
+ "Alfred"). This routine uses CompareStringW() for its comparison
+ function.
+
+
+Parameters:
+
+ String1 - Points to a unicode string to compare.
+
+ String2 - Points to a unicode string to compare.
+
+ IgnoreCase - indicates whether the comparison is to be case
+ sensitive (FALSE) or case insensitive (TRUE).
+
+Return Values:
+
+
+ -1 - String1 is lexically less than string 2. That is, String1
+ preceeds String2 in an ordered list.
+
+ 0 - String1 and String2 are lexically equivalent.
+
+ -1 - String1 is lexically greater than string 2. That is, String1
+ follows String2 in an ordered list.
+
+
+--*/
+
+
+{
+
+ INT
+ CompareResult;
+
+ DWORD
+ Options = 0;
+
+ if (IgnoreCase) {
+ Options = NORM_IGNORECASE;
+ }
+
+ CompareResult = CompareStringW( SampSystemDefaultLCID,
+ Options,
+ String1->Buffer,
+ (String1->Length / sizeof(WCHAR)),
+ String2->Buffer,
+ (String2->Length / sizeof(WCHAR))
+ );
+
+ //
+ // Note that CompareStringW() returns values 1, 2, and 3 for
+ // string1 less than, equal, or greater than string2 (respectively)
+ // So, to obtain the RtlCompareUnicodeString() return values of
+ // -1, 0, and 1 for the same meaning, we simply have to subtract 2.
+ //
+
+ CompareResult -= 2;
+
+ //
+ // CompareStringW has the property that alternate spellings may
+ // produce strings that compare identically while the rest of SAM
+ // treats the strings as different. To get around this, if the
+ // strings are the same we call RtlCompareUnicodeString to make
+ // sure the strings really are the same.
+ //
+
+ if (CompareResult == 0) {
+ CompareResult = RtlCompareUnicodeString(
+ String1,
+ String2,
+ IgnoreCase
+ );
+
+ }
+ return(CompareResult);
+}
+
+
+#if SAMP_DIAGNOSTICS
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Internal diagnostics //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#define SAMP_DISPLAY_DIAG_ENUM_RIDS (0x00000001)
+
+VOID
+SampDisplayDiagnosticSuccess(
+ IN NTSTATUS s,
+ IN BOOLEAN Eol
+ )
+
+/*++
+
+Routine Description:
+
+ This routine prints "Success" or "Failure" depending upon the
+ the passed in status value.
+
+ If failure, it also prints the status code.
+
+
+Parameters:
+
+ s - the status value.
+
+ Eol - if TRUE, causes an end of line to also be printed.
+
+
+Return Values:
+
+
+--*/
+{
+ if (NT_SUCCESS(s)) {
+ SampDiagPrint(DISPLAY_CACHE, ("Success"));
+ } else {
+ SampDiagPrint(DISPLAY_CACHE, ("Failure - Status: 0x%lx", s));
+ }
+
+ if (Eol) {
+ SampDiagPrint(DISPLAY_CACHE, ("\n"));
+ }
+ return;
+}
+
+
+VOID
+SampDisplayDiagnostic(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine provides internal diagnostics and test capabilities.
+
+ This routine is called whenever an account called SAMP_DIAG is
+ modified such that the display cache requires updating.
+
+ This routine breakpoints, allowing diagnostic parameters to be set.
+
+
+Parameters:
+
+ None.
+
+Return Values:
+
+
+--*/
+{
+
+ ULONG
+ DiagnosticRunCount = 0,
+ DiagnosticsToRun = 0;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: SampDisplayDiagnostic() called.\n"
+ " Breakpointing to allow diagnostic parameters to be set.\n"
+ " Diagnostic Flag Word: 0x%lx\n"
+ " Diagnostic values: \n"
+ " SamIEnumerateAccountRids: 0x%lx\n"
+ "\n",
+ &DiagnosticsToRun,
+ SAMP_DISPLAY_DIAG_ENUM_RIDS
+ ) );
+ DbgBreakPoint();
+
+ if ((DiagnosticsToRun & SAMP_DISPLAY_DIAG_ENUM_RIDS) != 0) {
+ SampDisplayDiagEnumRids();
+ DiagnosticRunCount++;
+ }
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: SampDisplayDiagnostic() - %d diagnostics run.\n",
+ DiagnosticRunCount) );
+
+
+ return;
+}
+
+
+VOID
+SampDisplayDiagEnumRids(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine tests the RID table enumeration api
+ (SamIEnumerateAccountRids()).
+
+
+Parameters:
+
+ None.
+
+Return Values:
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ i,
+ ReturnCount,
+ LastRid;
+
+ PULONG
+ AccountRids;
+
+ SAMPR_HANDLE
+ Server,
+ Domain;
+
+ SampDiagPrint(DISPLAY_CACHE,
+ ("SAM: Testing SamIEnumerateAccountRids...\n"));
+
+
+ NtStatus = SamIConnect( L"", //ServerName
+ &Server, //ServerHandle
+ 0, //DesiredAccess
+ TRUE //TrustedClient
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = SamrOpenDomain( Server,
+ 0, //DesiredAccess
+ SampDefinedDomains[1].Sid, //DomainId
+ &Domain
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ ///////////////////////////////////////////////////////////////////
+ // //
+ // Enumerate both USERs and GLOBAL GROUPs //
+ // //
+ ///////////////////////////////////////////////////////////////////
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating first three users and global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT | SAM_GLOBAL_GROUP_ACCOUNT,
+ 0, //StartingRid
+ 3*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 100 accounts
+ //
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 100 users and global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT | SAM_GLOBAL_GROUP_ACCOUNT,
+ LastRid, //StartingRid
+ 100*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 4000 accounts
+ //
+
+
+ if (NtStatus == STATUS_MORE_ENTRIES) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 4000 users and global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT | SAM_GLOBAL_GROUP_ACCOUNT,
+ LastRid, //StartingRid
+ 400*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ i=0;
+ if (ReturnCount > 8) {
+ for (i=0; i<ReturnCount-8; i=i+8) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+ AccountRids[i+0], AccountRids[i+1],
+ AccountRids[i+2], AccountRids[i+3],
+ AccountRids[i+4], AccountRids[i+5],
+ AccountRids[i+6], AccountRids[i+7] ));
+ }
+ }
+ for (i=i; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx ", AccountRids[i]));
+ }
+ SampDiagPrint(DISPLAY_CACHE, ("\n"));
+ LastRid = AccountRids[i];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////
+ // //
+ // Now try just USER accounts //
+ // //
+ ///////////////////////////////////////////////////////////////////
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating first three users ...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT,
+ 0, //StartingRid
+ 3*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 100 accounts
+ //
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 100 users...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT,
+ LastRid, //StartingRid
+ 100*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 4000 accounts
+ //
+
+
+ if (NtStatus == STATUS_MORE_ENTRIES) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 4000 users...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_USER_ACCOUNT,
+ LastRid, //StartingRid
+ 400*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ i=0;
+ if (ReturnCount > 8) {
+ for (i=0; i<ReturnCount-8; i=i+8) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+ AccountRids[i+0], AccountRids[i+1],
+ AccountRids[i+2], AccountRids[i+3],
+ AccountRids[i+4], AccountRids[i+5],
+ AccountRids[i+6], AccountRids[i+7] ));
+ }
+ }
+ for (i=i; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx ", AccountRids[i]));
+ }
+ SampDiagPrint(DISPLAY_CACHE, ("\n"));
+ LastRid = AccountRids[i];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////
+ // //
+ // Now just try GLOBAL GROUPs //
+ // //
+ ///////////////////////////////////////////////////////////////////
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating first three global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ 0, //StartingRid
+ 3*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 100 accounts
+ //
+
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 100 global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ LastRid, //StartingRid
+ 100*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ for (i=0; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx\n", AccountRids[i]));
+ }
+ LastRid = AccountRids[ReturnCount-1];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+ //
+ // Now try to continue for another 4000 accounts
+ //
+
+
+ if (NtStatus == STATUS_MORE_ENTRIES) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 4000 global groups...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ LastRid, //StartingRid
+ 4000*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ ASSERT(AccountRids != NULL);
+ i=0;
+ if (ReturnCount > 8) {
+ for (i=0; i<ReturnCount-8; i=i+8) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+ AccountRids[i+0], AccountRids[i+1],
+ AccountRids[i+2], AccountRids[i+3],
+ AccountRids[i+4], AccountRids[i+5],
+ AccountRids[i+6], AccountRids[i+7] ));
+ }
+ }
+ for (i=i; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx ", AccountRids[i]));
+ }
+ SampDiagPrint(DISPLAY_CACHE, ("\n"));
+ LastRid = AccountRids[i];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+ }
+
+
+
+ //
+ // Now try to continue an enumeration from the very last RID.
+ // At one point in time, this use to restart the enumeration
+ // (which was not correct behaviour). It should indicate that
+ // there are no more entries.
+ //
+
+ SampDiagPrint(DISPLAY_CACHE,
+ (" Enumerating next 5 global groups.."
+ " (should be none to enumerate) ...") );
+ NtStatus = SamIEnumerateAccountRids( Domain,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ LastRid, //StartingRid
+ 5*sizeof(ULONG), //PreferedMaximumLength
+ &ReturnCount,
+ &AccountRids
+ );
+
+ SampDisplayDiagnosticSuccess( NtStatus, TRUE );
+ if (NT_SUCCESS(NtStatus)) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" %d entries returned.\n", ReturnCount));
+ if (ReturnCount > 0) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" ERROR - there should be no RIDs returned ! !\n"));
+ ASSERT(AccountRids != NULL);
+ i=0;
+ if (ReturnCount > 8) {
+ for (i=0; i<ReturnCount-8; i=i+8) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
+ AccountRids[i+0], AccountRids[i+1],
+ AccountRids[i+2], AccountRids[i+3],
+ AccountRids[i+4], AccountRids[i+5],
+ AccountRids[i+6], AccountRids[i+7] ));
+ }
+ }
+ for (i=i; i<ReturnCount; i++) {
+ SampDiagPrint(DISPLAY_CACHE,
+ (" 0x%lx ", AccountRids[i]));
+ }
+ SampDiagPrint(DISPLAY_CACHE, ("\n"));
+ LastRid = AccountRids[i];
+ MIDL_user_free(AccountRids);
+ } else {
+ ASSERT(AccountRids == NULL);
+ }
+ }
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////
+ // //
+ // Hmmm, can't close handles because it will conflict //
+ // with the rxact state we already have going. Bummer. //
+ // //
+ ///////////////////////////////////////////////////////////////////
+
+ //NtStatus = SamrCloseHandle( &Domain ); ASSERT(NT_SUCCESS(NtStatus));
+ //NtStatus = SamrCloseHandle( &Server ); ASSERT(NT_SUCCESS(NtStatus));
+
+ return;
+}
+
+#endif //SAMP_DIAGNOSTICS
diff --git a/private/newsam/server/domain.c b/private/newsam/server/domain.c
new file mode 100644
index 000000000..e4ed0fbcb
--- /dev/null
+++ b/private/newsam/server/domain.c
@@ -0,0 +1,8012 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ domain.c
+
+Abstract:
+
+ This file contains services related to the SAM "domain" object.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include "ntlsa.h"
+#include "lmcons.h" // LM20_PWLEN
+#include "msaudite.h"
+#include <nlrepl.h> // I_NetNotifyMachineAccount prototype
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampInitializeSingleDomain(
+ ULONG Index
+ );
+
+NTSTATUS
+SampOpenDomainKey(
+ IN PSAMP_OBJECT DomainContext,
+ IN PRPC_SID DomainId
+ );
+
+NTSTATUS
+SampSetDomainPolicy( VOID );
+
+
+NTSTATUS
+SampBuildDomainKeyName(
+ OUT PUNICODE_STRING DomainKeyName,
+ IN PUNICODE_STRING DomainName OPTIONAL
+ );
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// RPC Dispatch routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SamrOpenDomain(
+ IN SAMPR_HANDLE ServerHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN PRPC_SID DomainId,
+ OUT SAMPR_HANDLE *DomainHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service is the RPC dispatch routine for SamrOpenDomain().
+
+Arguments:
+
+ ServerHandle - An active context handle to a Server object.
+
+ Access desired to the domain.
+
+ DomainId - The SID of the domain to open.
+
+ DomainHandle - If successful, will receive the context handle value
+ for the newly opened domain. Otherwise, NULL is returned.
+
+Return Value:
+
+ STATUS_SUCCESS - The object has been successfully openned.
+
+ STATUS_INSUFFICIENT_RESOURCES - The SAM server processes doesn't
+ have sufficient resources to process or accept another connection
+ at this time.
+
+ Other values as may be returned from:
+
+ NtAccessCheckAndAuditAlarm()
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT ServerContext, DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+ //
+ // Grab a read lock
+ //
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to server object.
+ //
+
+ ServerContext = (PSAMP_OBJECT)ServerHandle;
+ NtStatus = SampLookupContext(
+ ServerContext,
+ SAM_SERVER_LOOKUP_DOMAIN, // DesiredAccess
+ SampServerObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Try to create a context for the domain.
+ //
+
+ DomainContext = SampCreateContext(
+ SampDomainObjectType,
+ ServerContext->TrustedClient
+ );
+
+ if (DomainContext != NULL) {
+
+ //
+ // Open the specified domain's registry key.
+ //
+
+ NtStatus = SampOpenDomainKey(
+ DomainContext,
+ DomainId
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Reference the object for the validation
+ //
+
+ SampReferenceContext(DomainContext);
+
+ //
+ // Validate the caller's access.
+ //
+
+ NtStatus = SampValidateObjectAccess(
+ DomainContext, //Context
+ DesiredAccess, //DesiredAccess
+ FALSE //ObjectCreation
+ );
+
+ //
+ // Dereference object, discarding any changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext(DomainContext, FALSE);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // if we didn't pass the access test, then free up the context
+ // block and return the error status returned from the access
+ // validation routine. Otherwise, return the context handle
+ // value.
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( DomainContext );
+ } else {
+ (*DomainHandle) = DomainContext;
+ }
+ }
+
+ } else {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+
+ //
+ // De-reference the server object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( ServerContext, FALSE );
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+ return(NtStatus);
+
+
+}
+
+
+NTSTATUS
+SamrQueryInformationDomain2(
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ OUT PSAMPR_DOMAIN_INFO_BUFFER *Buffer
+ )
+{
+ //
+ // This is a thin veil to SamrQueryInformationDomain().
+ // This is needed so that new-release systems can call
+ // this routine without the danger of passing an info
+ // level that release 1.0 systems didn't understand.
+ //
+
+ return( SamrQueryInformationDomain(DomainHandle, DomainInformationClass, Buffer ) );
+}
+
+NTSTATUS
+SamrQueryInformationDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ OUT PSAMPR_DOMAIN_INFO_BUFFER *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves information about a domain object.
+
+Arguments:
+
+ DomainHandle - A handle obtained via a previous call to SamrOpenDomain().
+
+ DomainInformationClass - Indicates the type of information to retrieve.
+
+ Buffer - Receives the requested information. Several blocks of memory
+ will be returned: (one) containing a pointer to the (second) which
+ contains the requested information structure. This block may contain
+ pointers, which will point to other blocks of allocated memory, such
+ as string buffers. All of these blocks of memory must be
+ (independently) deallocated using MIDL_user_free().
+
+Return Value:
+
+
+ STATUS_SUCCESS - Indicates the service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller's handle does not have the appropriate
+ access to the object.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ PSAMP_DEFINED_DOMAINS Domain;
+ ULONG i;
+
+ //
+ // Used for tracking allocated blocks of memory - so we can deallocate
+ // them in case of error. Don't exceed this number of allocated buffers.
+ // ||
+ // vv
+ PVOID AllocatedBuffer[10];
+ ULONG AllocatedBufferCount = 0;
+
+#define RegisterBuffer(Buffer) \
+ if ((Buffer) != NULL) { \
+ ASSERT(AllocatedBufferCount < sizeof(AllocatedBuffer)/sizeof(*AllocatedBuffer)); \
+ AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
+ }
+
+#define AllocateBuffer(BufferPointer, Size) \
+ (BufferPointer) = MIDL_user_allocate((Size)); \
+ RegisterBuffer((BufferPointer));
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (Buffer != NULL);
+ ASSERT ((*Buffer) == NULL);
+
+
+
+ //
+ // Set the desired access based upon the Info class
+ //
+
+ switch (DomainInformationClass) {
+
+ case DomainPasswordInformation:
+ case DomainLockoutInformation:
+
+ DesiredAccess = DOMAIN_READ_PASSWORD_PARAMETERS;
+ break;
+
+
+ case DomainGeneralInformation:
+ case DomainLogoffInformation:
+ case DomainOemInformation:
+ case DomainNameInformation:
+ case DomainServerRoleInformation:
+ case DomainReplicationInformation:
+ case DomainModifiedInformation:
+ case DomainStateInformation:
+ case DomainUasInformation:
+ case DomainModifiedInformation2:
+
+ DesiredAccess = DOMAIN_READ_OTHER_PARAMETERS;
+ break;
+
+
+ case DomainGeneralInformation2:
+
+ DesiredAccess = DOMAIN_READ_PASSWORD_PARAMETERS |
+ DOMAIN_READ_OTHER_PARAMETERS;
+ break;
+
+ default:
+ return(STATUS_INVALID_INFO_CLASS);
+ } // end_switch
+
+
+
+ //
+ // Allocate the info structure
+ //
+
+ AllocateBuffer(*Buffer, sizeof(SAMPR_DOMAIN_INFO_BUFFER) );
+ if ((*Buffer) == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DesiredAccess,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+
+ //
+ // case on the type information requested
+ //
+
+ switch (DomainInformationClass) {
+
+ case DomainUasInformation:
+
+ (*Buffer)->General.UasCompatibilityRequired =
+ Domain->UnmodifiedFixed.UasCompatibilityRequired;
+
+ break;
+
+
+ case DomainGeneralInformation2:
+
+
+ (*Buffer)->General2.LockoutDuration =
+ Domain->UnmodifiedFixed.LockoutDuration;
+
+ (*Buffer)->General2.LockoutObservationWindow =
+ Domain->UnmodifiedFixed.LockoutObservationWindow;
+
+ (*Buffer)->General2.LockoutThreshold =
+ Domain->UnmodifiedFixed.LockoutThreshold;
+
+
+
+ //
+ // WARNING - GeneralInformation2 falls into the
+ // GeneralInformation code for the rest of its processing.
+ // This action assumes that the beginning of a GeneralInformation2
+ // structure is a GeneralInformation structure !!!
+ //
+
+ // don't break;
+
+ case DomainGeneralInformation:
+
+ ///////////////////////////////////////////////////////
+ // //
+ // Warning, the previous case falls into this case. //
+ // Be aware of this when working on this code. //
+ // //
+ ///////////////////////////////////////////////////////
+
+ (*Buffer)->General.ForceLogoff =
+ *((POLD_LARGE_INTEGER)&Domain->UnmodifiedFixed.ForceLogoff);
+
+ (*Buffer)->General.DomainModifiedCount =
+ *((POLD_LARGE_INTEGER)&Domain->UnmodifiedFixed.ModifiedCount);
+
+ (*Buffer)->General.DomainServerState =
+ Domain->UnmodifiedFixed.ServerState;
+
+ (*Buffer)->General.DomainServerRole =
+ Domain->UnmodifiedFixed.ServerRole;
+
+ (*Buffer)->General.UasCompatibilityRequired =
+ Domain->UnmodifiedFixed.UasCompatibilityRequired;
+
+
+ //
+ // Copy the domain name from our in-memory structure.
+ //
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES; // Default status if the allocate fails
+
+ AllocateBuffer((*Buffer)->General.DomainName.Buffer,
+ Domain->ExternalName.MaximumLength );
+
+ if ((*Buffer)->General.DomainName.Buffer != NULL) {
+
+ NtStatus = STATUS_SUCCESS;
+
+ (*Buffer)->General.DomainName.Length = Domain->ExternalName.Length;
+ (*Buffer)->General.DomainName.MaximumLength = Domain->ExternalName.MaximumLength;
+
+ RtlCopyMemory((*Buffer)->General.DomainName.Buffer,
+ Domain->ExternalName.Buffer,
+ Domain->ExternalName.MaximumLength
+ );
+
+ //
+ // Now get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_OEM_INFORMATION,
+ TRUE,
+ (PUNICODE_STRING)&((*Buffer)->General.OemInformation)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->General.OemInformation.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_REPLICA,
+ TRUE,
+ (PUNICODE_STRING)&((*Buffer)->General.ReplicaSourceNodeName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->General.ReplicaSourceNodeName.Buffer);
+ }
+ }
+ }
+
+
+ //
+ // Get the count of users and groups
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampRetrieveAccountCounts(
+ &(*Buffer)->General.UserCount,
+ &(*Buffer)->General.GroupCount,
+ &(*Buffer)->General.AliasCount );
+ }
+
+
+
+ break;
+
+
+ case DomainPasswordInformation:
+
+ (*Buffer)->Password.MinPasswordLength =
+ Domain->UnmodifiedFixed.MinPasswordLength;
+ (*Buffer)->Password.PasswordHistoryLength =
+ Domain->UnmodifiedFixed.PasswordHistoryLength;
+ (*Buffer)->Password.PasswordProperties =
+ Domain->UnmodifiedFixed.PasswordProperties;
+ (*Buffer)->Password.MaxPasswordAge =
+ Domain->UnmodifiedFixed.MaxPasswordAge;
+ (*Buffer)->Password.MinPasswordAge =
+ Domain->UnmodifiedFixed.MinPasswordAge;
+
+ break;
+
+
+ case DomainLogoffInformation:
+
+ (*Buffer)->Logoff.ForceLogoff =
+ Domain->UnmodifiedFixed.ForceLogoff;
+
+ break;
+
+ case DomainOemInformation:
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_OEM_INFORMATION,
+ TRUE,
+ (PUNICODE_STRING)&((*Buffer)->Oem.OemInformation)
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->Oem.OemInformation.Buffer);
+ }
+
+ break;
+
+ case DomainNameInformation:
+
+ //
+ // Copy the domain name from our in-memory structure.
+ //
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES; // Default status if the allocate fails
+
+ AllocateBuffer((*Buffer)->Name.DomainName.Buffer,
+ Domain->ExternalName.MaximumLength);
+
+ if ((*Buffer)->Name.DomainName.Buffer != NULL) {
+
+ NtStatus = STATUS_SUCCESS;
+
+ (*Buffer)->Name.DomainName.Length = Domain->ExternalName.Length;
+ (*Buffer)->Name.DomainName.MaximumLength = Domain->ExternalName.MaximumLength;
+
+ RtlCopyMemory((*Buffer)->Name.DomainName.Buffer,
+ Domain->ExternalName.Buffer,
+ Domain->ExternalName.MaximumLength
+ );
+ }
+
+ break;
+
+ case DomainServerRoleInformation:
+
+ (*Buffer)->Role.DomainServerRole =
+ Domain->UnmodifiedFixed.ServerRole;
+
+ break;
+
+ case DomainReplicationInformation:
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_REPLICA,
+ TRUE,
+ (PUNICODE_STRING)&((*Buffer)->Replication.ReplicaSourceNodeName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ RegisterBuffer((*Buffer)->Replication.ReplicaSourceNodeName.Buffer);
+ }
+
+ break;
+
+ case DomainModifiedInformation2:
+
+ (*Buffer)->Modified2.ModifiedCountAtLastPromotion =
+ Domain->UnmodifiedFixed.ModifiedCountAtLastPromotion;
+
+ //
+ // This case falls through to DomainModifiedInformation
+ //
+
+
+ case DomainModifiedInformation:
+
+ /////////////////////////////////
+ // //
+ // WARNING //
+ // //
+ // The previous case falls //
+ // into this one. //
+ // //
+ /////////////////////////////////
+
+ (*Buffer)->Modified.DomainModifiedCount =
+ Domain->UnmodifiedFixed.ModifiedCount;
+ (*Buffer)->Modified.CreationTime =
+ Domain->UnmodifiedFixed.CreationTime;
+
+ break;
+
+ case DomainStateInformation:
+
+ (*Buffer)->State.DomainServerState =
+ Domain->UnmodifiedFixed.ServerState;
+
+ break;
+
+
+ case DomainLockoutInformation:
+
+ (*Buffer)->Lockout.LockoutDuration =
+ Domain->UnmodifiedFixed.LockoutDuration;
+ (*Buffer)->Lockout.LockoutObservationWindow =
+ Domain->UnmodifiedFixed.LockoutObservationWindow;
+ (*Buffer)->Lockout.LockoutThreshold =
+ Domain->UnmodifiedFixed.LockoutThreshold;
+
+ break;
+
+ }
+
+
+
+
+
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+ //
+ // If we didn't succeed, free any allocated memory
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ for ( i=0; i<AllocatedBufferCount ; i++ ) {
+ MIDL_user_free( AllocatedBuffer[i] );
+ }
+ *Buffer = NULL;
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS SamrSetInformationDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
+ IN PSAMPR_DOMAIN_INFO_BUFFER DomainInformation
+ )
+
+/*++
+
+Routine Description:
+
+ This API sets the domain information to the values passed in the
+ buffer.
+
+
+Arguments:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DomainInformationClass - Class of information desired. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ------------------------- ----------------------------
+
+ DomainPasswordInformation DOMAIN_WRITE_PASSWORD_PARAMS
+
+ DomainGeneralInformation (not setable)
+
+ DomainLogoffInformation DOMAIN_WRITE_OTHER_PARAMETERS
+
+ DomainOemInformation DOMAIN_WRITE_OTHER_PARAMETERS
+
+ DomainNameInformation (Not valid for set operations.)
+
+ DomainServerRoleInformation DOMAIN_ADMINISTER_SERVER
+
+ DomainReplicationInformation DOMAIN_ADMINISTER_SERVER
+
+ DomainModifiedInformation (not valid for set operations)
+
+ DomainStateInformation DOMAIN_ADMINISTER_SERVER
+
+ DomainUasInformation DOMAIN_WRITE_OTHER_PARAMETERS
+
+ DomainGeneralInformation2 (not setable)
+
+ DomainLockoutInformation DOMAIN_WRITE_PASSWORD_PARAMS
+
+
+ DomainInformation - Buffer where the domain information can be
+ found.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
+ correct state (disabled or enabled) to perform the requested
+ operation. The domain server must be disabled before role
+ changes can be made.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ PSAMP_DEFINED_DOMAINS Domain;
+ BOOLEAN ReplicateImmediately = FALSE;
+ ULONG DomainIndex;
+ LARGE_INTEGER PromotionIncrement = DOMAIN_PROMOTION_INCREMENT;
+
+#if DBG
+
+ LARGE_INTEGER
+ TmpTime;
+
+ TIME_FIELDS
+ DT1, DT2, DT3, DT4;
+
+#endif //DBG
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (DomainInformation != NULL);
+
+
+
+ //
+ // Set the desired access based upon the Info class
+ //
+
+ switch (DomainInformationClass) {
+
+ case DomainPasswordInformation:
+ case DomainLockoutInformation:
+
+ ReplicateImmediately = TRUE;
+ DesiredAccess = DOMAIN_WRITE_PASSWORD_PARAMS;
+ break;
+
+
+ case DomainLogoffInformation:
+ case DomainOemInformation:
+
+ DesiredAccess = DOMAIN_WRITE_OTHER_PARAMETERS;
+ break;
+
+
+ case DomainReplicationInformation:
+ case DomainStateInformation:
+ case DomainServerRoleInformation:
+
+ DesiredAccess = DOMAIN_ADMINISTER_SERVER;
+ break;
+
+
+ case DomainModifiedInformation:
+ case DomainNameInformation:
+ case DomainGeneralInformation:
+ case DomainGeneralInformation2:
+ default:
+
+ return(STATUS_INVALID_INFO_CLASS);
+
+ } // end_switch
+
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DesiredAccess,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if ( ( NtStatus == STATUS_INVALID_DOMAIN_ROLE ) &&
+ ( DomainInformationClass == DomainServerRoleInformation ) ) {
+
+
+ //
+ // Non-trusted client isn't being allowed to write to backup
+ // server. But admin MUST be able to set the server role back
+ // to primary. So temporarily pretend that administering the
+ // server isn't a write operation, just long enough for the
+ // LookupContext to succeed.
+ //
+ // Note that before returning INVALID_DOMAIN_ROLE, LookupContext
+ // verified that the caller otherwise has proper access - if the
+ // caller didn't, then we would have gotten a different error.
+ //
+
+ SampObjectInformation[ SampDomainObjectType ].WriteOperations &=
+ ~DOMAIN_ADMINISTER_SERVER;
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DesiredAccess,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ SampObjectInformation[ SampDomainObjectType ].WriteOperations |=
+ DOMAIN_ADMINISTER_SERVER;
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ DomainIndex = DomainContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // case on the type information provided
+ //
+
+ switch (DomainInformationClass) {
+
+ case DomainPasswordInformation:
+
+ if (
+ ( DomainInformation->Password.PasswordHistoryLength >
+ SAMP_MAXIMUM_PASSWORD_HISTORY_LENGTH ) ||
+
+ ( DomainInformation->Password.MinPasswordAge.QuadPart > 0) ||
+
+ ( DomainInformation->Password.MaxPasswordAge.QuadPart > 0) ||
+
+ ( DomainInformation->Password.MaxPasswordAge.QuadPart >=
+ DomainInformation->Password.MinPasswordAge.QuadPart) ||
+
+ ( ( Domain->UnmodifiedFixed.UasCompatibilityRequired ) &&
+ ( DomainInformation->Password.MinPasswordLength > LM20_PWLEN ) )
+ ) {
+
+ //
+ // One of the following is wrong:
+ //
+ // 1. The history length is larger than we can allow (and
+ // still ensure everything will fit in a string)
+ // 2. The MinPasswordAge isn't a delta time
+ // 3. The MaxPasswordAge isn't a delta time
+ // 4. The MaxPasswordAge isn't greater than the
+ // MinPasswordAge (they're negative delta times)
+ // 5. UAS compatibility is required, but MinPasswordLength
+ // is greater than LM's max password length.
+ //
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ Domain->CurrentFixed.MinPasswordLength =
+ DomainInformation->Password.MinPasswordLength;
+
+ Domain->CurrentFixed.PasswordHistoryLength =
+ DomainInformation->Password.PasswordHistoryLength;
+
+ Domain->CurrentFixed.PasswordProperties =
+ DomainInformation->Password.PasswordProperties;
+
+ Domain->CurrentFixed.MaxPasswordAge =
+ DomainInformation->Password.MaxPasswordAge;
+
+ Domain->CurrentFixed.MinPasswordAge =
+ DomainInformation->Password.MinPasswordAge;
+ }
+
+ break;
+
+
+ case DomainLogoffInformation:
+
+ Domain->CurrentFixed.ForceLogoff =
+ DomainInformation->Logoff.ForceLogoff;
+
+ break;
+
+ case DomainOemInformation:
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_OEM_INFORMATION,
+ (PUNICODE_STRING)&(DomainInformation->Oem.OemInformation)
+ );
+ break;
+
+ case DomainServerRoleInformation:
+
+ //
+ // Only NTAS systems can be demoted.
+ //
+
+ if (SampProductType != NtProductLanManNt) {
+
+ if ( (DomainInformation->Role.DomainServerRole ==
+ DomainServerRoleBackup) //Trying to demote
+ ) {
+
+ NtStatus = STATUS_INVALID_DOMAIN_ROLE;
+ break;
+
+ }
+ }
+
+ //
+ // Are we being promoted to primary?
+ //
+
+ if ( (Domain->UnmodifiedFixed.ServerRole == DomainServerRoleBackup)
+ &&
+ (DomainInformation->Role.DomainServerRole == DomainServerRolePrimary)
+ ) {
+
+
+ //
+ // We are being promoted. Increment the ModifiedCount
+ // by the promotion increment.
+ //
+
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart +
+ PromotionIncrement.QuadPart;
+
+ Domain->CurrentFixed.ModifiedCountAtLastPromotion =
+ Domain->CurrentFixed.ModifiedCount;
+
+ Domain->CurrentFixed.ModifiedCountAtLastPromotion.QuadPart += 1;
+
+ SampDiagPrint( DISPLAY_ROLE_CHANGES,
+ ("SAM: Role Change: Promoting to primary\n"
+ " Old ModifiedId: [0x%lx, 0x%lx]\n"
+ " New ModifiedId: [0x%lx, 0x%lx]\n",
+ Domain->UnmodifiedFixed.ModifiedCount.HighPart,
+ Domain->UnmodifiedFixed.ModifiedCount.LowPart,
+ Domain->CurrentFixed.ModifiedCount.HighPart,
+ Domain->CurrentFixed.ModifiedCount.LowPart)
+ );
+#if DBG
+ } else {
+
+
+ SampDiagPrint( DISPLAY_ROLE_CHANGES,
+ ("SAM: Role Change: Demoting to backup\n"
+ " ModifiedId: [0x%lx, 0x%lx]\n",
+ Domain->CurrentFixed.ModifiedCount.HighPart,
+ Domain->CurrentFixed.ModifiedCount.LowPart )
+ );
+
+#endif //DBG
+ }
+
+
+ Domain->CurrentFixed.ServerRole =
+ DomainInformation->Role.DomainServerRole;
+
+
+ break;
+
+ case DomainReplicationInformation:
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ DomainContext,
+ SAMP_DOMAIN_REPLICA,
+ (PUNICODE_STRING)&(DomainInformation->Replication.ReplicaSourceNodeName) // Body
+ );
+ break;
+
+ case DomainStateInformation:
+
+ Domain->CurrentFixed.ServerState =
+ DomainInformation->State.DomainServerState;
+
+ break;
+
+ case DomainLockoutInformation:
+
+ if (
+ ( DomainInformation->Lockout.LockoutDuration.QuadPart > 0) ||
+
+ ( DomainInformation->Lockout.LockoutObservationWindow.QuadPart > 0 )
+
+ ) {
+
+ //
+ // One of the following is wrong:
+ //
+ // 1. The LockoutDuration isn't a delta time (or zero).
+ // 2. The LockoutObservationWindow isn't a delta time (or zero).
+ //
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+#if DBG
+ TmpTime.QuadPart = -Domain->CurrentFixed.LockoutObservationWindow.QuadPart;
+ RtlTimeToElapsedTimeFields( &TmpTime, &DT1 );
+ TmpTime.QuadPart = -Domain->CurrentFixed.LockoutDuration.QuadPart;
+ RtlTimeToElapsedTimeFields( &TmpTime, &DT2 );
+ TmpTime.QuadPart = -DomainInformation->Lockout.LockoutObservationWindow.QuadPart;
+ RtlTimeToElapsedTimeFields( &TmpTime, &DT3 );
+ TmpTime.QuadPart = -DomainInformation->Lockout.LockoutDuration.QuadPart;
+ RtlTimeToElapsedTimeFields( &TmpTime, &DT4 );
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationDomain: Changing Lockout values.\n"
+ " Old:\n"
+ " Window : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " Threshold: %ld\n"
+ " New:\n"
+ " Window : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " Threshold: %ld\n",
+ Domain->CurrentFixed.LockoutObservationWindow.HighPart,
+ Domain->CurrentFixed.LockoutObservationWindow.LowPart,
+ DT1.Hour, DT1.Minute, DT1.Second,
+ Domain->CurrentFixed.LockoutDuration.HighPart,
+ Domain->CurrentFixed.LockoutDuration.LowPart,
+ DT2.Hour, DT2.Minute, DT2.Second,
+ Domain->CurrentFixed.LockoutThreshold,
+ DomainInformation->Lockout.LockoutObservationWindow.HighPart,
+ DomainInformation->Lockout.LockoutObservationWindow.LowPart,
+ DT3.Hour, DT3.Minute, DT3.Second,
+ DomainInformation->Lockout.LockoutDuration.HighPart,
+ DomainInformation->Lockout.LockoutDuration.LowPart,
+ DT4.Hour, DT4.Minute, DT4.Second,
+ DomainInformation->Lockout.LockoutThreshold)
+ );
+#endif //DBG
+
+ Domain->CurrentFixed.LockoutDuration =
+ DomainInformation->Lockout.LockoutDuration;
+
+ Domain->CurrentFixed.LockoutObservationWindow =
+ DomainInformation->Lockout.LockoutObservationWindow;
+
+ Domain->CurrentFixed.LockoutThreshold =
+ DomainInformation->Lockout.LockoutThreshold;
+
+
+
+ }
+
+ break;
+ }
+
+
+ //
+ // Generate an audit if necessary
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(DomainIndex)) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_DOMAIN_POLICY_CHANGE, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ NULL, // Account Name (not used)
+ &Domain->ExternalName, // Domain
+ NULL, // Account Rid (not used)
+ NULL // Privileges used
+ );
+
+ }
+
+
+
+
+ //
+ // Have the changes written out to the RXACT, and
+ // de-reference the object.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampDeReferenceContext( DomainContext, TRUE );
+
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ }
+ }
+
+
+
+
+
+ //
+ // Commit changes, if successful, and notify Netlogon of changes.
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChange,
+ SecurityDbObjectSamDomain,
+ 0L,
+ (PUNICODE_STRING) NULL,
+ (DWORD) ReplicateImmediately,
+ NULL // Delta data
+ );
+ }
+ }
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampCreateGroupInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN WriteLockHeld,
+ OUT SAMPR_HANDLE *GroupHandle,
+ IN OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This API creates a new group in the account database. Initially,
+ this group does not contain any users. Note that creating a group
+ is a protected operation, and requires the DOMAIN_CREATE_GROUP
+ access type.
+
+ This call returns a handle to the newly created group that may be
+ used for successive operations on the group. This handle may be
+ closed with the SamCloseHandle API.
+
+ A newly created group will have the following initial field value
+ settings. If another value is desired, it must be explicitly
+ changed using the group object manipulation services.
+
+
+ Name - The name of the group will be as specified in the
+ creation API.
+
+ Attributes - The following attributes will be set:
+
+ Mandatory
+ EnabledByDefault
+
+ MemberCount - Zero. Initially the group has no members.
+
+ RelativeId - will be a uniquelly allocated ID.
+
+
+
+Arguments:
+
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+
+ AccountName - Points to the name of the new account. A case-insensitive
+ comparison must not find a group or user with this name already defined.
+
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the group.
+
+ GroupHandle - Receives a handle referencing the newly created
+ group. This handle will be required in successive calls to
+ operate on the group.
+
+ RelativeId - Receives the relative ID of the newly created group
+ account. The SID of the new group account is this relative ID
+ value prefixed with the domain's SID value. This RID will be a
+ new, uniquely allocated value - unless a non-zero RID was passed
+ in, in which case that RID is used (nothing is done if a group
+ with that RID already exists).
+
+
+Return Value:
+
+ STATUS_SUCCESS - The group was added successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
+ contains non-printable characters.
+
+ STATUS_GROUP_EXISTS - The name is already in use as a group.
+
+ STATUS_USER_EXISTS - The name is already in use as a user.
+
+ STATUS_ALIAS_EXISTS - The name is already in use as an alias.
+
+ STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
+ correct state (disabled or enabled) to perform the requested
+ operation. The domain server must be enabled before groups
+ can be created in it.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation. The domain server must be a primary server to
+ create group accounts.
+
+
+
+
+--*/
+
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS, IgnoreStatus;
+ PSAMP_OBJECT DomainContext, GroupContext;
+ SAMP_OBJECT_TYPE FoundType;
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ ULONG NewAccountRid, NewSecurityDescriptorLength;
+ UNICODE_STRING KeyName;
+ PSECURITY_DESCRIPTOR NewSecurityDescriptor;
+ SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed;
+ PRIVILEGE_SET PrivilegeSet;
+
+
+ if (GroupHandle == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Initialize the privilege set.
+ //
+
+ PrivilegeSet.PrivilegeCount = 0;
+ PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid(0L);
+ PrivilegeSet.Privilege[0].Attributes = 0;
+
+ //
+ // Make sure a name was provided
+ //
+
+ if (AccountName == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Length > AccountName->MaximumLength) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Buffer == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+
+
+
+ if ( !WriteLockHeld ) {
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+
+ //
+ // Validate type of, and access to domain object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_CREATE_GROUP, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ GroupContext = NULL;
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ //
+ // Make sure the name is valid and not already in use before we
+ // use it to create the new group.
+ //
+
+ NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+ if ( (*RelativeId) == 0 ) {
+
+ //
+ // No RID specified, so allocate a new account RID
+ //
+
+ NewAccountRid = Domain->CurrentFixed.NextRid;
+
+ Domain->CurrentFixed.NextRid += 1;
+ (*RelativeId) = NewAccountRid;
+
+ } else {
+
+ //
+ // A RID was passed in, so we want to use that rather than
+ // selecting a new one.
+ //
+
+ NewAccountRid = (*RelativeId);
+ }
+
+ //
+ // Increment the group count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampGroupObjectType, TRUE );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Create the registry key that has the group's name.
+ // This simply serves as a name to RID mapping. Save
+ // the name when done; we'll put it in the context.
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ (PUNICODE_STRING)AccountName
+ );
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ NewAccountRid,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString(&KeyName);
+ }
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now create a group context block
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ NewAccountRid,
+ DomainContext->TrustedClient,
+ FALSE, // Account exists
+ &GroupContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The existing reference count of 1 is for RPC.
+ // Reference the context again for the writes we're
+ // about to do to initialize it.
+ //
+
+ SampReferenceContext( GroupContext );
+
+ //
+ // If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
+ //
+
+ if (DesiredAccess & MAXIMUM_ALLOWED) {
+
+ DesiredAccess |= GENERIC_ALL;
+ }
+
+ //
+ // If ACCESS_SYSTEM_SECURITY is requested and we are
+ // a non-trusted client, check that we have
+ // SE_SECURITY_PRIVILEGE.
+ //
+
+ if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
+ (!DomainContext->TrustedClient)) {
+
+ NtStatus = SampRtlWellKnownPrivilegeCheck(
+ TRUE,
+ SE_SECURITY_PRIVILEGE,
+ NULL
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+
+ } else {
+
+ PrivilegeSet.PrivilegeCount = 1;
+ PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE);
+ PrivilegeSet.Privilege[0].Attributes = 0;
+ }
+ }
+
+ //
+ // Make sure the caller can be given the requested access
+ // to the new object
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ GroupContext->GrantedAccess = DesiredAccess;
+
+ RtlMapGenericMask(
+ &GroupContext->GrantedAccess,
+ &SampObjectInformation[SampGroupObjectType].GenericMapping
+ );
+
+ if ((SampObjectInformation[SampGroupObjectType].InvalidMappedAccess
+ & GroupContext->GrantedAccess) != 0) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+ }
+
+ } else {
+ GroupContext = NULL;
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+
+
+ //
+ // Set the V1_fixed attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Create the V1_Fixed key
+ //
+
+ V1Fixed.RelativeId = NewAccountRid;
+ V1Fixed.Attributes = (SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT);
+ V1Fixed.AdminCount = 0;
+ V1Fixed.OperatorCount = 0;
+ V1Fixed.Revision = SAMP_REVISION;
+
+ NtStatus = SampSetFixedAttributes(
+ GroupContext,
+ (PVOID *)&V1Fixed
+ );
+ }
+
+
+ //
+ // Set the SecurityDescriptor attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetNewAccountSecurity(
+ SampGroupObjectType,
+ FALSE, // Not member of ADMINISTRATORS alias
+ DomainContext->TrustedClient,
+ FALSE, //RestrictCreatorAccess
+ NewAccountRid,
+ &NewSecurityDescriptor,
+ &NewSecurityDescriptorLength
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetAccessAttribute(
+ GroupContext,
+ SAMP_GROUP_SECURITY_DESCRIPTOR,
+ NewSecurityDescriptor,
+ NewSecurityDescriptorLength
+ );
+
+ MIDL_user_free( NewSecurityDescriptor );
+ }
+ }
+
+
+ //
+ // Set the NAME attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ GroupContext,
+ SAMP_GROUP_NAME,
+ (PUNICODE_STRING)AccountName
+ );
+ }
+
+
+
+ //
+ // Set the AdminComment attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ GroupContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the MEMBERS attribute (with no members)
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUlongArrayAttribute(
+ GroupContext,
+ SAMP_GROUP_MEMBERS,
+ NULL,
+ 0,
+ 0
+ );
+ }
+ }
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // If we created an object, dereference it. Write out its attributes
+ // if everything was created OK.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ ASSERT(GroupContext != NULL);
+ NtStatus = SampDeReferenceContext( GroupContext, TRUE );
+
+ } else {
+
+ if (GroupContext != NULL) {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( GroupContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+
+
+ //
+ // Commit changes and notify netlogon
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
+
+ //
+ // Update the display information
+ //
+
+ AccountInfo.Name = *((PUNICODE_STRING)AccountName);
+ AccountInfo.Rid = NewAccountRid;
+ AccountInfo.AccountControl = V1Fixed.Attributes;
+ RtlInitUnicodeString(&AccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&AccountInfo.FullName, NULL);
+
+ IgnoreStatus = SampUpdateDisplayInformation(NULL,
+ &AccountInfo,
+ SampGroupObjectType);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbNew,
+ SecurityDbObjectSamGroup,
+ *RelativeId,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ //
+ // Generate Audit
+ //
+
+ if (SampDoAccountAuditing(DomainContext->DomainIndex)) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_GLOBAL_GROUP_CREATED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ (PUNICODE_STRING) AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &GroupContext->TypeBody.User.Rid, // Account Rid
+ &PrivilegeSet // Privileges used
+ );
+ }
+ }
+ }
+
+ //
+ // Return the context handle on success
+ // Delete the context block and return a NULL handle on failure
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT(GroupContext != NULL);
+ (*GroupHandle) = GroupContext;
+
+ } else {
+
+ if (GroupContext != NULL) {
+ SampDeleteContext(GroupContext);
+ }
+
+ (*GroupHandle) = (SAMPR_HANDLE)0;
+ }
+
+
+
+ //
+ // Release the lock
+ //
+
+ if ( !WriteLockHeld ) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrCreateGroupInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT SAMPR_HANDLE *GroupHandle,
+ OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This is just a wrapper for SampCreateGroupInDomain() that ensures that
+ RelativeId points to a RID of zero first.
+
+ A non-zero RID means that SampCreateGroupInDomain() was called by
+ SamICreateAccountByRid(), which specifies a RID to be used.
+
+Parameters:
+
+ Same as SampCreateGroupInDomain().
+
+Return Values:
+
+ Same as SampCreateGroupInDomain().
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+ (*RelativeId) = 0;
+
+ NtStatus = SampCreateGroupInDomain(
+ DomainHandle,
+ AccountName,
+ DesiredAccess,
+ FALSE,
+ GroupHandle,
+ RelativeId
+ );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS SamrEnumerateGroupsInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG CountReturned
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all the groups defined in the account database.
+ Since there may be more groups than can fit into a buffer, the
+ caller is provided with a handle that can be used across calls to
+ the API. On the initial call, EnumerationContext should point to a
+ SAM_ENUMERATE_HANDLE variable that is set to 0.
+
+ If the API returns STATUS_MORE_ENTRIES, then the API should be
+ called again with EnumerationContext. When the API returns
+ STATUS_SUCCESS or any error return, the context becomes invalid for
+ future use.
+
+ This API requires DOMAIN_LIST_GROUPS access to the Domain object.
+
+Arguments:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ EnumerationContext - API specific handle to allow multiple calls
+ (see below). This is a zero based index.
+
+ Buffer - Receives a pointer to the buffer containing the
+ requested information. The information returned is
+ structured as an array of SAM_RID_ENUMERATION data
+ structures. When this information is no longer needed, the
+ buffer must be freed using SamFreeMemory().
+
+ PreferedMaximumLength - Prefered maximum length of returned data
+ (in 8-bit bytes). This is not a hard upper limit, but serves
+ as a guide to the server. Due to data conversion between
+ systems with different natural data sizes, the actual amount
+ of data returned may be greater than this value.
+
+ CountReturned - Number of entries returned.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no addition entries.
+
+ STATUS_MORE_ENTRIES - There are more entries, so call again.
+ This is a successful return.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+ NtStatus = SampEnumerateAccountNamesCommon(
+ DomainHandle,
+ SampGroupObjectType,
+ EnumerationContext,
+ Buffer,
+ PreferedMaximumLength,
+ 0L, // no filter
+ CountReturned
+ );
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampCreateAliasInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN WriteLockHeld,
+ OUT SAMPR_HANDLE *AliasHandle,
+ IN OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This API creates a new alias in the account database. Initially,
+ this alias does not contain any users. Note that creating a alias
+ is a protected operation, and requires the DOMAIN_CREATE_ALIAS
+ access type.
+
+ This call returns a handle to the newly created alias that may be
+ used for successive operations on the alias. This handle may be
+ closed with the SamCloseHandle API.
+
+ A newly created alias will have the following initial field value
+ settings. If another value is desired, it must be explicitly
+ changed using the alias object manipulation services.
+
+
+ Name - The name of the alias will be as specified in the
+ creation API.
+
+ MemberCount - Zero. Initially the alias has no members.
+
+ RelativeId - will be a uniquelly allocated ID.
+
+
+
+Arguments:
+
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+
+ AccountName - Points to the name of the new account. A case-insensitive
+ comparison must not find an alias or user with this name already defined.
+
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the alias.
+
+ AliasHandle - Receives a handle referencing the newly created
+ alias. This handle will be required in successive calls to
+ operate on the alias.
+
+ RelativeId - Receives the relative ID of the newly created alias
+ account. The SID of the new alias account is this relative
+ ID value prefixed with the domain's SID value.
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - The alias was added successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
+ contains non-printable characters.
+
+ STATUS_GROUP_EXISTS - The name is already in use as a group.
+
+ STATUS_USER_EXISTS - The name is already in use as a user.
+
+ STATUS_ALIAS_EXISTS - The name is already in use as an alias.
+
+ STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
+ correct state (disabled or enabled) to perform the requested
+ operation. The domain server must be enabled before aliases
+ can be created in it.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation. The domain server must be a primary server to
+ create alias accounts.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS, IgnoreStatus;
+ PSAMP_OBJECT DomainContext, AliasContext;
+ SAMP_OBJECT_TYPE FoundType;
+ PSAMP_DEFINED_DOMAINS Domain;
+ ULONG NewAccountRid, NewSecurityDescriptorLength;
+ UNICODE_STRING KeyName;
+ PSECURITY_DESCRIPTOR NewSecurityDescriptor;
+ SAMP_V1_FIXED_LENGTH_ALIAS V1Fixed;
+ PRIVILEGE_SET Privileges;
+
+
+ if (AliasHandle == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Initialize the privilege set.
+ //
+
+ Privileges.PrivilegeCount = 0;
+ Privileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ Privileges.Privilege[0].Luid = RtlConvertLongToLuid(0L);
+ Privileges.Privilege[0].Attributes = 0;
+
+ //
+ // Make sure a name was provided
+ //
+
+ if (AccountName == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Length > AccountName->MaximumLength) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Buffer == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+
+
+ if ( !WriteLockHeld ) {
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+
+ //
+ // Validate type of, and access to domain object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_CREATE_ALIAS, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ AliasContext = NULL;
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ //
+ // Make sure the name is valid and not already in use before we
+ // use it to create the new alias.
+ //
+
+ NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+ if ( (*RelativeId) == 0 ) {
+
+ //
+ // Allocate a new account RID
+ //
+
+ NewAccountRid = Domain->CurrentFixed.NextRid;
+
+ Domain->CurrentFixed.NextRid += 1;
+ (*RelativeId) = NewAccountRid;
+
+ } else {
+
+ //
+ // Use the RID that was passed in.
+ //
+
+ NewAccountRid = (*RelativeId);
+ }
+
+ //
+ // Increment the alias count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampAliasObjectType, TRUE );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Create the registry key that has the alias's name.
+ // This simply serves as a name to RID mapping. Save
+ // the name when done; we'll put it in the context.
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ (PUNICODE_STRING)AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ NewAccountRid,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString(&KeyName);
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now create an alias context block
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampAliasObjectType,
+ NewAccountRid,
+ DomainContext->TrustedClient,
+ FALSE, // Account exists
+ &AliasContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The existing reference count of 1 is for RPC.
+ // Reference the context again for the writes we're
+ // about to do to initialize it.
+ //
+
+ SampReferenceContext( AliasContext );
+
+ //
+ // If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
+ //
+
+ if (DesiredAccess & MAXIMUM_ALLOWED) {
+
+ DesiredAccess |= GENERIC_ALL;
+ }
+
+ //
+ // If ACCESS_SYSTEM_SECURITY is requested and we are
+ // a non-trusted client, check that we have
+ // SE_SECURITY_PRIVILEGE.
+ //
+
+ if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
+ (!DomainContext->TrustedClient)) {
+
+ NtStatus = SampRtlWellKnownPrivilegeCheck(
+ TRUE,
+ SE_SECURITY_PRIVILEGE,
+ NULL
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+
+ } else {
+
+ Privileges.PrivilegeCount = 1;
+ Privileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ Privileges.Privilege[0].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE);
+ Privileges.Privilege[0].Attributes = 0;
+ }
+ }
+
+ //
+ // Make sure the caller can be given the requested access
+ // to the new object
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ AliasContext->GrantedAccess = DesiredAccess;
+
+ RtlMapGenericMask(
+ &AliasContext->GrantedAccess,
+ &SampObjectInformation[SampAliasObjectType].GenericMapping
+ );
+
+ if ((SampObjectInformation[SampAliasObjectType].InvalidMappedAccess &
+ AliasContext->GrantedAccess) != 0) {
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+ }
+
+ } else {
+ AliasContext = NULL;
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+
+ //
+ // Set the V1_fixed attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ V1Fixed.RelativeId = NewAccountRid;
+
+ NtStatus = SampSetFixedAttributes(
+ AliasContext,
+ (PVOID *)&V1Fixed
+ );
+ }
+
+ //
+ // Set the SECURITY DESCRIPTOR attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetNewAccountSecurity(
+ SampAliasObjectType,
+ FALSE, // Not member of ADMINISTRATORS alias
+ DomainContext->TrustedClient,
+ FALSE, //RestrictCreatorAccess
+ NewAccountRid,
+ &NewSecurityDescriptor,
+ &NewSecurityDescriptorLength
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetAccessAttribute(
+ AliasContext,
+ SAMP_ALIAS_SECURITY_DESCRIPTOR,
+ NewSecurityDescriptor,
+ NewSecurityDescriptorLength
+ );
+
+ MIDL_user_free( NewSecurityDescriptor );
+ }
+ }
+
+
+ //
+ // Set the NAME attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AliasContext,
+ SAMP_ALIAS_NAME,
+ (PUNICODE_STRING)AccountName
+ );
+ }
+
+
+ //
+ // Set the AdminComment attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AliasContext,
+ SAMP_ALIAS_ADMIN_COMMENT,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the MEMBERS attribute (with no members)
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUlongArrayAttribute(
+ AliasContext,
+ SAMP_ALIAS_MEMBERS,
+ NULL,
+ 0,
+ 0
+ );
+ }
+ }
+
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // If we created an object, dereference it. Write out its attributes
+ // if everything was created OK.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ ASSERT(AliasContext != NULL);
+ NtStatus = SampDeReferenceContext( AliasContext, TRUE );
+
+ } else {
+
+ if (AliasContext != NULL) {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AliasContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+
+
+
+ //
+ // Commit changes and notify netlogon
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbNew,
+ SecurityDbObjectSamAlias,
+ *RelativeId,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ //
+ // Generate audit here for local group creation
+ // here.
+ //
+
+ if (SampDoAccountAuditing(DomainContext->DomainIndex)) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_LOCAL_GROUP_CREATED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ (PUNICODE_STRING)AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &AliasContext->TypeBody.User.Rid, // Account Rid
+ &Privileges // Privileges used
+ );
+ }
+ }
+ }
+
+ //
+ // Return the context handle on success
+ // Delete the context block and return a NULL handle on failure
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT(AliasContext != NULL);
+ (*AliasHandle) = AliasContext;
+
+ } else {
+
+ if (AliasContext != NULL) {
+ SampDeleteContext(AliasContext);
+ }
+
+ (*AliasHandle) = (SAMPR_HANDLE)0;
+ }
+
+ //
+ // Release the lock
+ //
+
+ if ( !WriteLockHeld ) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrCreateAliasInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT SAMPR_HANDLE *AliasHandle,
+ OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This is just a wrapper for SampCreateAliasInDomain() that ensures that
+ RelativeId points to a RID of zero first.
+
+ A non-zero RID means that SampCreateAliasInDomain() was called by
+ SamICreateAccountByRid(), which specifies a RID to be used.
+
+Parameters:
+
+ Same as SampCreateAliasInDomain().
+
+Return Values:
+
+ Same as SampCreateAliasInDomain().
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+ (*RelativeId) = 0;
+
+ NtStatus = SampCreateAliasInDomain(
+ DomainHandle,
+ AccountName,
+ DesiredAccess,
+ FALSE,
+ AliasHandle,
+ RelativeId
+ );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS SamrEnumerateAliasesInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG CountReturned
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all the aliases defined in the account database.
+ Since there may be more aliass than can fit into a buffer, the
+ caller is provided with a handle that can be used across calls to
+ the API. On the initial call, EnumerationContext should point to a
+ SAM_ENUMERATE_HANDLE variable that is set to 0.
+
+ If the API returns STATUS_MORE_ENTRIES, then the API should be
+ called again with EnumerationContext. When the API returns
+ STATUS_SUCCESS or any error return, the context becomes invalid for
+ future use.
+
+ This API requires DOMAIN_LIST_ALIASES access to the Domain object.
+
+Arguments:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ EnumerationContext - API specific handle to allow multiple calls
+ (see below). This is a zero based index.
+
+ Buffer - Receives a pointer to the buffer containing the
+ requested information. The information returned is
+ structured as an array of SAM_RID_ENUMERATION data
+ structures. When this information is no longer needed, the
+ buffer must be freed using SamFreeMemory().
+
+ PreferedMaximumLength - Prefered maximum length of returned data
+ (in 8-bit bytes). This is not a hard upper limit, but serves
+ as a guide to the server. Due to data conversion between
+ systems with different natural data sizes, the actual amount
+ of data returned may be greater than this value.
+
+ CountReturned - Number of entries returned.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no addition entries.
+
+ STATUS_MORE_ENTRIES - There are more entries, so call again.
+ This is a successful return.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+ NtStatus = SampEnumerateAccountNamesCommon(
+ DomainHandle,
+ SampAliasObjectType,
+ EnumerationContext,
+ Buffer,
+ PreferedMaximumLength,
+ 0L, // no filter
+ CountReturned
+ );
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS SamrRemoveMemberFromForeignDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_SID MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes an account (group or user) from all aliases in
+ the given domain. It is meant to be called in domains OTHER than
+ domain in which the account was created.
+
+ This is typically called just before deleting the account from the
+ domain in which it was created.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ MemberId - The SID of the account being removed.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_SPECIAL_ACCOUNT - This operation may not be performed on
+ builtin accounts.
+
+
+--*/
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ SAMP_OBJECT_TYPE FoundType;
+ PSAMP_OBJECT DomainContext = NULL;
+ PULONG Membership = NULL;
+ PSID DomainSid = NULL;
+ ULONG MembershipCount, MemberRid, i;
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return(NtStatus);
+ }
+
+ //
+ // Validate type of, and access to domain object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LOOKUP, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( !DomainContext->TrustedClient ) {
+
+ //
+ // Return error if the SID passed in is for a builtin account.
+ // This may seem overly restrictive, but this API is meant to
+ // be called before deleting a user, and since deleting
+ // builtin accounts isn't allowed, it makes sense for this to
+ // fail too.
+ //
+
+ NtStatus = SampSplitSid(
+ MemberId,
+ &DomainSid,
+ &MemberRid );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ MIDL_user_free( DomainSid );
+
+ NtStatus = SampIsAccountBuiltIn( MemberRid );
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRemoveAccountFromAllAliases(
+ MemberId,
+ TRUE, // verify caller is allowed to do this
+ DomainHandle,
+ &MembershipCount,
+ &Membership
+ );
+
+ }
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ IgnoreStatus = STATUS_SUCCESS;
+
+ for ( i = 0;
+ ( ( i < MembershipCount ) && ( NT_SUCCESS( IgnoreStatus ) ) );
+ i++ ) {
+
+ //
+ // Notify netlogon once for each alias that the account was
+ // removed from. Netlogon requires that ModifiedCount be
+ // incremented each time; Commit increments ModifiedCount,
+ // so we do each notification after a commit.
+ //
+
+ IgnoreStatus = SampCommitAndRetainWriteLock();
+
+ if ( i == 0 ) {
+
+ //
+ // The first commit is the one that commits all the
+ // important changes, so we'll save it's status to return
+ // to the caller.
+ //
+
+ NtStatus = IgnoreStatus;
+
+ //
+ // Update the Cached Alias Information if necessary.
+ //
+
+ IgnoreStatus = SampAlRemoveAccountFromAllAliases(
+ MemberId,
+ FALSE,
+ DomainHandle,
+ NULL,
+ NULL
+ );
+ }
+
+ if ( NT_SUCCESS( IgnoreStatus ) ) {
+
+ //
+ // Notify if we were able to increment the modified count
+ // (which is done by SampCommitAndRetainWriteLock()).
+ //
+
+ SAM_DELTA_DATA DeltaData;
+
+ //
+ // Fill in id of member being removed
+ //
+
+ DeltaData.AliasMemberId.MemberSid = MemberId;
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangeMemberDel,
+ SecurityDbObjectSamAlias,
+ Membership[i],
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ &DeltaData
+ );
+ }
+ }
+ }
+
+ if ( Membership != NULL ) {
+
+ MIDL_user_free( Membership );
+ }
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampCreateUserInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ULONG AccountType,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN WriteLockHeld,
+ OUT SAMPR_HANDLE *UserHandle,
+ OUT PULONG GrantedAccess,
+ IN OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This API adds a new user to the account database. The account is
+ created in a disabled state. Default information is assigned to all
+ fields except the account name. A password must be provided before
+ the account may be enabled, unless the PasswordNotRequired control
+ field is set.
+
+ This api may be used in either of two ways:
+
+ 1) An administrative utility may use this api to create
+ any type of user account. In this case, the DomainHandle
+ is expected to be open for DOMAIN_CREATE_USER access.
+
+ 2) A non-administrative user may use this api to create
+ a machine account. In this case, the caller is expected
+ to have the SE_CREATE_MACHINE_ACCOUNT_PRIV privilege
+ and the DomainHandle is expected to be open for DOMAIN_LOOKUP
+ access.
+
+
+ For the normal administrative model ( #1 above), the creator will
+ be assigned as the owner of the created user account. Furthermore,
+ the new account will be give USER_WRITE access to itself.
+
+ For the special machine-account creation model (#2 above), the
+ "Administrators" will be assigned as the owner of the account.
+ Furthermore, the new account will be given NO access to itself.
+ Instead, the creator of the account will be give USER_WRITE and
+ DELETE access to the account.
+
+
+ This call returns a handle to the newly created user that may be
+ used for successive operations on the user. This handle may be
+ closed with the SamCloseHandle() API. If a machine account is
+ being created using model #2 above, then this handle will have
+ only USER_WRITE and DELETE access. Otherwise, it will be open
+ for USER_ALL_ACCESS.
+
+
+ A newly created user will automatically be made a member of the
+ DOMAIN_USERS group.
+
+ A newly created user will have the following initial field value
+ settings. If another value is desired, it must be explicitly
+ changed using the user object manipulation services.
+
+ UserName - the name of the account will be as specified in the
+ creation API.
+
+ FullName - will be null.
+
+ UserComment - will be null.
+
+ Parameters - will be null.
+
+ CountryCode - will be zero.
+
+ UserId - will be a uniquelly allocated ID.
+
+ PrimaryGroupId - Will be DOMAIN_USERS.
+
+ PasswordLastSet - will be the time the account was created.
+
+ HomeDirectory - will be null.
+
+ HomeDirectoryDrive - will be null.
+
+ UserAccountControl - will have the following flags set:
+
+ UserAccountDisable,
+ UserPasswordNotRequired,
+ and the passed account type.
+
+
+ ScriptPath - will be null.
+
+ WorkStations - will be null.
+
+ CaseInsensitiveDbcs - will be null.
+
+ CaseSensitiveUnicode - will be null.
+
+ LastLogon - will be zero delta time.
+
+ LastLogoff - will be zero delta time
+
+ AccountExpires - will be very far into the future.
+
+ BadPasswordCount - will be negative 1 (-1).
+
+ LastBadPasswordTime - will be SampHasNeverTime ( [High,Low] = [0,0] ).
+
+ LogonCount - will be negative 1 (-1).
+
+ AdminCount - will be zero.
+
+ AdminComment - will be null.
+
+ Password - will be "".
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ AccountName - Points to the name of the new account. A case-insensitive
+ comparison must not find a group or user with this name already defined.
+
+ AccountType - Indicates what type of account is being created.
+ Exactly one account type must be provided:
+
+ USER_INTERDOMAIN_TRUST_ACCOUNT
+ USER_WORKSTATION_TRUST_ACCOUNT
+ USER_SERVER_TRUST_ACCOUNT
+ USER_TEMP_DUPLICATE_ACCOUNT
+ USER_NORMAL_ACCOUNT
+ USER_MACHINE_ACCOUNT_MASK
+
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the user.
+
+ UserHandle - Receives a handle referencing the newly created
+ user. This handle will be required in successive calls to
+ operate on the user.
+
+ GrantedAccess - Receives the accesses actually granted to via
+ the UserHandle.
+
+ RelativeId - Receives the relative ID of the newly created user
+ account. The SID of the new user account is this relative ID
+ value prefixed with the domain's SID value.
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_GROUP_EXISTS - The name is already in use as a group.
+
+ STATUS_USER_EXISTS - The name is already in use as a user.
+
+ STATUS_ALIAS_EXISTS - The name is already in use as an alias.
+
+ STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
+ contains non-printable characters.
+
+ STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
+ correct state (disabled or enabled) to perform the requested
+ operation. The domain server must be enabled before users
+ can be created in it.
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation. The domain server must be a primary server to
+ create user accounts.
+
+
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ PSAMP_OBJECT
+ DomainContext,
+ UserContext,
+ GroupContext;
+
+ SAMP_OBJECT_TYPE
+ FoundType;
+
+ PSAMP_DEFINED_DOMAINS
+ Domain;
+
+ SAMP_V1_0A_FIXED_LENGTH_GROUP
+ GroupV1Fixed;
+
+ ULONG
+ NewAccountRid,
+ NewSecurityDescriptorLength;
+
+ UNICODE_STRING
+ KeyName;
+
+ PSECURITY_DESCRIPTOR
+ NewSecurityDescriptor;
+
+ SAMP_V1_0A_FIXED_LENGTH_USER
+ V1aFixed;
+
+ GROUP_MEMBERSHIP
+ DomainUsersMember;
+
+ BOOLEAN
+ DomainPasswordInformationAccessible = FALSE,
+ PrivilegedMachineAccountCreate = FALSE;
+
+ PRIVILEGE_SET
+ Privileges;
+
+ PPRIVILEGE_SET
+ PPrivileges = NULL; // No privileges in audit by default
+
+
+ ACCESS_MASK
+ AccessRestriction = USER_ALL_ACCESS |
+ ACCESS_SYSTEM_SECURITY; // No access restrictions by default
+
+ DomainUsersMember.RelativeId = DOMAIN_GROUP_RID_USERS;
+ DomainUsersMember.Attributes = (SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED |
+ SE_GROUP_ENABLED_BY_DEFAULT);
+
+
+ if (UserHandle == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+
+ //
+ // Make sure a name was provided
+ //
+
+ if (AccountName == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Length > AccountName->MaximumLength) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ if (AccountName->Buffer == NULL) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+
+
+ if ( !WriteLockHeld ) {
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+
+ //
+ // Validate type of, and access to domain object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_CREATE_USER, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ //
+ // if we don't have DOMAIN_CREATE_USER access, then see
+ // if we are creating a machine account and try for DOMAIN_LOOKUP.
+ // If this works, then we can see if the client has
+ // SE_CREATE_MACHINE_ACCOUNT_PRIVILEGE.
+ //
+
+ if ( (NtStatus == STATUS_ACCESS_DENIED) &&
+ (AccountType == USER_WORKSTATION_TRUST_ACCOUNT) ) {
+
+ SampTransactionWithinDomain = FALSE;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LOOKUP, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampRtlWellKnownPrivilegeCheck(
+ TRUE, // ImpersonateClient
+ SE_MACHINE_ACCOUNT_PRIVILEGE,
+ NULL); // ClientId - optional
+ if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Clients creating accounts in this fashion are limited
+ // in what they can do to the account.
+ //
+
+ AccessRestriction = DELETE |
+ USER_WRITE |
+ USER_FORCE_PASSWORD_CHANGE;
+ PrivilegedMachineAccountCreate = TRUE;
+
+
+
+ } else {
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+ }
+
+ UserContext = NULL;
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the domain handle allows reading the password parameters,
+ // note that now (best to call LookupContext early because of
+ // side effects; data will be copied to the user's context later)
+ // to make life easy for SampGetUserDomainPasswordInformation().
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ IgnoreStatus = SampLookupContext(
+ DomainHandle,
+ DOMAIN_READ_PASSWORD_PARAMETERS, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if ( NT_SUCCESS( IgnoreStatus ) ) {
+
+ DomainPasswordInformationAccessible = TRUE;
+
+ IgnoreStatus = SampDeReferenceContext( DomainHandle, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ //
+ // Make sure the name is valid and not already in use before we
+ // use it to create the new alias.
+ //
+
+ NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+
+ if ( (*RelativeId) == 0 ) {
+
+ //
+ // Allocate a new account RID
+ //
+
+ NewAccountRid = Domain->CurrentFixed.NextRid;
+
+ Domain->CurrentFixed.NextRid += 1;
+ (*RelativeId) = NewAccountRid;
+
+ } else {
+
+ //
+ // A RID was passed in, so we want to use that rather than
+ // select a new one.
+ //
+
+ NewAccountRid = (*RelativeId);
+ }
+
+
+ //
+ // Increment the User count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampUserObjectType, TRUE );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Create the registry key that has the User's name.
+ // This simply serves as a name to RID mapping. Save
+ // the name when finished; we'll put it in the context.
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ (PUNICODE_STRING)AccountName
+ );
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ NewAccountRid,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString(&KeyName);
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now create a User context block
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ NewAccountRid,
+ DomainContext->TrustedClient,
+ FALSE, // Account exists
+ &UserContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The existing reference count of 1 is for RPC.
+ // Reference the context again for the writes we're
+ // about to do to initialize it.
+ //
+
+ SampReferenceContext( UserContext );
+
+ //
+ // Stash away the password info accessible flag
+ //
+
+ UserContext->TypeBody.User.DomainPasswordInformationAccessible =
+ DomainPasswordInformationAccessible;
+
+ //
+ // If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
+ //
+
+ if (DesiredAccess & MAXIMUM_ALLOWED) {
+
+ DesiredAccess |= GENERIC_ALL;
+ }
+
+ //
+ // If ACCESS_SYSTEM_SECURITY is requested and we are
+ // a non-trusted client, check that we have
+ // SE_SECURITY_PRIVILEGE.
+ //
+
+ if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
+ (!DomainContext->TrustedClient)) {
+
+ NtStatus = SampRtlWellKnownPrivilegeCheck(
+ TRUE,
+ SE_SECURITY_PRIVILEGE,
+ NULL
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ }
+ }
+ }
+
+ //
+ // Make sure the caller can be given the requested access
+ // to the new object
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UserContext->GrantedAccess = DesiredAccess;
+
+ RtlMapGenericMask(
+ &UserContext->GrantedAccess,
+ &SampObjectInformation[SampUserObjectType].GenericMapping
+ );
+
+
+ if ((SampObjectInformation[SampUserObjectType].InvalidMappedAccess
+ & UserContext->GrantedAccess) != 0) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ } else {
+
+ //
+ // Restrict access if necessary
+ //
+
+ UserContext->GrantedAccess &= AccessRestriction;
+ (*GrantedAccess) = UserContext->GrantedAccess;
+ }
+ }
+
+ } else {
+ UserContext = NULL;
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+
+
+
+ //
+ // If the GROUP we're going to put this user in
+ // is part of an ADMIN alias, we must set the ACL
+ // on this user account to disallow access by
+ // account operators. Get group info to determine
+ // whether it's in an ADMIN alias or not.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ DOMAIN_GROUP_RID_USERS,
+ TRUE, // TrustedClient,
+ TRUE, // Account exists
+ &GroupContext
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ GroupContext,
+ &GroupV1Fixed
+ );
+
+ SampDeleteContext(GroupContext);
+ }
+ }
+
+
+ //
+ // Set the V1_fixed attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ V1aFixed.Revision = SAMP_REVISION;
+
+ V1aFixed.CountryCode = 0;
+ V1aFixed.CodePage = 0;
+ V1aFixed.BadPasswordCount = 0;
+ V1aFixed.LogonCount = 0;
+ V1aFixed.AdminCount = (GroupV1Fixed.AdminCount != 0) ? 1 : 0;
+ V1aFixed.OperatorCount = (GroupV1Fixed.OperatorCount != 0) ? 1 : 0;
+ V1aFixed.Unused1 = 0;
+ V1aFixed.Unused2 = 0;
+ V1aFixed.UserAccountControl = (USER_PASSWORD_NOT_REQUIRED |
+ AccountType);
+ //
+ // Disable the account unless this is a special creation
+ // in which the creator won't be able to enable the account.
+ //
+
+ if (!PrivilegedMachineAccountCreate) {
+ V1aFixed.UserAccountControl |= USER_ACCOUNT_DISABLED;
+ }
+
+ V1aFixed.UserId = NewAccountRid;
+ V1aFixed.PrimaryGroupId = DOMAIN_GROUP_RID_USERS;
+ V1aFixed.LastLogon = SampHasNeverTime;
+ V1aFixed.LastLogoff = SampHasNeverTime;
+ V1aFixed.PasswordLastSet = SampHasNeverTime;
+ V1aFixed.AccountExpires = SampWillNeverTime;
+ V1aFixed.LastBadPasswordTime = SampHasNeverTime;
+
+ NtStatus = SampSetFixedAttributes(
+ UserContext,
+ (PVOID *)&V1aFixed
+ );
+ }
+
+ //
+ // Set the SECURITY_DESCRIPTOR attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Build a security descriptor to protect the User.
+ //
+
+ NtStatus = SampGetNewAccountSecurity(
+ SampUserObjectType,
+ (BOOLEAN) (((GroupV1Fixed.AdminCount != 0) ||
+ (GroupV1Fixed.OperatorCount != 0)) ? TRUE : FALSE),
+ DomainContext->TrustedClient,
+ PrivilegedMachineAccountCreate,
+ NewAccountRid,
+ &NewSecurityDescriptor,
+ &NewSecurityDescriptorLength
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetAccessAttribute(
+ UserContext,
+ SAMP_USER_SECURITY_DESCRIPTOR,
+ NewSecurityDescriptor,
+ NewSecurityDescriptorLength
+ );
+
+ MIDL_user_free( NewSecurityDescriptor );
+ }
+ }
+
+
+ //
+ // Set the ACCOUNT_NAME attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_ACCOUNT_NAME,
+ (PUNICODE_STRING)AccountName
+ );
+ }
+
+
+ //
+ // Set the FULL_NAME attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_FULL_NAME,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the AdminComment attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_ADMIN_COMMENT,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the UserComment attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_USER_COMMENT,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the Parameters attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_PARAMETERS,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the HomeDirectory attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_HOME_DIRECTORY,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the HomeDirectoryDrive attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the ScriptPath attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_SCRIPT_PATH,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the ProfilePath attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_PROFILE_PATH,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the WorkStations attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_WORKSTATIONS,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the LogonHours attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ LOGON_HOURS LogonHours;
+
+ LogonHours.UnitsPerWeek = 0;
+ LogonHours.LogonHours = NULL;
+
+ NtStatus = SampSetLogonHoursAttribute(
+ UserContext,
+ SAMP_USER_LOGON_HOURS,
+ &LogonHours
+ );
+ }
+
+
+ //
+ // Set the Groups attribute (with membership in DomainUsers only)
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetLargeIntArrayAttribute(
+ UserContext,
+ SAMP_USER_GROUPS,
+ (PLARGE_INTEGER)&DomainUsersMember,
+ 1
+ );
+ }
+
+
+ //
+ // Set the CaseInsensitiveDbcs attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_DBCS_PWD,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Create the CaseSensitiveUnicode key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_UNICODE_PWD,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the NtPasswordHistory attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_NT_PWD_HISTORY,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Set the LmPasswordHistory attribute
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_LM_PWD_HISTORY,
+ &SampNullString
+ );
+ }
+
+
+ //
+ // Add this new user to the DomainUsers group
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ NtStatus = SampAddUserToGroup( DOMAIN_GROUP_RID_USERS, NewAccountRid );
+ }
+
+
+
+ }
+
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+
+
+ //
+ // If we created an object, dereference it. Write out its attributes
+ // if everything was created OK.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ ASSERT(UserContext != NULL);
+ NtStatus = SampDeReferenceContext( UserContext, TRUE );
+
+ } else {
+
+ if (UserContext != NULL) {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( UserContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+
+
+
+
+ //
+ // Commit changes and notify netlogon
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Commit the changes; hold on to the write lock for now.
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ //
+ // If we can't commit the mess for some reason, then delete
+ // the new context block and return null for the context handle.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
+
+ //
+ // Update the display information
+ //
+
+ AccountInfo.Name = *((PUNICODE_STRING)AccountName);
+ AccountInfo.Rid = NewAccountRid;
+ AccountInfo.AccountControl = V1aFixed.UserAccountControl;
+ RtlInitUnicodeString(&AccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&AccountInfo.FullName, NULL);
+
+ IgnoreStatus = SampUpdateDisplayInformation(NULL,
+ &AccountInfo,
+ SampUserObjectType);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ //
+ // Audit the creation before we free the write lock
+ // so that we have access to the context block.
+ //
+
+ if (SampDoAccountAuditing(UserContext->DomainIndex)) {
+
+ if (PrivilegedMachineAccountCreate) {
+
+ //
+ // Set up the privilege set for auditing
+ //
+
+
+ Privileges.PrivilegeCount = 1;
+ Privileges.Control = 0;
+ ASSERT(ANYSIZE_ARRAY >= 1);
+ Privileges.Privilege[0].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS;
+ Privileges.Privilege[0].Luid = RtlConvertUlongToLuid( SE_MACHINE_ACCOUNT_PRIVILEGE);
+ PPrivileges = &Privileges;
+ }
+
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_USER_CREATED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ (PUNICODE_STRING)AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &NewAccountRid, // Account Rid
+ PPrivileges // Privileges used
+ );
+ }
+
+ //
+ // Notify netlogon if a machine account was created.
+ //
+
+ if ( ( V1aFixed.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) != 0 ) {
+
+ //
+ // This was a machine account. Let
+ // NetLogon know of the change.
+ //
+
+ IgnoreStatus = I_NetNotifyMachineAccount(
+ NewAccountRid,
+ SampDefinedDomains[SampTransactionDomainIndex].Sid,
+ 0,
+ V1aFixed.UserAccountControl,
+ (PUNICODE_STRING)AccountName
+ );
+ }
+
+ //
+ // Notify netlogon of changes
+ //
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbNew,
+ SecurityDbObjectSamUser,
+ *RelativeId,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ }
+
+ }
+
+
+
+ //
+ // Return the context handle on success
+ // Delete the context block and return a NULL handle on failure
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT(UserContext != NULL);
+ (*UserHandle) = UserContext;
+
+ } else {
+
+ if (UserContext != NULL) {
+ SampDeleteContext(UserContext);
+ }
+
+ (*UserHandle) = (SAMPR_HANDLE)0;
+ }
+
+
+
+ //
+ // Release the lock
+ //
+
+ if ( !WriteLockHeld ) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS SamrCreateUser2InDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ULONG AccountType,
+ IN ACCESS_MASK DesiredAccess,
+ OUT SAMPR_HANDLE *UserHandle,
+ OUT PULONG GrantedAccess,
+ OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This is just a wrapper for SampCreateUserInDomain() that ensures
+ RelativeId points to a RID of zero first. It also guarantees
+ that AccountType is valid.
+
+ A non-zero RID means that SampCreateUserInDomain() was called by
+ SamICreateAccountByRid(), which specifies a RID to be used.
+
+Parameters:
+
+ Same as SampCreateUserInDomain() except AccountType maps to
+ AccountControl.
+
+Return Values:
+
+ Same as SampCreateUserInDomain().
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+ (*RelativeId) = 0;
+
+ //
+ // Make sure one, and only one, account type flag is set.
+ //
+
+ switch (AccountType) {
+
+ case USER_NORMAL_ACCOUNT :
+ case USER_WORKSTATION_TRUST_ACCOUNT :
+ case USER_INTERDOMAIN_TRUST_ACCOUNT :
+ case USER_SERVER_TRUST_ACCOUNT :
+ case USER_TEMP_DUPLICATE_ACCOUNT :
+
+ //
+ // AccountType is valid
+ //
+
+ break;
+
+ default :
+
+ //
+ // Bad account type value specified
+ //
+
+ return( STATUS_INVALID_PARAMETER );
+ }
+
+
+
+
+ NtStatus = SampCreateUserInDomain(
+ DomainHandle,
+ AccountName,
+ AccountType,
+ DesiredAccess,
+ FALSE,
+ UserHandle,
+ GrantedAccess,
+ RelativeId
+ );
+
+ return( NtStatus );
+}
+
+
+NTSTATUS SamrCreateUserInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT SAMPR_HANDLE *UserHandle,
+ OUT PULONG RelativeId
+ )
+
+/*++
+
+Routine Description:
+
+ This is just a wrapper for SampCreateUserInDomain() that ensures that
+ RelativeId points to a RID of zero first.
+
+ A non-zero RID means that SampCreateUserInDomain() was called by
+ SamICreateAccountByRid(), which specifies a RID to be used.
+
+Parameters:
+
+ Same as SampCreateUserInDomain() except AccountType is NORMAL_USER.
+
+Return Values:
+
+ Same as SampCreateUserInDomain().
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+ ULONG
+ GrantedAccess;
+
+ (*RelativeId) = 0;
+
+ NtStatus = SampCreateUserInDomain(
+ DomainHandle,
+ AccountName,
+ USER_NORMAL_ACCOUNT,
+ DesiredAccess,
+ FALSE,
+ UserHandle,
+ &GrantedAccess,
+ RelativeId
+ );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS SamrEnumerateUsersInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ IN ULONG UserAccountControl,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG CountReturned
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all the users defined in the account database. Since
+ there may be more users than can fit into a buffer, the caller is
+ provided with a handle that can be used across calls to the API. On
+ the initial call, EnumerationContext should point to a
+ SAM_ENUMERATE_HANDLE variable that is set to 0.
+
+ If the API returns STATUS_MORE_ENTRIES, then the API should be
+ called again with EnumerationContext. When the API returns
+ STATUS_SUCCESS or any error return, the handle becomes invalid for
+ future use.
+
+ This API requires DOMAIN_LIST_USERS access to the Domain object.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ EnumerationContext - API specific handle to allow multiple calls.
+ This is a zero based index.
+
+ UserAccountControl - Provides enumeration filtering information. Any
+ characteristics specified here will cause that type of User account
+ to be included in the enumeration process.
+
+ Buffer - Receives a pointer to the buffer containing the
+ requested information. The information returned is
+ structured as an array of SAM_RID_ENUMERATION data
+ structures. When this information is no longer needed, the
+ buffer must be freed using SamFreeMemory().
+
+ PreferedMaximumLength - Prefered maximum length of returned data
+ (in 8-bit bytes). This is not a hard upper limit, but serves
+ as a guide to the server. Due to data conversion between
+ systems with different natural data sizes, the actual amount
+ of data returned may be greater than this value.
+
+ CountReturned - Number of entries returned.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no additional entries.
+
+ STATUS_MORE_ENTRIES - There are more entries, so call again.
+ This is a successful return.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+--*/
+
+{
+
+ NTSTATUS NtStatus;
+
+ NtStatus = SampEnumerateAccountNamesCommon(
+ DomainHandle,
+ SampUserObjectType,
+ EnumerationContext,
+ Buffer,
+ PreferedMaximumLength,
+ UserAccountControl,
+ CountReturned
+ );
+
+ return(NtStatus);
+}
+
+
+
+
+
+NTSTATUS SamrLookupNamesInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN RPC_UNICODE_STRING Names[],
+ OUT PSAMPR_ULONG_ARRAY RelativeIds,
+ OUT PSAMPR_ULONG_ARRAY Use
+ )
+
+/*++
+
+Routine Description:
+
+ This API attempts to find relative IDs corresponding to name
+ strings. If a name can not be mapped to a relative ID, a zero is
+ placed in the corresponding relative ID array entry, and translation
+ continues.
+
+ DOMAIN_LOOKUP access to the domain is needed to use this service.
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ Count - Number of names to translate.
+
+ Names - Pointer to an array of Count UNICODE_STRINGs that contain
+ the names to map to relative IDs. Case-insensitive
+ comparisons of these names will be performed for the lookup
+ operation.
+
+ RelativeIds - Receives an array of Count Relative IDs.
+ The relative ID of the nth name will be the nth entry in this
+ array. Any names that could not be translated will have a
+ zero relative ID.
+
+ RelativeIds - Receives a pointer to a SAMPR_RETURNED_ULONG_ARRAY structure.
+ The nth entry in the array associated with this structure
+ contains the RID of the nth name looked up.
+ When this information is no longer needed, the caller is responsible
+ for deallocating each returned block (including the
+ SAMPR_ULONG_ARRAY structure itself) using SamFreeMemory().
+
+ Use - Receives a pointer to a SAMPR_RETURNED_ULONG_ARRAY structure.
+ The nth entry in the array associated with this structure
+ contains the SID_NAME_USE of the nth name looked up.
+ When this information is no longer needed, the caller is responsible
+ for deallocating each returned block (including the
+ SAMPR_ULONG_ARRAY structure itself) using SamFreeMemory().
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+ STATUS_SOME_NOT_MAPPED - Some of the names provided could not be
+ mapped. This is a successful return.
+
+ STATUS_NONE_MAPPED - No names could be mapped. This is an error
+ return.
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ HANDLE TempHandle;
+ LARGE_INTEGER IgnoreTimeStamp;
+ ULONG i, KeyValueLength, UnMappedCount;
+ ULONG ApproximateTotalLength;
+
+
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (Use != NULL);
+ ASSERT (Use->Element == NULL);
+ ASSERT (RelativeIds != NULL);
+ ASSERT (RelativeIds->Element == NULL);
+
+ Use->Count = 0;
+ RelativeIds->Count = 0;
+
+
+ if (Count == 0) {
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Make sure the parameter values are within reasonable bounds
+ //
+
+ if (Count > SAM_MAXIMUM_LOOKUP_COUNT) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ ApproximateTotalLength = (Count*(sizeof(ULONG) + sizeof(SID_NAME_USE)));
+
+ //
+ // Do the return test inside this loop to avoid overflow problems
+ // summing up the name lengths.
+ //
+
+ for ( i=0; i<Count; i++) {
+ ApproximateTotalLength += (ULONG)Names[i].MaximumLength;
+ if ( ApproximateTotalLength > SAMP_MAXIMUM_MEMORY_TO_USE ) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ }
+
+
+
+ //
+ // Allocate the return buffers
+ //
+
+ Use->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
+ if (Use->Element == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RelativeIds->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
+ if (RelativeIds->Element == NULL) {
+ MIDL_user_free( Use->Element);
+ Use->Element = NULL; // required to RPC doesn't free it again.
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+ Use->Count = Count;
+ RelativeIds->Count = Count;
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LOOKUP,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UnMappedCount = Count;
+ for ( i=0; i<Count; i++) {
+
+
+ //
+ // Search the groups for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ (PUNICODE_STRING)&Names[i]
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UnMappedCount -= 1;
+ Use->Element[i] = SidTypeGroup;
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &RelativeIds->Element[i],
+ NULL,
+ &KeyValueLength,
+ &IgnoreTimeStamp
+ );
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto unexpected_error;
+ }
+ ASSERT(KeyValueLength == 0);
+
+
+ } else {
+
+ //
+ // Search the aliases for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ (PUNICODE_STRING)&Names[i]
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UnMappedCount -= 1;
+ Use->Element[i] = SidTypeAlias;
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &RelativeIds->Element[i],
+ NULL,
+ &KeyValueLength,
+ &IgnoreTimeStamp
+ );
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto unexpected_error;
+ }
+ ASSERT(KeyValueLength == 0);
+
+
+ } else {
+
+ //
+ // Search the user for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ (PUNICODE_STRING)&Names[i]
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UnMappedCount -= 1;
+ Use->Element[i] = SidTypeUser;
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ &RelativeIds->Element[i],
+ NULL,
+ &KeyValueLength,
+ &IgnoreTimeStamp
+ );
+ IgnoreStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto unexpected_error;
+ }
+ ASSERT(KeyValueLength == 0);
+
+ } else if(NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ //
+ // This is fine. It just means that we don't
+ // have an account with the name being looked up.
+ //
+
+ Use->Element[i] = SidTypeUnknown;
+ RelativeIds->Element[i] = 0;
+ NtStatus = STATUS_SUCCESS;
+
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus) &&
+ NtStatus != STATUS_INVALID_ACCOUNT_NAME) {
+ goto unexpected_error;
+ }
+
+ } // end_for
+
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+
+ if (UnMappedCount == Count) {
+ NtStatus = STATUS_NONE_MAPPED;
+ } else {
+ if (UnMappedCount > 0) {
+ NtStatus = STATUS_SOME_NOT_MAPPED;
+ } else {
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ //
+ // If the status isn't one of the expected return values,
+ // then deallocate the return memory block
+ //
+
+ if ( ( NtStatus != STATUS_SUCCESS ) &&
+ ( NtStatus != STATUS_SOME_NOT_MAPPED ) ) {
+
+ Use->Count = 0;
+ MIDL_user_free( Use->Element );
+ Use->Element = NULL;
+ RelativeIds->Count = 0;
+ MIDL_user_free( RelativeIds->Element );
+ RelativeIds->Element = NULL;
+ }
+
+
+ return( NtStatus );
+
+
+unexpected_error:
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ //
+ // Don't return any memory
+ //
+
+ Use->Count = 0;
+ MIDL_user_free( Use->Element );
+ Use->Element = NULL; // Required so RPC doesn't try to free the element
+ RelativeIds->Count = 0;
+ MIDL_user_free( RelativeIds->Element );
+ RelativeIds->Element = NULL; // Required so RPC doesn't try to free the element
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS SamrLookupIdsInDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN ULONG Count,
+ IN PULONG RelativeIds,
+ OUT PSAMPR_RETURNED_USTRING_ARRAY Names,
+ OUT PSAMPR_ULONG_ARRAY Use
+ )
+
+
+/*++
+
+Routine Description:
+
+ This API maps a number of relative IDs to their corresponding names.
+ If a relative ID can not be mapped, a NULL value is placed in the slot
+ for the UNICODE_STRING, and STATUS_SOME_NOT_MAPPED is returned.
+ If none of the IDs can be mapped, then all array entries will contain
+ NULL values and STATUS_NONE_MAPPED is returned.
+
+ DOMAIN_LOOKUP access to the domain is needed to use this service.
+
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ Count - Provides the number of relative IDs to translate.
+
+ RelativeIds - Array of Count relative IDs to be mapped.
+
+ Names - Receives a pointer to an allocated SAMPR_UNICODE_STRING_ARRAY.
+ The nth entry in the array of names pointed to by this structure
+ corresonds to the nth relative id looked up.
+ Each name string buffer will be in a separate block of memory
+ allocated by this routine. When these names are no longer
+ needed, the caller is responsible for deallocating each
+ returned block (including the SAMPR_RETURNED_USTRING_ARRAY structure
+ itself) using SamFreeMemory().
+
+ Use - Receives a pointer to a SAMPR_ULONG_ARRAY structure.
+ The nth entry in the array associated with this structure
+ contains the SID_NAME_USE of the nth relative ID looked up.
+ When this information is no longer needed, the caller is responsible
+ for deallocating this memory using SamFreeMemory().
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+ STATUS_SOME_NOT_MAPPED - Some of the names provided could not be
+ mapped. This is a successful return.
+
+ STATUS_NONE_MAPPED - No names could be mapped. This is an error
+ return.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ SAMP_OBJECT_TYPE ObjectType;
+ PSAMP_OBJECT DomainContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG i, UnMappedCount;
+ ULONG TotalLength;
+ PSAMP_MEMORY NextMemory;
+ SAMP_MEMORY MemoryHead;
+
+ BOOLEAN LengthLimitReached = FALSE;
+
+ //
+ // Used for tracking allocated memory so we can deallocate it on
+ // error
+ //
+
+ MemoryHead.Memory = NULL;
+ MemoryHead.Next = NULL;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (RelativeIds != NULL);
+ ASSERT (Use != NULL);
+ ASSERT (Use->Element == NULL);
+ ASSERT (Names != NULL);
+ ASSERT (Names->Element == NULL);
+
+ Use->Count = 0;
+ Names->Count = 0;
+
+ if (Count == 0) {
+ return(STATUS_SUCCESS);
+ }
+
+ TotalLength = (Count*(sizeof(ULONG) + sizeof(UNICODE_STRING)));
+
+ if ( TotalLength > SAMP_MAXIMUM_MEMORY_TO_USE ) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ if (Count == 0) {
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Allocate the return buffers
+ //
+
+ Use->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
+ if (Use->Element == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+ Names->Element = MIDL_user_allocate( Count * sizeof(UNICODE_STRING) );
+ if (Names->Element == NULL) {
+ MIDL_user_free( Use->Element);
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ Use->Count = Count;
+ Names->Count = Count;
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_LOOKUP,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ UnMappedCount = Count;
+ for ( i=0; i<Count; i++) {
+
+ //
+ // allocate a block to track a name allocated for this mapping
+ //
+
+ NextMemory = MIDL_user_allocate( sizeof(SAMP_MEMORY) );
+ if (NextMemory == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto unexpected_error;
+ }
+
+
+ NtStatus = SampLookupAccountName(
+ RelativeIds[i],
+ (PUNICODE_STRING)&Names->Element[i],
+ &ObjectType
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto unexpected_error;
+ }
+
+
+ switch (ObjectType) {
+
+ case SampUserObjectType:
+ case SampGroupObjectType:
+ case SampAliasObjectType:
+
+ //
+ // We found the account
+ //
+
+ UnMappedCount -= 1;
+
+ NextMemory->Memory = (PVOID)&Names->Element[i].Buffer;
+ NextMemory->Next = MemoryHead.Next;
+ MemoryHead.Next = NextMemory;
+
+ switch (ObjectType) {
+
+ case SampUserObjectType:
+ Use->Element[i] = SidTypeUser;
+ break;
+
+ case SampGroupObjectType:
+ Use->Element[i] = SidTypeGroup;
+ break;
+
+ case SampAliasObjectType:
+ Use->Element[i] = SidTypeAlias;
+ break;
+ }
+
+ break;
+
+
+ case SampUnknownObjectType:
+
+ //
+ // Hmmm - don't know what this rid is. It's either been
+ // deleted, or a bogus RID.
+ //
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ if ( ( RelativeIds[i] >= SAMP_RESTRICTED_ACCOUNT_COUNT ) &&
+ ( RelativeIds[i] < Domain->CurrentFixed.NextRid ) ) {
+
+ Use->Element[i] = SidTypeDeletedAccount;
+
+ } else {
+
+ Use->Element[i] = SidTypeUnknown;
+ }
+
+ Names->Element[i].Length = 0;
+ Names->Element[i].MaximumLength = 0;
+ Names->Element[i].Buffer = NULL;
+ MIDL_user_free( NextMemory );
+
+ break;
+
+ default:
+
+ ASSERT(FALSE); // unexpected object type returned
+ break;
+ }
+
+ } // end_for
+
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+
+ if (UnMappedCount == Count) {
+ NtStatus = STATUS_NONE_MAPPED;
+ } else {
+ if (UnMappedCount > 0) {
+ NtStatus = STATUS_SOME_NOT_MAPPED;
+ } else {
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ //
+ // Free all the memory tracking blocks
+ //
+
+ NextMemory = MemoryHead.Next;
+ while ( NextMemory != NULL ) {
+ MemoryHead.Next = NextMemory->Next;
+ MIDL_user_free( NextMemory );
+ NextMemory = MemoryHead.Next;
+ }
+
+
+
+ return( NtStatus );
+
+
+unexpected_error:
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ //
+ // Free all the memory tracking blocks - and the memory they point to.
+ //
+
+ Use->Count = 0;
+ Names->Count = 0;
+ MIDL_user_free( Use->Element );
+ MIDL_user_free( Names->Element );
+ NextMemory = MemoryHead.Next;
+ while ( NextMemory != NULL ) {
+ if (NextMemory->Memory != NULL) {
+ MIDL_user_free( NextMemory->Memory );
+ }
+ MemoryHead.Next = NextMemory->Next;
+ MIDL_user_free( NextMemory );
+ NextMemory = MemoryHead.Next;
+ }
+
+ return( NtStatus );
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampOpenDomainKey(
+ IN PSAMP_OBJECT DomainContext,
+ IN PRPC_SID DomainId
+ )
+
+/*++
+
+Routine Description:
+
+ This service attempts to open the root registry key of the domain with
+ the specified SID. The root name and key handle are put in the
+ passed domain context.
+
+
+ If successful, and the domain key is opened, then the opened domain is
+ established as the transaction domain (using SampSetTransactionDomain()).
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock() HELD FOR READ OR
+ WRITE ACCESS.
+
+Arguments:
+
+ DomainContext - Context in which root namd and handle are stored.
+
+ DomainId - Specifies the SID of the domain to open.
+
+Return Value:
+
+ STATUS_SUCCESS - The domain has been openned.
+
+ STATUS_NO_SUCH_DOMAIN - The domain object could not be found.
+
+ STATUS_INSUFFICIENT_RESOURCES - The domain object could not be openned
+ due to the lack of some resource (probably memory).
+
+ STATUS_INVALID_SID - The sid provided as the domain identifier is not
+ a valid SID structure.
+
+ Other errors that might be returned are values returned by:
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG i;
+
+
+
+
+
+ //
+ // Make sure the SID provided is legitimate...
+ //
+
+ if ( !RtlValidSid(DomainId)) {
+ NtStatus = STATUS_INVALID_SID;
+ } else {
+
+ //
+ // Set our default completion status
+ //
+
+ NtStatus = STATUS_NO_SUCH_DOMAIN;
+
+
+ //
+ // Search the list of defined domains for a match.
+ //
+
+ for (i = 0; i<SampDefinedDomainsCount; i++ ) {
+
+ if (RtlEqualSid( DomainId, SampDefinedDomains[i].Sid)) {
+
+ //
+ // Copy the found name and handle into the context
+ // Note we reference the key handle in the defined_domains
+ // structure directly since it is not closed
+ // when the context is deleted.
+ //
+
+ DomainContext->RootKey = SampDefinedDomains[i].Context->RootKey;
+ DomainContext->RootName = SampDefinedDomains[i].Context->RootName;
+ DomainContext->DomainIndex = i;
+
+ //
+ // Set the transaction domain to the one found
+ //
+
+ SampSetTransactionDomain( i );
+
+ NtStatus = STATUS_SUCCESS;
+ break; // out of for
+ }
+ }
+ }
+
+
+ return(NtStatus);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines available to other SAM modules //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOLEAN
+SampInitializeDomainObject( VOID )
+
+/*++
+
+Routine Description:
+
+ This service performs initialization functions related to the Domain
+ object class.
+
+ This involves:
+
+ 1) Openning the DOMAINS registry key.
+
+ 2) Obtaining the name of each domain (builtin and account)
+ and opening that domain.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE - Indicates initialization was performed successfully.
+
+ FALSE - Indicates initialization was not performed successfully.
+
+
+--*/
+
+{
+
+ NTSTATUS NtStatus;
+ ULONG DefinedDomainsSize, i, j, k;
+ BOOLEAN ReturnStatus = TRUE;
+
+ //
+ // Open all domains and keep information about each in memory for
+ // somewhat fast processing and less complicated code strewn throughout.
+ //
+ // This concept will work in the future
+ // but we will have to allow dynamic re-sizing of this array
+ // when domains can be added and/or deleted. For the first
+ // revision, there exactly 2 domains and they can't be deleted.
+ //
+
+ SampDefinedDomainsCount = 2;
+ DefinedDomainsSize = SampDefinedDomainsCount * sizeof(SAMP_DEFINED_DOMAINS);
+ SampDefinedDomains = MIDL_user_allocate( DefinedDomainsSize );
+
+ //
+ // Get the BUILTIN and ACCOUNT domain information from the LSA
+ //
+
+ NtStatus = SampSetDomainPolicy();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(FALSE);
+ }
+
+
+ //
+ // Now prepare each of these domains
+ //
+
+ i = 0; // Index into DefinedDomains array
+ k = SampDefinedDomainsCount;
+ for (j=0; j<k; j++) {
+ NtStatus = SampInitializeSingleDomain( i );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ i++;
+
+ } else {
+
+ //
+ // If a domain didn't initialize, shift the last
+ // domain into its slot (assuming this isn't the last
+ // domain). Don't try to free the name buffers on error.
+ // The builtin domain's name is not in an allocated buffer.
+ //
+ //
+
+ if (i != (SampDefinedDomainsCount-1)) {
+
+ SampDefinedDomains[i] =
+ SampDefinedDomains[SampDefinedDomainsCount-1];
+
+ SampDefinedDomains[SampDefinedDomainsCount-1].ExternalName.Buffer = NULL;
+ SampDefinedDomains[SampDefinedDomainsCount-1].InternalName.Buffer = NULL;
+ SampDefinedDomains[SampDefinedDomainsCount-1].Sid = NULL;
+ }
+
+ //
+ // And reduce the number of defined domains we have
+ //
+
+ SampDefinedDomainsCount --;
+ }
+ }
+
+
+
+ //
+ // We might not have successfully initialized all domains,
+ // so set the final tally accordingly.
+ //
+
+#if DBG
+ if (SampDefinedDomainsCount != 2) {
+ NTSTATUS IgnoreStatus;
+ if (SampDefinedDomainsCount == 0) {
+
+
+ DbgPrint("\n\n");
+ DbgPrint("SAMSS: Neither of the two SAM domains has initialized.\n");
+ DbgPrint(" This will not prevent the system from booting,\n");
+ DbgPrint(" but logons will be prohibited and the system will\n");
+ DbgPrint(" not be usable.\n");
+ DbgPrint("\n\n");
+
+ } else {
+
+ DbgPrint("\n\n");
+ DbgPrint("SAMSS: Only one of the two SAM domains has initialized.\n");
+ DbgPrint(" This will not prevent the system from booting,\n");
+ DbgPrint(" but may result in abnormal logon or user security\n");
+ DbgPrint(" context behaviour.\n\n");
+ DbgPrint(" One typical cause of this is changing your machine\n");
+ DbgPrint(" from a WinNt system to a LanManNT system (or vica versa).\n");
+ DbgPrint(" You will need to delete (or rename) your existing\n");
+ DbgPrint(" SAM database and generate a new one.\n");
+ DbgPrint("\n\n");
+
+ IgnoreStatus = NtClose(SampDefinedDomains[0].Context->RootKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ SampFreeUnicodeString(&SampDefinedDomains[0].Context->RootName);
+ }
+ DbgPrint(" NOTE: in the end-product this will prevent booting.\n");
+ DbgPrint(" For development, the System account is still\n");
+ DbgPrint(" usable.\n");
+ DbgPrint("\n\n");
+ DbgPrint(" You probably want to logon as SYSTEM and run BLDSAM2.EXE\n\n");
+
+ //
+ // Allow the existing SAM database to be moved or deleted
+ // by closing handles that are still open within it.
+ //
+
+ IgnoreStatus = NtClose( SampKey );
+ }
+#endif //DBG
+
+
+ return(TRUE);
+
+}
+
+
+NTSTATUS
+SampInitializeSingleDomain(
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This service opens a single domain that is expected to be in the
+ SAM database.
+
+ The name and SID of the DefinedDomains array entry are expected
+ to be filled in by the caller.
+
+Arguments:
+
+ Index - An index into the DefinedDomains array. This array
+ contains information about the domain being openned,
+ including its name. The remainder of this array entry
+ is filled in by this routine.
+
+
+Return Value:
+
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT DomainContext;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PSID Sid;
+ PSAMP_V1_0A_FIXED_LENGTH_DOMAIN V1aFixed;
+
+
+
+ //
+ // Initialize everything we might have to cleanup on error
+ //
+
+ DomainContext = NULL;
+
+ //
+ // Initialize the user & group context block list heads
+ //
+
+ InitializeListHead( &SampDefinedDomains[Index].UserContextHead );
+ InitializeListHead( &SampDefinedDomains[Index].GroupContextHead );
+ InitializeListHead( &SampDefinedDomains[Index].AliasContextHead );
+
+
+ //
+ // Create a context for this domain object.
+ // We'll keep this context around until SAM is shutdown
+ // We store the context handle in the defined_domains structure.
+ //
+
+ DomainContext = SampCreateContext( SampDomainObjectType, TRUE );
+
+ if ( DomainContext == NULL ) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto error_cleanup;
+ }
+
+ DomainContext->DomainIndex = Index;
+
+ //
+ // Create the name of the root key name of this domain in the registry.
+ //
+
+ NtStatus = SampBuildDomainKeyName(
+ &DomainContext->RootName,
+ &SampDefinedDomains[Index].InternalName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ DomainContext->RootName.Buffer = NULL;
+ goto error_cleanup;
+ }
+
+
+ //
+ // Open the root key and store the handle in the context
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &DomainContext->RootName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &DomainContext->RootKey,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+#if DBG
+ DbgPrint("SAMSS: Failed to open %Z Domain.\n",
+ &SampDefinedDomains[Index].ExternalName);
+#endif //DBG
+ DomainContext->RootKey = INVALID_HANDLE_VALUE;
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the fixed length data for the domain and store in
+ // the defined_domain structure
+ //
+
+ NtStatus = SampGetFixedAttributes(
+ DomainContext,
+ FALSE, // Don't make copy
+ (PVOID *)&V1aFixed
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+#if DBG
+ DbgPrint("SAMSS: Failed to get fixed attributes for %Z Domain.\n",
+ &SampDefinedDomains[Index].ExternalName);
+#endif //DBG
+
+ goto error_cleanup;
+ }
+
+
+ RtlMoveMemory(
+ &SampDefinedDomains[Index].UnmodifiedFixed,
+ V1aFixed,
+ sizeof(*V1aFixed)
+ );
+
+
+ //
+ // Get the sid attribute of the domain
+ //
+
+ NtStatus = SampGetSidAttribute(
+ DomainContext,
+ SAMP_DOMAIN_SID,
+ FALSE,
+ &Sid
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+#if DBG
+ DbgPrint("SAMSS: Failed to get SID attribute for %Z Domain.\n",
+ &SampDefinedDomains[Index].ExternalName);
+#endif //DBG
+ goto error_cleanup;
+ }
+
+
+ //
+ // Make sure this sid agrees with the one we were passed
+ //
+
+ if (RtlEqualSid(Sid, SampDefinedDomains[Index].Sid) != TRUE) {
+#if DBG
+ DbgPrint("SAMSS: Database corruption for %Z Domain.\n",
+ &SampDefinedDomains[Index].ExternalName);
+#endif //DBG
+ NtStatus = STATUS_INVALID_ID_AUTHORITY;
+ goto error_cleanup;
+ }
+
+
+ //
+ // Build security descriptors for use in user and group account creations
+ // in this domain.
+ //
+
+ NtStatus = SampInitializeDomainDescriptors( Index );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto error_cleanup;
+ }
+
+ //
+ // Intialize the cached display information
+ //
+
+ NtStatus = SampInitializeDisplayInformation( Index );
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Store the context handle in the defined_domain structure
+ //
+
+ SampDefinedDomains[Index].Context = DomainContext;
+ }
+
+
+ return(NtStatus);
+
+
+error_cleanup:
+
+#if DBG
+ DbgPrint(" Status is 0x%lx \n", NtStatus);
+#endif //DBG
+
+
+ if (DomainContext != 0) {
+
+ SampFreeUnicodeString(&DomainContext->RootName);
+
+ if (DomainContext->RootKey != INVALID_HANDLE_VALUE) {
+
+ IgnoreStatus = NtClose(DomainContext->RootKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampSetDomainPolicy(
+ )
+/*++
+
+
+Routine Description:
+
+ This routine sets the names and SIDs for the builtin and account domains.
+ The builtin account domain has a well known name and SID.
+ The account domain has these stored in the Policy database.
+
+
+ It places the information for the builtin domain in
+ SampDefinedDomains[0] and the information for the account
+ domain in SampDefinedDomains[1].
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo;
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+ //
+ // Builtin domain - Well-known External Name and SID
+ // Constant Internal Name
+
+ RtlInitUnicodeString( &SampDefinedDomains[0].InternalName, L"Builtin");
+ RtlInitUnicodeString( &SampDefinedDomains[0].ExternalName, L"Builtin");
+
+ SampDefinedDomains[0].Sid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+ ASSERT( SampDefinedDomains[0].Sid != NULL );
+ RtlInitializeSid(
+ SampDefinedDomains[0].Sid, &BuiltinAuthority, 1 );
+ *(RtlSubAuthoritySid( SampDefinedDomains[0].Sid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+
+ //
+ // Account domain - Configurable External Name and Sid
+ // The External Name is held in the LSA Policy
+ // Database. It is equal to the Domain Name for DC's
+ // or the Computer Name for Workstations.
+ // Constant Internal Name
+ //
+
+ NtStatus = SampGetAccountDomainInfo( &PolicyAccountDomainInfo );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ SampDefinedDomains[1].Sid = PolicyAccountDomainInfo->DomainSid;
+ SampDefinedDomains[1].ExternalName = PolicyAccountDomainInfo->DomainName;
+ RtlInitUnicodeString( &SampDefinedDomains[1].InternalName, L"Account");
+ }
+
+ return(NtStatus);;
+}
+
+
+NTSTATUS
+SampReInitializeSingleDomain(
+ ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This service reinitializes a single domain after a registry hive refresh.
+
+Arguments:
+
+ Index - An index into the DefinedDomains array.
+
+Return Value:
+
+ STATUS_SUCCESS : The domain was re-initialized successfully.
+
+ Other failure codes.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSAMP_OBJECT DomainContext;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PSAMP_V1_0A_FIXED_LENGTH_DOMAIN V1aFixed;
+
+
+ Domain = &SampDefinedDomains[Index];
+
+ //
+ // Create a context for this domain object.
+ // We'll keep this context around until SAM is shutdown
+ // We store the context handle in the defined_domains structure.
+ //
+
+ DomainContext = SampCreateContext( SampDomainObjectType, TRUE );
+
+ if ( DomainContext == NULL ) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto error_cleanup;
+ }
+
+ DomainContext->DomainIndex = Index;
+
+ //
+ // Create the name of the root key name of this domain in the registry.
+ //
+
+ NtStatus = SampBuildDomainKeyName(
+ &DomainContext->RootName,
+ &SampDefinedDomains[Index].InternalName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ RtlInitUnicodeString(&DomainContext->RootName, NULL);
+ goto error_cleanup;
+ }
+
+
+ //
+ // Open the root key and store the handle in the context
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &DomainContext->RootName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &DomainContext->RootKey,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAMSS: Failed to open %Z Domain.\n", &SampDefinedDomains[Index].ExternalName));
+ DomainContext->RootKey = INVALID_HANDLE_VALUE;
+ goto error_cleanup;
+ }
+
+ KdPrint(("SAM New domain %d key : 0x%lx\n", Index, DomainContext->RootKey));
+
+ //
+ // Get the fixed length data for the domain and store in
+ // the defined_domain structure
+ //
+
+ NtStatus = SampGetFixedAttributes(
+ DomainContext,
+ FALSE, // Don't make copy
+ (PVOID *)&V1aFixed
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAMSS: Failed to get fixed attributes for %Z Domain.\n", &SampDefinedDomains[Index].ExternalName));
+ goto error_cleanup;
+ }
+
+ //
+ // Copy the fixed-length data into our in-memory data area for this domain.
+ //
+
+ RtlMoveMemory(
+ &SampDefinedDomains[Index].UnmodifiedFixed,
+ V1aFixed,
+ sizeof(*V1aFixed)
+ );
+
+
+ //
+ // Delete any cached display information
+ //
+
+ {
+ ULONG OldTransactionDomainIndex = SampTransactionDomainIndex;
+ SampTransactionDomainIndex = Index;
+
+ NtStatus = SampMarkDisplayInformationInvalid(SampUserObjectType);
+ NtStatus = SampMarkDisplayInformationInvalid(SampGroupObjectType);
+
+ SampTransactionDomainIndex = OldTransactionDomainIndex;
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Store the context handle in the defined_domain structure
+ //
+
+ SampDeleteContext(Domain->Context);
+ Domain->Context = DomainContext;
+
+
+ //
+ // Close all account context registry handles for existing
+ // open contexts
+ //
+
+ SampInvalidateContextListKeys(&Domain->UserContextHead, TRUE);
+ SampInvalidateContextListKeys(&Domain->GroupContextHead, TRUE);
+ SampInvalidateContextListKeys(&Domain->AliasContextHead, TRUE);
+ }
+
+
+ return(NtStatus);
+
+
+error_cleanup:
+
+ KdPrint((" Status is 0x%lx \n", NtStatus));
+
+ if (DomainContext != NULL) {
+ SampDeleteContext(DomainContext);
+ }
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampCollisionError(
+ IN SAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by SamICreateAccountByRid when there is a
+ name or RID collision. It accepts the type of account which caused
+ the collision, and returns the appropriate error status.
+
+Arguments:
+
+ ObjectType - The type of account that has the same Rid or Name (but
+ not both) as the account that was to be created.
+
+Return Value:
+
+ STATUS_USER_EXISTS - An object with the specified name could not be
+ created because a User account with that name or RID already exists.
+
+ STATUS_GROUP_EXISTS - An object with the specified name could not be
+ created because a Group account with that name or RID already exists.
+
+ STATUS_ALIAS_EXISTS - An object with the specified name could not be
+ created because an Alias account with that name or RID already exists.
+
+--*/
+{
+
+ //
+ // Name collision. Return offending RID and appropriate
+ // error code.
+ //
+
+ switch ( ObjectType ) {
+
+ case SampAliasObjectType: {
+
+ return STATUS_ALIAS_EXISTS;
+ }
+
+ case SampGroupObjectType: {
+
+ return STATUS_GROUP_EXISTS;
+ }
+
+ case SampUserObjectType: {
+
+ return STATUS_USER_EXISTS;
+ }
+ }
+}
+
+
+
+NTSTATUS
+SamICreateAccountByRid(
+ IN SAMPR_HANDLE DomainHandle,
+ IN SAM_ACCOUNT_TYPE AccountType,
+ IN ULONG RelativeId,
+ IN PRPC_UNICODE_STRING AccountName,
+ IN ACCESS_MASK DesiredAccess,
+ OUT SAMPR_HANDLE *AccountHandle,
+ OUT ULONG *ConflictingAccountRid
+ )
+
+/*++
+
+Routine Description:
+
+ This service creates a user, group or alias account with a specific
+ RID value.
+
+
+Arguments:
+
+ DomainHandle - A handle to an open domain.
+
+ AccountType - Specifies which type of account is being created.
+
+ RelativeId - The relative ID to be assigned to the account. If an
+ account of the specified type and specified RID value and
+ specified name already exists, then it will be opened. If an
+ account exists with any of this information in conflict, then an
+ error will be returned indicating what the problem is.
+
+ AccountName - The name to assign to the account. If an account of
+ the specified type and specified RID value and specified name
+ already exists, then it will be opened. If an account exists with
+ any of this information in conflict, then an error will be returned
+ indicating what the problem is.
+
+ DesiredAccess - Specifies the accesses desired to the account object.
+
+ AccountHandle - Recieves a handle to the account object.
+
+ ConflictingAccountRid - If another account with the same name or RID
+ prevents this account from being created, then this will receive
+ the RID of the conflicting account (in the case of conflicting
+ RIDs, this means that we return the RID that was passed in).
+ The error value indicates the type of the account.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The object has been successfully opened or created.
+
+ STATUS_OBJECT_TYPE_MISMATCH - The specified object type did not match
+ the type of the object found with the specified RID.
+
+ STATUS_USER_EXISTS - An object with the specified name could not be
+ created because a User account with that name already exists.
+
+ STATUS_GROUP_EXISTS - An object with the specified name could not be
+ created because a Group account with that name already exists.
+
+ STATUS_ALIAS_EXISTS - An object with the specified name could not be
+ created because an Alias account with that name already exists.
+
+
+--*/
+{
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ SAM_ACCOUNT_TYPE ObjectType, SecondObjectType, ThirdObjectType;
+ SID_NAME_USE SidNameUse;
+ HANDLE KeyHandle;
+ NTSTATUS NtStatus, IgnoreStatus,
+ NotFoundStatus, FoundButWrongStatus;
+ ACCESS_MASK GrantedAccess;
+
+ ASSERT( RelativeId != 0 );
+
+ switch ( AccountType ) {
+
+ case SamObjectUser: {
+
+ ObjectType = SampUserObjectType;
+ SecondObjectType = SampAliasObjectType;
+ ThirdObjectType = SampGroupObjectType;
+ NotFoundStatus = STATUS_NO_SUCH_USER;
+ FoundButWrongStatus = STATUS_USER_EXISTS;
+ break;
+ }
+
+ case SamObjectGroup: {
+
+ ObjectType = SampGroupObjectType;
+ SecondObjectType = SampAliasObjectType;
+ ThirdObjectType = SampUserObjectType;
+ NotFoundStatus = STATUS_NO_SUCH_GROUP;
+ FoundButWrongStatus = STATUS_GROUP_EXISTS;
+ break;
+ }
+
+ case SamObjectAlias: {
+
+ ObjectType = SampAliasObjectType;
+ SecondObjectType = SampGroupObjectType;
+ ThirdObjectType = SampUserObjectType;
+ NotFoundStatus = STATUS_NO_SUCH_ALIAS;
+ FoundButWrongStatus = STATUS_ALIAS_EXISTS;
+ break;
+ }
+
+ default: {
+
+ return( STATUS_INVALID_PARAMETER );
+ }
+ }
+
+ //
+ // See if the account specified already exists.
+ //
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampLookupAccountRid(
+ ObjectType,
+ (PUNICODE_STRING)AccountName,
+ NotFoundStatus,
+ ConflictingAccountRid,
+ &SidNameUse
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // The NAME exists; now we have to check the RID.
+ //
+
+ if ( (*ConflictingAccountRid) == RelativeId ) {
+
+ //
+ // The correct account already exists, so just open it.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampOpenAccount(
+ ObjectType,
+ DomainHandle,
+ DesiredAccess,
+ RelativeId,
+ TRUE, //we already have the lock
+ AccountHandle
+ );
+
+ goto Done;
+
+ } else {
+
+ //
+ // An account with the given name, but a different RID, exists.
+ // Return error.
+ //
+
+ NtStatus = FoundButWrongStatus;
+ }
+
+ } else {
+
+ if ( NtStatus == NotFoundStatus ) {
+
+ //
+ // Account doesn't exist, that's good
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check for name collision with 2nd object type
+ //
+
+ NtStatus = SampLookupAccountRid(
+ SecondObjectType,
+ (PUNICODE_STRING)AccountName,
+ STATUS_UNSUCCESSFUL,
+ ConflictingAccountRid,
+ &SidNameUse
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // The name was found; return an error.
+ //
+
+ NtStatus = SampCollisionError( SecondObjectType );
+
+ } else {
+
+ if ( NtStatus == STATUS_UNSUCCESSFUL ) {
+
+ //
+ // Account doesn't exist, that's good
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check for name collision with 3rd object type
+ //
+
+ NtStatus = SampLookupAccountRid(
+ ThirdObjectType,
+ (PUNICODE_STRING)AccountName,
+ STATUS_UNSUCCESSFUL,
+ ConflictingAccountRid,
+ &SidNameUse
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampCollisionError( ThirdObjectType );
+
+ } else {
+
+ if ( NtStatus == STATUS_UNSUCCESSFUL ) {
+
+ //
+ // Account doesn't exist, that's good
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We didn't find the name as an alias, group or user.
+ // Now, check to see if the RID is already in use. First,
+ // check to see if the specified RID is already in use as
+ // the specified account type, but with a different name.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampOpenAccount(
+ ObjectType,
+ DomainHandle,
+ DesiredAccess,
+ RelativeId,
+ TRUE, //we already have the lock
+ AccountHandle
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // The RID is in use, but the name is wrong.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+ IgnoreStatus = SamrCloseHandle( AccountHandle );
+ ASSERT( NT_SUCCESS( IgnoreStatus ) );
+
+ *ConflictingAccountRid = RelativeId;
+
+ NtStatus = FoundButWrongStatus;
+
+ } else {
+
+ //
+ // RID not in use, that's good
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now check to see if the RID is already in use, but in
+ // an account type other than the one specified. (type2)
+ //
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SecondObjectType,
+ &KeyName,
+ RelativeId,
+ NULL // Don't give a sub-key name
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+
+ NtStatus = RtlpNtOpenKey(
+ &KeyHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // There is an account with the specified RID.
+ // Return error.
+ //
+
+ IgnoreStatus = NtClose( KeyHandle );
+ ASSERT( NT_SUCCESS( IgnoreStatus ) );
+
+ *ConflictingAccountRid = RelativeId;
+
+ NtStatus = SampCollisionError( SecondObjectType );
+
+ } else {
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now check to see if the RID is already in use, but in
+ // an account type other than the one specified. (type3)
+ //
+
+ NtStatus = SampBuildAccountSubKeyName(
+ ThirdObjectType,
+ &KeyName,
+ RelativeId,
+ NULL // Don't give a sub-key name
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+
+ NtStatus = RtlpNtOpenKey(
+ &KeyHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // There is an account with the specified
+ // RID. Return error.
+ //
+
+ IgnoreStatus = NtClose( KeyHandle );
+ ASSERT( NT_SUCCESS( IgnoreStatus ) );
+
+ *ConflictingAccountRid = RelativeId;
+
+ NtStatus = SampCollisionError( ThirdObjectType );
+
+ } else {
+
+ NtStatus = STATUS_SUCCESS;
+ }
+ }
+ }
+
+
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We haven't found a conflicting account, so go ahead
+ // and create this one with the name and RID specified.
+ //
+
+ switch ( AccountType ) {
+
+ case SamObjectUser: {
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampCreateUserInDomain(
+ DomainHandle,
+ AccountName,
+ USER_NORMAL_ACCOUNT,
+ DesiredAccess,
+ TRUE,
+ AccountHandle,
+ &GrantedAccess,
+ &RelativeId
+ );
+
+ break;
+ }
+
+ case SamObjectGroup: {
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampCreateGroupInDomain(
+ DomainHandle,
+ AccountName,
+ DesiredAccess,
+ TRUE,
+ AccountHandle,
+ &RelativeId
+ );
+ break;
+ }
+
+ case SamObjectAlias: {
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = SampCreateAliasInDomain(
+ DomainHandle,
+ AccountName,
+ DesiredAccess,
+ TRUE,
+ AccountHandle,
+ &RelativeId
+ );
+ break;
+ }
+ }
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // We may have created a new account. Set the domain's RID marker,
+ // if necessary, to make sure we don't reuse the RID we just created.
+ //
+
+ PSAMP_DEFINED_DOMAINS Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ if ( RelativeId >= Domain->CurrentFixed.NextRid ) {
+ Domain->CurrentFixed.NextRid = RelativeId + 1;
+ }
+ }
+ }
+
+
+Done:
+ //
+ // De-reference the domain object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampReleaseWriteLock( TRUE );
+
+ } else {
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamIGetSerialNumberDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ OUT PLARGE_INTEGER ModifiedCount,
+ OUT PLARGE_INTEGER CreationTime
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves the creation time and modified count of the
+ domain. This information is used as a serial number for the domain.
+
+Arguments:
+
+ DomainHandle - Handle to the domain being replicated.
+
+ ModifiedCount - Retrieves the current count of modifications to the
+ domain.
+
+ CreationTime - Receives the date/time the domain was created.
+
+Return Value:
+
+ STATUS_SUCCESS - The service has completed successfully.
+
+--*/
+{
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+
+ SampAcquireReadLock();
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0L,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ (*ModifiedCount) = Domain->UnmodifiedFixed.ModifiedCount;
+ (*CreationTime) = Domain->UnmodifiedFixed.CreationTime;
+
+ //
+ // De-reference the domain object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ SampReleaseReadLock();
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamISetSerialNumberDomain(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PLARGE_INTEGER ModifiedCount,
+ IN PLARGE_INTEGER CreationTime,
+ IN BOOLEAN StartOfFullSync
+ )
+
+/*++
+
+Routine Description:
+
+ This routine causes the creation time and modified count of the
+ domain to be replaced. This information is used as a serial number
+ for the domain.
+
+Arguments:
+
+ DomainHandle - Handle to the domain being replicated.
+
+ ModifiedCount - Provides the current count of modifications to the
+ domain.
+
+ CreationTime - Provides the date/time the domain was created.
+
+ StartOfFullSync - This boolean indicates whether a full sync is being
+ initiated. If this is TRUE, then a full sync is to follow and
+ all existing domain information may be discarded. If this is
+ FALSE, then only specific domain information is to follow and all
+ changes must not violate statndard SAM operation behaviour.
+
+Return Value:
+
+ STATUS_SUCCESS - The service has completed successfully.
+
+ Other failures may be returned from SampReleaseWriteLock().
+
+--*/
+{
+ LARGE_INTEGER LargeOne, AdjustedModifiedCount;
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+ UNREFERENCED_PARAMETER( StartOfFullSync );
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return(NtStatus);
+ }
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0L,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ //
+ // Now set the Domain's ModifiedCount and CreationTime to the values
+ // specified.
+ //
+
+ Domain->CurrentFixed.CreationTime = (*CreationTime);
+
+ if ( SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
+ == DomainServerRoleBackup ) {
+
+ //
+ // Go ahead and use the ModifiedCount that was passed in.
+ // Since this is a BDC, the commit code will not increment
+ // the ModifiedCount.
+ //
+
+ Domain->CurrentFixed.ModifiedCount = (*ModifiedCount);
+
+ } else {
+
+ //
+ // This is a PDC, so the commit code will increment the
+ // ModifiedCount before writing it out to disk. So decrement
+ // it here so that it ends up at the right value.
+ //
+
+
+ AdjustedModifiedCount.QuadPart = ModifiedCount->QuadPart - 1 ;
+
+ Domain->CurrentFixed.ModifiedCount = AdjustedModifiedCount;
+ }
+
+ if ( !( ModifiedCount->QuadPart == 0) ||
+ !StartOfFullSync ) {
+
+ //
+ // If ModifiedCount is non-zero, we must be ending a full
+ // or partial replication of the database...or perhaps we've
+ // just finished a 128k chunk over a WAN or somesuch. Let's
+ // ask to flush this stuff out to disk right away, rather
+ // than waiting for the flush thread to get around to it.
+ //
+
+ FlushImmediately = TRUE;
+ }
+
+
+
+
+ SampDiagPrint( DISPLAY_ROLE_CHANGES,
+ ("SAM: SamISetSerialNumberDomain\n"
+ " Old ModifiedId: [0x%lx, 0x%lx]\n"
+ " New ModifiedId: [0x%lx, 0x%lx]\n",
+ Domain->UnmodifiedFixed.ModifiedCount.HighPart,
+ Domain->UnmodifiedFixed.ModifiedCount.LowPart,
+ Domain->CurrentFixed.ModifiedCount.HighPart,
+ Domain->CurrentFixed.ModifiedCount.LowPart )
+ );
+
+
+ //
+ // De-reference the domain object
+ // Don't save changes - the domain fixed info will be written
+ // out when the write lock is released.
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ NtStatus = SampReleaseWriteLock( TRUE );
+
+ } else {
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampGetPrivateUserData(
+ PSAMP_OBJECT UserContext,
+ OUT PULONG DataLength,
+ OUT PVOID *Data
+ )
+
+/*++
+
+Routine Description:
+
+ This service is used during replication of private user
+ type-specific information. It reads the private user information from
+ the registry, and adjusts it if necessary (ie if the password history
+ value is smaller than it used to be).
+
+Arguments:
+
+ UserContext - A handle to a User.
+
+ DataLength - The length of the data returned.
+
+ Data - Receives a pointer to a buffer of length DataLength allocated
+ and returned by SAM. The buffer must be freed to the process
+ heap when it is no longer needed.
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the service completed successfully.
+
+ STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
+ does not support this operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING TempString;
+ PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PVOID BufferPointer;
+
+ Domain = &SampDefinedDomains[ UserContext->DomainIndex ];
+
+ //
+ // Return data length as the maximum possible for this domain
+ // - the size of the structure, plus the maximum size of the
+ // NT and LM password histories.
+ //
+
+ *DataLength = ( ( Domain->UnmodifiedFixed.PasswordHistoryLength )
+ * ENCRYPTED_NT_OWF_PASSWORD_LENGTH ) +
+ ( ( Domain->UnmodifiedFixed.PasswordHistoryLength ) *
+ ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) +
+ sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE );
+
+ *Data = MIDL_user_allocate( *DataLength );
+
+ if ( *Data == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ PasswordData = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)*Data;
+ PasswordData->DataType = 0; // set correctly when we're done
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_DBCS_PWD,
+ FALSE,
+ &TempString
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->CaseInsensitiveDbcs = TempString;
+
+ RtlCopyMemory(
+ &(PasswordData->CaseInsensitiveDbcsBuffer),
+ PasswordData->CaseInsensitiveDbcs.Buffer,
+ PasswordData->CaseInsensitiveDbcs.Length );
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_UNICODE_PWD,
+ FALSE,
+ &TempString
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->CaseSensitiveUnicode = TempString;
+
+ RtlCopyMemory(
+ &(PasswordData->CaseSensitiveUnicodeBuffer),
+ PasswordData->CaseSensitiveUnicode.Buffer,
+ PasswordData->CaseSensitiveUnicode.Length );
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_NT_PWD_HISTORY,
+ FALSE,
+ &TempString
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If history is too long, must shorten here
+ //
+
+ PasswordData->NtPasswordHistory = TempString;
+
+ if ( PasswordData->NtPasswordHistory.Length > (USHORT)
+ ( Domain->UnmodifiedFixed.PasswordHistoryLength
+ * ENCRYPTED_NT_OWF_PASSWORD_LENGTH ) ) {
+
+ PasswordData->NtPasswordHistory.Length = (USHORT)
+ ( Domain->UnmodifiedFixed.PasswordHistoryLength
+ * ENCRYPTED_NT_OWF_PASSWORD_LENGTH );
+ }
+
+ //
+ // Put the body of the Nt password history
+ // immediately following the structure.
+ //
+
+ BufferPointer = (PVOID)(((PCHAR)PasswordData) +
+ sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
+
+ RtlCopyMemory(
+ BufferPointer,
+ PasswordData->NtPasswordHistory.Buffer,
+ PasswordData->NtPasswordHistory.Length );
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_LM_PWD_HISTORY,
+ FALSE,
+ &TempString
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->LmPasswordHistory = TempString;
+
+ if ( PasswordData->LmPasswordHistory.Length > (USHORT)
+ ( Domain->UnmodifiedFixed.PasswordHistoryLength
+ * ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) ) {
+
+ PasswordData->LmPasswordHistory.Length = (USHORT)
+ ( Domain->UnmodifiedFixed.PasswordHistoryLength
+ * ENCRYPTED_LM_OWF_PASSWORD_LENGTH );
+ }
+
+ //
+ // Put the body of the Lm password history
+ // immediately following the Nt password
+ // history.
+ //
+
+ BufferPointer = (PVOID)(((PCHAR)(BufferPointer)) +
+ PasswordData->NtPasswordHistory.Length );
+
+ RtlCopyMemory(
+ BufferPointer,
+ PasswordData->LmPasswordHistory.Buffer,
+ PasswordData->LmPasswordHistory.Length );
+
+ PasswordData->DataType = SamPrivateDataPassword;
+ }
+ }
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamIGetPrivateData(
+ IN SAMPR_HANDLE SamHandle,
+ IN PSAMI_PRIVATE_DATA_TYPE PrivateDataType,
+ OUT PBOOLEAN SensitiveData,
+ OUT PULONG DataLength,
+ OUT PVOID *Data
+ )
+
+/*++
+
+Routine Description:
+
+ This service is used to replicate private object type-specific
+ information. This information must be replicated for each instance
+ of the object type that is replicated.
+
+Arguments:
+
+ SamHandle - A handle to a Domain, User, Group or Alias.
+
+ PrivateDataType - Indicates which private data is being retrieved.
+ The data type must correspond to the type of object that the
+ handle is to.
+
+ SensitiveData - Indicates that the data returned must be encrypted
+ before being sent anywhere.
+
+ DataLength - The length of the data returned.
+
+ Data - Receives a pointer to a buffer of length DataLength allocated
+ and returned by SAM. The buffer must be freed to the process
+ heap when it is no longer needed.
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the service completed successfully.
+
+ STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
+ does not support this operation.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ SAMP_OBJECT_TYPE FoundType;
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ SampAcquireReadLock();
+
+ switch ( *PrivateDataType ) {
+
+ case SamPrivateDataNextRid: {
+
+ PSAMP_OBJECT DomainContext;
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)SamHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0L,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData;
+
+ //
+ // Return the domain's NextRid.
+ //
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ *Data = MIDL_user_allocate( sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ) );
+
+ if ( *Data == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ NextRidData = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)*Data;
+ NextRidData->NextRid = Domain->CurrentFixed.NextRid;
+ NextRidData->DataType = SamPrivateDataNextRid;
+ }
+
+ *DataLength = sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE );
+
+ *SensitiveData = FALSE;
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ break;
+ }
+
+ case SamPrivateDataPassword: {
+
+ PSAMP_OBJECT UserContext;
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ UserContext = (PSAMP_OBJECT)SamHandle;
+ NtStatus = SampLookupContext(
+ UserContext,
+ 0L,
+ SampUserObjectType, //ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetPrivateUserData(
+ UserContext,
+ DataLength,
+ Data
+ );
+
+ *SensitiveData = TRUE;
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( UserContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ break;
+ }
+
+ default: {
+
+ //
+ // Since caller is trusted, assume we've got a version mismatch
+ // or somesuch.
+ //
+
+ NtStatus = STATUS_NOT_IMPLEMENTED;
+
+ break;
+ }
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampSetPrivateUserData(
+ PSAMP_OBJECT UserContext,
+ IN ULONG DataLength,
+ IN PVOID Data
+ )
+
+/*++
+
+Routine Description:
+
+ This service is used to replicate private user type-specific
+ information. It writes the private data (passwords and password
+ histories) to the registry.
+
+
+Arguments:
+
+ UserContext - Handle to a User object.
+
+ DataLength - The length of the data being set.
+
+ Data - A pointer to a buffer of length DataLength containing the
+ private data.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
+ does not support this operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData;
+ BOOLEAN ReplicateImmediately = FALSE;
+
+ ASSERT( Data != NULL );
+
+ if ( ( Data != NULL ) &&
+ ( DataLength >= sizeof(SAMI_PRIVATE_DATA_PASSWORD_TYPE) ) ) {
+
+ PasswordData = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)Data;
+
+ PasswordData->CaseInsensitiveDbcs.Buffer = (PWSTR)
+ (&(PasswordData->CaseInsensitiveDbcsBuffer));
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_DBCS_PWD,
+ &(PasswordData->CaseInsensitiveDbcs)
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->CaseSensitiveUnicode.Buffer = (PWSTR)
+ (&(PasswordData->CaseSensitiveUnicodeBuffer));
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_UNICODE_PWD,
+ &(PasswordData->CaseSensitiveUnicode)
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->NtPasswordHistory.Buffer =
+ (PWSTR)(((PCHAR)PasswordData) +
+ sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_NT_PWD_HISTORY,
+ &(PasswordData->NtPasswordHistory)
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData->LmPasswordHistory.Buffer =
+ (PWSTR)(((PCHAR)(PasswordData->NtPasswordHistory.Buffer)) +
+ PasswordData->NtPasswordHistory.Length );
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ UserContext,
+ SAMP_USER_LM_PWD_HISTORY,
+ &(PasswordData->LmPasswordHistory)
+ );
+ }
+ }
+ }
+
+ } else {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamISetPrivateData(
+ IN SAMPR_HANDLE SamHandle,
+ IN ULONG DataLength,
+ IN PVOID Data
+ )
+
+/*++
+
+Routine Description:
+
+ This service is used to replicate private object type-specific
+ information. This information must be replicated for each instance
+ of the object type that is replicated.
+
+
+Arguments:
+
+ SamHandle - Handle to a Domain, User, Group or Alias object. See
+ SamIGetPrivateInformation() for a list of supported object
+ types.
+
+ DataLength - The length of the data being set.
+
+ Data - A pointer to a buffer of length DataLength containing the
+ private data.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
+ does not support this operation.
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ SAMP_OBJECT_TYPE FoundType;
+ BOOLEAN ReplicateImmediately = FALSE;
+
+ ASSERT( Data != NULL );
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ switch ( *((PSAMI_PRIVATE_DATA_TYPE)(Data)) ) {
+
+ case SamPrivateDataNextRid: {
+
+ PSAMP_OBJECT DomainContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)SamHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ 0L,
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Set the domain's NextRid.
+ //
+
+ Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
+
+ if ( ( Data != NULL ) &&
+ ( DataLength == sizeof(SAMI_PRIVATE_DATA_NEXTRID_TYPE) ) ) {
+
+ PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData;
+
+ //
+ // We can trust Data to be a valid pointer; since our
+ // caller is trusted.
+ //
+
+ NextRidData = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)Data;
+
+ //
+ // We used to set the domain's NextRid here. But we've
+ // decided that, rather than trying to replicate an exact
+ // copy of the database, we're going to try to patch any
+ // problems as we replicate. To ensure that we don't
+ // create any problems on the way, we want to make sure
+ // that the NextRid value on a BDC is NEVER decreased.
+ // Not that it matters; nobody calls this anyway. So the
+ // Get/SetPrivateData code for domains could be removed.
+ //
+
+ // Domain->CurrentFixed.NextRid = NextRidData->NextRid;
+
+ } else {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // De-reference the object
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampDeReferenceContext(
+ DomainContext,
+ TRUE
+ );
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext(
+ DomainContext,
+ FALSE
+ );
+ }
+ }
+
+ break;
+ }
+
+ case SamPrivateDataPassword: {
+
+ PSAMP_OBJECT UserContext;
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ UserContext = (PSAMP_OBJECT)SamHandle;
+ NtStatus = SampLookupContext(
+ UserContext,
+ 0L,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampSetPrivateUserData(
+ UserContext,
+ DataLength,
+ Data
+ );
+ //
+ // De-reference the object, adding attribute changes to the
+ // RXACT if everything went OK.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampDeReferenceContext(
+ UserContext,
+ TRUE
+ );
+
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext(
+ UserContext,
+ FALSE
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+ }
+
+ break;
+ }
+
+ default: {
+
+ //
+ // We've either got a version mismatch, or the caller passed us
+ // bad data, or SamIGetPrivateData() never finished getting the
+ // data into the buffer.
+ //
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+
+ break;
+ }
+ }
+
+
+ //
+ // Release the write lock - commit only if successful.
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampReleaseWriteLock( TRUE );
+
+ //
+ // No need to call SampNotifyNetlogonOfDelta, since the replicator
+ // is the one that made this call.
+ //
+
+ } else {
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrTestPrivateFunctionsDomain(
+ IN SAMPR_HANDLE DomainHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service is called to test functions that are normally only
+ accessible inside the security process.
+
+
+Arguments:
+
+ DomainHandle - Handle to the domain being tested.
+
+Return Value:
+
+ STATUS_SUCCESS - The tests completed successfully.
+
+ Any errors are as propogated from the tests.
+
+
+--*/
+{
+#if SAM_SERVER_TESTS
+
+ LARGE_INTEGER ModifiedCount1;
+ LARGE_INTEGER CreationTime1;
+ PSAMP_DEFINED_DOMAINS Domain;
+ NTSTATUS NtStatus, TmpStatus;
+ SAMI_PRIVATE_DATA_TYPE DataType = SamPrivateDataNextRid;
+ SAMI_PRIVATE_DATA_NEXTRID_TYPE LocalNextRidData;
+ PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData1 = NULL;
+ PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData2 = NULL;
+ PVOID NextRidDataPointer = NULL;
+ ULONG DataLength = 0;
+ BOOLEAN SensitiveData = TRUE;
+
+ Domain = &SampDefinedDomains[ ((PSAMP_OBJECT)DomainHandle)->DomainIndex ];
+
+ //
+ // Test SamIGetSerialNumberDomain(). Just do a GET to make sure we
+ // don't blow up.
+ //
+
+ NtStatus = SamIGetSerialNumberDomain(
+ DomainHandle,
+ &ModifiedCount1,
+ &CreationTime1 );
+
+ //
+ // Test SamISetSerialNumberDomain().
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ LARGE_INTEGER ModifiedCount2;
+ LARGE_INTEGER ModifiedCount3;
+ LARGE_INTEGER CreationTime2;
+ LARGE_INTEGER CreationTime3;
+
+ //
+ // Try a simple SET to make sure we don't blow up.
+ //
+
+ ModifiedCount2.HighPart = 7;
+ ModifiedCount2.LowPart = 4;
+ CreationTime2.HighPart = 6;
+ CreationTime2.LowPart = 9;
+
+ NtStatus = SamISetSerialNumberDomain(
+ DomainHandle,
+ &ModifiedCount2,
+ &CreationTime2,
+ FALSE );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Now do a GET to see if our SET worked.
+ //
+
+ NtStatus = SamIGetSerialNumberDomain(
+ DomainHandle,
+ &ModifiedCount3,
+ &CreationTime3 );
+
+ if ( ( CreationTime2.HighPart != CreationTime3.HighPart ) ||
+ ( CreationTime2.LowPart != CreationTime3.LowPart ) ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Do another SET to put CreationTime back where it should
+ // be. ModifiedCount will be 1 too big, so what.
+ //
+
+ NtStatus = SamISetSerialNumberDomain(
+ DomainHandle,
+ &ModifiedCount1,
+ &CreationTime1,
+ FALSE );
+ }
+ }
+ }
+
+ //
+ // Test SamIGetPrivateData().
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamIGetPrivateData(
+ DomainHandle,
+ &DataType,
+ &SensitiveData,
+ &DataLength,
+ &NextRidDataPointer );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NextRidData1 = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)NextRidDataPointer;
+
+ if ( ( DataLength != sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ) ) ||
+ ( SensitiveData != FALSE ) ||
+ ( NextRidData1->DataType != SamPrivateDataNextRid ) ||
+ ( NextRidData1->NextRid != Domain->CurrentFixed.NextRid ) ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+ }
+ }
+
+// //
+// // Test SamISetPrivateData().
+// //
+// // NO, don't test it, since it no longer does anything. We don't
+// // ever want NextRid to get set, because we never want it to get
+// // smaller.
+//
+// if ( NT_SUCCESS( NtStatus ) ) {
+//
+// //
+// // First do a random domain set to make sure we don't blow up.
+// //
+//
+// LocalNextRidData.DataType = SamPrivateDataNextRid;
+// LocalNextRidData.NextRid = 34567;
+//
+// NtStatus = SamISetPrivateData(
+// DomainHandle,
+// sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ),
+// &LocalNextRidData
+// );
+//
+// if ( NT_SUCCESS( NtStatus ) ) {
+//
+// //
+// // Now do a domain get to make sure our set worked.
+// //
+//
+// NtStatus = SamIGetPrivateData(
+// DomainHandle,
+// &DataType,
+// &SensitiveData,
+// &DataLength,
+// &NextRidDataPointer );
+//
+// if ( NT_SUCCESS( NtStatus ) ) {
+//
+// //
+// // Verify the data is as we set it.
+// //
+//
+// NextRidData2 = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)NextRidDataPointer;
+//
+// if ( NextRidData2->NextRid != LocalNextRidData.NextRid ) {
+//
+// NtStatus = STATUS_DATA_ERROR;
+// }
+//
+// //
+// // Now do a domain set to restore things to their original state.
+// //
+//
+// TmpStatus = SamISetPrivateData(
+// DomainHandle,
+// sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ),
+// NextRidData1
+// );
+//
+// if ( NT_SUCCESS( NtStatus ) ) {
+//
+// NtStatus = TmpStatus;
+// }
+// }
+// }
+//
+// if ( NextRidData1 != NULL ) {
+//
+// MIDL_user_free( NextRidData1 );
+// }
+//
+// if ( NextRidData2 != NULL ) {
+//
+// MIDL_user_free( NextRidData2 );
+// }
+// }
+
+ //
+ // Test SamICreateAccountByRid().
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ RPC_UNICODE_STRING AccountNameU;
+ RPC_UNICODE_STRING AccountName2U;
+ SAMPR_HANDLE UserAccountHandle;
+ SAMPR_HANDLE BadAccountHandle;
+ SAMPR_HANDLE GroupAccountHandle;
+ NTSTATUS TmpStatus;
+ ULONG RelativeId = 1111;
+ ULONG ConflictingAccountRid;
+ BOOLEAN AllTestsCompleted = FALSE;
+
+ //
+ // Create a unique account - a user with a known name and RID.
+ //
+
+ RtlInitUnicodeString( &AccountNameU, L"USER1SRV" );
+ RtlInitUnicodeString( &AccountName2U, L"USER2SRV" );
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectUser,
+ RelativeId,
+ &AccountNameU,
+ USER_ALL_ACCESS,
+ &UserAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // User is open. Close it, and make the same call as above to
+ // make sure that the user gets opened. We'll need it open
+ // later to delete it anyway.
+ //
+
+ TmpStatus = SamrCloseHandle( &UserAccountHandle );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectUser,
+ RelativeId,
+ &AccountName,
+ USER_ALL_ACCESS,
+ &UserAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Make same call as above, but with a different RID.
+ // Should get an error because of the name collision.
+ //
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectUser,
+ RelativeId + 1,
+ &AccountName,
+ 0L,
+ &BadAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NtStatus == STATUS_USER_EXISTS ) {
+
+ //
+ // Make same call as above, but with a different name. Should
+ // get an error because of the RID collision.
+ //
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectUser,
+ RelativeId,
+ &AccountName2,
+ 0L,
+ &BadAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NtStatus == STATUS_USER_EXISTS ) {
+
+ //
+ // Create a different type - a group - with the
+ // user's RID. Should get an error because of
+ // the RID collision.
+ //
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectGroup,
+ RelativeId,
+ &AccountName,
+ 0L,
+ &BadAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NtStatus == STATUS_USER_EXISTS ) {
+
+ //
+ // Try a different type - a group - with a
+ // different name, but still the same RID.
+ // This should still fail due to the RID
+ // collision.
+ //
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectGroup,
+ RelativeId,
+ &AccountName2,
+ 0L,
+ &BadAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NtStatus == STATUS_USER_EXISTS ) {
+
+ //
+ // Create a group with the user's name, but
+ // a different RID. This should fail
+ // because of the name collision.
+ //
+
+ NtStatus = SamICreateAccountByRid(
+ DomainHandle,
+ SamObjectGroup,
+ RelativeId + 1,
+ &AccountName,
+ GROUP_ALL_ACCESS,
+ &GroupAccountHandle,
+ &ConflictingAccountRid );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Ack! This shouldn't have happened.
+ // Close and delete the group we just created.
+ //
+
+ TmpStatus = SamrDeleteGroup( &GroupAccountHandle );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+ NtStatus = STATUS_UNSUCCESSFUL;
+
+ } else {
+
+ if ( NtStatus == STATUS_USER_EXISTS ) {
+
+ NtStatus = STATUS_SUCCESS;
+ AllTestsCompleted = TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // Now delete the user.
+ //
+
+ TmpStatus = SamrDeleteUser( &UserAccountHandle );
+ ASSERT( NT_SUCCESS( TmpStatus ) );
+ }
+
+ if ( ( !AllTestsCompleted ) && ( NtStatus == STATUS_SUCCESS ) ) {
+
+ //
+ // STATUS_SUCCESS means everything succeeded (it was set after
+ // the last one succeeded) or a test that was supposed to fail
+ // didn't. If the former, set an error.
+ //
+
+ NtStatus = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ return( NtStatus );
+
+#else
+
+ return( STATUS_NOT_IMPLEMENTED );
+
+#endif // SAM_SERVER_TESTS
+
+}
+
+
+
+NTSTATUS
+SamrTestPrivateFunctionsUser(
+ IN SAMPR_HANDLE UserHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service is called to test functions that are normally only
+ accessible inside the security process.
+
+
+Arguments:
+
+ UserHandle - Handle to the user being tested.
+
+Return Value:
+
+ STATUS_SUCCESS - The tests completed successfully.
+
+ Any errors are as propogated from the tests.
+
+
+--*/
+{
+
+#if SAM_SERVER_TESTS
+
+ UNICODE_STRING WorkstationsU, LogonWorkstationU;
+ LOGON_HOURS LogonHours;
+ PVOID LogonHoursPointer, WorkstationsPointer;
+ LARGE_INTEGER LogoffTime, KickoffTime;
+ NTSTATUS NtStatus, TmpStatus;
+ SAMI_PRIVATE_DATA_TYPE DataType = SamPrivateDataPassword;
+ PVOID PasswordDataPointer = NULL;
+ PCHAR BufferPointer;
+ ULONG OriginalDataLength = 0;
+ ULONG DataLength = 0;
+ USHORT i;
+ BOOLEAN SensitiveData = FALSE;
+ SAMI_PRIVATE_DATA_PASSWORD_TYPE LocalPasswordData;
+ PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData1;
+ PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData2;
+ PUSER_ALL_INFORMATION All = NULL;
+ PUSER_ALL_INFORMATION All2 = NULL;
+
+ // --------------------------------------------------------------
+ // Test Query and SetInformationUser for UserAllInformation level
+ //
+ // The handle is passed to us from user space. Make it look like
+ // a trusted handle so we can test the trusted stuff.
+ //
+
+ ((PSAMP_OBJECT)(UserHandle))->TrustedClient = TRUE;
+
+ NtStatus = SamrQueryInformationUser(
+ UserHandle,
+ UserAllInformation,
+ (PSAMPR_USER_INFO_BUFFER *)&All
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Now change some of the data, and set it
+ //
+
+ RtlInitUnicodeString( (PUNICODE_STRING)(&All->FullName), L"FullName );
+
+ RtlInitUnicodeString( (PUNICODE_STRING)(&All->HomeDirectory), L"HomeDirectory" );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->HomeDirectoryDrive),
+ L"HomeDirectoryDrive"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->ScriptPath),
+ L"ScriptPath"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->ProfilePath),
+ L"ProfilePath"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->AdminComment),
+ L"AdminComment"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->WorkStations),
+ L"WorkStations"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->UserComment),
+ L"UserComment"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->Parameters),
+ L"Parameters"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->NtPassword),
+ L"12345678"
+ );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)(&All->LmPassword),
+ L"87654321"
+ );
+
+ All->BadPasswordCount = 5;
+ All->LogonCount = 6;
+ All->CountryCode = 7;
+ All->CodePage = 8;
+
+ All->PasswordExpired = TRUE;
+ All->NtPasswordPresent = TRUE;
+ All->LmPasswordPresent = TRUE;
+
+ All->LogonHours.UnitsPerWeek = 7;
+
+ All->WhichFields =
+ USER_ALL_FULLNAME |
+ USER_ALL_HOMEDIRECTORY |
+ USER_ALL_HOMEDIRECTORYDRIVE |
+ USER_ALL_SCRIPTPATH |
+ USER_ALL_PROFILEPATH |
+ USER_ALL_ADMINCOMMENT |
+ USER_ALL_WORKSTATIONS |
+ USER_ALL_USERCOMMENT |
+ USER_ALL_PARAMETERS |
+ USER_ALL_BADPASSWORDCOUNT |
+ USER_ALL_LOGONCOUNT |
+ USER_ALL_COUNTRYCODE |
+ USER_ALL_CODEPAGE |
+ USER_ALL_PASSWORDEXPIRED |
+ USER_ALL_LMPASSWORDPRESENT |
+ USER_ALL_NTPASSWORDPRESENT |
+ USER_ALL_LOGONHOURS;
+
+ NtStatus = SamrSetInformationUser(
+ UserHandle,
+ UserAllInformation,
+ (PSAMPR_USER_INFO_BUFFER)All
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SamrQueryInformationUser(
+ UserHandle,
+ UserAllInformation,
+ (PSAMPR_USER_INFO_BUFFER *)&All2
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Verify that queried info is as we set it
+ //
+
+ if (
+
+ //
+ // Fields that we didn't touch. Note that private
+ // data and PasswordMustChange will change anyway
+ // due to password changes.
+ //
+
+ ( All2->WhichFields != (USER_ALL_READ_GENERAL_MASK |
+ USER_ALL_READ_LOGON_MASK |
+ USER_ALL_READ_ACCOUNT_MASK |
+ USER_ALL_READ_PREFERENCES_MASK |
+ USER_ALL_READ_TRUSTED_MASK) ) ||
+ ( !(All->LastLogon.QuadPart == All2->LastLogon.QuadPart) ) ||
+ ( !(All->LastLogoff.QuadPart == All2->LastLogoff.QuadPart) ) ||
+ ( !(All->PasswordLastSet.QuadPart == All2->PasswordLastSet.QuadPart) ) ||
+ ( !(All->AccountExpires.QuadPart == All2->AccountExpires.QuadPart) ) ||
+ ( !(All->PasswordCanChange.QuadPart == All2->PasswordCanChange.QuadPart) ) ||
+ ( (All->PasswordMustChange.QuadPart == All2->PasswordMustChange.QuadPart) ) ||
+ (RtlCompareUnicodeString(
+ &(All->UserName),
+ &(All2->UserName),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->PrivateData),
+ &(All2->PrivateData),
+ FALSE) == 0) ||
+ ( All->SecurityDescriptor.Length !=
+ All2->SecurityDescriptor.Length ) ||
+ ( All->UserId != All2->UserId ) ||
+ ( All->PrimaryGroupId != All2->PrimaryGroupId ) ||
+ ( All->UserAccountControl != All2->UserAccountControl ) ||
+ ( All->PrivateDataSensitive !=
+ All2->PrivateDataSensitive ) ||
+
+ // Fields that we changed
+
+ (RtlCompareUnicodeString(
+ &(All->FullName),
+ &(All2->FullName),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->HomeDirectory),
+ &(All2->HomeDirectory),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->HomeDirectoryDrive),
+ &(All2->HomeDirectoryDrive),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->ScriptPath),
+ &(All2->ScriptPath),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->ProfilePath),
+ &(All2->ProfilePath),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->AdminComment),
+ &(All2->AdminComment),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->WorkStations),
+ &(All2->WorkStations),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->UserComment),
+ &(All2->UserComment),
+ FALSE) != 0) ||
+ (RtlCompareUnicodeString(
+ &(All->Parameters),
+ &(All2->Parameters),
+ FALSE) != 0) ||
+ ( All->BadPasswordCount != All2->BadPasswordCount ) ||
+ ( All->LogonCount != All2->LogonCount ) ||
+ ( All->CountryCode != All2->CountryCode ) ||
+ ( All->CodePage != All2->CodePage ) ||
+ ( All->PasswordExpired != All2->PasswordExpired ) ||
+ ( All->LmPasswordPresent != All2->LmPasswordPresent ) ||
+ ( All->NtPasswordPresent != All2->NtPasswordPresent ) ||
+ ( All->LogonHours.UnitsPerWeek !=
+ All2->LogonHours.UnitsPerWeek )
+ ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+
+ MIDL_user_free( All2 );
+ }
+ }
+
+ MIDL_user_free( All );
+ }
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ // --------------------------------------------------------------
+ // Test SamIAccountRestrictions
+ // NOTE: We really should have more tests for this
+ //
+
+ RtlInitUnicodeString( &WorkstationsU, L"machine1,CHADS2 chads1" );
+
+ NtStatus = SamrSetInformationUser(
+ UserHandle,
+ UserWorkStationsInformation,
+ (PSAMPR_USER_INFO_BUFFER) &WorkstationsU
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ LogonHours.UnitsPerWeek = 168;
+ LogonHours.LogonHours = MIDL_user_allocate( 21 );
+ ASSERT( LogonHours.LogonHours != NULL );
+
+ for ( i = 0; i < 21; i++ ) {
+
+ LogonHours.LogonHours[i] = 0xa1;
+ }
+
+ NtStatus = SamrSetInformationUser(
+ UserHandle,
+ UserLogonHoursInformation,
+ (PSAMPR_USER_INFO_BUFFER)&LogonHours
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ LogonHoursPointer = NULL;
+
+ NtStatus = SamrQueryInformationUser(
+ UserHandle,
+ UserLogonHoursInformation,
+ (PSAMPR_USER_INFO_BUFFER *)&LogonHoursPointer
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ WorkstationsPointer = NULL;
+
+ NtStatus = SamrQueryInformationUser(
+ UserHandle,
+ UserWorkStationsInformation,
+ (PSAMPR_USER_INFO_BUFFER *)&WorkstationsPointer
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ RtlInitUnicodeString( &WorkstationsU, L"ChadS2" );
+
+ NtStatus = SamIAccountRestrictions(
+ UserHandle,
+ &LogonWorkstation,
+ WorkstationsPointer,
+ LogonHoursPointer,
+ &LogoffTime,
+ &KickoffTime
+ );
+
+ if ( NtStatus == STATUS_INVALID_LOGON_HOURS ) {
+
+ //
+ // We hate to use 0xff all the time as a test value, but using
+ // 0xA1 as a test value means that this test may fail depending
+ // on when it runs. So only IF we get this error, will we try
+ // again with 0xff as the logon hours.
+ //
+
+ LogonHours.UnitsPerWeek = 168;
+
+ for ( i = 0; i < 21; i++ ) {
+
+ LogonHours.LogonHours[i] = 0xff;
+ }
+
+ NtStatus = SamrSetInformationUser(
+ UserHandle,
+ UserLogonHoursInformation,
+ (PSAMPR_USER_INFO_BUFFER)&LogonHours
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ MIDL_user_free( LogonHoursPointer );
+ LogonHoursPointer = NULL;
+
+ NtStatus = SamrQueryInformationUser(
+ UserHandle,
+ UserLogonHoursInformation,
+ (PSAMPR_USER_INFO_BUFFER *)&LogonHoursPointer
+ );
+ ASSERT( NT_SUCCESS( NtStatus ) ) ;
+
+ NtStatus = SamIAccountRestrictions(
+ UserHandle,
+ &LogonWorkstationU,
+ WorkstationsPointer,
+ LogonHoursPointer,
+ &LogoffTime,
+ &KickoffTime
+ );
+ }
+
+ MIDL_user_free( LogonHours.LogonHours );
+
+ MIDL_user_free( LogonHoursPointer );
+ MIDL_user_free( WorkstationsPointer );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ // --------------------------------------------------------------
+ // Test SamIGetPrivateData
+ //
+
+ NtStatus = SamIGetPrivateData(
+ UserHandle,
+ &DataType,
+ &SensitiveData,
+ &OriginalDataLength,
+ &PasswordDataPointer );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ PasswordData1 = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)PasswordDataPointer;
+
+ if ( ( !( OriginalDataLength >= sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) ) ) ||
+ ( SensitiveData != TRUE ) ||
+ ( PasswordData1->DataType != SamPrivateDataPassword ) ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+ }
+
+ // --------------------------------------------------------------
+ // Now test SamISetPrivateData() for user objects.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // First do a random user set to make sure we don't blow up.
+ //
+
+ LocalPasswordData.DataType = SamPrivateDataPassword;
+
+ LocalPasswordData.CaseInsensitiveDbcs.Length = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
+ LocalPasswordData.CaseInsensitiveDbcs.MaximumLength = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
+ LocalPasswordData.CaseInsensitiveDbcs.Buffer = (PWSTR)&(LocalPasswordData.CaseInsensitiveDbcsBuffer);
+
+ BufferPointer = (PCHAR)&(LocalPasswordData.CaseInsensitiveDbcsBuffer);
+
+ for ( i = 0; i < ENCRYPTED_LM_OWF_PASSWORD_LENGTH; i++ ) {
+
+ *BufferPointer++ = (CHAR)(i + 12);
+ }
+
+ LocalPasswordData.CaseSensitiveUnicode.Length = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
+ LocalPasswordData.CaseSensitiveUnicode.MaximumLength = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
+ LocalPasswordData.CaseSensitiveUnicode.Buffer = (PWSTR)&(LocalPasswordData.CaseSensitiveUnicodeBuffer);
+
+ BufferPointer = (PCHAR)(&LocalPasswordData.CaseSensitiveUnicodeBuffer);
+
+ for ( i = 0; i < ENCRYPTED_NT_OWF_PASSWORD_LENGTH; i++ ) {
+
+ *BufferPointer++ = (CHAR)(i + 47);
+ }
+
+ LocalPasswordData.LmPasswordHistory.Length = 0;
+ LocalPasswordData.LmPasswordHistory.MaximumLength = 0;
+ LocalPasswordData.LmPasswordHistory.Buffer = (PWSTR)
+ ( &LocalPasswordData + sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
+
+ LocalPasswordData.NtPasswordHistory.Length = 0;
+ LocalPasswordData.NtPasswordHistory.MaximumLength = 0;
+ LocalPasswordData.NtPasswordHistory.Buffer = (PWSTR)
+ ( &LocalPasswordData + sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
+
+ NtStatus = SamISetPrivateData(
+ UserHandle,
+ sizeof( LocalPasswordData ),
+ &LocalPasswordData
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Now do a user get to make sure our set worked.
+ //
+
+ NtStatus = SamIGetPrivateData(
+ UserHandle,
+ &DataType,
+ &SensitiveData,
+ &DataLength,
+ &PasswordDataPointer );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Verify the data is as we set it.
+ //
+
+ PasswordData2 = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)PasswordDataPointer;
+
+ if ( ( PasswordData2->DataType != LocalPasswordData.DataType ) ||
+
+ ( PasswordData2->CaseInsensitiveDbcs.Length != LocalPasswordData.CaseInsensitiveDbcs.Length ) ||
+
+ ( PasswordData2->CaseSensitiveUnicode.Length != LocalPasswordData.CaseSensitiveUnicode.Length ) ||
+
+ ( PasswordData2->LmPasswordHistory.Length != LocalPasswordData.LmPasswordHistory.Length ) ||
+
+ ( PasswordData2->NtPasswordHistory.Length != LocalPasswordData.NtPasswordHistory.Length ) ||
+
+ ( RtlCompareMemory(
+ &LocalPasswordData.CaseInsensitiveDbcsBuffer,
+ &(PasswordData2->CaseInsensitiveDbcsBuffer),
+ ENCRYPTED_LM_OWF_PASSWORD_LENGTH) != ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) ||
+
+ ( RtlCompareMemory(
+ &LocalPasswordData.CaseSensitiveUnicodeBuffer,
+ &(PasswordData2->CaseSensitiveUnicodeBuffer),
+ ENCRYPTED_NT_OWF_PASSWORD_LENGTH) != ENCRYPTED_NT_OWF_PASSWORD_LENGTH )
+
+ ) {
+
+ NtStatus = STATUS_DATA_ERROR;
+ }
+
+ //
+ // Now do a user set to restore things to their original state.
+ //
+
+ TmpStatus = SamISetPrivateData(
+ UserHandle,
+ OriginalDataLength,
+ PasswordData1
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = TmpStatus;
+ }
+ }
+ }
+
+ if ( PasswordData1 != NULL ) {
+
+ MIDL_user_free( PasswordData1 );
+ }
+
+ if ( PasswordData2 != NULL ) {
+
+ MIDL_user_free( PasswordData2 );
+ }
+ }
+
+ return( NtStatus );
+
+#else
+
+ return( STATUS_NOT_IMPLEMENTED );
+
+#endif // SAM_SERVER_TESTS
+
+}
+
+
+
+NTSTATUS
+SampBuildDomainKeyName(
+ OUT PUNICODE_STRING DomainKeyName,
+ IN PUNICODE_STRING DomainName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds the name of a domain registry key.
+ The name produced is relative to the SAM root and will be the name of
+ key whose name is the name of the domain.
+
+ The name built up is comprized of the following components:
+
+ 1) The constant named domain parent key name ("DOMAINS").
+
+ 2) A backslash
+
+ 3) The name of the domain.
+
+
+ For example, given a DomainName of "ABC_DOMAIN" this would
+ yield a resultant DomainKeyName of "DOMAINS\ABC_DOMAIN"
+
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+
+
+Arguments:
+
+ DomainKeyName - The address of a unicode string whose buffer is to be
+ filled in with the full name of the registry key. If successfully
+ created, this string must be released with SampFreeUnicodeString()
+ when no longer needed.
+
+
+ DomainName - The name of the domain. This string is not modified.
+
+
+Return Value:
+
+ STATUS_SUCCESS - DomainKeyName points at the full key name.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USHORT TotalLength, DomainNameLength;
+
+
+ //
+ // Allocate a buffer large enough to hold the entire name.
+ // Only count the domain name if it is passed.
+ //
+
+ DomainNameLength = 0;
+ if (ARGUMENT_PRESENT(DomainName)) {
+ DomainNameLength = DomainName->Length + SampBackSlash.Length;
+ }
+
+ TotalLength = SampNameDomains.Length +
+ DomainNameLength +
+ (USHORT)(sizeof(UNICODE_NULL)); // for null terminator
+
+ NtStatus = SampInitUnicodeString( DomainKeyName, TotalLength );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomains);
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (ARGUMENT_PRESENT(DomainName)) {
+
+ //
+ // "DOMAINS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)"
+ //
+
+ NtStatus = SampAppendUnicodeString(
+ DomainKeyName,
+ DomainName
+ );
+ }
+ }
+ }
+ }
+
+
+ //
+ // Clean-up on failure
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( DomainKeyName );
+ }
+
+ return(NtStatus);
+
+}
diff --git a/private/newsam/server/gentab2.c b/private/newsam/server/gentab2.c
new file mode 100644
index 000000000..fea707673
--- /dev/null
+++ b/private/newsam/server/gentab2.c
@@ -0,0 +1,3561 @@
+/*++
+
+Copyright (c) 1994 Microsoft Corporation
+
+Module Name:
+
+ gentab2.c
+
+Abstract:
+
+ GenericTable2 package
+
+ Generic table services for maintaining data sets. The primary
+ characteristic of this generic table package is that it maintains
+ a relatively balanced tree, which provides for good (O(log(N))
+ performance.
+
+ The GenericTable2 routines are similar to the original
+ GenericTable routines provided by Gary Kimure except that the
+ GenericTable2 routines use a 2-3-tree rather than a splay tree.
+ 2-3-trees are described in "Data Structures And Algorithms", by
+ Aho, Hopcroft, and Ullman, published by Addison Wesley Publishing
+ Company.
+
+ Another difference between this package and the original Generic
+ Table package is that this one references element buffers that are
+ inserted rather than copying the data (as in the orignal package).
+ This characteristic is nice if you have to sort large numbers of
+ records by multiple keys
+
+ 2-3-trees have better characteristics than splay-trees when the
+ data being maintained is not random. For example, maintaining a
+ dictionary, in which the data quite often is provided in an orderly
+ manner, is an ideal application for 2-3-trees.
+
+ This package does not support the retrieval of elements in inserted
+ order that is supported in the original Generic Table package.
+
+ Differences between the algorithm outlined in Aho, et al and what
+ is coded here are:
+
+ 1) I provide an additional means of obtaining the elements
+ in the tree in sorted order (for enumeration performance).
+ I keep a linked list of elements in addition to the tree
+ structure.
+
+ 1) Aho et al point directly to elements in the tree from
+ nodes in the tree. In order to allow me to keep the linked
+ list mentioned in (1), I have a separate leaf element pointed
+ to from nodes which point to the element values. This leaf
+ component has the LIST_ENTRY structures used to link the
+ elements together.
+
+ 3) Aho et al's algorithms ignore the fact that they may fail
+ to allocate memory (that is, they assume the Pascal "new"
+ function always succeeds). This package assumes that
+ any memory allocation may fail and will always leave the
+ tree in a valid form (although an insertion may fail in
+ this case).
+
+
+Author:
+
+ Jim Kelly (JimK) 20-Jan-1994
+
+Environment:
+
+ Run time library, user or kernel mode.
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#include <nt.h>
+#include <ntrtl.h>
+#include <samsrvp.h>
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// defines ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+//
+// The following define controls the diagnostic capabilities that
+// are built into this package.
+//
+
+#if DBG
+#define GTBP_DIAGNOSTICS 1
+#endif // DBG
+
+
+//
+// These definitions are useful diagnostics aids
+//
+
+#if GTBP_DIAGNOSTICS
+
+//
+// defining the following symbol causes significant amounts of
+// development assistance code to be built
+//
+
+//#define GTBP_DEVELOPER_BUILD 1
+
+//
+// Global Diagnostics Flags
+//
+
+ULONG GtbpGlobalFlag;
+
+//
+// Test for diagnostics enabled
+//
+
+#define IF_GTBP_GLOBAL( FlagName ) \
+ if (GtbpGlobalFlag & (GTBP_DIAG_##FlagName))
+
+//
+// Diagnostics print statement
+//
+
+#define GtbpDiagPrint( FlagName, _Text_ ) \
+ IF_GTBP_GLOBAL( FlagName ) \
+ DbgPrint _Text_
+
+
+#else
+
+//
+// No diagnostics included in build
+//
+
+//
+// Test for diagnostics enabled
+//
+
+#define IF_GTBP_GLOBAL( FlagName ) if (FALSE)
+
+
+//
+// Diagnostics print statement (nothing)
+//
+
+#define GtbpDiagPrint( FlagName, Text ) ;
+
+
+#endif // GTBP_DIAGNOSTICS
+
+//
+// The following flags enable or disable various diagnostic
+// capabilities within SAM. These flags are set in
+// GtbpGlobalFlag.
+//
+// INSERT - print diagnostic messages related to insertion
+// operations.
+//
+// DELETION - print diagnostic messages related to deletion
+// operations.
+//
+// LEAF_AND_NODE_ALLOC - print diagnostic messages related
+// to allocation of leaf and node objects for insertion
+// operations.
+//
+// ENUMERATE - print diagnostic messages related to enumeration
+// operations. This includes getting restart keys.
+//
+// LOOKUP - print diagnostic messages related to element lookup
+// operations.
+//
+// COLLISIONS - print diagnostic messages indicating when collisions
+// occur on insert.
+//
+// VALIDATE - print diagnostic messages to be printed during table
+// validations.
+//
+
+#define GTBP_DIAG_INSERT ((ULONG) 0x00000001L)
+#define GTBP_DIAG_DELETION ((ULONG) 0x00000002L)
+#define GTBP_DIAG_LEAF_AND_NODE_ALLOC ((ULONG) 0x00000004L)
+#define GTBP_DIAG_ENUMERATE ((ULONG) 0X00000008L)
+#define GTBP_DIAG_LOOKUP ((ULONG) 0X00000010L)
+#define GTBP_DIAG_COLLISIONS ((ULONG) 0X00000020L)
+#define GTBP_DIAG_VALIDATE ((ULONG) 0X00000040L)
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Macros ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+//
+// GtbpChildrenAreLeaves(
+// IN GTB_TWO_THREE_NODE N
+// )
+// Returns TRUE if children of N are leaves.
+// Otherwise returns FALSE.
+//
+
+#define GtbpChildrenAreLeaves( N ) ((((N)->Control) & GTB_CHILDREN_ARE_LEAVES) != 0)
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Private structures and definitions //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// GTB_TWO_THREE_NODE.Control field values
+//
+
+#define GTB_CHILDREN_ARE_LEAVES (0x00000001)
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Internal Routine Definitions ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+VOID
+GtbpDeleteFromSubTree (
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PVOID Element,
+ OUT PGTB_TWO_THREE_LEAF *LowOfNode,
+ OUT BOOLEAN *ElementDeleted,
+ OUT BOOLEAN *OnlyOneChildLeft
+ );
+
+BOOLEAN
+GtbpInsertIntoSubTree (
+ PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN BOOLEAN NodeIsLeaf,
+ IN PVOID Element,
+ IN ULONG SplitCount,
+ IN PVOID *FoundElement,
+ OUT PGTB_TWO_THREE_NODE *ExtraNode,
+ OUT PGTB_TWO_THREE_LEAF *LowLeaf,
+ OUT PLIST_ENTRY *AllocatedNodes
+ );
+
+ULONG
+GtbpNumberOfChildren(
+ IN PGTB_TWO_THREE_NODE Node
+ );
+
+VOID
+GtbpGetSubTreeOfElement(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PVOID Element,
+ OUT PGTB_TWO_THREE_NODE *SubTreeNode,
+ OUT ULONG *SubTree
+ );
+
+VOID
+GtbpCoalesceChildren(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN ULONG SubTree,
+ OUT BOOLEAN *OnlyOneChildLeft
+ );
+
+VOID
+GtbpSplitNode(
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PGTB_TWO_THREE_NODE NodePassedBack,
+ IN PGTB_TWO_THREE_LEAF LowPassedBack,
+ IN ULONG SubTree,
+ IN PLIST_ENTRY AllocatedNodes,
+ OUT PGTB_TWO_THREE_NODE *NewNode,
+ OUT PGTB_TWO_THREE_LEAF *LowLeaf
+ );
+
+PGTB_TWO_THREE_LEAF
+GtbpAllocateLeafAndNodes(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN ULONG SplitCount,
+ OUT PLIST_ENTRY *AllocatedNodes
+ );
+
+PGTB_TWO_THREE_NODE
+GtbpGetNextAllocatedNode(
+ IN PLIST_ENTRY AllocatedNodes
+ );
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Exported Services ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+
+VOID
+RtlInitializeGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PRTL_GENERIC_2_COMPARE_ROUTINE CompareRoutine,
+ PRTL_GENERIC_2_ALLOCATE_ROUTINE AllocateRoutine,
+ PRTL_GENERIC_2_FREE_ROUTINE FreeRoutine
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize the table by initializing the corresponding
+ (empty) two-three tree and the extra linked-list we have
+ going through the tree.
+
+ Two-three trees are described in "Data Structures And Algorithms"
+ by Alfred Aho, John Hopcroft, and Jeffrey Ullman (Addison Wesley
+ publishing).
+
+Arguments:
+
+ Table - Pointer to the generic table to be initialized. This gets
+ typecast internally, but we export this so that we don't have to
+ invent another type of generic table for users to worry about.
+
+ CompareRoutine - User routine to be used to compare to keys in the
+ table.
+
+ AllocateRoutine - Used by the table package to allocate memory
+ when necessary.
+
+ FreeRoutine - Used by the table package to free memory previously
+ allocated using the AllocateRoutine.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+
+ //
+ // Tree is empty.
+ //
+
+ Table->Root = NULL;
+ Table->ElementCount = 0;
+
+ Table->Compare = CompareRoutine;
+ Table->Allocate = AllocateRoutine;
+ Table->Free = FreeRoutine;
+
+ InitializeListHead(&Table->SortOrderHead);
+
+ return;
+}
+
+
+PVOID
+RtlInsertElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PBOOLEAN NewElement
+ )
+
+/*++
+
+Routine Description:
+
+
+ This function inserts an element into the table.
+
+ If the element is successfully inserted into the table
+ then NewElement will be returned as TRUE and the function will
+ return the value passed via the Element parameter.
+
+ If the element already exists in the table, then NewElement
+ is returned as FALSE and the function will return the value
+ of the element already found in the table.
+
+
+ The caller is responsible for ensuring that an element referenced by
+ the table is not modified or deallocated while it is still in the
+ table.
+
+Arguments:
+
+ Table - Pointer to the generic table to which the Element is to
+ be inserted.
+
+ Element - Pointer to the element to be entered into the table.
+
+ NewElement - Receives TRUE if the element was added to the table.
+ Receives FALSE if the element collided with an element already
+ in the table (that is, an element with the same comparison
+ value already exists in the table).
+
+
+Return Value:
+
+ Pointer to the element inserted, or the element that was already
+ in the table with the same value as the one being inserted.
+
+ If NULL is returned, then memory could not be allocated to add
+ the new element.
+
+--*/
+{
+
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+
+ PGTB_TWO_THREE_NODE
+ NodePassedBack,
+ NewNode,
+ SubTreeNode,
+ Node;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf,
+ LowLeaf,
+ LowPassedBack;
+
+ ULONG
+ SplitCount,
+ SubTree;
+
+ PVOID
+ FoundElement;
+
+ PLIST_ENTRY
+ AllocatedNodes;
+
+ BOOLEAN
+ NodeIsLeaf;
+
+
+ GtbpDiagPrint( INSERT,
+ ("GTB: Inserting Element 0x%lx into table 0x%lx\n", Element, Table));
+
+ //
+ // Except for errors, one of the following will occur:
+ //
+ // o There is no root ==>
+ // 1) Allocate a root and leaf
+ // 2) put the element in the leaf and make it the
+ // 3) first child of the root
+ //
+ // o There is a root with only one child ==>
+ // 1) If the elements are equal, return without new entry
+ // 2) If the new element is less, move child 1 to 2 and
+ // make new leaf child 1.
+ // 3) Otherwise element is greater, allocate it a leaf
+ // and make it child 2.
+ //
+ // o There is a root with at least two children ==>
+ // 1) If there are already 3 children, then set split
+ // count = 2, otherwise set it to 1.
+ // 2) Call normal insertion routine to insert into
+ // appropriate SubTree.
+ // 3) If there is a split needed, then establish
+ // a newly allocated node as the root, and make it the
+ // parent of the current node. Then use the normal
+ // split routine.
+ //
+
+
+
+
+
+ //
+ // If empty, then create a root node and add the element.
+ //
+
+ if (Table->ElementCount == 0) {
+
+ GtbpDiagPrint( INSERT,
+ ("GTB: Table empty. Creating root node.\n"));
+
+ NewNode = (PGTB_TWO_THREE_NODE)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_NODE) ));
+ if (NewNode == NULL) {
+ GtbpDiagPrint(INSERT,
+ ("GTB: Couldn't allocate memory for root node.\n"));
+ (*NewElement) = FALSE;
+ return( NULL );
+ }
+ GtbpDiagPrint( INSERT,
+ ("GTB: New root node is: 0x%lx\n", NewNode));
+
+
+ NewNode->ParentNode = NULL; // Doesn't have a parent. Special case.
+ NewNode->Control = GTB_CHILDREN_ARE_LEAVES;
+ NewNode->SecondChild = NULL;
+ NewNode->ThirdChild = NULL;
+
+ //
+ // Allocate a leaf and put the element in it.
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_LEAF) ));
+
+ if (Leaf == NULL) {
+ GtbpDiagPrint(INSERT,
+ ("GTB: Couldn't allocate memory for leaf.\n"));
+ ((*Table->Free)( NewNode ));
+ (*NewElement) = FALSE;
+ return( NULL );
+ }
+
+
+ InsertHeadList( &Table->SortOrderHead, &Leaf->SortOrderEntry );
+ Leaf->Element = Element;
+ NewNode->FirstChild = (PGTB_TWO_THREE_NODE)Leaf;
+
+ Table->Root = NewNode;
+ Table->ElementCount++;
+ ASSERT(Table->ElementCount == 1);
+ (*NewElement) = TRUE;
+ return(Element);
+ }
+
+
+ //
+ // We have a root with at least one child in it.
+ //
+
+ if (Table->Root->SecondChild == NULL) {
+
+ //
+ // The root doesn't have two children.
+ // If it didn't have any children it would have been
+ // deallocated. So, it must have a degenerate case of
+ // only one child.
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)Table->Root->FirstChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+ (*NewElement) = FALSE;
+
+ GtbpDiagPrint( COLLISIONS,
+ ("GTB: Insertion attempt resulted in collision.\n"
+ " Element NOT being inserted.\n"
+ " Elements in table: %d\n",
+ Table->ElementCount));
+ return( Leaf->Element );
+ }
+
+
+ //
+ // Need a new leaf
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_LEAF) ));
+
+ if (Leaf == NULL) {
+ GtbpDiagPrint(INSERT,
+ ("GTB: Couldn't allocate memory for leaf.\n"));
+ (*NewElement) = FALSE;
+ return( NULL );
+ }
+ Leaf->Element = Element;
+
+ //
+ // it is either the first child or second
+ //
+
+ if (CompareResult == GenericLessThan) {
+
+ //
+ // Move the first child to be the second child and make
+ // a new first child leaf for the new element.
+ //
+
+ InsertHeadList( &Table->SortOrderHead, &Leaf->SortOrderEntry );
+
+
+
+ Table->Root->SecondChild = Table->Root->FirstChild;
+ Table->Root->LowOfSecond = (PGTB_TWO_THREE_LEAF)
+ Table->Root->SecondChild; //it is the leaf
+
+ Table->Root->FirstChild = (PGTB_TWO_THREE_NODE)Leaf;
+
+
+ } else {
+
+ //
+ // new element is greater than existing element.
+ // make it the second child.
+ //
+
+ InsertTailList( &Table->SortOrderHead, &Leaf->SortOrderEntry );
+
+ Table->Root->SecondChild = (PGTB_TWO_THREE_NODE)Leaf;
+ Table->Root->LowOfSecond = Leaf;
+
+ }
+
+ Table->ElementCount++;
+ ASSERT(Table->ElementCount == 2);
+
+ (*NewElement) = TRUE; //Set return value
+ return(Element);
+
+ }
+
+ //
+ // Normal insertion.
+ // If we get an ExtraNode coming back, then we may have to
+ // split the root. Normally for a node with three children
+ // you would need to allow for one node in a split. However,
+ // we will need a new root as well, so allow for two new nodes.
+ //
+
+ if (Table->Root->ThirdChild != NULL) {
+ SplitCount = 2;
+ } else {
+ SplitCount = 0;
+ }
+
+ GtbpGetSubTreeOfElement( Table, Table->Root, Element, &SubTreeNode, &SubTree);
+ NodeIsLeaf = GtbpChildrenAreLeaves(Table->Root);
+
+ (*NewElement) = GtbpInsertIntoSubTree ( Table,
+ SubTreeNode,
+ NodeIsLeaf,
+ Element,
+ SplitCount,
+ &FoundElement,
+ &NodePassedBack,
+ &LowPassedBack,
+ &AllocatedNodes
+ );
+
+ //
+ // One of several things could have happened:
+ //
+ // 1) We didn't have enough memory to add the new element.
+ // In this case we are done and simply return.
+ //
+ // 2) The element was added, and no-rearrangement to this
+ // node is needed. In this case we are done and simply
+ // return.
+ //
+ // 3) The element was added and caused a node to be pushed
+ // out of the SubTree. We have some work to do.
+ //
+
+
+ if ( (FoundElement == NULL) || // Insufficient memory, or
+ (NodePassedBack == NULL) ) { // no work for this node
+
+ return(FoundElement);
+ }
+
+
+ Node = Table->Root;
+ if (Node->ThirdChild == NULL) {
+
+ //
+ // Root doesn't yet have a third child, so use it.
+ // This might require shuffling the second child to the
+ // be the third child.
+ //
+
+ if (SubTree == 2) {
+
+ //
+ // NodePassedBack fell out of second SubTree and root does't
+ // have a third SubTree. Make that node the third SubTree.
+ //
+
+ Node->ThirdChild = NodePassedBack;
+ Node->LowOfThird = LowPassedBack;
+
+ } else {
+
+ //
+ // Node fell out of first SubTree.
+ // Make the second SubTree the third SubTree and
+ // then make the passed back node the second SubTree.
+ //
+
+ ASSERT(SubTree == 1);
+
+ Node->ThirdChild = Node->SecondChild;
+ Node->LowOfThird = Node->LowOfSecond;
+ Node->SecondChild = NodePassedBack;
+ Node->LowOfSecond = LowPassedBack;
+
+ }
+ } else {
+
+ //
+ // Node already has three children - split it.
+ // Do this by setting a new parent first.
+ //
+
+ NewNode = GtbpGetNextAllocatedNode( AllocatedNodes );
+ ASSERT(NewNode != NULL);
+
+ Table->Root = NewNode;
+ NewNode->ParentNode = NULL;
+ NewNode->Control = 0;
+ NewNode->FirstChild = Node;
+ NewNode->SecondChild = NULL;
+ NewNode->ThirdChild = NULL;
+
+ Node->ParentNode = NewNode;
+
+
+ GtbpSplitNode( Node,
+ NodePassedBack,
+ LowPassedBack,
+ SubTree,
+ AllocatedNodes,
+ &NewNode,
+ &LowLeaf
+ );
+
+ Table->Root->SecondChild = NewNode;
+ Table->Root->LowOfSecond = LowLeaf;
+ }
+
+ return(FoundElement);
+}
+
+
+BOOLEAN
+RtlDeleteElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ )
+
+/*++
+
+Routine Description:
+
+ The function DeleteElementGenericTable2 will find and delete an element
+ from a generic table. If the element is located and deleted the return
+ value is TRUE, otherwise if the element is not located the return value
+ is FALSE. The user supplied input buffer is only used as a key in
+ locating the element in the table.
+
+ The value of the passed element is compared to elements in the table
+ to determine whether or not the element is in the table. Therefore,
+ the Element passed in may be the address of the element in the table
+ to delete, or it may be an element with the same value that is not
+ in the table.
+
+Arguments:
+
+ Table - Pointer to the table in which to (possibly) delete the
+ element referenced by the buffer.
+
+ Element - Passed to the user comparasion routine. Its contents are
+ up to the user but one could imagine that it contains some
+ sort of key value.
+
+Return Value:
+
+ BOOLEAN - If the table contained the Element then TRUE, otherwise FALSE.
+
+--*/
+{
+
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ PGTB_TWO_THREE_NODE
+ Node,
+ SubTreeNode;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf,
+ LowOfNode;
+
+ BOOLEAN
+ ElementDeleted,
+ OnlyOneChildLeft;
+
+ ULONG
+ SubTree;
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Request received to delete element 0x%lx\n", Element));
+
+
+ //
+ // There are the following special cases:
+ //
+ // 1) The table is empty.
+ // 2) The table has only one leaf
+ //
+ // Otherwise, all operations work the same.
+ //
+
+ if (Table->ElementCount == 0) {
+ GtbpDiagPrint( DELETION,
+ ("GTB: No elements in table to delete.\n"));
+ return(FALSE);
+ }
+
+ if (GtbpChildrenAreLeaves(Table->Root)) {
+
+
+ //
+ // See if any of the elements match the one passed in.
+ // If so, delete the element and shift larger elements
+ // to take up the free'd child's spot (unless it is the
+ // third child).
+ //
+
+ if (Table->Root->ThirdChild != NULL) {
+ Leaf = (PGTB_TWO_THREE_LEAF)Table->Root->ThirdChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting child 3 (0x%lx) from root node.\n"
+ " Element count before deletion: %d\n",
+ Leaf, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Table->Root->ThirdChild = NULL;
+
+ Table->ElementCount--;
+ ASSERT(Table->ElementCount == 2);
+
+
+ return(TRUE);
+ }
+ }
+
+ //
+ // Try second child
+ //
+
+ if (Table->Root->SecondChild != NULL) {
+ Leaf = (PGTB_TWO_THREE_LEAF)Table->Root->SecondChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting child 2 (0x%lx) from root node.\n"
+ " Element count before deletion: %d\n",
+ Leaf, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Table->Root->SecondChild = Table->Root->ThirdChild;
+ Table->Root->ThirdChild = NULL;
+
+ Table->Root->LowOfSecond = Table->Root->LowOfThird;
+
+ Table->ElementCount--;
+ ASSERT(Table->ElementCount <= 2);
+
+ return(TRUE);
+ }
+ }
+
+ //
+ // Try first child
+ //
+
+ ASSERT(Table->Root->FirstChild != NULL);
+ Leaf = (PGTB_TWO_THREE_LEAF)Table->Root->FirstChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting child 1 (0x%lx) from root node.\n"
+ " Element count before deletion: %d\n",
+ Leaf, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Table->Root->FirstChild = Table->Root->SecondChild;
+ Table->Root->SecondChild = Table->Root->ThirdChild;
+ Table->Root->LowOfSecond = Table->Root->LowOfThird;
+ Table->Root->ThirdChild = NULL;
+
+
+ Table->ElementCount--;
+ ASSERT(Table->ElementCount <= 2);
+
+ //
+ // If that was the last element, then free the root as well.
+ //
+
+ if (Table->ElementCount == 0) {
+ (*Table->Free)(Table->Root);
+ Table->Root = NULL;
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleted last element. Deleting Root node.\n"));
+
+ }
+
+ return(TRUE);
+ }
+
+ //
+ // Didn't match any of the leaves
+ //
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: No matching element found on DELETE attempt.\n"));
+ return(FALSE);
+
+ }
+
+
+
+
+
+ //
+ // We have:
+ //
+ // - Root with at least two children
+ // - Root's children are not leaves.
+ //
+
+ //
+ // Find which sub-tree the element might be in.
+ //
+
+ Node = Table->Root;
+ GtbpGetSubTreeOfElement( Table, Node, Element, &SubTreeNode, &SubTree );
+
+ GtbpDeleteFromSubTree( Table,
+ SubTreeNode,
+ Element,
+ &LowOfNode,
+ &ElementDeleted,
+ &OnlyOneChildLeft
+ );
+
+
+ //
+ // If we deleted an entry from either the second or third
+ // subtree, then we may need to set a new LowOfXxx value.
+ // If it was the first subtree, then we simply have to return
+ // the LowLeaf value we received.
+ //
+
+ if (LowOfNode != 0) {
+ if (SubTree == 2) {
+ Node->LowOfSecond = LowOfNode;
+ } else if (SubTree == 3) {
+ Node->LowOfThird = LowOfNode;
+ }
+
+ }
+
+
+ //
+ // If the SubTreeNode has only one child left, then some
+ // adjustments are going to be necessary. Otherwise,
+ // we are done.
+ //
+
+ if (OnlyOneChildLeft) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Only one child left in 0x%lx\n", SubTreeNode));
+
+ //
+ // We are at the root and one of our children has only one
+ // child. Re-shuffle our kid's kids.
+ //
+
+ GtbpCoalesceChildren( Table,
+ Node,
+ SubTree,
+ &OnlyOneChildLeft
+ );
+
+ //
+ // After coellescing our children, the root may have only one child
+ // left. Since we are the root node, we can't pass responsibility
+ // for fixing this problem to our caller.
+ //
+
+ if (OnlyOneChildLeft) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Root has only one child. \n"
+ " Replacing root with child: 0x%lx\n", Node->FirstChild));
+ Table->Root = Table->Root->FirstChild;
+ Table->Root->ParentNode = NULL;
+
+ (*Table->Free)((PVOID)Node);
+ }
+ }
+
+ return(ElementDeleted);
+
+}
+
+
+PVOID
+RtlLookupElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ )
+
+/*++
+
+Routine Description:
+
+
+ The function LookupElementGenericTable2 will find an element in a
+ generic table. If the element is located the return value is a
+ pointer to the user defined structure associated with the element,
+ otherwise if the element is not located the return value is NULL.
+ The user supplied input buffer is only used as a key in locating
+ the element in the table.
+
+
+Arguments:
+
+ Table - Pointer to the users generic table.
+
+ Element - Used for the comparison.
+
+Return Value:
+
+ PVOID - returns a pointer to the user data if found, otherwise NULL.
+
+--*/
+
+{
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ PGTB_TWO_THREE_NODE
+ Node;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ ULONG
+ SubTree;
+
+
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Looking up element 0x%lx in table 0x%lx\n",
+ Element, Table));
+ //
+ // If the table is empty, then no possible match.
+ //
+
+ if (Table->ElementCount == 0) {
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Element not found. No elements in table.\n"));
+ return(NULL);
+ }
+
+ Node = Table->Root;
+
+ //
+ // traverse the tree until we reach a node that has leaves as
+ // children.
+ //
+ // We don't need to use recursion here because there
+ // is no tree re-structuring necessary. That is, there
+ // is no need to perform any operations back up the tree
+ // once we find the element, so it is much more efficient
+ // not to use recursion (which uses lots of push, call,
+ // pop, and ret instructions rather than short loop
+ // termination tests).
+ //
+
+ while (!GtbpChildrenAreLeaves(Node)) {
+ GtbpGetSubTreeOfElement( Table, Node, Element, &Node, &SubTree );
+ }
+
+ //
+ // We are at the node which "might" contain the element.
+ // See if any of the children match.
+ //
+
+ //
+ // Try first child
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->FirstChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Element found: 2nd child (0x%lx) of node 0x%lx\n",
+ Leaf, Node));
+ return(Leaf->Element);
+ }
+
+ //
+ // Try second child
+ //
+
+ if (Node->SecondChild != NULL) { // Must allow for Root node case
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->SecondChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Element found: 2nd child (0x%lx) of node 0x%lx\n",
+ Leaf, Node));
+ return(Leaf->Element);
+ }
+ }
+ //
+ // Try third child
+ //
+
+ if (Node->ThirdChild != NULL) {
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->ThirdChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Element found: 3rd child (0x%lx) of node 0x%lx\n",
+ Leaf, Node));
+ return(Leaf->Element);
+ }
+ }
+
+
+ GtbpDiagPrint( LOOKUP,
+ ("GTB: Element NOT found in node 0x%lx\n", Node));
+
+ return(NULL);
+
+}
+
+
+PVOID
+RtlEnumerateGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID *RestartKey
+ )
+
+/*++
+
+Routine Description:
+
+
+ The function EnumerateGenericTable2 will return to the
+ caller, one-by-one, the elements of a table (in sorted order).
+ The return value is a pointer to the user defined structure
+ associated with the element.
+
+ The input parameter RestartKey indicates where the enumeration should
+ proceed from. If there are no more new elements to return the return
+ value is NULL.
+
+ A RestartKey value of NULL will cause the enumeration to proceed
+ from the beginning of the list.
+
+ As an example of its use, to enumerate all of the elements in a table
+ the user would write:
+
+ RestartKey = NULL;
+ for (ptr = EnumerateGenericTable2(Table, &RestartKey);
+ ptr != NULL;
+ ptr = EnumerateGenericTable2(Table, &RestartKey)) {
+ :
+ }
+
+
+ If you wish to initiate an enumeration at a point other than the first
+ entry, you may use RestartKeyByIndexGenericTable2() or
+ RestartKeyByValueGenericTable2(). If a RestartKey
+ for the I'th entry was obtained via RestartKeyByIndexGenericTable2(),
+ then passing that RestartKey to this routine will cause the (I+1)th
+ element to be returned. If a RestartKey was obtained matching
+ a value passed to RestartKeyByValueGenericTable2(), then passing
+ that RestartKey to this routine will cause the entry with the
+ next higher value to be returned.
+
+ ! WARNING !
+ A RestartKey value may become invalid and cause an access violation
+ if any entries have been removed from the table. If enumeration
+ is to be carried out and it is unknown whether or not the table
+ contents have changed, it is best to obtain a RestartKey using
+ RestartKeyByIndexGenericTable2() or
+ RestartKeyByValueGenericTable2().
+
+
+Arguments:
+
+ Table - Pointer to the generic table to enumerate.
+
+ RestartKey - Upon call, indicates where the enumeration is to
+ begin. Upon return, receives context that may be used to
+ continue enumeration in a successive call. NULL indicates
+ enumeration should start at the beginning of the table.
+ A return value of NULL indicates the last entry has been
+ returned.
+
+Return Value:
+
+ PVOID - Pointer to the next enumerated element or NULL.
+ NULL is returned if the entire table has already been
+ enumerated.
+
+--*/
+
+{
+ PLIST_ENTRY
+ ListEntry;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ ListEntry = (PLIST_ENTRY)(*RestartKey);
+
+ //
+ // The restart key is a pointer to our leaf element.
+ // Since all leaves are linked together in the SortOrderList,
+ // this makes it really trivial to return the next element.
+ //
+
+ if (ListEntry == NULL) {
+ ListEntry = &Table->SortOrderHead; //Point to previous element
+ }
+
+ //
+ // RestartKey pointed to the last enumerated leaf.
+ // Advance to the new one.
+ //
+
+ ListEntry = ListEntry->Flink;
+
+ //
+ // See if we have reached the end of the list
+ //
+
+ if (ListEntry == &Table->SortOrderHead) {
+ (*RestartKey) = NULL;
+ return(NULL);
+ }
+
+ //
+ // Otherwise, return the address of the element from this leaf.
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)((PVOID)ListEntry);
+
+ (*RestartKey) = (PVOID)Leaf;
+ return(Leaf->Element);
+
+}
+
+
+
+PVOID
+RtlRestartKeyByIndexGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ ULONG I,
+ PVOID *RestartKey
+ )
+
+/*++
+
+Routine Description:
+
+
+ The function RestartKeyByIndexGenericTable2 will return a RestartKey
+ which may then be passed to EnumerateGenericTable2() to perform
+ an enumeration of sorted elements following the I'th sorted element
+ (zero based).
+
+ This routine also returns a pointer to the I'th element.
+
+ I = 0 implies restart at the second sorted element.
+
+ I = (RtlNumberGenericTable2Elements(Table)-1) will return the last
+ sorted element in the generic table.
+
+ Values of I greater than (NumberGenericTableElements(Table)-1)
+ will return NULL and the returned RestartKey will cause an
+ enumeration to be performed from the beginning of the sorted list
+ if passed to EnumerateGenericTable2().
+
+ WARNING - You may be tempted to use this routine, passing
+ first 0, then 1, then 2, et cetera, to perform
+ enumerations. DON'T. This is a very expensive
+ operation compared to the enumeration call.
+
+Arguments:
+
+ Table - Pointer to the generic table.
+
+ I - Indicates the point following which you wish to be able
+ to resume enumeration. For example, if you pass 7 for I,
+ then a RestartKey will be returned that continues enumeration
+ at the 8th element (skipping elements 0 through 6).
+
+ RestartKey - Receives context that may be used to continue
+ enumeration in a successive call. If there is no I'th
+ element, then NULL is returned.
+
+ Return Value:
+
+ PVOID - Pointer to the I'th Element. If there is no I'th element,
+ then returns NULL.
+
+--*/
+
+{
+ PLIST_ENTRY
+ ListEntry;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ ULONG
+ i;
+
+ if (I >= Table->ElementCount) {
+ (*RestartKey) = NULL;
+ return(NULL);
+ }
+
+ //
+ // Point to the first entry on the list.
+ //
+
+ ListEntry = Table->SortOrderHead.Flink;
+
+ //
+ // Move to the desired index
+ //
+
+ for (i=0; i<I; i++) {
+ ListEntry = ListEntry->Flink;
+ }
+
+
+ //
+ // Found the I'th element .
+ //
+
+ (*RestartKey) = (PVOID)ListEntry;
+ Leaf = (PGTB_TWO_THREE_LEAF)((PVOID)ListEntry);
+ return(Leaf->Element);
+
+}
+
+
+PVOID
+RtlRestartKeyByValueGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PVOID *RestartKey
+ )
+
+/*++
+
+Routine Description:
+
+
+ The function RestartKeyByValueGenericTable2 will return a RestartKey
+ which may then be passed to EnumerateGenericTable2() to perform
+ an enumeration of sorted elements. The RestartKey will have a
+ value that will cause the enumeration to begin starting with
+ the first element whose value is greater than the passed in element
+ value.
+
+ There does not have to be an element in the tree whose value
+ exactly matches the passed in value. A pointer to the element
+ with the largest value that is less than or equal to the passed
+ in value will be returned and serve as the continuation point
+ for the enumeration.
+
+
+
+Arguments:
+
+ Table - Pointer to the generic table.
+
+ Value - points to an element whose value indicates where you
+ wish enumeration to continue.
+
+ RestartKey - Receives context that may be used to continue
+ enumeration in a successive call.
+
+ Return Value:
+
+ PVOID - Pointer to the element with the largest value less than
+ or equal to the element value passed in. If there are no
+ elements in the table less than or equal to the passed value,
+ then a value of NULL will be returned.
+
+--*/
+
+{
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ PGTB_TWO_THREE_NODE
+ Node;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ ULONG
+ Children,
+ SubTree;
+
+ BOOLEAN
+ LargestElementPath;
+
+ //
+ // This routine is real similar to LookupElement
+ //
+
+ //
+ // handle the special "table is empty" case.
+ //
+
+ if (Table->ElementCount == 0) {
+ (*RestartKey) = NULL;
+ return(NULL);
+ }
+
+
+ Node = Table->Root;
+
+ //
+ // traverse the tree until we reach a node that has leaves as
+ // children.
+ //
+ // We don't need to use recursion here because there
+ // is no tree re-structuring necessary. That is, there
+ // is no need to perform any operations back up the tree
+ // once we find the element, so it is much more efficient
+ // not to use recursion (which uses lots of push, call,
+ // pop, and ret instructions rather than short loop
+ // termination tests).
+ //
+
+ LargestElementPath = TRUE;
+ while (!GtbpChildrenAreLeaves(Node)) {
+ Children = GtbpNumberOfChildren( Node );
+ GtbpGetSubTreeOfElement( Table, Node, Element, &Node, &SubTree );
+ if (Children > SubTree) { //did we take the highest value path?
+ LargestElementPath = FALSE;
+ }
+ }
+
+ Children = GtbpNumberOfChildren(Node);
+
+ //
+ // We are at the node which "might" contain the element.
+ // See if any of the children match.
+ //
+ // MUST compare 3rd, then 2nd, then 1st child !!
+ //
+
+ //
+ // Try third child...
+ // If we are evaluating the largest element in the
+ // table, then the RestartKey will be set to continue
+ // at the beginning of the table. Otherwise, it is
+ // set to continue from here.
+ //
+
+ if (Children == 3) {
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->ThirdChild;
+ CompareResult = (*Table->Compare)( Leaf->Element, Element );
+
+ if ( (CompareResult == GenericEqual) ||
+ (CompareResult == GenericLessThan) ) {
+ if (LargestElementPath && (Children == 3)) {
+ (*RestartKey) = NULL; // Restart at beginning of list
+ } else {
+ (*RestartKey) = (PVOID)(Leaf); // Restart here
+ }
+ return(Leaf->Element);
+ }
+ }
+
+ //
+ // Try second child
+ //
+
+ if (Children >= 2) { // Must allow for Root node case
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->SecondChild;
+ CompareResult = (*Table->Compare)( Leaf->Element, Element );
+
+ if ( (CompareResult == GenericEqual) ||
+ (CompareResult == GenericLessThan) ) {
+ if (LargestElementPath && (Children == 2)) {
+ (*RestartKey) = NULL; // Restart at beginning of list
+ } else {
+ (*RestartKey) = (PVOID)(Leaf); // Restart here
+ }
+ return(Leaf->Element);
+ }
+ }
+
+ //
+ // Try first child
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->FirstChild;
+ CompareResult = (*Table->Compare)( Leaf->Element, Element );
+
+ if ( (CompareResult == GenericEqual) ||
+ (CompareResult == GenericLessThan) ) {
+ if (LargestElementPath && (Children == 1)) {
+ (*RestartKey) = NULL; // Restart at beginning of list
+ } else {
+ (*RestartKey) = (PVOID)(Leaf); // Restart here
+ }
+ return(Leaf->Element);
+ }
+
+
+
+ (*RestartKey) = NULL;
+ return(NULL);
+}
+
+
+ULONG
+RtlNumberElementsGenericTable2(
+ PRTL_GENERIC_TABLE2 Table
+ )
+
+/*++
+
+Routine Description:
+
+ The function NumberGenericTableElements returns a ULONG value
+ which is the number of generic table elements currently inserted
+ in the generic table.
+
+
+Arguments:
+
+ Table - Pointer to the generic table.
+
+
+ Return Value:
+
+ ULONG - The number of elements in the table.
+
+--*/
+
+{
+ return Table->ElementCount;
+}
+
+
+BOOLEAN
+RtlIsGenericTable2Empty (
+ PRTL_GENERIC_TABLE2 Table
+ )
+/*++
+
+Routine Description:
+
+ The function IsGenericTableEmpty will return to the caller TRUE if
+ the generic table is empty (i.e., does not contain any elements)
+ and FALSE otherwise.
+
+
+Arguments:
+
+ Table - Pointer to the generic table.
+
+
+ Return Value:
+
+ BOOLEAN - True if table is empty, otherwise FALSE.
+
+--*/
+
+{
+ return (Table->ElementCount == 0);
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Internal (private) Services ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+
+VOID
+GtbpDeleteFromSubTree (
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PVOID Element,
+ OUT PGTB_TWO_THREE_LEAF *LowOfNode,
+ OUT BOOLEAN *ElementDeleted,
+ OUT BOOLEAN *OnlyOneChildLeft
+ )
+
+/*++
+
+Routine Description:
+
+ Delete an element from a SubTree.
+
+
+Arguments:
+
+ Table - Points to the table. This is needed for comparison
+ and memory-free routine.
+
+ Node - Points to the child node within which the element to
+ delete resides (if it is in the tree at all).
+
+ Element - points to an element. We are to delete any element
+ found to be equal to this element.
+
+ LowOfNode - If the first child of Node isn't changed, then
+ a zero will be returned to this parameter, signifying that
+ the caller doesn't have to worry about updating LowOfXxx values.
+ However, if the first child of Node does change, then this
+ value will point to the new Low Leaf for the node's subtrees.
+
+ ElementDeleted - Receives a boolean value indicating whether or
+ not an element was actually deleted. TRUE is returned if
+ an element was deleted. FALSE is returned if no element
+ was deleted.
+
+ OnlyOneChildLeft - Receives a boolean value indicating whether or
+ not ChildNode was reduced to having only a single child.
+ TRUE indicates ChildNode now has only one child.
+ FALSE indicates ChildNode has at least two children.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ PGTB_TWO_THREE_NODE
+ SubTreeNode;
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ ULONG
+ SubTree;
+
+ (*LowOfNode) = 0; // Default is no change
+ (*OnlyOneChildLeft) = FALSE; // Default return value
+
+
+ //
+ // If we are a parent of leaves, then we can look for an
+ // element to delete. Otherwise, just find the subtree
+ // to continue or search in and recurse.
+ //
+
+ if (GtbpChildrenAreLeaves(Node)) {
+
+ (*ElementDeleted) = FALSE; // Default return value
+
+ //
+ // See if any of the elements match the one passed in.
+ // If so, delete the element and shift larger elements
+ // to take up the free'd child's spot (unless it is the
+ // third child).
+ //
+
+ if (Node->ThirdChild != NULL) {
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->ThirdChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting 3rd child (0x%lx) of node 0x%lx\n"
+ " ElementCount before deletion: %d\n",
+ Leaf, Node, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Node->ThirdChild = NULL;
+
+ Table->ElementCount--;
+
+ (*ElementDeleted) = TRUE;
+ return;
+ }
+ }
+
+ //
+ // Try second child
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->SecondChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting 2nd child (0x%lx) of node 0x%lx\n"
+ " ElementCount before deletion: %d\n",
+ Leaf, Node, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Node->SecondChild = Node->ThirdChild;
+ Node->LowOfSecond = Node->LowOfThird;
+ Node->ThirdChild = NULL;
+
+
+ //
+ // If we are down to the last element, let that
+ // be known.
+ //
+
+ if (Node->SecondChild == NULL) {
+ GtbpDiagPrint( DELETION,
+ ("GTB: Only one child left in node (0x%lx).\n",
+ Node));
+ (*OnlyOneChildLeft) = TRUE;
+ }
+
+ Table->ElementCount--;
+ (*ElementDeleted) = TRUE;
+ return;
+ }
+
+ //
+ // Try first child
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)Node->FirstChild;
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Deleting 1st child (0x%lx) of node 0x%lx\n"
+ " ElementCount before deletion: %d\n",
+ Leaf, Node, Table->ElementCount));
+
+ RemoveEntryList( &Leaf->SortOrderEntry );
+ (*Table->Free)(Leaf);
+ Node->FirstChild = Node->SecondChild;
+ (*LowOfNode) = Node->LowOfSecond;
+
+ Node->SecondChild = Node->ThirdChild;
+ Node->LowOfSecond = Node->LowOfThird;
+
+ Node->ThirdChild = NULL;
+
+
+ //
+ // If we are down to the last element, let that
+ // be known.
+ //
+
+ if (Node->SecondChild == NULL) {
+ GtbpDiagPrint( DELETION,
+ ("GTB: Only one child left in node (0x%lx).\n",
+ Node));
+ (*OnlyOneChildLeft) = TRUE;
+ }
+
+ Table->ElementCount--;
+ (*ElementDeleted) = TRUE;
+ return;
+ }
+
+ //
+ // Didn't match any of the leaves
+ //
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: No matching element found on DELETE attempt.\n"));
+
+ return; // Default value already set
+ }
+
+ //
+ // Find a subtree to continue our search...
+ //
+
+ GtbpGetSubTreeOfElement( Table, Node, Element, &SubTreeNode, &SubTree );
+
+ GtbpDeleteFromSubTree( Table,
+ SubTreeNode,
+ Element,
+ LowOfNode,
+ ElementDeleted,
+ OnlyOneChildLeft
+ );
+
+
+ //
+ // If we deleted an entry from either the second or third
+ // subtree, then we may need to set a new LowOfXxx value.
+ // If it was the first subtree, then we simply have to return
+ // the LowLeaf value we received.
+ //
+
+ if ((*LowOfNode) != 0) {
+ if (SubTree == 2) {
+ Node->LowOfSecond = (*LowOfNode);
+ (*LowOfNode) = NULL;
+ } else if (SubTree == 3) {
+ Node->LowOfThird = (*LowOfNode);
+ (*LowOfNode) = NULL;
+ }
+ }
+
+
+ //
+ // If the SubTreeNode has only one child left, then some
+ // adjustments are going to be necessary. Otherwise,
+ // we are done.
+ //
+
+ if ((*OnlyOneChildLeft)) {
+
+ GtbpDiagPrint( DELETION,
+ ("GTB: Only one child left in 0x%lx\n", SubTreeNode));
+
+ //
+ // One of our children has only one child.
+ // Re-shuffle our kid's kids.
+ //
+
+ GtbpCoalesceChildren( Table,
+ Node,
+ SubTree,
+ OnlyOneChildLeft
+ );
+ }
+
+ return;
+}
+
+
+BOOLEAN
+GtbpInsertIntoSubTree (
+ PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN BOOLEAN NodeIsLeaf,
+ IN PVOID Element,
+ IN ULONG SplitCount,
+ IN PVOID *FoundElement,
+ OUT PGTB_TWO_THREE_NODE *ExtraNode,
+ OUT PGTB_TWO_THREE_LEAF *LowLeaf,
+ OUT PLIST_ENTRY *AllocatedNodes
+ )
+
+/*++
+
+Routine Description:
+
+ Insert an element into the SubTree specified by Node.
+
+ Special note:
+
+ if FoundElement is returned as NULL, that means we
+ couldn't allocate memory to add the new element.
+
+Arguments:
+
+ Table - Points to the table being inserted into. This is needed
+ for its allocation routine.
+
+
+ Node - Points to the root node of the SubTree into
+ which the element is to be inserted.
+
+ NodeIsLeaf - TRUE if the node passed in is a leaf. FALSE
+ if it is an internal node.
+
+ Element - Points to the element to be inserted.
+
+ SplitCount - indicates how many nodes have been traversed since
+ a node with only two children. When inserting a new element
+ that causes nodes to be split, this will indicate how many
+ nodes will split. This allows all memory that will be required
+ to split nodes to be allocated at the very bottom routine
+ (before any changes to the tree are made). See the description
+ of the AllocatedNodes parameter for more information.
+
+ FoundElement - Receives a pointer to the element that
+ was either inserted, or one already in the table
+ but found to match the one being inserted.
+ If null is returned, then not enough memory could be
+ allocated to insert the new element.
+
+ ExtraNode - If it was necessary to create a new node to
+ accomodate the insertion, then ExtraNode will receive
+ a pointer to that node, otherwise ExtraNode will receive
+ NULL.
+
+ LowLeaf - This value points to the lowest value leaf of the
+ SubTree starting at Node.
+
+ AllocatedNodes - This is a tricky parameter. We have the problem
+ that when we insert an element in the tree, we may need to
+ allocate additional internal nodes further up the tree as
+ we return out of our recursive calls. We must avoid the
+ situation where we start making changes to the tree only to
+ find we can't allocate memory to re-arrange higher levels of
+ the tree. To accomodate this situation, we always allocate
+ all the nodes we will need at the very bottom of the call
+ chain and pass back a linked list of GTB_TWO_THREE_NODEs using
+ this parameter. We know how many nodes we will need to
+ allocate because it is the number of nodes between the leaf
+ and the lowest level node in the path between the leaf and the
+ root that has only 2 children. That is, all nodes directly
+ above the leaf that have 3 children will need to be split.
+ Example:
+
+ 3
+ / | \
+ +-----+ | +----
+ Won't split --> 2 ... ...
+ / |
+ +-----+ |
+ ... 3 <-- Will split
+ / | \
+ +-----+ | +----+
+ ... 3 <--- Will split
+ / | \
+ +-----+ | +----+
+ Leaf Leaf Leaf <- Add fourth leaf here.
+
+ Adding a fourth leaf where indicated will cause a split at the
+ two nodes indicated. So, you can see that keeping a count of
+ the nodes with three children since the last encountered node
+ with only two children will tell us how many nodes will split.
+
+
+
+
+
+
+
+Return Value:
+
+ TRUE - if element was added.
+ FALSE - if element not added (due to collision or out-of-memory)
+
+--*/
+
+{
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ ULONG
+ SubTree; // To track which SubTree an element is being placed in.
+
+
+ PGTB_TWO_THREE_NODE
+ SubTreeNode,
+ NodePassedBack;
+
+
+ PGTB_TWO_THREE_LEAF
+ NewLeaf,
+ LowPassedBack;
+
+ BOOLEAN
+ Inserted,
+ SubNodeIsLeaf;
+
+
+ //
+ // Don't have an extra node to pass back yet.
+ //
+
+ (*ExtraNode) = NULL;
+
+
+ //
+ // We are either at a leaf, or an internal node.
+ //
+
+ if (NodeIsLeaf) {
+
+ //
+ // Typecast the Node into a leaf
+ //
+
+ PGTB_TWO_THREE_LEAF Leaf = (PGTB_TWO_THREE_LEAF)((PVOID)Node);
+
+ //
+ // See if the value matches.
+ //
+
+ CompareResult = (*Table->Compare)( Element, Leaf->Element );
+
+ if (CompareResult == GenericEqual) {
+ (*LowLeaf) = Leaf;
+ (*FoundElement) = Leaf->Element;
+
+ GtbpDiagPrint( COLLISIONS,
+ ("GTB: Insertion attempt resulted in collision.\n"
+ " Element NOT being inserted.\n"
+ " Elements in table: %d\n",
+ Table->ElementCount));
+
+ return(FALSE);
+ } //end_if equal
+
+ //
+ // The new element isn't in the tree.
+ // Allocate a new leaf for it.
+ //
+
+ NewLeaf = GtbpAllocateLeafAndNodes( Table, SplitCount, AllocatedNodes );
+ if (NewLeaf == NULL) {
+
+ //
+ // The following (unusual) return value indicates we
+ // couldn't allocate memory to add the entry into the
+ // tree.
+ //
+
+ (*FoundElement) = NULL;
+ return(FALSE);
+
+ } //end_if (NewLeaf == NULL)
+
+ switch (CompareResult) {
+
+ case GenericLessThan: {
+
+ //
+ // Move the original element into the new leaf. Notice
+ // that the SortOrderEntry of the existing leaf is
+ // still in the right place in the linked-list, even
+ // though the leaf now points at a different element.
+ //
+
+ NewLeaf->Element = Leaf->Element;
+ Leaf->Element = Element;
+
+ break;
+ } //end_case
+
+ case GenericGreaterThan: {
+
+ //
+ // The new element does not supplant the existing element.
+ // Put it in the new leaf.
+ //
+
+ NewLeaf->Element = Element;
+ break;
+ } //end_case
+
+
+ } //end_switch
+
+ //
+ // At this point, the lower-value element is in Leaf
+ // and the higher-value element is in NewLeaf. The
+ // caller is responsible to putting NewLeaf someplace
+ // else in the tree.
+ //
+
+ //
+ // Now link the new leaf into our sort-order list.
+ // The new leaf immediately follows our existing leaf,
+ // regardless of which element is in the new leaf (original
+ // or new element).
+ //
+
+ InsertHeadList(&Leaf->SortOrderEntry, &NewLeaf->SortOrderEntry);
+ Table->ElementCount++; // Increment the element count
+
+ (*ExtraNode) = (PGTB_TWO_THREE_NODE)((PVOID)NewLeaf);
+ (*LowLeaf) = NewLeaf;
+ (*FoundElement) = Element;
+
+ return(TRUE);
+
+ } //end_if NodeIsLeaf
+
+ //
+ // Node is internal (not a leaf)
+ //
+
+ //
+ // See if we should re-set or increment the SplitCount.
+ //
+
+ if (Node->ThirdChild == NULL) {
+ SplitCount = 0;
+ } else {
+ SplitCount += 1;
+ }
+
+ GtbpGetSubTreeOfElement( Table, Node, Element, &SubTreeNode, &SubTree);
+ SubNodeIsLeaf = GtbpChildrenAreLeaves(Node);
+
+ Inserted = GtbpInsertIntoSubTree ( Table,
+ SubTreeNode,
+ SubNodeIsLeaf,
+ Element,
+ SplitCount,
+ FoundElement,
+ &NodePassedBack,
+ &LowPassedBack,
+ AllocatedNodes
+ );
+
+ //
+ // One of several things could have happened:
+ //
+ // 1) We didn't have enough memory to add the new element.
+ // In this case we are done and simply return.
+ //
+ // 2) The element was added, and no-rearrangement to this
+ // node is needed. In this case we are done and simply
+ // return.
+ //
+ // 3) The element was added and caused a leaf to be pushed
+ // out of the SubTree. We have some work to do.
+ //
+
+ if ( (FoundElement == NULL) || // Insufficient memory, or
+ (NodePassedBack == NULL) ) { // no work for this node
+ return(Inserted);
+ }
+
+ if (Node->ThirdChild == NULL) {
+
+ if (!GtbpChildrenAreLeaves(Node)) {
+ NodePassedBack->ParentNode = Node;
+ }
+
+ //
+ // Node doesn't yet have a third child, so use it.
+ // This might require shuffling the second child to the
+ // be the third child.
+ //
+
+ if (SubTree == 2) {
+
+ //
+ // Node fell out of second SubTree and we don't have a
+ // third SubTree. Make that node the third SubTree.
+ //
+
+ Node->ThirdChild = NodePassedBack;
+ Node->LowOfThird = LowPassedBack;
+
+ } else {
+
+ //
+ // Node fell out of first SubTree.
+ // Make the second SubTree the third SubTree and
+ // then make the passed back node the second SubTree.
+ //
+
+ ASSERT(SubTree == 1);
+
+ Node->ThirdChild = Node->SecondChild;
+ Node->LowOfThird = Node->LowOfSecond;
+ Node->SecondChild = NodePassedBack;
+ Node->LowOfSecond = LowPassedBack;
+
+ //
+ //
+
+ }
+ } else {
+
+ //
+ // Node already has three children - split it.
+ //
+
+ GtbpSplitNode( Node,
+ NodePassedBack,
+ LowPassedBack,
+ SubTree,
+ (*AllocatedNodes),
+ ExtraNode,
+ LowLeaf
+ );
+
+ }
+
+ return(Inserted);
+}
+
+
+ULONG
+GtbpNumberOfChildren(
+ IN PGTB_TWO_THREE_NODE Node
+ )
+
+/*++
+
+Routine Description:
+
+ Return the number of children of a specified node.
+
+Arguments:
+
+ Node - points to the node whose children are to be counted.
+
+Return Values:
+
+ 0, 1, 2, or 3.
+
+--*/
+{
+ if (Node->ThirdChild != NULL) {
+ return(3);
+ }
+ if (Node->SecondChild != NULL) {
+ return(2);
+ }
+ if (Node->FirstChild != NULL) {
+ return(1);
+ }
+ return(0);
+
+}
+
+
+VOID
+GtbpGetSubTreeOfElement(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PVOID Element,
+ OUT PGTB_TWO_THREE_NODE *SubTreeNode,
+ OUT ULONG *SubTree
+ )
+
+/*++
+
+Routine Description:
+
+ Find which SubTree of Node that Element might be in (or should be
+ in, if being inserted).
+
+Arguments:
+
+ Table - Points to the table This is needed for its comparison routine.
+
+ Node - Is the node, one of whose SubTrees is to be chosen as the
+ subtree in which Element could/should reside.
+
+ Element - is the element we are interested in placing or locating.
+
+ SubTreeNode - Receives a pointer to the node of the SubTree in
+ which the element could/should reside.
+
+ SubTree - Receives the index (1, 2, or 3) of the subtree of Node
+ in which the element could/should reside.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ RTL_GENERIC_COMPARE_RESULTS
+ CompareResult;
+
+ CompareResult = (*Table->Compare)( Element, Node->LowOfSecond->Element );
+
+ if (CompareResult == GenericLessThan) {
+
+ (*SubTree) = 1;
+ (*SubTreeNode) = Node->FirstChild;
+
+ } else {
+
+ //
+ // default to the second subtree, but
+ // if there is a subtree we may change it.
+ //
+
+ (*SubTree) = 2;
+ (*SubTreeNode) = Node->SecondChild;
+
+ if (Node->ThirdChild != NULL) {
+
+ CompareResult = (*Table->Compare)( Element, Node->LowOfThird->Element );
+ if ( (CompareResult == GenericGreaterThan) ||
+ (CompareResult == GenericEqual) ) {
+
+ (*SubTree) = 3;
+ (*SubTreeNode) = Node->ThirdChild;
+ }
+ }
+ }
+
+ return;
+
+}
+
+
+
+VOID
+GtbpCoalesceChildren(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN PGTB_TWO_THREE_NODE Node,
+ IN ULONG SubTree,
+ OUT BOOLEAN *OnlyOneChildLeft
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called following a deletion that leaves a child
+ node with only one child of its own. That is, a child of the
+ Node parameter has only one child. The SubTree parameter indicates
+ which child of Node has only one child.
+
+
+
+
+Arguments:
+
+ Table - Points to the table.
+
+ Node - Is the node, one of whose children has only one child.
+
+ NOTE: The ParentNode field of this node must be valid.
+ The Low values of ParentNode will be referenced.
+
+ SubTree - Indicates which child of Node (1, 2, or 3) has only one
+ child.
+
+ OnlyOneChildLeft - Receives a boolean indicating whether or not
+ Node itself has been left with only one child due to the
+ coalescing.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ PGTB_TWO_THREE_NODE
+ A,
+ B,
+ C;
+
+ (*OnlyOneChildLeft) = FALSE; // Default return value
+
+ //
+ // For the following discussion, using the following:
+ //
+ // N is the parent node
+ // S is the node which has only one child
+ // (S is a child of N)
+ //
+ // A is the first child of N
+ // B is the second child of N
+ // C is the third child of N
+ //
+ // If S is the first child of N (meaning S is A)
+ // then:
+ //
+ // if B has three children (let A adopt the smallest)
+ // then:
+ //
+ // Move B(1) to A(2)
+ // Move B(2) to B(1)
+ // Move B(3) to B(2)
+ //
+ // else (B has two children)
+ //
+ // (move the orphan into B)
+ // Move B(2) to B(3)
+ // Move B(1) to B(2)
+ // Move A(1) to B(1)
+ //
+ // Free A
+ // Make B the first child of N
+ // if (C is a real child)
+ // then:
+ // Make C the second child of N
+ // else (N only has one child now)
+ // (*OnlyOneChildLeft) = TRUE;
+ //
+ // else if S is the second child of N (meaning S is B)
+ // then:
+ //
+ // if A has three children
+ // then:
+ // Move B(1) to B(2)
+ // Move A(3) to B(1)
+ //
+ // else if C exists and has three children
+ // then:
+ //
+ // Move C(1) to B(2)
+ // Move C(2) to C(1)
+ // Move C(3) to C(2)
+ //
+ // else: (no other child of N has three children)
+ //
+ // (Move the orphan into A)
+ // Move B(1) to A(3)
+ //
+ // Free B
+ // if (C is a real child)
+ // then:
+ // Make C the second child of N
+ // else: (N only has one child now)
+ // (*OnlyOneChildLeft) = TRUE;
+ //
+ // else: (S is the third child of N (meaning S is C))
+ //
+ // if B has three children
+ // then:
+ // (move one into C)
+ // Move C(1) to C(2)
+ // Move B(3) to C(1)
+ //
+ // else: (B only has two children)
+ //
+ // (move the orphan into B)
+ // Move C(1) to B(3)
+ // Free C
+ // Wow!
+
+
+ A = Node->FirstChild;
+ B = Node->SecondChild;
+ C = Node->ThirdChild;
+
+
+ //
+ // SubTree indicates which child has the orphan.
+ //
+
+ if (SubTree == 1) {
+
+ //
+ // First child has the orphan
+ //
+
+ if (B->ThirdChild != NULL) {
+
+ // (B has three children - let A adopt the smallest)
+ //
+ // Move B(1) to A(2)
+ // Move B(2) to B(1)
+ // Move B(3) to B(2)
+ //
+
+ A->SecondChild = B->FirstChild;
+ A->LowOfSecond = Node->LowOfSecond;
+
+ B->FirstChild = B->SecondChild;
+ Node->LowOfSecond = B->LowOfSecond;
+
+ B->SecondChild = B->ThirdChild;
+ B->LowOfSecond = B->LowOfThird;
+ B->ThirdChild = NULL;
+
+ } else {
+
+ //
+ // (B has two children)
+ //
+ // (move the orphan into B)
+ // Move B(2) to B(3)
+ // Move B(1) to B(2)
+ // Move A(1) to B(1)
+ //
+
+ B->ThirdChild = B->SecondChild;
+ B->LowOfThird = B->LowOfSecond;
+
+ B->SecondChild = B->FirstChild;
+ B->LowOfSecond = Node->LowOfSecond;
+
+ B->FirstChild = A->FirstChild;
+ //Node->LowOfSecond = Node->LowOfFirst; // This gets moved back in a few steps
+
+ // Free A
+ // Make B the first child of N
+ // if (C is a real child)
+ // then:
+ // Make C the second child of N
+ // else (N only has one child now)
+ // (*OnlyOneChildLeft) = TRUE;
+ //
+
+ (*Table->Free)(A);
+ Node->FirstChild = B;
+ //Node->LowOfFirst = Node->LowOfSecond; // See comment a few lines up
+
+ if (C != NULL) {
+ Node->SecondChild = C;
+ Node->LowOfSecond = Node->LowOfThird;
+ Node->ThirdChild = NULL;
+ } else {
+ Node->SecondChild = NULL;
+ (*OnlyOneChildLeft) = TRUE;
+ }
+ }
+
+
+ } else if (SubTree == 2) {
+
+ //
+ // Second child has the orphan
+ //
+
+ if (A->ThirdChild != NULL) {
+
+ //
+ // (A has three children)
+ //
+ // Move B(1) to B(2)
+ // Move A(3) to B(1)
+ //
+
+ B->SecondChild = B->FirstChild;
+ B->LowOfSecond = Node->LowOfSecond;
+
+ B->FirstChild = A->ThirdChild;
+ Node->LowOfSecond = A->LowOfThird;
+ A->ThirdChild = NULL;
+
+ } else {
+
+ if (C != NULL &&
+ C->ThirdChild != NULL) {
+
+ //
+ // (C exists and has three children)
+ // (move the smallest into B)
+ //
+ // Move C(1) to B(2)
+ // Move C(2) to C(1)
+ // Move C(3) to C(2)
+ //
+
+ B->SecondChild = C->FirstChild;
+ B->LowOfSecond = Node->LowOfThird;
+
+ C->FirstChild = C->SecondChild;
+ Node->LowOfThird = C->LowOfSecond;
+
+ C->SecondChild = C->ThirdChild;
+ C->LowOfSecond = C->LowOfThird;
+ C->ThirdChild = NULL;
+
+
+
+
+
+ } else {
+
+ //
+ // (no other child of N has three children)
+ // (Move the orphan into A)
+ //
+ // Move B(1) to A(3)
+ //
+ // Free B
+ // if (C is a real child)
+ // then:
+ // Make C the second child of N
+ // else: (N only has one child now)
+ // (*OnlyOneChildLeft) = TRUE;
+ //
+
+ A->ThirdChild = B->FirstChild;
+ A->LowOfThird = Node->LowOfSecond;
+
+ (*Table->Free)(B);
+
+ if (C != NULL) {
+ Node->SecondChild = C;
+ Node->LowOfSecond = Node->LowOfThird;
+ Node->ThirdChild = NULL;
+ } else {
+ Node->SecondChild = NULL;
+ (*OnlyOneChildLeft) = TRUE;
+ }
+ }
+ }
+
+
+
+ } else {
+
+ //
+ // Third child has the orphan
+ //
+
+ ASSERT(SubTree == 3);
+ ASSERT(C != NULL);
+ ASSERT(B != NULL);
+
+ if (B->ThirdChild != NULL) {
+
+ //
+ // (B has three children)
+ // (move the largest of them into C)
+ // Move C(1) to C(2)
+ // Move B(3) to C(1)
+ //
+
+ C->SecondChild = C->FirstChild;
+ C->LowOfSecond = Node->LowOfThird;
+
+ C->FirstChild = B->ThirdChild;
+ Node->LowOfThird = B->LowOfThird;
+ B->ThirdChild = 0;
+ } else {
+
+ //
+ // (B only has two children)
+ // (move the orphan into B)
+ // Move C(1) to B(3)
+ // Free C
+ //
+
+ B->ThirdChild = C->FirstChild;
+ B->LowOfThird = Node->LowOfThird;
+ Node->ThirdChild = NULL;
+
+ (*Table->Free)(C);
+
+ }
+ }
+
+ return;
+
+}
+
+
+VOID
+GtbpSplitNode(
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PGTB_TWO_THREE_NODE NodePassedBack,
+ IN PGTB_TWO_THREE_LEAF LowPassedBack,
+ IN ULONG SubTree,
+ IN PLIST_ENTRY AllocatedNodes,
+ OUT PGTB_TWO_THREE_NODE *NewNode,
+ OUT PGTB_TWO_THREE_LEAF *LowLeaf
+ )
+
+/*++
+
+Routine Description:
+
+ This routine splits a node that already has three children.
+ Memory necessary to perform the split is expected to have
+ already been allocated and available via the AllocatedNodes
+ parameter.
+
+
+Parameters:
+
+ Node - The node to be split.
+
+ NodePassedBack - The 4th node passed back from an insertion operation
+ into the SubTree of Node specified by the SubTree parameter.
+ NOTE: that this may, in fact, be a GTB_TWO_THREE_LEAF !!!
+
+ LowPassedBack - points the the low leaf value passed back by the
+ insertion operation that is causing the split.
+
+ SubTree - Indicates which SubTree of Node an element was inserted
+ into which is causing the split.
+
+ AllocatedNodes - Contains a list of allocated GTB_TWO_THREE_NODE
+ blocks for use in an insertion operation (which this split
+ is assumed to be part of).
+
+ NewNode - Receives a pointer to the node generated by the split.
+
+ LowLeaf - receives a pointer to the low leaf of the NewNode's SubTree.
+
+
+Return Values:
+
+ None.
+
+--*/
+{
+
+ PGTB_TWO_THREE_NODE
+ LocalNode;
+
+
+
+ // Make a new node and split things up.
+ // The node has already been allocated and passed back to
+ // this routine via the AllocatedNodes parameter.
+ //
+
+ LocalNode = GtbpGetNextAllocatedNode( AllocatedNodes );
+ ASSERT( LocalNode != NULL);
+ (*NewNode) = LocalNode;
+
+ //
+ // Set known fields of new node
+ //
+
+ LocalNode->ParentNode = Node->ParentNode;
+ LocalNode->Control = Node->Control;
+ LocalNode->ThirdChild = NULL; //Low of third is left undefined
+
+ //
+ // Now move around children...
+ //
+
+ if (SubTree == 3) {
+
+ //
+ // We were inserting into the 3rd SubTree. This implies:
+ //
+ // Node(child 3) moves to new(child 1)
+ // Back is put in new(child 2)
+ //
+
+ LocalNode->FirstChild = Node->ThirdChild;
+ LocalNode->SecondChild = NodePassedBack;
+ LocalNode->LowOfSecond = LowPassedBack;
+ (*LowLeaf) = Node->LowOfThird; // low of the new node is low of old third
+
+ Node->ThirdChild = NULL; //Low of third is left undefined
+
+
+
+ } else {
+
+ //
+ // We inserted into either SubTree 1 or 2.
+ // These cases cause:
+ //
+ // 1 => Node(child 3) moves to new(child 2)
+ // Node(child 2) moves to New(child 1)
+ // Back is put in Node(child 2)
+ //
+ // 2 => Node(child 3) moves to new(child 2)
+ // Back is put in New(child 1)
+ //
+ // In both these cases, Node(child 3) moves to New(child 2)
+ // and there are no third children. So do that.
+ //
+
+ LocalNode->SecondChild = Node->ThirdChild;
+ LocalNode->LowOfSecond = Node->LowOfThird;
+
+
+ if (SubTree == 2) {
+
+ LocalNode->FirstChild = NodePassedBack;
+ (*LowLeaf) = LowPassedBack;
+
+ if (!GtbpChildrenAreLeaves(Node)) {
+ NodePassedBack->ParentNode = LocalNode;
+ }
+
+ } else {
+
+ //
+ // SubTree == 1
+ //
+
+ LocalNode->FirstChild = Node->SecondChild;
+ (*LowLeaf) = Node->LowOfSecond;
+
+ Node->SecondChild = NodePassedBack;
+ Node->LowOfSecond = LowPassedBack;
+ if (!GtbpChildrenAreLeaves(Node)) {
+ NodePassedBack->ParentNode = Node;
+ }
+
+ }
+ }
+
+ //
+ // Neither node has a third child anymore
+ //
+
+ LocalNode->ThirdChild = NULL; //Low of third is left undefined
+ Node->ThirdChild = NULL;
+
+ //
+ // Set the ParentNodes only if the children aren't leaves.
+ //
+
+ if (!GtbpChildrenAreLeaves(Node)) {
+
+ Node->FirstChild->ParentNode = Node;
+ Node->SecondChild->ParentNode = Node;
+
+ LocalNode->FirstChild->ParentNode = LocalNode;
+ LocalNode->SecondChild->ParentNode = LocalNode;
+ }
+
+
+ return;
+}
+
+
+
+PGTB_TWO_THREE_LEAF
+GtbpAllocateLeafAndNodes(
+ IN PRTL_GENERIC_TABLE2 Table,
+ IN ULONG SplitCount,
+ OUT PLIST_ENTRY *AllocatedNodes
+ )
+/*++
+
+Routine Description:
+
+ Allocate a leaf and some number of internal nodes. This is
+ used in conjunction with GtbpGetNextAllocatedNode() when splitting
+ nodes following an insertion. These two routines allow all necessary
+ memory to be allocated all at once, rather than trying to deal with
+ memory allocation failures once changes to the tree have begun.
+
+
+Arguments:
+
+ Table - the table into which the nodes will be added. This
+ provides the allocation routine.
+
+ SplitCount - indicates how many nodes will need splitting, and,
+ thus, how many nodes need to be allocated.
+
+
+Return Value:
+
+ Pointer to the leaf if successful.
+ NULL if unsuccessful.
+
+--*/
+{
+
+ PGTB_TWO_THREE_LEAF
+ Leaf;
+
+ PLIST_ENTRY
+ NodeRoot,
+ NextNode;
+
+ ULONG
+ i;
+
+#ifdef GTBP_DIAGNOSTICS
+ PGTB_TWO_THREE_NODE
+ N;
+#endif //GTBP_DIAGNOSTICS
+
+ NodeRoot = NULL;
+
+ //
+ // Allocate a chain of Nodes, if necessary
+ //
+
+ if (SplitCount > 0) {
+
+ NodeRoot = (PLIST_ENTRY)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_NODE)));
+ if (NodeRoot == NULL) {
+ goto error_return;
+ }
+
+ InitializeListHead( NodeRoot );
+
+#ifdef GTBP_DIAGNOSTICS
+
+ GtbpDiagPrint(LEAF_AND_NODE_ALLOC,
+ ("GTB: Allocating %d nodes with leaf, root: 0x%lx\n",
+ SplitCount, NodeRoot));
+ N = (PGTB_TWO_THREE_NODE)NodeRoot;
+ N->Control = 10000; //Used as a diagnostic allocation counter/index
+
+#endif //GTBP_DIAGNOSTICS
+
+ for (i=1; i<SplitCount; i++) {
+ NextNode = (PLIST_ENTRY)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_NODE)));
+ if (NextNode == NULL) {
+ goto error_return;
+ }
+ InsertTailList( NodeRoot, NextNode );
+
+#ifdef GTBP_DIAGNOSTICS
+
+ N = (PGTB_TWO_THREE_NODE)NextNode;
+ N->Control = 10000+i; //Used as a diagnostic allocation counter/index
+
+#endif //GTBP_DIAGNOSTICS
+ }
+ }
+
+
+ //
+ // Finally, allocate the leaf
+ //
+
+ Leaf = (PGTB_TWO_THREE_LEAF)
+ ((*Table->Allocate)( sizeof(GTB_TWO_THREE_LEAF)));
+
+ if (Leaf == NULL) {
+ goto error_return;
+ }
+
+ (*AllocatedNodes) = NodeRoot;
+ return(Leaf);
+
+error_return:
+
+ GtbpDiagPrint(LEAF_AND_NODE_ALLOC,
+ ("GTB: ** error allocating leaf and nodes - insufficient memory.\n"));
+ //
+ // Deallocate any nodes that have already been allocated.
+ //
+
+ if (NodeRoot != NULL) {
+
+ NextNode = NodeRoot->Flink;
+ while (NextNode != NodeRoot) {
+ RemoveEntryList( NextNode );
+ (*Table->Free)( NextNode );
+
+
+ }
+
+ (*Table->Free)( NodeRoot );
+ }
+
+ return(NULL);
+}
+
+
+PGTB_TWO_THREE_NODE
+GtbpGetNextAllocatedNode(
+ IN PLIST_ENTRY AllocatedNodes
+ )
+/*++
+
+Routine Description:
+
+ Take the next node off of the list of allocated nodes and
+ return its address.
+
+
+Arguments:
+
+ AllocatedNodes - Points to the list of nodes allocated using
+ GtbpAllocateLeafAndNodes().
+
+
+Return Value:
+
+ Pointer to the node.
+ any other return value indicates an error in the caller.
+
+--*/
+{
+ PLIST_ENTRY
+ NextNode;
+
+#ifdef GTBP_DIAGNOSTICS
+ PGTB_TWO_THREE_NODE
+ N;
+#endif //GTBP_DIAGNOSTICS
+
+ //
+ // Remove the first entry on the list.
+ // This ensures that the passed in root is the last entry
+ // returned.
+ //
+
+ NextNode = AllocatedNodes->Flink;
+ RemoveEntryList( NextNode );
+
+#ifdef GTBP_DIAGNOSTICS
+
+ NextNode->Flink = NULL; //Just to prevent accidental re-use
+ N = (PGTB_TWO_THREE_NODE)NextNode;
+ ASSERT(N->Control >= 10000); //under 10000 mplies it has already been allocated.
+
+
+ GtbpDiagPrint(LEAF_AND_NODE_ALLOC,
+ ("GTB: Allocating node (index: %d) from root: 0x%lx\n",
+ (N->Control-10000), AllocatedNodes));
+#endif //GTBP_DIAGNOSTICS
+
+ return( (PGTB_TWO_THREE_NODE)((PVOID)NextNode) );
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Diagnostic (Developer) routines ... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef GTBP_DEVELOPER_BUILD
+
+#include <string.h>
+
+//
+// This routine is expected to dump an element's value
+//
+
+typedef
+VOID
+(NTAPI *PGTBP_DEV_DUMP_ELEMENT_ROUTINE) (
+ PVOID Element
+ );
+
+
+VOID
+GtbpDevIndent(
+ ULONG Depth
+ )
+{
+ UNICODE_STRING
+ Indent;
+
+ RtlInitUnicodeString( &Indent,
+ L" +");
+
+ Indent.Length = (USHORT)(Depth*4);
+ if (Indent.Length > Indent.MaximumLength) {
+ Indent.Length = Indent.MaximumLength;
+ }
+
+ DbgPrint("\n%wZ%d: ", &Indent, Depth);
+ return;
+}
+
+
+VOID
+GtbpDevDumpNode(
+ PGTB_TWO_THREE_NODE Parent,
+ PGTB_TWO_THREE_NODE N,
+ PGTB_TWO_THREE_LEAF Low,
+ ULONG Depth,
+ PGTBP_DEV_DUMP_ELEMENT_ROUTINE DumpElement
+ )
+/*++
+
+Routine Description:
+
+ Dump the 2-3 tree starting at the specified node.
+
+Arguments:
+
+ N - Points to the node to start the dump at.
+
+ Depth - Indicates the depth of the tree prior to this node.
+ This is used to indent the printing.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ ASSERT(Parent == N->ParentNode);
+
+
+ GtbpDevIndent( Depth );
+ DbgPrint("0x%lx ", N);
+ if (ARGUMENT_PRESENT(Low)) {
+ DbgPrint("(LowElement): ");
+ DumpElement( Low->Element );
+ }
+
+ Depth++;
+
+ if (GtbpChildrenAreLeaves(N)) {
+
+ GtbpDevIndent( Depth );
+ DumpElement( ((PGTB_TWO_THREE_LEAF)N->FirstChild)->Element );
+
+ if (N->SecondChild != NULL) {
+ GtbpDevIndent( Depth );
+ DumpElement( ((PGTB_TWO_THREE_LEAF)N->SecondChild)->Element );
+
+ if (N->ThirdChild != NULL) {
+ GtbpDevIndent( Depth );
+ DumpElement( ((PGTB_TWO_THREE_LEAF)N->ThirdChild)->Element );
+ }
+ }
+ } else {
+
+ GtbpDevDumpNode( N, N->FirstChild, NULL, Depth, DumpElement );
+
+ if (N->SecondChild != NULL) {
+ GtbpDevDumpNode( N, N->SecondChild, N->LowOfSecond, Depth, DumpElement );
+
+ if (N->ThirdChild != NULL) {
+ GtbpDevDumpNode( N, N->ThirdChild, N->LowOfThird, Depth, DumpElement );
+ }
+ }
+ }
+
+ return;
+}
+
+
+VOID
+GtbpDevDumpTwoThreeTree(
+ PRTL_GENERIC_TABLE2 Table,
+ PGTBP_DEV_DUMP_ELEMENT_ROUTINE DumpElement
+ )
+/*++
+
+Routine Description:
+
+ This routine causes the entire 2-3 tree to be dumped.
+
+
+Arguments:
+
+ Table - The table containing the 2-3 tree to dump.
+
+ DumpElement - A caller supplied routine that may be called
+ to dump element values.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY
+ Next;
+
+
+ DbgPrint("\n\n\n **** Dump Of Generic Table2 (2-3 tree) **** \n\n");
+
+ DbgPrint("Table : 0x%lx\n", Table);
+ DbgPrint("Element Count: %d\n", Table->ElementCount);
+
+
+ DbgPrint("\n\nSort Order Of Elements...");
+
+ Next = Table->SortOrderHead.Flink;
+ if (Next == &(Table->SortOrderHead)) {
+ DbgPrint("Sorted list is empty.\n");
+ } else {
+
+ while (Next != &(Table->SortOrderHead)) {
+ DbgPrint("\n0x%lx: ", Next);
+ (*DumpElement)( ((PGTB_TWO_THREE_LEAF)((PVOID)Next))->Element );
+ Next = Next->Flink;
+ } //end_while
+ } //end_if
+
+ DbgPrint("\n\n\nTree Structure...");
+
+ if (Table->Root == NULL) {
+ DbgPrint(" Root of table is NULL - no tree present\n");
+ } else {
+
+ //
+ // Walk the tree first-SubTree, second-SubTree, third-SubTree order
+ //
+
+ GtbpDevDumpNode(NULL, Table->Root, NULL, 0, DumpElement);
+ }
+
+ DbgPrint("\n\n");
+
+
+ return;
+}
+
+
+
+BOOLEAN
+GtbpDevValidateLeaf(
+ IN PGTB_TWO_THREE_LEAF Leaf,
+ IN PLIST_ENTRY ListHead,
+ IN OUT ULONG *ElementCount,
+ IN OUT PLIST_ENTRY *ListEntry
+ )
+
+/*++
+
+Routine Description:
+
+ Validate the specified leaf matches the next leaf in the
+ SortOrder list.
+
+
+Arguments:
+
+ Leaf - Points to the leaf to validate.
+
+ ListHead - Points to the head of the SortOrderList.
+
+ ElementCount - Contains a count of elements already validated.
+ This parameter will be incremented by 1 if the leaf is
+ found to be valid.
+
+ ListEntry - Points to the next element in the SortOrderList.
+ This pointer will be updated to point to the next element
+ in the list if the leaf is found to be valid.
+
+Return Value:
+
+ TRUE - Leaf is valid.
+
+ FALSE - Leaf is not valid.
+
+--*/
+{
+
+ if ((*ListEntry) == ListHead) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Exhausted entries in SortOrderList while there are still\n"
+ " entries in the tree.\n"));
+ }
+
+
+ if ((PVOID)Leaf == (PVOID)(*ListEntry)) {
+ (*ElementCount)++;
+ (*ListEntry) = (*ListEntry)->Flink;
+ return(TRUE);
+ } else {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Tree leaf doesn't match sort order leaf.\n"
+ " Tree Leaf : 0x%lx\n"
+ " sort order leaf: 0x%lx\n",
+ Leaf, (*ListEntry)));
+ return(FALSE);
+ }
+}
+
+
+BOOLEAN
+GtbpDevValidateTwoThreeSubTree(
+ IN PGTB_TWO_THREE_NODE Node,
+ IN PLIST_ENTRY ListHead,
+ IN OUT ULONG *ElementCount,
+ IN OUT PLIST_ENTRY *ListEntry
+ )
+
+/*++
+
+Routine Description:
+
+ Validate the specified subtree of a 2-3 tree.
+
+ The ParentNode of the tree is expected to already have been
+ validated by the caller of this routine.
+
+
+Arguments:
+
+ Node - Pointer to the root node of the subtree to validate.
+ Validate the specified leaf matches the next leaf in the
+ SortOrder list.
+
+
+Arguments:
+
+ Leaf - Points to the leaf to validate.
+
+ ListHead - points to the SortOrderList's listhead.
+
+ ElementCount - Contains a count of elements already validated.
+ This parameter will be incremented by the number of elements
+ in this subtree.
+
+ ListEntry - Points to the next element in the SortOrderList.
+ This pointer will be updated as elements are encountered and
+ compared to the elements in the SortOrderList.
+
+Return Value:
+
+ TRUE - SubTree structure is valid.
+
+ FALSE - SubTree structure is not valid.
+
+--*/
+{
+
+ BOOLEAN
+ Result;
+
+
+ //
+ // Must have at least two children unless we are the root node.
+ //
+
+ if (Node->ParentNode != NULL) {
+ if (Node->SecondChild == NULL) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Non-root node has fewer than two children.\n"
+ " Node : 0x%lx\n"
+ " FirstChild : 0x%lx\n"
+ " SecondChild: 0x%lx\n"
+ " ThirdChild : 0x%lx\n",
+ Node, Node->FirstChild, Node->SecondChild,
+ Node->ThirdChild));
+ return(FALSE);
+ }
+ }
+
+ if (Node->FirstChild == NULL) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Non-root node does not have first child.\n"
+ " Node : 0x%lx\n"
+ " FirstChild : 0x%lx\n"
+ " SecondChild: 0x%lx\n"
+ " ThirdChild : 0x%lx\n",
+ Node, Node->FirstChild, Node->SecondChild,
+ Node->ThirdChild));
+ return(FALSE);
+ }
+
+
+
+ if (!GtbpChildrenAreLeaves(Node)) {
+
+
+ Result = GtbpDevValidateTwoThreeSubTree( Node->FirstChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+
+ if ( Result && (Node->SecondChild != NULL) ) {
+ Result = GtbpDevValidateTwoThreeSubTree( Node->SecondChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+ if ( Result && (Node->ThirdChild != NULL) ) {
+ Result = GtbpDevValidateTwoThreeSubTree( Node->ThirdChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+ }
+ }
+
+ return(Result);
+ }
+
+
+ //
+ // We are at a leaf node
+ // Check that we have a SortOrderList entry matching each
+ // leaf.
+ //
+
+ Result = GtbpDevValidateLeaf( (PGTB_TWO_THREE_LEAF)Node->FirstChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+
+ if (Result && (Node->SecondChild != NULL)) {
+ Result = GtbpDevValidateLeaf( (PGTB_TWO_THREE_LEAF)Node->SecondChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+ if (Result && (Node->ThirdChild != NULL)) {
+ Result = GtbpDevValidateLeaf( (PGTB_TWO_THREE_LEAF)Node->ThirdChild,
+ ListHead,
+ ElementCount,
+ ListEntry);
+ }
+ }
+
+ if (!Result) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: previous error in child analysis prevents further\n"
+ " validation of node: 0x%lx\n", Node));
+ }
+
+ return(Result);
+}
+
+BOOLEAN
+GtbpDevValidateGenericTable2(
+ PRTL_GENERIC_TABLE2 Table
+ )
+/*++
+
+Routine Description:
+
+ This routine causes the entire 2-3 tree's structure to be
+ validated.
+
+ !! DOESN'T YET VALIDATE LowOfChild VALUES !!
+
+Arguments:
+
+ Table - The table containing the 2-3 tree to validate.
+
+
+Return Value:
+
+ TRUE - Table structure is valid.
+
+ FALSE - Table structure is not valid.
+
+--*/
+{
+ ULONG
+ ElementCount,
+ ElementsInList;
+
+ PGTB_TWO_THREE_NODE
+ Node;
+
+ PLIST_ENTRY
+ ListEntry;
+
+ BOOLEAN
+ Result;
+
+ //
+ // Walk the tree and the linked list simultaneously.
+ // Walk the tree first-child, second-child, third-child
+ // order to get ascending values that match the linked list.
+ //
+
+
+ if (Table->ElementCount == 0) {
+ if (Table->Root != NULL) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: ElementCount is zero, but root node is not null.\n"
+ " Table: 0x%lx Root: 0x%lx\n", Table, Table->Root));
+ Result = FALSE;
+ } else {
+ return(TRUE);
+ }
+
+
+ } else {
+ if (Table->Root == NULL) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: ElementCount is non-zero, but root node is null.\n"
+ " Table: 0x%lx ElementCount: %d\n",
+ Table, Table->ElementCount));
+ Result = FALSE;
+ }
+
+
+ if (Table->SortOrderHead.Flink == &Table->SortOrderHead) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: ElementCount is non-zero, but sort order list is empty.\n"
+ " Table: 0x%lx ElementCount: %d\n",
+ Table, Table->ElementCount));
+ Result = FALSE;
+ }
+
+ }
+
+ if (Result) {
+
+ ListEntry = Table->SortOrderHead.Flink;
+ Node = Table->Root;
+
+ //
+ // Verify parent pointer (responsibility of caller)
+ //
+
+ if (Node->ParentNode != NULL) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Root parent pointer is non-null.\n"
+ " Table: 0x%lx Root: 0x%lx ParentNode: 0x%lx\n",
+ Table, Node, Node->ParentNode));
+ Result = FALSE;
+ }
+
+ if (Result) {
+
+ ElementCount = 0;
+ Result = GtbpDevValidateTwoThreeSubTree( Node,
+ &Table->SortOrderHead,
+ &ElementCount,
+ &ListEntry);
+ if (Result) {
+
+ ElementsInList = ElementCount;
+ while (ListEntry != &Table->SortOrderHead) {
+ ElementsInList++;
+ ListEntry = ListEntry->Flink;
+ }
+ if ( (ElementCount != Table->ElementCount) ||
+ (ElementsInList != ElementCount) ) {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: Table is valid except either the ElementCount doesn't match\n"
+ " the number of elements in the table or there were entries on\n"
+ " the SortOrderList that weren't in the table.\n"
+ " Table : 0x%lx\n"
+ " Root : 0x%lx\n"
+ " ElementCount : %d\n"
+ " Elements In Tree: %d\n"
+ " Elements In List: %d\n",
+ Table, Node, Table->ElementCount,
+ ElementCount, ElementsInList));
+ Result = FALSE;
+ }
+ } else {
+ GtbpDiagPrint( VALIDATE,
+ ("GTB: previous validation error in table 0x%lx prevents\n"
+ " further processing.\n", Table));
+ }
+ }
+ }
+
+ if (!Result) {
+ DbgBreakPoint();
+ }
+
+
+ return(Result);
+}
+#endif //GTBP_DEVELOPER_BUILD
diff --git a/private/newsam/server/global.c b/private/newsam/server/global.c
new file mode 100644
index 000000000..e7d07a437
--- /dev/null
+++ b/private/newsam/server/global.c
@@ -0,0 +1,304 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ global.c
+
+Abstract:
+
+ This file contains global variables for the SAM server program.
+
+ Note: There are also some global variables in the files generated
+ by the RPC midl compiler. These variables start with the
+ prefix "samr_".
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Global variables //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+#if SAMP_DIAGNOSTICS
+//
+// SAM Global Controls - see flags in samsrvp.h
+//
+
+ULONG SampGlobalFlag = 0;
+#endif //SAMP_DIAGNOSTICS
+
+
+//
+// Internal data structure and Registry database synchronization lock
+//
+// The SampTransactionWithinDomain field is used to track whether a
+// lock held for exclusive WRITE access is for a transaction within
+// a single domain. If so, then SampTransactionDomainIndex contains
+// the index into SampDefinedDomains of the domain being modified.
+//
+
+RTL_RESOURCE SampLock;
+BOOLEAN SampTransactionWithinDomain;
+ULONG SampTransactionDomainIndex;
+
+
+//
+// The type of product this SAM server is running in
+//
+
+NT_PRODUCT_TYPE SampProductType;
+
+
+
+//
+// Used to indicate whether the SAM service is currently processing
+// normal client calls. If not, then trusted client calls will still
+// be processed, but non-trusted client calls will be rejected.
+//
+
+SAMP_SERVICE_STATE SampServiceState;
+
+
+//
+// This boolean is set to TRUE if the LSA auditing policy indicates
+// account auditing is enabled. Otherwise, this will be FALSE.
+//
+// This enables SAM to skip all auditing processing unless auditing
+// is currently enabled.
+//
+
+BOOLEAN SampAccountAuditingEnabled;
+
+
+
+//
+// This is a handle to the root of the SAM backstore information in the
+// registry. This is the level at which the RXACT information is
+// established. This key can not be closed if there are any SERVER object
+// context blocks active.
+// ("SAM")
+//
+
+HANDLE SampKey;
+
+
+//
+// This is the pointer to the RXactContext structure that will be created
+// when RXact is initialized. It must be passed into each RXact call.
+//
+
+PRTL_RXACT_CONTEXT SampRXactContext;
+
+
+//
+// Keep a list of server and domain contexts
+//
+
+LIST_ENTRY SampContextListHead;
+
+//
+// This array contains information about each domain known to this
+// SAM server. Reference and Modification of this array is protected
+// by the SampLock.
+//
+
+ULONG SampDefinedDomainsCount;
+PSAMP_DEFINED_DOMAINS SampDefinedDomains;
+
+
+
+
+
+//
+// Object type-independent information for each of the various
+// SAM defined objects.
+// This information is READ-ONLY once initialized.
+
+SAMP_OBJECT_INFORMATION SampObjectInformation[ SampUnknownObjectType ];
+
+
+
+
+//
+// Count of the number of active opens
+//
+
+ULONG SampActiveContextCount;
+
+
+
+//
+// Address of DLL routine to do password filtering.
+//
+
+//PSAM_PF_PASSWORD_FILTER SampPasswordFilterDllRoutine;
+
+
+
+//
+// Unicode strings containing well known registry key names.
+// These are read-only values once initialized.
+//
+
+UNICODE_STRING SampNameDomains;
+UNICODE_STRING SampNameDomainGroups;
+UNICODE_STRING SampNameDomainAliases;
+UNICODE_STRING SampNameDomainAliasesMembers;
+UNICODE_STRING SampNameDomainUsers;
+UNICODE_STRING SampNameDomainAliasesNames;
+UNICODE_STRING SampNameDomainGroupsNames;
+UNICODE_STRING SampNameDomainUsersNames;
+UNICODE_STRING SampCombinedAttributeName;
+UNICODE_STRING SampFixedAttributeName;
+UNICODE_STRING SampVariableAttributeName;
+
+
+
+//
+// A plethora of other useful characters or strings
+//
+
+UNICODE_STRING SampBackSlash; // "/"
+UNICODE_STRING SampNullString; // Null string
+UNICODE_STRING SampSamSubsystem; // "Security Account Manager"
+UNICODE_STRING SampServerObjectName; // Name of root SamServer object
+
+
+//
+// Useful times
+//
+
+LARGE_INTEGER SampImmediatelyDeltaTime;
+LARGE_INTEGER SampNeverDeltaTime;
+LARGE_INTEGER SampHasNeverTime;
+LARGE_INTEGER SampWillNeverTime;
+
+
+//
+// Useful encryption constants
+//
+
+LM_OWF_PASSWORD SampNullLmOwfPassword;
+NT_OWF_PASSWORD SampNullNtOwfPassword;
+
+
+//
+// Useful Sids
+//
+
+PSID SampWorldSid;
+PSID SampAnonymousSid;
+PSID SampAdministratorUserSid;
+PSID SampAdministratorsAliasSid;
+
+
+//
+// Variables for the thread that flushes changes to the registry.
+//
+// LastUnflushedChange - if there are no changes to be flushed, this
+// has a value of "Never". If there are changes to be flushed,
+// this is the time of the last change that was made. The flush
+// thread will flush if a SampFlushThreadMinWaitSeconds has passed
+// since the last change.
+//
+// FlushThreadCreated - set TRUE as soon as the flush thread is created,
+// and FALSE when the thread exits. A new thread will be created
+// when this is FALSE, unless FlushImmediately is TRUE.
+//
+// FlushImmediately - an important event has occurred, so we want to
+// flush the changes immediately rather than waiting for the flush
+// thread to do it. LastUnflushedChange should be set to "Never"
+// so the flush thread knows it doesn't have to flush.
+//
+
+LARGE_INTEGER LastUnflushedChange;
+BOOLEAN FlushThreadCreated;
+BOOLEAN FlushImmediately;
+
+//
+// These should probably be #defines, but we want to play with them.
+//
+// SampFlushThreadMinWaitSeconds - The unit of time that the flush thread
+// waits. If one of these has passed since the last unflushed change,
+// the changes will be flushed.
+//
+// SampFlushThreadMaxWaitSeconds - If this amount of time has passed since
+// the flush thread was created or last flushed, the thread will force
+// a flush even if the database is still being changed.
+//
+// SampFlushThreadExitDelaySeconds - How long the flush thread waits
+// around after a flush to see if any more changes occur. If they
+// do, it starts waiting again; but if they don't, it will exit
+// to keep down thread overhead.
+//
+
+LONG SampFlushThreadMinWaitSeconds;
+LONG SampFlushThreadMaxWaitSeconds;
+LONG SampFlushThreadExitDelaySeconds;
+
+//
+// Special SIDs
+//
+
+PSID SampBuiltinDomainSid = NULL;
+PSID SampAccountDomainSid = NULL;
+
+//
+// Null token handle. This is used when clients connect via unauthenticated
+// RPC instead of authenticated RPC or named pipes. Since they can't be
+// authenticated, we impersonate this pre-built Null sesssion token.
+//
+
+HANDLE SampNullSessionToken;
+
+//
+// Flag indicating whether Netware server installed.
+//
+
+BOOLEAN SampNetwareServerInstalled = FALSE;
+
+//
+// Flag indicating whether to start listening on TCP/IP
+//
+
+BOOLEAN SampIpServerInstalled = FALSE;
+
+//
+// Flag indicating whether to start listening on apple talk
+//
+
+BOOLEAN SampAppletalkServerInstalled = FALSE;
+
+//
+// Flag indicating whether to start listening on Vines
+//
+
+BOOLEAN SampVinesServerInstalled = FALSE;
+
+
diff --git a/private/newsam/server/group.c b/private/newsam/server/group.c
new file mode 100644
index 000000000..729bd65ad
--- /dev/null
+++ b/private/newsam/server/group.c
@@ -0,0 +1,3158 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ group.c
+
+Abstract:
+
+ This file contains services related to the SAM "group" object.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <msaudite.h>
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampDeleteGroupKeys(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampChangeGroupAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ );
+
+NTSTATUS
+SampReplaceGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG MemberCount,
+ IN PULONG Members
+ );
+
+NTSTATUS
+SampAddAccountToGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG UserRid
+ );
+
+NTSTATUS
+SampRemoveAccountFromGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG AccountRid
+ );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Exposed RPC'able Services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+NTSTATUS
+SamrOpenGroup(
+ IN SAMPR_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG GroupId,
+ OUT SAMPR_HANDLE *GroupHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API opens an existing group in the account database. The group
+ is specified by a ID value that is relative to the SID of the
+ domain. The operations that will be performed on the group must be
+ declared at this time.
+
+ This call returns a handle to the newly opened group that may be
+ used for successive operations on the group. This handle may be
+ closed with the SamCloseHandle API.
+
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the group. These access types are reconciled
+ with the Discretionary Access Control list of the group to
+ determine whether the accesses will be granted or denied.
+
+ GroupId - Specifies the relative ID value of the group to be
+ opened.
+
+ GroupHandle - Receives a handle referencing the newly opened
+ group. This handle will be required in successive calls to
+ operate on the group.
+
+Return Values:
+
+ STATUS_SUCCESS - The group was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_NO_SUCH_GROUP - The specified group does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ NtStatus = SampOpenAccount(
+ SampGroupObjectType,
+ DomainHandle,
+ DesiredAccess,
+ GroupId,
+ FALSE,
+ GroupHandle
+ );
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrQueryInformationGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ IN GROUP_INFORMATION_CLASS GroupInformationClass,
+ OUT PSAMPR_GROUP_INFO_BUFFER *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API retrieves information on the group specified.
+
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ GroupInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ----------------------- ----------------------
+
+ GroupGeneralInformation GROUP_READ_INFORMATION
+ GroupNameInformation GROUP_READ_INFORMATION
+ GroupAttributeInformation GROUP_READ_INFORMATION
+ GroupAdminInformation GROUP_READ_INFORMATION
+
+ Buffer - Receives a pointer to a buffer containing the requested
+ information. When this information is no longer needed, this
+ buffer must be freed 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 handle passed is invalid.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ ULONG i;
+ SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed;
+
+ //
+ // Used for tracking allocated blocks of memory - so we can deallocate
+ // them in case of error. Don't exceed this number of allocated buffers.
+ // ||
+ // vv
+ PVOID AllocatedBuffer[10];
+ ULONG AllocatedBufferCount = 0;
+
+ #define RegisterBuffer(Buffer) \
+ { \
+ if ((Buffer) != NULL) { \
+ \
+ ASSERT(AllocatedBufferCount < \
+ sizeof(AllocatedBuffer) / sizeof(*AllocatedBuffer)); \
+ \
+ AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
+ } \
+ }
+
+ #define AllocateBuffer(NewBuffer, Size) \
+ { \
+ (NewBuffer) = MIDL_user_allocate(Size); \
+ RegisterBuffer(NewBuffer); \
+ } \
+
+
+ //
+ // 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 (GroupInformationClass) {
+
+ case GroupGeneralInformation:
+ case GroupNameInformation:
+ case GroupAttributeInformation:
+ case GroupAdminCommentInformation:
+
+ DesiredAccess = GROUP_READ_INFORMATION;
+ break;
+
+ default:
+ (*Buffer) = NULL;
+ return(STATUS_INVALID_INFO_CLASS);
+ } // end_switch
+
+
+
+ //
+ // Allocate the info structure
+ //
+
+ (*Buffer) = MIDL_user_allocate( sizeof(SAMPR_GROUP_INFO_BUFFER) );
+ if ((*Buffer) == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ RegisterBuffer(*Buffer);
+
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)GroupHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // If the information level requires, retrieve the V1_FIXED record
+ // from the registry.
+ //
+
+ switch (GroupInformationClass) {
+
+ case GroupGeneralInformation:
+ case GroupAttributeInformation:
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ AccountContext,
+ &V1Fixed
+ );
+ break; //out of switch
+
+ default:
+ NtStatus = STATUS_SUCCESS;
+
+ } // end_switch
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // case on the type information requested
+ //
+
+ switch (GroupInformationClass) {
+
+ case GroupGeneralInformation:
+
+
+ (*Buffer)->General.Attributes = V1Fixed.Attributes;
+
+
+ //
+ // Get the member count
+ //
+
+ NtStatus = SampRetrieveGroupMembers(
+ AccountContext,
+ &(*Buffer)->General.MemberCount,
+ NULL // Only need members
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.Name)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.Name.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.AdminComment)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.AdminComment.Buffer);
+ }
+ }
+ }
+
+
+ break;
+
+
+ case GroupNameInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Name.Name)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Name.Name.Buffer);
+ }
+
+
+ break;
+
+
+ case GroupAdminCommentInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->AdminComment.AdminComment)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->AdminComment.AdminComment.Buffer);
+ }
+
+
+ break;
+
+
+ case GroupAttributeInformation:
+
+
+ (*Buffer)->Attribute.Attributes = V1Fixed.Attributes;
+
+ break;
+
+ } // end_switch
+
+
+ } // end_if
+
+
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+ //
+ // 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
+SamrSetInformationGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ IN GROUP_INFORMATION_CLASS GroupInformationClass,
+ IN PSAMPR_GROUP_INFO_BUFFER Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API allows the caller to modify group information.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ GroupInformationClass - Class of information to retrieve. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ------------------------ -------------------------
+
+ GroupGeneralInformation (can't write)
+
+ GroupNameInformation GROUP_WRITE_ACCOUNT
+ GroupAttributeInformation GROUP_WRITE_ACCOUNT
+ GroupAdminInformation GROUP_WRITE_ACCOUNT
+
+ Buffer - Buffer where information retrieved is placed.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_GROUP - The group specified is unknown.
+
+ STATUS_SPECIAL_GROUP - The group specified is a special group and
+ cannot be operated on in the requested fashion.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+{
+
+ NTSTATUS NtStatus,
+ TmpStatus,
+ IgnoreStatus;
+
+ PSAMP_OBJECT AccountContext;
+
+ SAMP_OBJECT_TYPE FoundType;
+
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ ACCESS_MASK DesiredAccess;
+
+ SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed;
+
+ UNICODE_STRING OldAccountName,
+ NewAdminComment,
+ NewAccountName,
+ NewFullName;
+
+ ULONG ObjectRid,
+ OldGroupAttributes,
+ DomainIndex;
+
+ BOOLEAN Modified = FALSE,
+ MustUpdateAccountDisplay = FALSE;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ if (Buffer == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Reset any strings that we'll be freeing in clean-up code
+ //
+
+ RtlInitUnicodeString(&OldAccountName, NULL);
+ RtlInitUnicodeString(&NewAccountName, NULL);
+ RtlInitUnicodeString(&NewFullName, NULL);
+ RtlInitUnicodeString(&NewAdminComment, NULL);
+
+ //
+ // Set the desired access based upon the Info class
+ //
+
+ switch (GroupInformationClass) {
+
+ case GroupNameInformation:
+ case GroupAttributeInformation:
+ case GroupAdminCommentInformation:
+
+ DesiredAccess = GROUP_WRITE_ACCOUNT;
+ break;
+
+
+ case GroupGeneralInformation:
+ default:
+
+ return(STATUS_INVALID_INFO_CLASS);
+
+ } // end_switch
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)GroupHandle;
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+
+ //
+ // If the information level requires, retrieve the V1_FIXED record
+ // from the registry. This includes anything that will cause
+ // us to update the display cache.
+ //
+
+ switch (GroupInformationClass) {
+
+ case GroupAdminCommentInformation:
+ case GroupNameInformation:
+ case GroupAttributeInformation:
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ AccountContext,
+ &V1Fixed
+ );
+
+ MustUpdateAccountDisplay = TRUE;
+ OldGroupAttributes = V1Fixed.Attributes;
+ break; //out of switch
+
+
+ default:
+ NtStatus = STATUS_SUCCESS;
+
+ } // end_switch
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // case on the type information requested
+ //
+
+ switch (GroupInformationClass) {
+
+ case GroupNameInformation:
+
+ NtStatus = SampChangeGroupAccountName(
+ AccountContext,
+ (PUNICODE_STRING)&(Buffer->Name.Name),
+ &OldAccountName
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ OldAccountName.Buffer = NULL;
+ }
+
+ //
+ // Don't free OldAccountName yet; we'll need it at the
+ // very end.
+ //
+
+ break;
+
+
+ case GroupAdminCommentInformation:
+
+ //
+ // build the key name
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ (PUNICODE_STRING)&(Buffer->AdminComment.AdminComment)
+ );
+
+ break;
+
+
+ case GroupAttributeInformation:
+
+ MustUpdateAccountDisplay = TRUE;
+
+ V1Fixed.Attributes = Buffer->Attribute.Attributes;
+
+ NtStatus = SampReplaceGroupV1Fixed(
+ AccountContext, // ParentKey
+ &V1Fixed
+ );
+
+ break;
+
+
+ } // end_switch
+
+
+ } // end_if
+
+
+ //
+ // Go fetch any data we'll need to update the display cache
+ // Do this before we dereference the context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( MustUpdateAccountDisplay ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ &NewAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_ADMIN_COMMENT,
+ TRUE, // Make copy
+ &NewAdminComment
+ );
+ //
+ // If the account name has changed, then OldAccountName
+ // is already filled in. If the account name hasn't changed
+ // then the OldAccountName is the same as the new!
+ //
+
+ if (NT_SUCCESS(NtStatus) && (OldAccountName.Buffer == NULL)) {
+
+ NtStatus = SampDuplicateUnicodeString(
+ &OldAccountName,
+ &NewAccountName);
+ }
+ }
+ }
+ }
+
+
+ //
+ // Generate an audit if necessary
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(DomainIndex)) {
+
+ UNICODE_STRING
+ AccountName;
+
+ IgnoreStatus = SampGetUnicodeStringAttribute(
+ AccountContext, // Context
+ SAMP_GROUP_NAME, // AttributeIndex
+ FALSE, // MakeCopy
+ &AccountName // UnicodeAttribute
+ );
+ if (NT_SUCCESS(IgnoreStatus)) {
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_GLOBAL_GROUP_CHANGE, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &AccountContext->TypeBody.Group.Rid, // Account Rid
+ NULL // Privileges used
+ );
+ }
+
+ }
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+
+ } //end_if
+
+
+
+ //
+ // Commit the transaction, update the display cache,
+ // and notify netlogon of the changes
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+
+ //
+ // Update the display information if the cache may be affected
+ //
+
+ if ( MustUpdateAccountDisplay ) {
+
+ SAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo;
+ SAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo;
+
+ OldAccountInfo.Name = OldAccountName;
+ OldAccountInfo.Rid = ObjectRid;
+ OldAccountInfo.AccountControl = OldGroupAttributes;
+ RtlInitUnicodeString(&OldAccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&OldAccountInfo.FullName, NULL); // Not used for groups
+
+ NewAccountInfo.Name = NewAccountName;
+ NewAccountInfo.Rid = ObjectRid;
+ NewAccountInfo.AccountControl = V1Fixed.Attributes;
+ NewAccountInfo.Comment = NewAdminComment;
+ NewAccountInfo.FullName = NewFullName;
+
+ IgnoreStatus = SampUpdateDisplayInformation(&OldAccountInfo,
+ &NewAccountInfo,
+ SampGroupObjectType);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+ if ( GroupInformationClass == GroupNameInformation ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbRename,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ &OldAccountName,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ } else {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChange,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+ }
+
+
+ }
+ }
+
+
+ //
+ // Release the write lock
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+
+ //
+ // Clean up strings
+ //
+
+ SampFreeUnicodeString( &OldAccountName );
+ SampFreeUnicodeString( &NewAccountName );
+ SampFreeUnicodeString( &NewFullName );
+ SampFreeUnicodeString( &NewAdminComment );
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrAddMemberToGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ IN ULONG MemberId,
+ IN ULONG Attributes
+ )
+
+/*++
+
+Routine Description:
+
+ This API adds a member to a group. Note that this API requires the
+ GROUP_ADD_MEMBER access type for the group.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ MemberId - Relative ID of the member to add.
+
+ Attributes - The attributes of the group assigned to the user.
+ The attributes assigned here may have any value. However,
+ at logon time these attributes are minimized by the
+ attributes of the group as a whole.
+
+ Mandatory - If the Mandatory attribute is assigned to
+ the group as a whole, then it will be assigned to
+ the group for each member of the group.
+
+ EnabledByDefault - This attribute may be set to any value
+ for each member of the group. It does not matter
+ what the attribute value for the group as a whole
+ is.
+
+ Enabled - This attribute may be set to any value for each
+ member of the group. It does not matter what the
+ attribute value for the group as a whole is.
+
+ Owner - If the Owner attribute of the group as a
+ whole is not set, then the value assigned to
+ members is ignored.
+
+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_NO_SUCH_MEMBER - The member specified is unknown.
+
+ STATUS_MEMBER_IN_GROUP - The member already belongs to the group.
+
+ STATUS_INVALID_GROUP_ATTRIBUTES - Indicates the group attribute
+ values being assigned to the member are not compatible with
+ the attribute values of the group as a whole.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+
+
+--*/
+{
+
+ SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed;
+ NTSTATUS NtStatus, TmpStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG ObjectRid;
+ BOOLEAN UserAccountActive;
+ UNICODE_STRING GroupName;
+
+
+
+ //
+ // Initialize buffers we will cleanup at the end
+ //
+
+ RtlInitUnicodeString(&GroupName, NULL);
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(GroupHandle);
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ GROUP_ADD_MEMBER,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ AccountContext,
+ &GroupV1Fixed
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Perform the user object side of things
+ //
+
+ NtStatus = SampAddGroupToUserMembership(
+ ObjectRid,
+ Attributes,
+ MemberId,
+ (GroupV1Fixed.AdminCount == 0) ? NoChange : AddToAdmin,
+ (GroupV1Fixed.OperatorCount == 0) ? NoChange : AddToAdmin,
+ &UserAccountActive
+ );
+
+
+
+ //
+ // Now perform the group side of things
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the user to the group (should not fail)
+ //
+
+ NtStatus = SampAddAccountToGroupMembers(
+ AccountContext,
+ MemberId
+ );
+ }
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get and save the account name for
+ // I_NetNotifyLogonOfDelta.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ &GroupName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ RtlInitUnicodeString(&GroupName, NULL);
+ }
+ }
+
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+ }
+
+
+ //
+ // Commit the transaction and notify net logon of the changes
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAM_DELTA_DATA DeltaData;
+
+ //
+ // Fill in id of member being added
+ //
+
+ DeltaData.GroupMemberId.MemberRid = MemberId;
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangeMemberAdd,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ &GroupName,
+ (DWORD) FALSE, // Replicate immediately
+ &DeltaData
+ );
+ }
+ }
+
+
+ //
+ // Free up the group name
+ //
+
+ SampFreeUnicodeString(&GroupName);
+
+
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrDeleteGroup(
+ IN SAMPR_HANDLE *GroupHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API removes a group from the account database. There may be no
+ members in the group or the deletion request will be rejected. Note
+ that this API requires DELETE access to the specific group being
+ deleted.
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+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. This may be
+ because someone has deleted the group while it was open.
+
+ STATUS_SPECIAL_ACCOUNT - The group specified is a special group and
+ cannot be operated on in the requested fashion.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+{
+
+ UNICODE_STRING GroupName;
+ NTSTATUS NtStatus, TmpStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PSID AccountSid;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG MemberCount,
+ ObjectRid,
+ DomainIndex;
+
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(*GroupHandle);
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DELETE,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // Make sure the account is one that can be deleted.
+ // Can't be a built-in account, unless the caller is trusted.
+ //
+
+ if ( !AccountContext->TrustedClient ) {
+
+ NtStatus = SampIsAccountBuiltIn( ObjectRid );
+ }
+
+
+ if (NT_SUCCESS( NtStatus) ) {
+
+ //
+ // and it can't have any members
+ //
+
+ NtStatus = SampRetrieveGroupMembers(
+ AccountContext,
+ &MemberCount,
+ NULL // Only need member count (not list)
+ );
+
+ if (MemberCount != 0) {
+ NtStatus = STATUS_MEMBER_IN_GROUP;
+ }
+
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove this account from all aliases
+ //
+
+
+ NtStatus = SampCreateAccountSid(AccountContext, &AccountSid);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRemoveAccountFromAllAliases(
+ AccountSid,
+ FALSE,
+ NULL,
+ NULL,
+ NULL );
+ }
+ }
+
+
+ //
+ // Looks promising.
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // First get and save the account name for
+ // I_NetNotifyLogonOfDelta.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ &GroupName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // This must be done before we invalidate contexts, because our
+ // own handle to the group gets closed as well.
+ //
+
+ NtStatus = SampDeleteGroupKeys( AccountContext );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We must invalidate any open contexts to this group.
+ // This will close all handles to the group's keys.
+ // THIS IS AN IRREVERSIBLE PROCESS.
+ //
+
+ SampInvalidateGroupContexts( ObjectRid );
+
+
+ //
+ // Commit the whole mess
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
+
+ //
+ // Update the Cached Alias Information
+ //
+
+ NtStatus = SampAlRemoveAccountFromAllAliases(
+ AccountSid,
+ FALSE,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ MIDL_user_free(AccountSid);
+
+
+ //
+ // Update the display information
+ //
+
+ AccountInfo.Name = GroupName;
+ AccountInfo.Rid = ObjectRid;
+ AccountInfo.AccountControl = 0; // Don't care about this value for delete
+ RtlInitUnicodeString(&AccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&AccountInfo.FullName, NULL);
+
+ TmpStatus = SampUpdateDisplayInformation(&AccountInfo,
+ NULL,
+ SampGroupObjectType);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ //
+ // Audit the deletion before we free the write lock
+ // so that we have access to the context block.
+ //
+
+ if (SampDoAccountAuditing(DomainIndex) &&
+ NT_SUCCESS(NtStatus) ) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_GLOBAL_GROUP_DELETED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &GroupName, // Account Name
+ &Domain->ExternalName, // Domain
+ &ObjectRid, // Account Rid
+ NULL // Privileges used
+ );
+
+ }
+
+ //
+ // Do delete auditing
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ (VOID) NtDeleteObjectAuditAlarm(
+ &SampSamSubsystem,
+ *GroupHandle,
+ AccountContext->AuditOnClose
+ );
+ }
+
+ //
+ // Notify netlogon of the change
+ //
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbDelete,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ &GroupName,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+
+ SampFreeUnicodeString( &GroupName );
+ }
+ }
+
+
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we actually deleted the group, delete the context and
+ // let RPC know that the handle is invalid.
+ //
+
+ SampDeleteContext( AccountContext );
+
+ (*GroupHandle) = NULL;
+ }
+
+ } //end_if
+
+ //
+ // Free the lock -
+ //
+ // Everything has already been committed above, so we must indicate
+ // no additional changes have taken place.
+ //
+ //
+ //
+
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+ if (NtStatus == STATUS_SUCCESS) {
+ NtStatus = TmpStatus;
+ }
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrRemoveMemberFromGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ IN ULONG MemberId
+ )
+
+/*++
+
+Routine Description:
+
+ This service
+
+Arguments:
+
+ ????
+
+Return Value:
+
+
+ ????
+
+
+--*/
+{
+ SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed;
+ NTSTATUS NtStatus, TmpStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG ObjectRid;
+ BOOLEAN UserAccountActive;
+ UNICODE_STRING GroupName;
+
+
+
+ //
+ // Initialize buffers to be cleaned up at the end
+ //
+
+ RtlInitUnicodeString(&GroupName, NULL);
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(GroupHandle);
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ GROUP_REMOVE_MEMBER,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ AccountContext,
+ &GroupV1Fixed
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Perform the user object side of things
+ //
+
+ NtStatus = SampRemoveMembershipUser(
+ ObjectRid,
+ MemberId,
+ (GroupV1Fixed.AdminCount == 0) ? NoChange : RemoveFromAdmin,
+ (GroupV1Fixed.OperatorCount == 0) ? NoChange : RemoveFromAdmin,
+ &UserAccountActive
+ );
+
+
+
+ //
+ // Now perform the group side of things
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove the user from the group (should not fail)
+ //
+
+ NtStatus = SampRemoveAccountFromGroupMembers(
+ AccountContext,
+ MemberId
+ );
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get and save the account name for
+ // I_NetNotifyLogonOfDelta.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ &GroupName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ RtlInitUnicodeString(&GroupName, NULL);
+ }
+ }
+
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAM_DELTA_DATA DeltaData;
+
+ //
+ // Fill in id of member being deleted
+ //
+
+ DeltaData.GroupMemberId.MemberRid = MemberId;
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangeMemberDel,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ &GroupName,
+ (DWORD) FALSE, // Replicate immediately
+ &DeltaData
+ );
+ }
+ }
+
+
+ //
+ // Free up the group name
+ //
+
+ SampFreeUnicodeString(&GroupName);
+
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrGetMembersInGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ OUT PSAMPR_GET_MEMBERS_BUFFER *GetMembersBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all the members in a group. This API may be called
+ repeatedly, passing a returned context handle, to retrieve large
+ amounts of data. This API requires GROUP_LIST_MEMBERS access to the
+ group.
+
+
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+ GROUP_LIST_MEMBERS access is needed to the group.
+
+ GetMembersBuffer - Receives a pointer to a set of returned structures
+ with the following format:
+
+ +-------------+
+ --------->| MemberCount |
+ |-------------+ +-------+
+ | Members --|------------------->| Rid-0 |
+ |-------------| +------------+ | ... |
+ | Attributes-|-->| Attribute0 | | |
+ +-------------+ | ... | | Rid-N |
+ | AttributeN | +-------+
+ +------------+
+
+ Each block individually allocated with MIDL_user_allocate.
+
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no addition entries.
+
+ STATUS_ACCESS_DENIED - Caller does not have privilege required to
+ request that data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+ This service
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ ULONG i;
+ ULONG ObjectRid;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (GetMembersBuffer != NULL);
+
+ if ((*GetMembersBuffer) != NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+
+ //
+ // Allocate the first of the return buffers
+ //
+
+ (*GetMembersBuffer) = MIDL_user_allocate( sizeof(SAMPR_GET_MEMBERS_BUFFER) );
+
+ if ( (*GetMembersBuffer) == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+
+ //
+ // Grab the lock
+ //
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)GroupHandle;
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ GROUP_LIST_MEMBERS,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveGroupMembers(
+ AccountContext,
+ &(*GetMembersBuffer)->MemberCount,
+ &(*GetMembersBuffer)->Members
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Allocate a buffer for the attributes - which we get from
+ // the individual user records
+ //
+
+ (*GetMembersBuffer)->Attributes = MIDL_user_allocate((*GetMembersBuffer)->MemberCount * sizeof(ULONG) );
+ if ((*GetMembersBuffer)->Attributes == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ for ( i=0; (i<((*GetMembersBuffer)->MemberCount) && NT_SUCCESS(NtStatus)); i++) {
+
+ (*GetMembersBuffer)->Attributes[i] = SAMP_DEFAULT_GROUP_ATTRIBUTES;
+
+ //
+ // Don't call the user code to get the attribute - this is too
+ // expensive. Since group attributes are not really used we
+ // hardwire the result to be the default.
+ //
+
+//
+// NtStatus = SampRetrieveUserGroupAttribute(
+// (*GetMembersBuffer)->Members[i],
+// ObjectRid,
+// &(*GetMembersBuffer)->Attributes[i]
+// );
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ MIDL_user_free( (*GetMembersBuffer)->Members );
+ if ((*GetMembersBuffer)->Attributes != NULL) {
+ MIDL_user_free( (*GetMembersBuffer)->Attributes );
+ }
+ }
+ }
+
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ if (!NT_SUCCESS(NtStatus) || ((*GetMembersBuffer)->MemberCount == 0)){
+
+ (*GetMembersBuffer)->MemberCount = 0;
+ (*GetMembersBuffer)->Members = NULL;
+ (*GetMembersBuffer)->Attributes = NULL;
+ }
+
+ return( NtStatus );
+}
+
+
+NTSTATUS
+SamrSetMemberAttributesOfGroup(
+ IN SAMPR_HANDLE GroupHandle,
+ IN ULONG MemberId,
+ IN ULONG Attributes
+ )
+
+/*++
+
+Routine Description:
+
+
+ This routine modifies the group attributes of a member of the group.
+
+
+
+
+Parameters:
+
+ GroupHandle - The handle of an opened group to operate on.
+
+ MemberId - Contains the relative ID of member whose attributes
+ are to be modified.
+
+ Attributes - The group attributes to set for the member. These
+ attributes must not conflict with the attributes of the group
+ as a whole. See SamAddMemberToGroup() for more information
+ on compatible attribute settings.
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no addition entries.
+
+ STATUS_INVALID_INFO_CLASS - The class provided was invalid.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_NO_SUCH_USER - The user specified does not exist.
+
+ STATUS_MEMBER_NOT_IN_GROUP - Indicates the specified relative ID
+ is not a member of the group.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+
+--*/
+
+{
+
+ NTSTATUS NtStatus, TmpStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG ObjectRid;
+
+
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(GroupHandle);
+ ObjectRid = AccountContext->TypeBody.Group.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ GROUP_ADD_MEMBER,
+ SampGroupObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Update user object
+ //
+
+ NtStatus = SampSetGroupAttributesOfUser(
+ ObjectRid,
+ Attributes,
+ MemberId
+ );
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+ }
+
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChange,
+ SecurityDbObjectSamGroup,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+
+ return(NtStatus);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Internal Services Available For Use in Other SAM Modules //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampAddUserToGroup(
+ IN ULONG GroupRid,
+ IN ULONG UserRid
+ )
+
+/*++
+
+Routine Description:
+
+ This service is expected to be used when a user is being created.
+ It is used to add that user as a member to a specified group.
+ This is done by simply adding the user's ID to the list of IDs
+ in the MEMBERS sub-key of the the specified group.
+
+
+ The caller of this service is expected to be in the middle of a
+ RXACT transaction. This service simply adds some actions to that
+ RXACT transaction.
+
+
+ If the group is the DOMAIN_ADMIN group, the caller is responsible
+ for updating the ActiveAdminCount (if appropriate).
+
+
+
+Arguments:
+
+ GroupRid - The RID of the group the user is to be made a member of.
+
+ UserRid - The RID of the user being added as a new member.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user has been added.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT GroupContext;
+
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ GroupRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &GroupContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the user to the group member list.
+ //
+
+ NtStatus = SampAddAccountToGroupMembers(
+ GroupContext,
+ UserRid
+ );
+
+ //
+ // Write out any changes to the group account
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampStoreObjectAttributes(GroupContext, FALSE);
+ }
+
+ //
+ // Clean up the group context
+ //
+
+ SampDeleteContext(GroupContext);
+
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampRemoveUserFromGroup(
+ IN ULONG GroupRid,
+ IN ULONG UserRid
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to Remove a user from a specified group.
+ This is done by simply Removing the user's ID From the list of IDs
+ in the MEMBERS sub-key of the the specified group.
+
+ It is the caller's responsibility to know that the user is, in fact,
+ currently a member of the group.
+
+
+ The caller of this service is expected to be in the middle of a
+ RXACT transaction. This service simply adds some actions to that
+ RXACT transaction.
+
+
+ If the group is the DOMAIN_ADMIN group, the caller is responsible
+ for updating the ActiveAdminCount (if appropriate).
+
+
+
+Arguments:
+
+ GroupRid - The RID of the group the user is to be removed from.
+
+ UserRid - The RID of the user being Removed.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user has been Removed.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT GroupContext;
+
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ GroupRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &GroupContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove the user from the group member list.
+ //
+
+ NtStatus = SampRemoveAccountFromGroupMembers(
+ GroupContext,
+ UserRid
+ );
+
+ //
+ // Write out any changes to the group account
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampStoreObjectAttributes(GroupContext, FALSE);
+ }
+
+ //
+ // Clean up the group context
+ //
+
+ SampDeleteContext(GroupContext);
+
+ }
+
+ return(NtStatus);
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Private to this file //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampRetrieveGroupV1Fixed(
+ IN PSAMP_OBJECT GroupContext,
+ IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves the V1 fixed length information related to
+ a specified group.
+
+
+Arguments:
+
+ GroupRootKey - Root key for the group whose V1_FIXED information is
+ to be retrieved.
+
+ V1Fixed - Is a buffer into which the information is to be returned.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampGetFixedAttributes()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PVOID FixedData;
+
+
+ NtStatus = SampGetFixedAttributes(
+ GroupContext,
+ FALSE, // Don't make copy
+ &FixedData
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Copy data into return buffer
+ // *V1Fixed = *((PSAMP_V1_0A_FIXED_LENGTH_GROUP)FixedData);
+ //
+
+ RtlMoveMemory(
+ V1Fixed,
+ FixedData,
+ sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP)
+ );
+ }
+
+
+ return( NtStatus );
+
+}
+
+
+
+
+NTSTATUS
+SampReplaceGroupV1Fixed(
+ IN PSAMP_OBJECT Context,
+ IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed
+ )
+
+/*++
+
+Routine Description:
+
+ This service replaces the current V1 fixed length information related to
+ a specified group.
+
+ The change is made to the in-memory object data only.
+
+
+Arguments:
+
+ Context - Points to the account context whose V1_FIXED information is
+ to be replaced.
+
+ V1Fixed - Is a buffer containing the new V1_FIXED information.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been replaced.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampSetFixedAttributes()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ NtStatus = SampSetFixedAttributes(
+ Context,
+ (PVOID)V1Fixed
+ );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampRetrieveGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN PULONG MemberCount,
+ IN PULONG *Members OPTIONAL
+ )
+
+/*++
+Routine Description:
+
+ This service retrieves the number of members in a group. If desired,
+ it will also retrieve an array of RIDs of the members of the group.
+
+
+Arguments:
+
+ GroupContext - Group context block
+
+ MemberCount - Receives the number of members currently in the group.
+
+ Members - (Optional) Receives a pointer to a buffer containing an array
+ of member Relative IDs. If this value is NULL, then this information
+ is not returned. The returned buffer is allocated using
+ MIDL_user_allocate() and must be freed using MIDL_user_free() when
+ no longer needed.
+
+ The Members array returned always includes space for one new entry.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ string to be returned in.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampGetUlongArrayAttribute()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PULONG Array;
+ ULONG LengthCount;
+
+ NtStatus = SampGetUlongArrayAttribute(
+ GroupContext,
+ SAMP_GROUP_MEMBERS,
+ FALSE, // Reference data directly
+ &Array,
+ MemberCount,
+ &LengthCount
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Fill in return info
+ //
+
+ if (Members != NULL) {
+
+ //
+ // Allocate a buffer large enough to hold the existing membership
+ // data plus one.
+ //
+
+ ULONG BytesNow = (*MemberCount) * sizeof(ULONG);
+ ULONG BytesRequired = BytesNow + sizeof(ULONG);
+
+ *Members = MIDL_user_allocate(BytesRequired);
+
+ if (*Members == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ RtlCopyMemory(*Members, Array, BytesNow);
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampReplaceGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG MemberCount,
+ IN PULONG Members
+ )
+
+/*++
+Routine Description:
+
+ This service sets the members of a group.
+
+ The information is updated in the in-memory copy of the group's data only.
+ The data is not written out by this routine.
+
+
+Arguments:
+
+ GroupContext - The group whose member list is to be replaced
+
+ MemberCount - The number of new members
+
+ Membership - A pointer to a buffer containing an array of account rids.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been set.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampSetUlongArrayAttribute()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ PULONG LocalMembers;
+ ULONG LengthCount;
+ ULONG SmallListGrowIncrement = 25;
+ ULONG BigListGrowIncrement = 250;
+ ULONG BigListSize = 800;
+
+ //
+ // These group user lists can get pretty big, and grow many
+ // times by a very small amount as each user is added. The
+ // registry doesn't like that kind of behaviour (it tends to
+ // eat up free space something fierce) so we'll try to pad
+ // out the list size.
+ //
+
+ if ( MemberCount < BigListSize ) {
+
+ //
+ // If less than 800 users, make the list size the smallest
+ // possible multiple of 25 users.
+ //
+
+ LengthCount = ( ( MemberCount + SmallListGrowIncrement - 1 ) /
+ SmallListGrowIncrement ) *
+ SmallListGrowIncrement;
+
+ } else {
+
+ //
+ // If 800 users or more, make the list size the smallest
+ // possible multiple of 250 users.
+ //
+
+ LengthCount = ( ( MemberCount + BigListGrowIncrement - 1 ) /
+ BigListGrowIncrement ) *
+ BigListGrowIncrement;
+ }
+
+ ASSERT( LengthCount >= MemberCount );
+
+ if ( LengthCount == MemberCount ) {
+
+ //
+ // Just the right size. Use the buffer that was passed in.
+ //
+
+ LocalMembers = Members;
+
+ } else {
+
+ //
+ // We need to allocate a larger buffer before we set the attribute.
+ //
+
+ LocalMembers = MIDL_user_allocate( LengthCount * sizeof(ULONG));
+
+ if ( LocalMembers == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Copy the old buffer to the larger buffer, and zero out the
+ // empty stuff at the end.
+ //
+
+ RtlCopyMemory( LocalMembers, Members, MemberCount * sizeof(ULONG));
+
+ RtlZeroMemory(
+ (LocalMembers + MemberCount),
+ (LengthCount - MemberCount) * sizeof(ULONG)
+ );
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampSetUlongArrayAttribute(
+ GroupContext,
+ SAMP_GROUP_MEMBERS,
+ LocalMembers,
+ MemberCount,
+ LengthCount
+ );
+ }
+
+ if ( LocalMembers != Members ) {
+
+ //
+ // We must have allocated a larger local buffer, so free it.
+ //
+
+ MIDL_user_free( LocalMembers );
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampDeleteGroupKeys(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+Routine Description:
+
+ This service deletes all registry keys related to a group object.
+
+
+Arguments:
+
+ Context - Points to the group context whose registry keys are
+ being deleted.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG Rid;
+ UNICODE_STRING AccountName, KeyName;
+
+
+ Rid = Context->TypeBody.Group.Rid;
+
+
+ //
+ // Groups are arranged as follows:
+ //
+ // +-- Groups [Count]
+ // ---+--
+ // +-- Names
+ // | --+--
+ // | +-- (GroupName) [GroupRid,]
+ // |
+ // +-- (GroupRid) [Revision,SecurityDescriptor]
+ // ---+-----
+ // +-- V1_Fixed [,SAM_V1_0A_FIXED_LENGTH_GROUP]
+ // +-- Name [,Name]
+ // +-- AdminComment [,unicode string]
+ // +-- Members [Count,(Member0Rid, (...), MemberX-1Rid)]
+ //
+ // This all needs to be deleted from the bottom up.
+ //
+
+
+ //
+ // Decrement the group count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampGroupObjectType, FALSE );
+
+
+
+
+ //
+ // Delete the registry key that has the group's name to RID mapping.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the name
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ &AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ &AccountName
+ );
+
+ SampFreeUnicodeString( &AccountName );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+ }
+
+
+
+
+ //
+ // Delete the attribute keys
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampDeleteAttributeKeys(
+ Context
+ );
+ }
+
+
+
+
+ //
+ // Delete the RID key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ Rid,
+ NULL
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+
+
+NTSTATUS
+SampChangeGroupAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ )
+
+/*++
+Routine Description:
+
+ This routine changes the account name of a group account.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ Context - Points to the group context whose name is to be changed.
+
+ NewAccountName - New name to give this account
+
+ OldAccountName - old name is returned here. The buffer should be freed
+ by calling MIDL_user_free.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ SampGetUnicodeStringAttribute()
+ SampSetUnicodeStringAttribute()
+ SampValidateAccountNameChange()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+
+ /////////////////////////////////////////////////////////////
+ // There are two copies of the name of each account. //
+ // one is under the DOMAIN\(domainName)\GROUP\NAMES key, //
+ // one is the value of the //
+ // DOMAIN\(DomainName)\GROUP\(rid)\NAME key //
+ /////////////////////////////////////////////////////////////
+
+ //
+ // Get the current name so we can delete the old Name->Rid
+ // mapping key.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ OldAccountName
+ );
+
+ //
+ // Make sure the name is valid and not already in use
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampValidateAccountNameChange(
+ NewAccountName,
+ OldAccountName
+ );
+
+ //
+ // Delete the old name key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ OldAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+
+ }
+
+ //
+ //
+ // Create the new Name->Rid mapping key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ NewAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ULONG GroupRid = Context->TypeBody.Group.Rid;
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ GroupRid,
+ (PVOID)NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+
+
+
+
+ //
+ // replace the account's name
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ SAMP_GROUP_NAME,
+ NewAccountName
+ );
+ }
+
+ //
+ // Free up the old account name if we failed
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString(OldAccountName);
+ }
+
+ }
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampAddAccountToGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG AccountRid
+ )
+
+/*++
+
+Routine Description:
+
+ This service adds the specified account rid to the member list
+ for the specified group. This is a low-level function that
+ simply edits the member attribute of the group context passed.
+
+Arguments:
+
+ GroupContext - The group whose member list will be modified
+
+ AccountRid - The RID of the account being added as a new member.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The account has been added.
+
+ STATUS_MEMBER_IN_GROUP - The account is already a member
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG MemberCount, i;
+ PULONG MemberArray;
+
+ //
+ // Get the existing member list
+ // Note that the member array always includes space
+ // for one new member
+ //
+
+ NtStatus = SampRetrieveGroupMembers(
+ GroupContext,
+ &MemberCount,
+ &MemberArray
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Fail if the account is already a member
+ //
+
+ for (i = 0; i<MemberCount ; i++ ) {
+
+ if ( MemberArray[i] == AccountRid ) {
+
+ ASSERT(FALSE);
+ NtStatus = STATUS_MEMBER_IN_GROUP;
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the user's RID to the end of the list
+ //
+
+ MemberArray[MemberCount] = AccountRid;
+ MemberCount += 1;
+
+ //
+ // Set the new group member list
+ //
+
+ NtStatus = SampReplaceGroupMembers(
+ GroupContext,
+ MemberCount,
+ MemberArray
+ );
+
+
+ //
+ // audit this, if necessary.
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(GroupContext->DomainIndex)) {
+
+ PSAMP_DEFINED_DOMAINS Domain;
+ UNICODE_STRING NameString;
+ SAMP_OBJECT_TYPE ObjectType;
+ NTSTATUS Status;
+
+ Domain = &SampDefinedDomains[ GroupContext->DomainIndex ];
+
+ Status = SampLookupAccountName(
+ GroupContext->TypeBody.Alias.Rid,
+ &NameString,
+ &ObjectType
+ );
+
+ if ( !NT_SUCCESS( Status )) {
+ RtlInitUnicodeString( &NameString, L"-" );
+ }
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_GLOBAL_GROUP_ADD, // AuditId
+ Domain->Sid, // Domain SID
+ &AccountRid, // Member Rid
+ NULL, // Member Sid (not used)
+ &NameString, // Account Name
+ &Domain->ExternalName, // Domain
+ &GroupContext->TypeBody.Group.Rid, // Account Rid
+ NULL // Privileges used
+ );
+
+ if ( NT_SUCCESS( Status )) {
+ MIDL_user_free( NameString.Buffer );
+ }
+ }
+ }
+
+ //
+ // Free up the member list
+ //
+
+ MIDL_user_free( MemberArray );
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampRemoveAccountFromGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN ULONG AccountRid
+ )
+
+/*++
+
+Routine Description:
+
+ This service removes the specified account rid from the member list
+ for the specified group. This is a low-level function that
+ simply edits the member attribute of the group context passed.
+
+Arguments:
+
+ GroupContext - The group whose member list will be modified
+
+ AccountRid - The RID of the account being added as a new member.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The account has been added.
+
+ STATUS_MEMBER_NOT_IN_GROUP - The account is not a member of the group.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG MemberCount, i;
+ PULONG MemberArray;
+
+ //
+ // Get the existing member list
+ //
+
+ NtStatus = SampRetrieveGroupMembers(
+ GroupContext,
+ &MemberCount,
+ &MemberArray
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Remove the account
+ //
+
+ NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
+
+ for (i = 0; i<MemberCount ; i++ ) {
+
+ if (MemberArray[i] == AccountRid) {
+
+ MemberArray[i] = MemberArray[MemberCount-1];
+ MemberCount -=1;
+
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Set the new group member list
+ //
+
+ NtStatus = SampReplaceGroupMembers(
+ GroupContext,
+ MemberCount,
+ MemberArray
+ );
+
+ //
+ // audit this, if necessary.
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(GroupContext->DomainIndex)) {
+
+ PSAMP_DEFINED_DOMAINS Domain;
+ UNICODE_STRING NameString;
+ SAMP_OBJECT_TYPE ObjectType;
+ NTSTATUS Status;
+
+ Status = SampLookupAccountName(
+ GroupContext->TypeBody.Alias.Rid,
+ &NameString,
+ &ObjectType
+ );
+
+ if ( !NT_SUCCESS( Status )) {
+ RtlInitUnicodeString( &NameString, L"-" );
+ }
+ Domain = &SampDefinedDomains[ GroupContext->DomainIndex ];
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_GLOBAL_GROUP_REM, // AuditId
+ Domain->Sid, // Domain SID
+ &AccountRid, // Member Rid
+ NULL, // Member Sid (not used)
+ &NameString, // Account Name
+ &Domain->ExternalName, // Domain
+ &GroupContext->TypeBody.Group.Rid, // Account Rid
+ NULL // Privileges used
+ );
+
+
+ if ( NT_SUCCESS( Status )) {
+ MIDL_user_free( NameString.Buffer );
+ }
+ }
+ }
+
+ //
+ // Free up the member list
+ //
+
+ MIDL_user_free( MemberArray );
+ }
+
+ return(NtStatus);
+}
diff --git a/private/newsam/server/makefile b/private/newsam/server/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/newsam/server/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/newsam/server/makefile.inc b/private/newsam/server/makefile.inc
new file mode 100644
index 000000000..14e4fd9d6
--- /dev/null
+++ b/private/newsam/server/makefile.inc
@@ -0,0 +1,2 @@
+sampmsgs.h msg00001.bin: sampmsgs.mc
+ mc -v sampmsgs.mc
diff --git a/private/newsam/server/notify.c b/private/newsam/server/notify.c
new file mode 100644
index 000000000..d6779a533
--- /dev/null
+++ b/private/newsam/server/notify.c
@@ -0,0 +1,581 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ notify.c
+
+Abstract:
+
+ This file contains services which load notification packages and call
+ them when passwords are changed using the SamChangePasswordUser2 API.
+
+
+Author:
+
+ Mike Swift (MikeSw) 30-December-1994
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Private prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SampConfigurePackage(
+ IN PWSTR ValueName,
+ IN ULONG ValueType,
+ IN PVOID ValueData,
+ IN ULONG ValueLength,
+ IN PVOID Context,
+ IN PVOID EntryContext
+ );
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service data and types //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+typedef struct _SAMP_NOTIFICATION_PACKAGE {
+ struct _SAMP_NOTIFICATION_PACKAGE * Next;
+ UNICODE_STRING PackageName;
+ PSAM_PASSWORD_NOTIFICATION_ROUTINE PasswordNotificationRoutine;
+ PSAM_DELTA_NOTIFICATION_ROUTINE DeltaNotificationRoutine;
+ PSAM_PASSWORD_FILTER_ROUTINE PasswordFilterRoutine;
+} SAMP_NOTIFICATION_PACKAGE, *PSAMP_NOTIFICATION_PACKAGE;
+
+PSAMP_NOTIFICATION_PACKAGE SampNotificationPackages = NULL;
+
+RTL_QUERY_REGISTRY_TABLE SampRegistryConfigTable [] = {
+ {SampConfigurePackage, 0, L"Notification Packages",
+ NULL, REG_NONE, NULL, 0},
+ {NULL, 0, NULL,
+ NULL, REG_NONE, NULL, 0}
+ };
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampConfigurePackage(
+ IN PWSTR ValueName,
+ IN ULONG ValueType,
+ IN PVOID ValueData,
+ IN ULONG ValueLength,
+ IN PVOID Context,
+ IN PVOID EntryContext
+ )
+/*++
+
+Routine Description:
+
+ This routine loads a notification package by loading its DLL and getting
+ the address of the notification routine.
+
+Arguments:
+ ValueName - Contains the name of the registry value, ignored.
+ ValueType - Contains type of Value, must be REG_SZ.
+ ValueData - Contains the package name null-terminated string.
+ ValueLength - Length of package name and null terminator, in bytes.
+ Context - Passed from caller of RtlQueryRegistryValues, ignored
+ EntryContext - Ignored
+
+
+
+Return Value:
+
+--*/
+{
+ UNICODE_STRING PackageName;
+ STRING NotificationRoutineName;
+ PSAMP_NOTIFICATION_PACKAGE NewPackage = NULL;
+ PVOID ModuleHandle = NULL;
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ ULONG PackageSize;
+ PSAM_INIT_NOTIFICATION_ROUTINE InitNotificationRoutine = NULL;
+
+ //
+ // Make sure we got a string.
+ //
+
+ if (ValueType != REG_SZ) {
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Build the package name from the value data.
+ //
+
+ PackageName.Buffer = (LPWSTR) ValueData;
+ PackageName.Length = (USHORT) (ValueLength - sizeof( UNICODE_NULL ));
+ PackageName.MaximumLength = (USHORT) ValueLength;
+
+ //
+ // Build the package structure.
+ //
+
+ PackageSize = sizeof(SAMP_NOTIFICATION_PACKAGE) + ValueLength;
+ NewPackage = (PSAMP_NOTIFICATION_PACKAGE) RtlAllocateHeap(
+ RtlProcessHeap(),
+ 0,
+ PackageSize
+ );
+ if (NewPackage == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlZeroMemory(
+ NewPackage,
+ PackageSize
+ );
+
+ //
+ // Copy in the package name.
+ //
+
+ NewPackage->PackageName = PackageName;
+
+ NewPackage->PackageName.Buffer = (LPWSTR) (NewPackage + 1);
+
+
+ RtlCopyUnicodeString(
+ &NewPackage->PackageName,
+ &PackageName
+ );
+
+ //
+ // Load the notification library.
+ //
+
+ NtStatus = LdrLoadDll(
+ NULL,
+ NULL,
+ &PackageName,
+ &ModuleHandle
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RtlInitString(
+ &NotificationRoutineName,
+ SAM_INIT_NOTIFICATION_ROUTINE
+ );
+
+ NtStatus = LdrGetProcedureAddress(
+ ModuleHandle,
+ &NotificationRoutineName,
+ 0,
+ (PVOID *) &InitNotificationRoutine
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ ASSERT(InitNotificationRoutine != NULL);
+
+ //
+ // Call the init routine. If it returns false, unload this
+ // DLL and continue on.
+ //
+
+ try {
+
+ if (!InitNotificationRoutine()) {
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+ } except (EXCEPTION_EXECUTE_HANDLER) {
+ KdPrint(("Exception thrown in Password Notification Routine: 0x%x (%d)\n",
+ GetExceptionCode(),GetExceptionCode() ));
+ NtStatus = STATUS_ACCESS_VIOLATION;
+ }
+ } else {
+ //
+ // This call isn't required, so reset the status to
+ // STATUS_SUCCESS.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RtlInitString(
+ &NotificationRoutineName,
+ SAM_PASSWORD_CHANGE_NOTIFY_ROUTINE
+ );
+
+ (void) LdrGetProcedureAddress(
+ ModuleHandle,
+ &NotificationRoutineName,
+ 0,
+ (PVOID *) &NewPackage->PasswordNotificationRoutine
+ );
+
+ RtlInitString(
+ &NotificationRoutineName,
+ SAM_DELTA_NOTIFY_ROUTINE
+ );
+
+ (void) LdrGetProcedureAddress(
+ ModuleHandle,
+ &NotificationRoutineName,
+ 0,
+ (PVOID *) &NewPackage->DeltaNotificationRoutine
+ );
+
+ RtlInitString(
+ &NotificationRoutineName,
+ SAM_PASSWORD_FILTER_ROUTINE
+ );
+
+ (void) LdrGetProcedureAddress(
+ ModuleHandle,
+ &NotificationRoutineName,
+ 0,
+ (PVOID *) &NewPackage->PasswordFilterRoutine
+ );
+
+ }
+
+
+ //
+ // At least one of the two functions must be present
+ //
+
+ if ((NewPackage->PasswordNotificationRoutine == NULL) &&
+ (NewPackage->DeltaNotificationRoutine == NULL) &&
+ (NewPackage->PasswordFilterRoutine == NULL)) {
+
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+
+ //
+ // If all this succeeded, add the routine to the global list.
+ //
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ NewPackage->Next = SampNotificationPackages;
+ SampNotificationPackages = NewPackage;
+
+ //
+ // Notify the auditing code to record this event.
+ //
+
+ LsaIAuditNotifyPackageLoad(
+ &PackageName
+ );
+
+
+ } else {
+
+ //
+ // Otherwise delete the entry.
+ //
+
+ RtlFreeHeap(
+ RtlProcessHeap(),
+ 0,
+ NewPackage
+ );
+
+ if (ModuleHandle != NULL) {
+ (VOID) LdrUnloadDll( ModuleHandle );
+ }
+ }
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampLoadNotificationPackages(
+ )
+/*++
+
+Routine Description:
+
+ This routine retrieves the list of packages to be notified during
+ password change.
+
+Arguments:
+
+ none
+
+
+Return Value:
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ NtStatus = RtlQueryRegistryValues(
+ RTL_REGISTRY_CONTROL,
+ L"Lsa",
+ SampRegistryConfigTable,
+ NULL, // no context
+ NULL // no enviroment
+ );
+ //
+ // Always return STATUS_SUCCESS so we don't block the system from
+ // booting.
+ //
+
+
+ return(STATUS_SUCCESS);
+}
+
+
+NTSTATUS
+SampPasswordChangeNotify(
+ IN PUNICODE_STRING UserName,
+ IN ULONG RelativeId,
+ IN PUNICODE_STRING NewPassword
+ )
+/*++
+
+Routine Description:
+
+ This routine notifies packages of a password change. It requires that
+ the user no longer be locked so that other packages can write to the
+ user parameters field.
+
+
+Arguments:
+
+ UserName - Name of user whose password changed
+
+ RelativeId - RID of the user whose password changed
+
+ NewPassword - Cleartext new password for the user
+
+Return Value:
+
+ STATUS_SUCCESS only - errors from packages are ignored.
+
+--*/
+{
+ PSAMP_NOTIFICATION_PACKAGE Package;
+ NTSTATUS NtStatus;
+
+ Package = SampNotificationPackages;
+
+ while (Package != NULL) {
+ if ( Package->PasswordNotificationRoutine != NULL ) {
+ try {
+ NtStatus = Package->PasswordNotificationRoutine(
+ UserName,
+ RelativeId,
+ NewPassword
+ );
+ } except (EXCEPTION_EXECUTE_HANDLER) {
+ KdPrint(("Exception thrown in Password Notification Routine: 0x%x (%d)\n",
+ GetExceptionCode(),GetExceptionCode() ));
+ NtStatus = STATUS_ACCESS_VIOLATION;
+ }
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("Package %wZ failed to accept password change for user %wZ\n",
+ &Package->PackageName, UserName ));
+ }
+ }
+
+ Package = Package->Next;
+
+
+ }
+ return(STATUS_SUCCESS);
+}
+
+
+NTSTATUS
+SampPasswordChangeFilter(
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING FullName,
+ IN PUNICODE_STRING NewPassword,
+ IN BOOLEAN SetOperation
+ )
+/*++
+
+Routine Description:
+
+ This routine notifies packages of a password change. It requires that
+ the user no longer be locked so that other packages can write to the
+ user parameters field.
+
+
+Arguments:
+
+ UserName - Name of user whose password changed
+
+ FullName - Full name of the user whose password changed
+
+ NewPassword - Cleartext new password for the user
+
+ SetOperation - TRUE if the password was SET rather than CHANGED
+
+Return Value:
+
+ Status codes from the notification packages.
+
+--*/
+{
+ PSAMP_NOTIFICATION_PACKAGE Package;
+ BOOLEAN Result;
+ NTSTATUS Status;
+
+ Package = SampNotificationPackages;
+
+ while (Package != NULL) {
+ if ( Package->PasswordFilterRoutine != NULL ) {
+ try {
+ Result = Package->PasswordFilterRoutine(
+ UserName,
+ FullName,
+ NewPassword,
+ SetOperation
+ );
+ if (!Result)
+ {
+ Status = STATUS_PASSWORD_RESTRICTION;
+ }
+ } except (EXCEPTION_EXECUTE_HANDLER) {
+ KdPrint(("Exception thrown in Password Notification Routine: 0x%x (%d)\n",
+ GetExceptionCode(),GetExceptionCode() ));
+
+ //
+ // Set result to FALSE so the change fails.
+ //
+
+ Status = STATUS_ACCESS_VIOLATION;
+ Result = FALSE;
+ }
+
+ if (!Result) {
+ KdPrint(("Package %wZ failed to accept password change for user %wZ: 0x%x\n",
+ &Package->PackageName, UserName, Status));
+ return(Status);
+ }
+
+ }
+
+ Package = Package->Next;
+
+
+ }
+ return(STATUS_SUCCESS);
+}
+
+
+NTSTATUS
+SampDeltaChangeNotify(
+ IN PSID DomainSid,
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PUNICODE_STRING ObjectName,
+ IN PLARGE_INTEGER ModifiedCount,
+ IN PSAM_DELTA_DATA DeltaData OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ This routine notifies packages of a change to the SAM database. The
+ database is still locked for write access so it requires that nothing
+ it calls try to lock the database.
+
+Arguments:
+
+ DomainSid - SID of domain for delta
+
+ DeltaType - Type of delta (change, add ,delete)
+
+ ObjectType - Type of object changed (user, alias, group ...)
+
+ ObjectRid - ID of object changed
+
+ ObjectName - Name of object changed
+
+ ModifiedCount - Serial number of database after this last change
+
+ DeltaData - Data describing the exact modification.
+
+Return Value:
+
+ STATUS_SUCCESS only - errors from packages are ignored.
+
+--*/
+{
+ PSAMP_NOTIFICATION_PACKAGE Package;
+ NTSTATUS NtStatus;
+
+ Package = SampNotificationPackages;
+
+ while (Package != NULL) {
+
+ if (Package->DeltaNotificationRoutine != NULL) {
+
+ try {
+ NtStatus = Package->DeltaNotificationRoutine(
+ DomainSid,
+ DeltaType,
+ ObjectType,
+ ObjectRid,
+ ObjectName,
+ ModifiedCount,
+ DeltaData
+ );
+
+ } except (EXCEPTION_EXECUTE_HANDLER) {
+ KdPrint(("Exception thrown in Password Notification Routine: 0x%x (%d)\n",
+ GetExceptionCode(),GetExceptionCode() ));
+ NtStatus = STATUS_ACCESS_VIOLATION;
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("Package %wZ failed to accept deltachange for object %wZ\n",
+ &Package->PackageName, ObjectName ));
+ }
+ }
+
+ Package = Package->Next;
+
+
+ }
+ return(STATUS_SUCCESS);
+}
+
diff --git a/private/newsam/server/oldstub.c b/private/newsam/server/oldstub.c
new file mode 100644
index 000000000..4011d5b87
--- /dev/null
+++ b/private/newsam/server/oldstub.c
@@ -0,0 +1,659 @@
+/*++
+
+Copyright (c) 1994 Microsoft Corporation
+
+Module Name:
+
+ oldstub.c
+
+Abstract:
+
+ This file contains functions generated by midl v1.0. These
+ function were designed to only be called by the stubs, but
+ these paticular functions are called by user code. This
+ file is needed in order to compile sam with midl v2.0 which
+ doesn't generated these paticular functions anymore.
+
+Author:
+
+ Mario Goertzel (MarioGo) Jan 10, 1994
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+#include <samrpc.h>
+
+/* routine that frees graph for struct _RPC_UNICODE_STRING */
+void _fgs__RPC_UNICODE_STRING (RPC_UNICODE_STRING * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_RID_ENUMERATION */
+void _fgs__SAMPR_RID_ENUMERATION (SAMPR_RID_ENUMERATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Name);
+ }
+
+/* routine that frees graph for struct _SAMPR_ENUMERATION_BUFFER */
+void _fgs__SAMPR_ENUMERATION_BUFFER (SAMPR_ENUMERATION_BUFFER * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ {
+ unsigned long _sym9;
+ for (_sym9 = 0; _sym9 < (unsigned long )(0 + _source->EntriesRead); _sym9++)
+ {
+ _fgs__SAMPR_RID_ENUMERATION ((SAMPR_RID_ENUMERATION *)&_source->Buffer[_sym9]);
+ }
+ }
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_SR_SECURITY_DESCRIPTOR */
+void _fgs__SAMPR_SR_SECURITY_DESCRIPTOR (SAMPR_SR_SECURITY_DESCRIPTOR * _source)
+ {
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_GET_GROUPS_BUFFER */
+void _fgs__SAMPR_GET_GROUPS_BUFFER (SAMPR_GET_GROUPS_BUFFER * _source)
+ {
+ if (_source->Groups !=0)
+ {
+ MIDL_user_free((void *)(_source->Groups));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_GET_MEMBERS_BUFFER */
+void _fgs__SAMPR_GET_MEMBERS_BUFFER (SAMPR_GET_MEMBERS_BUFFER * _source)
+ {
+ if (_source->Members !=0)
+ {
+ MIDL_user_free((void *)(_source->Members));
+ }
+ if (_source->Attributes !=0)
+ {
+ MIDL_user_free((void *)(_source->Attributes));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_LOGON_HOURS */
+void _fgs__SAMPR_LOGON_HOURS (SAMPR_LOGON_HOURS * _source)
+ {
+ if (_source->LogonHours !=0)
+ {
+ MIDL_user_free((void *)(_source->LogonHours));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_ULONG_ARRAY */
+void _fgs__SAMPR_ULONG_ARRAY (SAMPR_ULONG_ARRAY * _source)
+ {
+ if (_source->Element !=0)
+ {
+ MIDL_user_free((void *)(_source->Element));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_SID_INFORMATION */
+void _fgs__SAMPR_SID_INFORMATION (SAMPR_SID_INFORMATION * _source)
+ {
+ if (_source->SidPointer !=0)
+ {
+ MIDL_user_free((void *)(_source->SidPointer));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_PSID_ARRAY */
+void _fgs__SAMPR_PSID_ARRAY (SAMPR_PSID_ARRAY * _source)
+ {
+ if (_source->Sids !=0)
+ {
+ MIDL_user_free((void *)(_source->Sids));
+ }
+ }
+
+
+/* routine that frees graph for struct _SAMPR_RETURNED_USTRING_ARRAY */
+void _fgs__SAMPR_RETURNED_USTRING_ARRAY (SAMPR_RETURNED_USTRING_ARRAY * _source)
+ {
+ if (_source->Element !=0)
+ {
+ {
+ unsigned long _sym26;
+ for (_sym26 = 0; _sym26 < (unsigned long )(0 + _source->Count); _sym26++)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Element[_sym26]);
+ }
+ }
+ MIDL_user_free((void *)(_source->Element));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_GENERAL_INFORMATION */
+void _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION (SAMPR_DOMAIN_GENERAL_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->OemInformation);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->DomainName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ReplicaSourceNodeName);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_GENERAL_INFORMATION2 */
+void _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION2 (SAMPR_DOMAIN_GENERAL_INFORMATION2 * _source)
+ {
+ _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION ((SAMPR_DOMAIN_GENERAL_INFORMATION *)&_source->I1);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_OEM_INFORMATION */
+void _fgs__SAMPR_DOMAIN_OEM_INFORMATION (SAMPR_DOMAIN_OEM_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->OemInformation);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_NAME_INFORMATION */
+void _fgs__SAMPR_DOMAIN_NAME_INFORMATION (SAMPR_DOMAIN_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->DomainName);
+ }
+
+/* routine that frees graph for struct SAMPR_DOMAIN_REPLICATION_INFORMATION */
+void _fgs_SAMPR_DOMAIN_REPLICATION_INFORMATION (SAMPR_DOMAIN_REPLICATION_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ReplicaSourceNodeName);
+ }
+
+/* routine that frees graph for union _SAMPR_DOMAIN_INFO_BUFFER */
+void _fgu__SAMPR_DOMAIN_INFO_BUFFER (SAMPR_DOMAIN_INFO_BUFFER * _source, DOMAIN_INFORMATION_CLASS _branch)
+ {
+ switch (_branch)
+ {
+ case DomainPasswordInformation :
+ {
+ break;
+ }
+ case DomainGeneralInformation :
+ {
+ _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION ((SAMPR_DOMAIN_GENERAL_INFORMATION *)&_source->General);
+ break;
+ }
+ case DomainLogoffInformation :
+ {
+ break;
+ }
+ case DomainOemInformation :
+ {
+ _fgs__SAMPR_DOMAIN_OEM_INFORMATION ((SAMPR_DOMAIN_OEM_INFORMATION *)&_source->Oem);
+ break;
+ }
+ case DomainNameInformation :
+ {
+ _fgs__SAMPR_DOMAIN_NAME_INFORMATION ((SAMPR_DOMAIN_NAME_INFORMATION *)&_source->Name);
+ break;
+ }
+ case DomainServerRoleInformation :
+ {
+ break;
+ }
+ case DomainReplicationInformation :
+ {
+ _fgs_SAMPR_DOMAIN_REPLICATION_INFORMATION ((SAMPR_DOMAIN_REPLICATION_INFORMATION *)&_source->Replication);
+ break;
+ }
+ case DomainModifiedInformation :
+ {
+ break;
+ }
+ case DomainStateInformation :
+ {
+ break;
+ }
+ case DomainGeneralInformation2 :
+ {
+ _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION2 ((SAMPR_DOMAIN_GENERAL_INFORMATION2 *)&_source->General2);
+ break;
+ }
+ case DomainLockoutInformation :
+ {
+ break;
+ }
+ case DomainModifiedInformation2 :
+ {
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_GROUP_GENERAL_INFORMATION */
+void _fgs__SAMPR_GROUP_GENERAL_INFORMATION (SAMPR_GROUP_GENERAL_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Name);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ }
+
+/* routine that frees graph for struct _SAMPR_GROUP_NAME_INFORMATION */
+void _fgs__SAMPR_GROUP_NAME_INFORMATION (SAMPR_GROUP_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Name);
+ }
+
+/* routine that frees graph for struct _SAMPR_GROUP_ADM_COMMENT_INFORMATION */
+void _fgs__SAMPR_GROUP_ADM_COMMENT_INFORMATION (SAMPR_GROUP_ADM_COMMENT_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ }
+
+/* routine that frees graph for union _SAMPR_GROUP_INFO_BUFFER */
+void _fgu__SAMPR_GROUP_INFO_BUFFER (SAMPR_GROUP_INFO_BUFFER * _source, GROUP_INFORMATION_CLASS _branch)
+ {
+ switch (_branch)
+ {
+ case GroupGeneralInformation :
+ {
+ _fgs__SAMPR_GROUP_GENERAL_INFORMATION ((SAMPR_GROUP_GENERAL_INFORMATION *)&_source->General);
+ break;
+ }
+ case GroupNameInformation :
+ {
+ _fgs__SAMPR_GROUP_NAME_INFORMATION ((SAMPR_GROUP_NAME_INFORMATION *)&_source->Name);
+ break;
+ }
+ case GroupAttributeInformation :
+ {
+ break;
+ }
+ case GroupAdminCommentInformation :
+ {
+ _fgs__SAMPR_GROUP_ADM_COMMENT_INFORMATION ((SAMPR_GROUP_ADM_COMMENT_INFORMATION *)&_source->AdminComment);
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_ALIAS_GENERAL_INFORMATION */
+void _fgs__SAMPR_ALIAS_GENERAL_INFORMATION (SAMPR_ALIAS_GENERAL_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Name);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ }
+
+/* routine that frees graph for struct _SAMPR_ALIAS_NAME_INFORMATION */
+void _fgs__SAMPR_ALIAS_NAME_INFORMATION (SAMPR_ALIAS_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Name);
+ }
+
+/* routine that frees graph for struct _SAMPR_ALIAS_ADM_COMMENT_INFORMATION */
+void _fgs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION (SAMPR_ALIAS_ADM_COMMENT_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ }
+
+/* routine that frees graph for union _SAMPR_ALIAS_INFO_BUFFER */
+void _fgu__SAMPR_ALIAS_INFO_BUFFER (SAMPR_ALIAS_INFO_BUFFER * _source, ALIAS_INFORMATION_CLASS _branch)
+ {
+ switch (_branch)
+ {
+ case AliasGeneralInformation :
+ {
+ _fgs__SAMPR_ALIAS_GENERAL_INFORMATION ((SAMPR_ALIAS_GENERAL_INFORMATION *)&_source->General);
+ break;
+ }
+ case AliasNameInformation :
+ {
+ _fgs__SAMPR_ALIAS_NAME_INFORMATION ((SAMPR_ALIAS_NAME_INFORMATION *)&_source->Name);
+ break;
+ }
+ case AliasAdminCommentInformation :
+ {
+ _fgs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION ((SAMPR_ALIAS_ADM_COMMENT_INFORMATION *)&_source->AdminComment);
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_ALL_INFORMATION */
+void _fgs__SAMPR_USER_ALL_INFORMATION (SAMPR_USER_ALL_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectory);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectoryDrive);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ScriptPath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ProfilePath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->WorkStations);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Parameters);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->LmOwfPassword);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->NtOwfPassword);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->PrivateData);
+ _fgs__SAMPR_SR_SECURITY_DESCRIPTOR ((SAMPR_SR_SECURITY_DESCRIPTOR *)&_source->SecurityDescriptor);
+ _fgs__SAMPR_LOGON_HOURS ((SAMPR_LOGON_HOURS *)&_source->LogonHours);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_INTERNAL3_INFORMATION */
+void _fgs__SAMPR_USER_INTERNAL3_INFORMATION (SAMPR_USER_INTERNAL3_INFORMATION * _source)
+ {
+ _fgs__SAMPR_USER_ALL_INFORMATION ((SAMPR_USER_ALL_INFORMATION *)&_source->I1);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_GENERAL_INFORMATION */
+void _fgs__SAMPR_USER_GENERAL_INFORMATION (SAMPR_USER_GENERAL_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserComment);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_PREFERENCES_INFORMATION */
+void _fgs__SAMPR_USER_PREFERENCES_INFORMATION (SAMPR_USER_PREFERENCES_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Reserved1);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_PARAMETERS_INFORMATION */
+void _fgs__SAMPR_USER_PARAMETERS_INFORMATION (SAMPR_USER_PARAMETERS_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Parameters);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_LOGON_INFORMATION */
+void _fgs__SAMPR_USER_LOGON_INFORMATION (SAMPR_USER_LOGON_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectory);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectoryDrive);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ScriptPath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ProfilePath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->WorkStations);
+ _fgs__SAMPR_LOGON_HOURS ((SAMPR_LOGON_HOURS *)&_source->LogonHours);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_ACCOUNT_INFORMATION */
+void _fgs__SAMPR_USER_ACCOUNT_INFORMATION (SAMPR_USER_ACCOUNT_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectory);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectoryDrive);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ScriptPath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ProfilePath);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->WorkStations);
+ _fgs__SAMPR_LOGON_HOURS ((SAMPR_LOGON_HOURS *)&_source->LogonHours);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_A_NAME_INFORMATION */
+void _fgs__SAMPR_USER_A_NAME_INFORMATION (SAMPR_USER_A_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_F_NAME_INFORMATION */
+void _fgs__SAMPR_USER_F_NAME_INFORMATION (SAMPR_USER_F_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_NAME_INFORMATION */
+void _fgs__SAMPR_USER_NAME_INFORMATION (SAMPR_USER_NAME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->UserName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_HOME_INFORMATION */
+void _fgs__SAMPR_USER_HOME_INFORMATION (SAMPR_USER_HOME_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectory);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->HomeDirectoryDrive);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_SCRIPT_INFORMATION */
+void _fgs__SAMPR_USER_SCRIPT_INFORMATION (SAMPR_USER_SCRIPT_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ScriptPath);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_PROFILE_INFORMATION */
+void _fgs__SAMPR_USER_PROFILE_INFORMATION (SAMPR_USER_PROFILE_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->ProfilePath);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_ADMIN_COMMENT_INFORMATION */
+void _fgs__SAMPR_USER_ADMIN_COMMENT_INFORMATION (SAMPR_USER_ADMIN_COMMENT_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_WORKSTATIONS_INFORMATION */
+void _fgs__SAMPR_USER_WORKSTATIONS_INFORMATION (SAMPR_USER_WORKSTATIONS_INFORMATION * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->WorkStations);
+ }
+
+/* routine that frees graph for struct _SAMPR_USER_LOGON_HOURS_INFORMATION */
+void _fgs__SAMPR_USER_LOGON_HOURS_INFORMATION (SAMPR_USER_LOGON_HOURS_INFORMATION * _source)
+ {
+ _fgs__SAMPR_LOGON_HOURS ((SAMPR_LOGON_HOURS *)&_source->LogonHours);
+ }
+
+/* routine that frees graph for union _SAMPR_USER_INFO_BUFFER */
+void _fgu__SAMPR_USER_INFO_BUFFER (SAMPR_USER_INFO_BUFFER * _source, USER_INFORMATION_CLASS _branch)
+ {
+ switch (_branch)
+ {
+ case UserGeneralInformation :
+ {
+ _fgs__SAMPR_USER_GENERAL_INFORMATION ((SAMPR_USER_GENERAL_INFORMATION *)&_source->General);
+ break;
+ }
+ case UserPreferencesInformation :
+ {
+ _fgs__SAMPR_USER_PREFERENCES_INFORMATION ((SAMPR_USER_PREFERENCES_INFORMATION *)&_source->Preferences);
+ break;
+ }
+ case UserLogonInformation :
+ {
+ _fgs__SAMPR_USER_LOGON_INFORMATION ((SAMPR_USER_LOGON_INFORMATION *)&_source->Logon);
+ break;
+ }
+ case UserLogonHoursInformation :
+ {
+ _fgs__SAMPR_USER_LOGON_HOURS_INFORMATION ((SAMPR_USER_LOGON_HOURS_INFORMATION *)&_source->LogonHours);
+ break;
+ }
+ case UserAccountInformation :
+ {
+ _fgs__SAMPR_USER_ACCOUNT_INFORMATION ((SAMPR_USER_ACCOUNT_INFORMATION *)&_source->Account);
+ break;
+ }
+ case UserNameInformation :
+ {
+ _fgs__SAMPR_USER_NAME_INFORMATION ((SAMPR_USER_NAME_INFORMATION *)&_source->Name);
+ break;
+ }
+ case UserAccountNameInformation :
+ {
+ _fgs__SAMPR_USER_A_NAME_INFORMATION ((SAMPR_USER_A_NAME_INFORMATION *)&_source->AccountName);
+ break;
+ }
+ case UserFullNameInformation :
+ {
+ _fgs__SAMPR_USER_F_NAME_INFORMATION ((SAMPR_USER_F_NAME_INFORMATION *)&_source->FullName);
+ break;
+ }
+ case UserPrimaryGroupInformation :
+ {
+ break;
+ }
+ case UserHomeInformation :
+ {
+ _fgs__SAMPR_USER_HOME_INFORMATION ((SAMPR_USER_HOME_INFORMATION *)&_source->Home);
+ break;
+ }
+ case UserScriptInformation :
+ {
+ _fgs__SAMPR_USER_SCRIPT_INFORMATION ((SAMPR_USER_SCRIPT_INFORMATION *)&_source->Script);
+ break;
+ }
+ case UserProfileInformation :
+ {
+ _fgs__SAMPR_USER_PROFILE_INFORMATION ((SAMPR_USER_PROFILE_INFORMATION *)&_source->Profile);
+ break;
+ }
+ case UserAdminCommentInformation :
+ {
+ _fgs__SAMPR_USER_ADMIN_COMMENT_INFORMATION ((SAMPR_USER_ADMIN_COMMENT_INFORMATION *)&_source->AdminComment);
+ break;
+ }
+ case UserWorkStationsInformation :
+ {
+ _fgs__SAMPR_USER_WORKSTATIONS_INFORMATION ((SAMPR_USER_WORKSTATIONS_INFORMATION *)&_source->WorkStations);
+ break;
+ }
+ case UserControlInformation :
+ {
+ break;
+ }
+ case UserExpiresInformation :
+ {
+ break;
+ }
+ case UserInternal1Information :
+ {
+ break;
+ }
+ case UserInternal2Information :
+ {
+ break;
+ }
+ case UserParametersInformation :
+ {
+ _fgs__SAMPR_USER_PARAMETERS_INFORMATION ((SAMPR_USER_PARAMETERS_INFORMATION *)&_source->Parameters);
+ break;
+ }
+ case UserAllInformation :
+ {
+ _fgs__SAMPR_USER_ALL_INFORMATION ((SAMPR_USER_ALL_INFORMATION *)&_source->All);
+ break;
+ }
+ case UserInternal3Information :
+ {
+ _fgs__SAMPR_USER_INTERNAL3_INFORMATION ((SAMPR_USER_INTERNAL3_INFORMATION *)&_source->Internal3);
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_USER */
+void _fgs__SAMPR_DOMAIN_DISPLAY_USER (SAMPR_DOMAIN_DISPLAY_USER * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->LogonName);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->AdminComment);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->FullName);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_MACHINE */
+void _fgs__SAMPR_DOMAIN_DISPLAY_MACHINE (SAMPR_DOMAIN_DISPLAY_MACHINE * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Machine);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Comment);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_GROUP */
+void _fgs__SAMPR_DOMAIN_DISPLAY_GROUP (SAMPR_DOMAIN_DISPLAY_GROUP * _source)
+ {
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Group);
+ _fgs__RPC_UNICODE_STRING ((RPC_UNICODE_STRING *)&_source->Comment);
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_USER_BUFFER */
+void _fgs__SAMPR_DOMAIN_DISPLAY_USER_BUFFER (SAMPR_DOMAIN_DISPLAY_USER_BUFFER * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ {
+ unsigned long _sym32;
+ for (_sym32 = 0; _sym32 < (unsigned long )(0 + _source->EntriesRead); _sym32++)
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_USER ((SAMPR_DOMAIN_DISPLAY_USER *)&_source->Buffer[_sym32]);
+ }
+ }
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER */
+void _fgs__SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER (SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ {
+ unsigned long _sym38;
+ for (_sym38 = 0; _sym38 < (unsigned long )(0 + _source->EntriesRead); _sym38++)
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_MACHINE ((SAMPR_DOMAIN_DISPLAY_MACHINE *)&_source->Buffer[_sym38]);
+ }
+ }
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER */
+void _fgs__SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER (SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ {
+ unsigned long _sym44;
+ for (_sym44 = 0; _sym44 < (unsigned long )(0 + _source->EntriesRead); _sym44++)
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_GROUP ((SAMPR_DOMAIN_DISPLAY_GROUP *)&_source->Buffer[_sym44]);
+ }
+ }
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for union _SAMPR_DISPLAY_INFO_BUFFER */
+void _fgu__SAMPR_DISPLAY_INFO_BUFFER (SAMPR_DISPLAY_INFO_BUFFER * _source, DOMAIN_DISPLAY_INFORMATION _branch)
+ {
+ switch (_branch)
+ {
+ case DomainDisplayUser :
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_USER_BUFFER ((SAMPR_DOMAIN_DISPLAY_USER_BUFFER *)&_source->UserInformation);
+ break;
+ }
+ case DomainDisplayMachine :
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER ((SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER *)&_source->MachineInformation);
+ break;
+ }
+ case DomainDisplayGroup :
+ {
+ _fgs__SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER ((SAMPR_DOMAIN_DISPLAY_GROUP_BUFFER *)&_source->GroupInformation);
+ break;
+ }
+ }
+ }
+
diff --git a/private/newsam/server/regnames.txt b/private/newsam/server/regnames.txt
new file mode 100644
index 000000000..cea74c6b5
--- /dev/null
+++ b/private/newsam/server/regnames.txt
@@ -0,0 +1,260 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ regnames.txt
+
+Abstract:
+
+ This file describes the registry namespace used to back-store the SAM
+ database.
+
+
+Author:
+
+ Jim Kelly (JimK) 3-June-1991
+
+Revision History:
+
+
+ 1.0 - Initial implementation
+ 1.1 - Conversion to FlexAdmin model
+
+--*/
+
+/*
+
+ The following notation is used:
+
+ Xxx is the unicode name of a registry key.
+ For example, "PasswordExpires".
+
+ (Xxx) is a description of a registry key's name.
+ For example, "(UserName)" might indicate that the key name
+ is a user's name.
+
+ [kvt,Value] kvt is the key value type, and Value describes the
+ value of a registry key. If no specific key value type is used,
+ then [,Value] references just the value. If the key has a
+ key value type, but no key value, then [kvt,] notation is used.
+
+ Individual keys or key values may be referenced in the description
+ as follows:
+
+ SAM/Domains/(DomainName)/Users/(UserName)
+ - references a particular user name in a particular domain.
+
+ SAM/Domains/(DomainName)/Users/(UserName)[,Rid]
+ - references a value of a named key.
+
+ .../(UserName) or
+ .../(UserName)[Rid] may also be used as a shorthand notation when
+ I get tired of typing out the whole name.
+
+
+
+
+ NOTE: In several instances, and RID is used as a key name. In this
+ case an ASCII conversion of the ULONG value is used. The name
+ is printable and contains no zero bytes.
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+ The structure of the registry namespace used to back-store the SAM
+ database is as follows:
+
+ SAM [Revision,SecurityDescriptor]
+ --+-
+ +-- Domains
+ ----+--
+ +-- (DomainName1) [,SecurityDescriptor]
+ | (...)
+ +-- (DomainNameN) [,SecurityDescriptor]
+
+
+ The structure under each named domain is as follows:
+
+ (DomainName) [Revision, SecurityDescriptor]
+ ----+-------
+ +-- V1_Fixed [, SAMP_V1_FIXED_LENGTH_DOMAIN]
+ +-- DomainSid [,SidValue]
+ +-- OemInformation [,unicode string]
+ +-- ReplicaSourceNodeName [,unicode string]
+ |
+ +-- Users [Count,]
+ | ---+-
+ | +-- Names
+ | | --+---
+ | | +-- (UserName1) [UserRid,]
+ | | | (...)
+ | | +-- (UserNameL) [UserRid,]
+ | |
+ | +-- (UserRid1) [Revision,SecurityDescriptor]
+ | | (...)
+ | +-- (UserRidL) [Revision,SecurityDescriptor]
+ |
+ +-- Groups [Count,]
+ | ---+--
+ | +-- Names
+ | | --+---
+ | | +-- (GroupName1) [GroupRid,]
+ | | | (...)
+ | | +-- (GroupNameM) [GroupRid,]
+ | |
+ | +-- (GroupRid1) [Revision,SecurityDescriptor]
+ | | (...)
+ | +-- (GroupRidM) [Revision,SecurityDescriptor]
+ |
+ |
+ +-- Aliases [Count,]
+ ---+---
+ +-- Names
+ | --+---
+ | +-- (AliasName1) [AliasRid,]
+ | | (...)
+ | +-- (AliasNameN) [AliasRid,]
+ |
+ +-- (AliasRid1) [Revision,SecurityDescriptor]
+ | (...)
+ +-- (AliasRidN) [Revision,SecurityDescriptor]
+ |
+ |
+ +-- Members [DomainCount,]
+ --+----
+ +-- (DomainSid1) [RidCount,]
+ | -------+----
+ | +-- (AccountRid0) [AliasCount,(Alias0Rid, (...), AliasX-1Rid)]
+ | | (...)
+ | +-- (AccountRidY) [AliasCount,(Alias0Rid, (...), AliasX-1Rid)]
+ |
+ +-- (DomainSid2) [RidCount,]
+ | ------------
+ | +-- (AccountRid0) [AliasCount,(Alias0Rid, (...), AliasX-1Rid)]
+ | | (...)
+ | +-- (AccountRidZ) [AliasCount,(Alias0Rid, (...), AliasX-1Rid)]
+ .
+ .
+ .
+
+
+ The structure under each (UserRid) is as follows:
+
+ (UserRid) [Revision,SecurityDescriptor]
+ +-- V1_Fixed [,SAMP_V1_FIXED_LENGTH_USER]
+ +-- AccountName [,unicode string]
+ +-- FullName [,unicode string]
+ +-- AdminComment [,unicode string]
+ +-- UserComment [,unicode string]
+ +-- Parameters [,unicode string]
+ +-- HomeDirectory [,unicode string]
+ +-- HomeDirectoryDrive [,unicode string]
+ +-- ScriptPath [,unicode string]
+ +-- Workstations [,unicode string]
+ +-- CaseInsensitiveDbcs [,dbcs string]
+ +-- CaseSensitiveUnicode [,unicode string]
+ +-- LmPasswordHistory [,unicode string]
+ +-- NtPasswordHistory [,unicode string]
+ +-- LogonHours [See Note On Logon Hours]
+ +-- ProfilePath [,unicode string]
+ +-- Groups [Count,(Group0Rid/Attributes, (...), GroupY-1Rid/Attributes)]
+
+
+ The structure under each (GroupRid) is as follows:
+
+ (GroupRid) [Revision,SecurityDescriptor]
+ ---+-----
+ +-- V1_Fixed [,SAM_V1_FIXED_LENGTH_GROUP]
+ +-- Name [,Name]
+ +-- AdminComment [,unicode string]
+ +-- Members [Count,(Member0Rid, (...), MemberX-1Rid)]
+
+
+ The structure under each (AliasRid) is as follows:
+
+ (AliasRid) [Revision,SecurityDescriptor]
+ ---+-----
+ +-- V1_Fixed [,SAM_V1_FIXED_LENGTH_ADMIN]
+ +-- Name [,Name]
+ +-- AdminComment [,unicode string]
+ +-- Members [Count,(Member0Sid, (...), MemberX-1Sid)]
+
+
+
+ The structure under the Alias\Members key is used for looking up the aliases
+ an SID is a member of (at logon time). These keys have the following
+ description:
+
+ - keyValueType of Alias\Members - This field contains a count of
+ domains whose accounts are included as alias members. For
+ example, if there are three aliases, and these aliases collectively
+ have the following members:
+
+ \MS\SYS\NTDEV\JIMK
+ \MS\SYS\NTDEV\DAVEC
+ \MS\SYS\NTDEV\CHADS
+ \MS\SYS\NTPGM\BOBMU
+ \MS\EXEC\BILLG
+ \MS\EXEC\PAULMA
+ \MS\EXEC\STEVEB
+
+ then this represents accounts from 3 domains ("\MS\SYS\NTDEV",
+ "\MS\SYS\NTPGM", and "\MS\EXEC"). So, the DomainCount would
+ be three.
+
+ - Each Alias\Members\(DomainSid) key - These each have a name representing
+ the SID of the domains counted in the DomainCount.
+
+ - Under each Alias\Members\(DomainSid) key - There is a single key for each
+ account in that domain that is a member of an alias. The name of these
+ keys are printable representations of their RIDs. The KeyValueType
+ field of these keys contains a count of the aliases the SID is a member
+ of. The KeyValue field contains an array of RIDs of the Aliases that
+ the SID is a member of.
+
+
+===============================================================================
+
+Logon Hours are stored as follows:
+
+
+ The KeyValueType is used to store the UnitsPerWeek value.
+ This value may not exceed SAM_MINUTES_PER_WEEK (10080).
+
+ The actual bitmask of legitimate logon times is stored as
+ the key value. The number of bytes stored is
+ ((KeyValueType + 1) / 8).
+
+ If there are no logon time restrictions, the key will have
+ a KeyValueType of zero and there will be no KeyValue.
+
+
+
+
+
+
+REVISION HISTORY
+----------------
+
+Revision 1.0, 3-June-1991, Jim Kelly (JimK)
+
+ - Initial implementation
+
+
+Revision 1.1, 4-Jan-1992, Jim Kelly (JimK)
+
+ - Conversion to FlexAdmin model.
+
+ - Added all Alias fields. Notice that the members of aliases
+ are SIDs, not RIDs. This makes alias membership marshalling
+ much more difficult than for Group objects.
+
+ - Drop the following fields:
+
+ (UserRid)/LogonServer
+
+ - Added the following fields:
+
+ (UserRid)/ProfilePath
diff --git a/private/newsam/server/rundown.c b/private/newsam/server/rundown.c
new file mode 100644
index 000000000..810742252
--- /dev/null
+++ b/private/newsam/server/rundown.c
@@ -0,0 +1,150 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ rundown.c
+
+Abstract:
+
+ This file contains context handle rundown services
+ related to the SAM server RPC interface package..
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+void
+SAMPR_HANDLE_rundown(
+ IN SAMPR_HANDLE SamHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service is called if a binding breaks when a context_handle is
+ still active.
+
+ Note that the RPC runtime will eliminate any race condition caused
+ by an outstanding call to the server with this context handle that
+ might cause the handle to become invalid before the rundown routine
+ is called.
+
+Arguments:
+
+ SamHandle - The context handle value whose context must be rundown.
+ Note that as far as RPC is concerned, this handle is no longer
+ valid at the time the rundown call is made.
+
+Return Value:
+
+
+ None.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+
+ Context = (PSAMP_OBJECT)(SamHandle);
+
+
+
+ SampAcquireReadLock();
+
+ //
+ // Lookup the context block
+ //
+
+ NtStatus = SampLookupContext(
+ Context, // Context
+ 0L, // DesiredAccess
+ SampUnknownObjectType, // ExpectedType
+ &FoundType // FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ // TEMPORARY
+ //DbgPrint("Rundown of ");
+ //if (FoundType == SampServerObjectType) DbgPrint("Server ");
+ //if (FoundType == SampDomainObjectType) DbgPrint("Domain ");
+ //if (FoundType == SampGroupObjectType) DbgPrint("Group ");
+ //if (FoundType == SampUserObjectType) DbgPrint("User ");
+ //DbgPrint("context.\n");
+ //DbgPrint(" Handle Value is: 0x%lx\n", Context );
+ //if (Context->ReferenceCount != 2) {
+ //DbgPrint(" REFERENCE COUNT is: 0x%lx\n", Context->ReferenceCount);
+ //}
+ // TEMPORARY
+
+ //
+ // Delete this context...
+ //
+
+ SampDeleteContext( Context );
+
+
+ //
+ // And drop our reference from the lookup operation
+ //
+
+ SampDeReferenceContext( Context, FALSE);
+
+
+ }
+
+
+ SampReleaseReadLock();
+
+
+ return;
+}
diff --git a/private/newsam/server/sam_rev.rc b/private/newsam/server/sam_rev.rc
new file mode 100644
index 000000000..a0d85b4e0
--- /dev/null
+++ b/private/newsam/server/sam_rev.rc
@@ -0,0 +1,10 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DLL
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "SAM Server DLL"
+#define VER_INTERNALNAME_STR "samsrv.dll"
+
+#include "common.ver"
+RCINCLUDE sampmsgs.rc
diff --git a/private/newsam/server/samifree.c b/private/newsam/server/samifree.c
new file mode 100644
index 000000000..e8a3b9c9d
--- /dev/null
+++ b/private/newsam/server/samifree.c
@@ -0,0 +1,405 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ samifree.c
+
+Abstract:
+
+ This file contains routines to free structure allocated by the Samr
+ routines. These routines are used by SAM clients which live in the
+ same process as the SAM server and call the Samr routines directly.
+
+
+Author:
+
+ Cliff Van Dyke (CliffV) 26-Feb-1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+VOID
+SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR (
+ PSAMPR_SR_SECURITY_DESCRIPTOR Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_SR_SECURITY_DESCRIPTOR and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_SR_SECURITY_DESCRIPTOR ( Source );
+ MIDL_user_free (Source);
+ }
+}
+
+
+
+VOID
+SamIFree_SAMPR_DOMAIN_INFO_BUFFER (
+ PSAMPR_DOMAIN_INFO_BUFFER Source,
+ DOMAIN_INFORMATION_CLASS Branch
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_DOMAIN_INFO_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+ Branch - Specifies which branch of the union to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgu__SAMPR_DOMAIN_INFO_BUFFER ( Source, Branch );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_ENUMERATION_BUFFER (
+ PSAMPR_ENUMERATION_BUFFER Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_ENUMERATION_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_ENUMERATION_BUFFER ( Source );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_PSID_ARRAY (
+ PSAMPR_PSID_ARRAY Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a the graph of allocated nodes pointed to
+ by a PSAMPR_PSID_ARRAY
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_PSID_ARRAY ( Source );
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_ULONG_ARRAY (
+ PSAMPR_ULONG_ARRAY Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_ULONG_ARRAY and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_ULONG_ARRAY ( Source );
+ // SAM never allocates this.
+ // MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_RETURNED_USTRING_ARRAY (
+ PSAMPR_RETURNED_USTRING_ARRAY Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_RETURNED_USTRING_ARRAY and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_RETURNED_USTRING_ARRAY ( Source );
+ // SAM never allocates this.
+ // MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_GROUP_INFO_BUFFER (
+ PSAMPR_GROUP_INFO_BUFFER Source,
+ GROUP_INFORMATION_CLASS Branch
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_GROUP_INFO_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+ Branch - Specifies which branch of the union to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgu__SAMPR_GROUP_INFO_BUFFER ( Source, Branch );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_ALIAS_INFO_BUFFER (
+ PSAMPR_ALIAS_INFO_BUFFER Source,
+ ALIAS_INFORMATION_CLASS Branch
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_ALIAS_INFO_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+ Branch - Specifies which branch of the union to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgu__SAMPR_ALIAS_INFO_BUFFER ( Source, Branch );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_GET_MEMBERS_BUFFER (
+ PSAMPR_GET_MEMBERS_BUFFER Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_GET_MEMBERS_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_GET_MEMBERS_BUFFER ( Source );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_USER_INFO_BUFFER (
+ PSAMPR_USER_INFO_BUFFER Source,
+ USER_INFORMATION_CLASS Branch
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_USER_INFO_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+ Branch - Specifies which branch of the union to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgu__SAMPR_USER_INFO_BUFFER ( Source, Branch );
+ MIDL_user_free (Source);
+ }
+}
+
+
+VOID
+SamIFree_SAMPR_GET_GROUPS_BUFFER (
+ PSAMPR_GET_GROUPS_BUFFER Source
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_GET_GROUPS_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgs__SAMPR_GET_GROUPS_BUFFER ( Source );
+ MIDL_user_free (Source);
+ }
+}
+
+
+
+VOID
+SamIFree_SAMPR_DISPLAY_INFO_BUFFER (
+ PSAMPR_DISPLAY_INFO_BUFFER Source,
+ DOMAIN_DISPLAY_INFORMATION Branch
+ )
+
+/*++
+
+Routine Description:
+
+ This routine free a SAMPR_DISPLAY_INFO_BUFFER and the graph of
+ allocated nodes it points to.
+
+Parameters:
+
+ Source - A pointer to the node to free.
+
+ Branch - Specifies which branch of the union to free.
+
+Return Values:
+
+ None.
+
+--*/
+{
+ if ( Source != NULL ) {
+ _fgu__SAMPR_DISPLAY_INFO_BUFFER ( Source, Branch );
+ // SAM never allocates this.
+ // MIDL_user_free (Source);
+ }
+}
diff --git a/private/newsam/server/sampmsgs.mc b/private/newsam/server/sampmsgs.mc
new file mode 100644
index 000000000..c39dc38ba
--- /dev/null
+++ b/private/newsam/server/sampmsgs.mc
@@ -0,0 +1,259 @@
+;/*++ BUILD Version: 0001 // Increment this if a change has global effects
+;
+;Copyright (c) 1991-1993 Microsoft Corporation
+;
+;Module Name:
+;
+; sampmsgs.mc
+;
+;Abstract:
+;
+; SAM localizable text
+;
+;Author:
+;
+; Jim Kelly 1-Apr-1993
+;
+;Revision History:
+;
+;Notes:
+;
+;
+;--*/
+;
+;#ifndef _SAMPMSGS_
+;#define _SAMPMSGS_
+;
+;/*lint -save -e767 */ // Don't complain about different definitions // winnt
+
+MessageIdTypedef=DWORD
+
+
+;//
+;// Force facility code message to be placed in .h file
+;//
+MessageId=0x1FFF SymbolicName=SAMP_UNUSED_MESSAGE
+Language=English
+.
+
+
+;////////////////////////////////////////////////////////////////////////////
+;// //
+;// //
+;// SAM Account Names //
+;// //
+;// //
+;////////////////////////////////////////////////////////////////////////////
+
+
+MessageId=0x2000 SymbolicName=SAMP_USER_NAME_ADMIN
+Language=English
+Administrator
+.
+
+MessageId=0x2001 SymbolicName=SAMP_USER_NAME_GUEST
+Language=English
+Guest
+.
+
+MessageId=0x2002 SymbolicName=SAMP_GROUP_NAME_ADMINS
+Language=English
+Domain Admins
+.
+
+MessageId=0x2003 SymbolicName=SAMP_GROUP_NAME_USERS
+Language=English
+Domain Users
+.
+
+MessageId=0x2004 SymbolicName=SAMP_GROUP_NAME_NONE
+Language=English
+None
+.
+
+MessageId=0x2005 SymbolicName=SAMP_ALIAS_NAME_ADMINS
+Language=English
+Administrators
+.
+
+MessageId=0x2006 SymbolicName=SAMP_ALIAS_NAME_SERVER_OPS
+Language=English
+Server Operators
+.
+
+MessageId=0x2007 SymbolicName=SAMP_ALIAS_NAME_POWER_USERS
+Language=English
+Power Users
+.
+
+MessageId=0x2008 SymbolicName=SAMP_ALIAS_NAME_USERS
+Language=English
+Users
+.
+
+MessageId=0x2009 SymbolicName=SAMP_ALIAS_NAME_GUESTS
+Language=English
+Guests
+.
+
+MessageId=0x200A SymbolicName=SAMP_ALIAS_NAME_ACCOUNT_OPS
+Language=English
+Account Operators
+.
+
+MessageId=0x200B SymbolicName=SAMP_ALIAS_NAME_PRINT_OPS
+Language=English
+Print Operators
+.
+
+MessageId=0x200C SymbolicName=SAMP_ALIAS_NAME_BACKUP_OPS
+Language=English
+Backup Operators
+.
+
+MessageId=0x200D SymbolicName=SAMP_ALIAS_NAME_REPLICATOR
+Language=English
+Replicator
+.
+
+
+;// Added for NT 1.0A
+MessageId=0x200E SymbolicName=SAMP_GROUP_NAME_GUESTS
+Language=English
+Domain Guests
+.
+
+
+
+
+;////////////////////////////////////////////////////////////////////////////
+;// //
+;// //
+;// SAM Account Comments //
+;// //
+;// //
+;////////////////////////////////////////////////////////////////////////////
+
+
+MessageId=0x2100 SymbolicName=SAMP_USER_COMMENT_ADMIN
+Language=English
+Built-in account for administering the computer/domain
+.
+
+MessageId=0x2101 SymbolicName=SAMP_USER_COMMENT_GUEST
+Language=English
+Built-in account for guest access to the computer/domain
+.
+
+MessageId=0x2102 SymbolicName=SAMP_GROUP_COMMENT_ADMINS
+Language=English
+Designated administrators of the domain
+.
+
+MessageId=0x2103 SymbolicName=SAMP_GROUP_COMMENT_USERS
+Language=English
+All domain users
+.
+
+MessageId=0x2104 SymbolicName=SAMP_GROUP_COMMENT_NONE
+Language=English
+Ordinary users
+.
+
+MessageId=0x2105 SymbolicName=SAMP_ALIAS_COMMENT_ADMINS
+Language=English
+Members can fully administer the computer/domain
+.
+
+MessageId=0x2106 SymbolicName=SAMP_ALIAS_COMMENT_SERVER_OPS
+Language=English
+Members can administer domain servers
+.
+
+MessageId=0x2107 SymbolicName=SAMP_ALIAS_COMMENT_POWER_USERS
+Language=English
+Members can share directories and printers
+.
+
+MessageId=0x2108 SymbolicName=SAMP_ALIAS_COMMENT_USERS
+Language=English
+Ordinary users
+.
+
+MessageId=0x2109 SymbolicName=SAMP_ALIAS_COMMENT_GUESTS
+Language=English
+Users granted guest access to the computer/domain
+.
+
+MessageId=0x210A SymbolicName=SAMP_ALIAS_COMMENT_ACCOUNT_OPS
+Language=English
+Members can administer domain user and group accounts
+.
+
+MessageId=0x210B SymbolicName=SAMP_ALIAS_COMMENT_PRINT_OPS
+Language=English
+Members can administer domain printers
+.
+
+MessageId=0x210C SymbolicName=SAMP_ALIAS_COMMENT_BACKUP_OPS
+Language=English
+Members can bypass file security to back up files
+.
+
+MessageId=0x210D SymbolicName=SAMP_ALIAS_COMMENT_REPLICATOR
+Language=English
+Supports file replication in a domain
+.
+
+
+;// Added for NT1.0A
+MessageId=0x210E SymbolicName=SAMP_GROUP_COMMENT_GUESTS
+Language=English
+All domain guests
+.
+
+
+;//////////////////////////////////////////////////////////////////////
+;//
+;// SAM Database Commit/Refresh Events
+;//
+;//////////////////////////////////////////////////////////////////////
+
+MessageId=0x3000
+ SymbolicName=SAMMSG_COMMIT_FAILED
+ Language=English
+SAM failed to write changes to the database. This is most likely due to
+a memory or disk-space shortage. The SAM database will be restored to
+an earlier state. Recent changes will be lost. Check the disk-space
+available, maximum pagefile size setting, and maximum registry size
+setting.
+.
+
+
+MessageId=0x3001
+ SymbolicName=SAMMSG_REFRESH_FAILED
+ Language=English
+SAM failed to restore the database to an earlier state. SAM has shutdown.
+You must reboot the machine to re-enable SAM.
+.
+
+
+MessageId=0x3002
+ SymbolicName=SAMMSG_UPDATE_FAILED
+ Language=English
+SAM failed to update the SAM database. It will try again next time you
+reboot the machine.
+.
+
+MessageId=0x3003
+ SymbolicName=SAMMSG_RPC_INIT_FAILED
+ Language=English
+SAM failed to start the TCP/IP or SPX/IPX listening thread
+.
+
+
+
+;/*lint -restore */ // Resume checking for different macro definitions // winnt
+;
+;
+;#endif // _SAMPMSGS_
diff --git a/private/newsam/server/samsrv.def b/private/newsam/server/samsrv.def
new file mode 100644
index 000000000..89a863a5e
--- /dev/null
+++ b/private/newsam/server/samsrv.def
@@ -0,0 +1,126 @@
+LIBRARY SAMSRV
+
+DESCRIPTION 'Security Account Manager Server'
+
+EXPORTS
+
+;
+; Initialization
+;
+
+ SamIInitialize
+
+
+;
+; Internal routines to support NetLogon
+;
+
+ SamIAccountRestrictions
+ SamIConnect
+
+;
+; Internal routines to support clients in the Security Process.
+;
+
+ SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER
+ SamIFree_SAMPR_DISPLAY_INFO_BUFFER
+ SamIFree_SAMPR_ENUMERATION_BUFFER
+ SamIFree_SAMPR_PSID_ARRAY
+ SamIFree_SAMPR_ULONG_ARRAY
+ SamIFree_SAMPR_RETURNED_USTRING_ARRAY
+ SamIFree_SAMPR_GROUP_INFO_BUFFER
+ SamIFree_SAMPR_ALIAS_INFO_BUFFER
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER
+ SamIFree_SAMPR_USER_INFO_BUFFER
+ SamIFree_SAMPR_GET_GROUPS_BUFFER
+
+ SamIGetPrivateData
+ SamISetPrivateData
+ SamICreateAccountByRid
+ SamIGetSerialNumberDomain
+ SamISetSerialNumberDomain
+ SamINotifyDelta
+ SamISetAuditingInformation
+ SamIEnumerateAccountRids
+
+
+
+;
+; Exported RPC Services
+;
+
+ SamrConnect
+ SamrCloseHandle
+ SamrShutdownSamServer
+
+ SamrSetSecurityObject
+ SamrQuerySecurityObject
+
+ SamrLookupDomainInSamServer
+ SamrEnumerateDomainsInSamServer
+
+ SamrOpenAlias
+ SamrDeleteAlias
+ SamrQueryInformationAlias
+ SamrSetInformationAlias
+ SamrAddMemberToAlias
+ SamrAddMultipleMembersToAlias
+ SamrRemoveMemberFromAlias
+ SamrRemoveMultipleMembersFromAlias
+ SamrGetMembersInAlias
+
+ SamrOpenDomain
+ SamrQueryInformationDomain
+ SamrSetInformationDomain
+ SamrCreateGroupInDomain
+ SamrEnumerateGroupsInDomain
+ SamrCreateUserInDomain
+ SamrEnumerateUsersInDomain
+ SamrCreateAliasInDomain
+ SamrEnumerateAliasesInDomain
+ SamrGetAliasMembership
+ SamrRemoveMemberFromForeignDomain
+
+ SamrLookupNamesInDomain
+ SamrLookupIdsInDomain
+
+ SamrQueryDisplayInformation
+
+ SamrOpenGroup
+ SamrQueryInformationGroup
+ SamrSetInformationGroup
+ SamrAddMemberToGroup
+ SamrDeleteGroup
+ SamrRemoveMemberFromGroup
+ SamrGetMembersInGroup
+ SamrSetMemberAttributesOfGroup
+
+ SamrOpenUser
+ SamrDeleteUser
+ SamrQueryInformationUser
+ SamrSetInformationUser
+ SamrChangePasswordUser
+ SamrGetGroupsForUser
+
+;
+; Routines used by the client side, but not directly by the user (these
+; can not return information that is not already obtainable from the client
+; side).
+;
+
+ SamrGetUserDomainPasswordInformation
+
+;
+; Routines used for testing; these only work on special builds.
+;
+
+ SamrTestPrivateFunctionsDomain
+ SamrTestPrivateFunctionsUser
+
+;
+; Interfaces used only by bldsam2
+;
+
+ SampInitializeRegistry
+ SampRtlConvertUlongToUnicodeString
diff --git a/private/newsam/server/samsrv.prf b/private/newsam/server/samsrv.prf
new file mode 100644
index 000000000..c83a7a0a9
--- /dev/null
+++ b/private/newsam/server/samsrv.prf
@@ -0,0 +1,699 @@
+SamrGetAliasMembership@12
+SampAlQueryAliasMembership@12
+SampAlLookupMemberAccount@12
+SampAlLookupMemberDomain@12
+SampAlInfoIsValidForDomain@4
+SampLookupContext@16
+SampDeReferenceContext@8
+SampValidateContextAddress@4
+SampAcquireReadLock@0
+SampReleaseReadLock@0
+SampSetTransactionDomain@4
+SampSplitSid@12
+SampGetFixedAttributes@12
+SampGetUnicodeStringAttribute@16
+SampGetAccessAttribute@20
+SampGetLargeIntArrayAttribute@20
+SampGetLogonHoursAttribute@16
+SampValidateAttributes@8
+SampObjectAttributeAddress@8
+SampObjectAttributeLength@8
+SampObjectAttributeQualifier@8
+SampGetAttributeBufferReadInfo@20
+SampExtendAttributeBuffer@8
+SampReadRegistryAttribute@20
+SampFreeAttributeBuffer@4
+SamrCloseHandle@4
+SampCreateContext@8
+SampDeleteContext@4
+SampReferenceContext@4
+SampInvalidateContextAddress@4
+SampRtlConvertUlongToUnicodeString@20
+SampAddNewValidContextAddress@4
+SampGetPrivateUserData@12
+SamIFree_SAMPR_ULONG_ARRAY@4
+SamIFree_SAMPR_USER_INFO_BUFFER@8
+SamIFree_SAMPR_GET_GROUPS_BUFFER@4
+_fgs__RPC_UNICODE_STRING@4
+_fgs__SAMPR_SR_SECURITY_DESCRIPTOR@4
+_fgs__SAMPR_GET_GROUPS_BUFFER@4
+_fgs__SAMPR_LOGON_HOURS@4
+_fgs__SAMPR_ULONG_ARRAY@4
+_fgs__SAMPR_USER_ALL_INFORMATION@4
+_fgu__SAMPR_USER_INFO_BUFFER@8
+SampGetObjectSD@12
+SampValidateObjectAccess@12
+SamrOpenUser@16
+SamrQueryInformationUser@12
+SamrGetGroupsForUser@8
+SamIAccountRestrictions@24
+SampGetPasswordMustChange@20
+SampRetrieveUserPasswords@24
+SampRetrieveUserMembership@16
+SampRetrieveUserLogonHours@8
+SampRetrieveUserV1aFixed@8
+SampMatchworkstation@8
+SampInitUnicodeString@8
+SampAppendUnicodeString@8
+SampFreeUnicodeString@4
+SampBuildAccountSubKeyName@16
+SampOpenAccount@24
+SampCreateAccountContext@20
+SamrLookupNamesInDomain@20
+SampBuildAccountKeyName@12
+SamrQueryDisplayInformation@32
+SamrGetDisplayEnumerationIndex@16
+SampInitializeDisplayInformation@4
+SampDeleteDisplayInformation@8
+SampCreateDisplayInformation@4
+SampUpdateDisplayInformation@12
+SampDeleteDisplayAccount@12
+SampAddDisplayAccount@12
+SampUpdateDisplayAccount@12
+SampInitializeUserInfo@12
+SampInitializeMachineInfo@12
+SampDuplicateUserInfo@12
+SampDuplicateMachineInfo@12
+SampFreeUserInfo@4
+SampFreeMachineInfo@4
+SampBytesRequiredUserNode@4
+SampBytesRequiredMachineNode@4
+SamrOpenAlias@16
+SamrQueryInformationAlias@12
+SamrSetInformationAlias@12
+SamrDeleteAlias@4
+SamrAddMemberToAlias@8
+SamrRemoveMemberFromAlias@8
+SamrGetMembersInAlias@8
+SampRemoveAccountFromAllAliases@20
+SampRetrieveAliasMembership@16
+SampAddAccountToAlias@8
+SampRemoveAccountFromAlias@8
+SampAddAliasToAccountMembership@8
+SampRemoveAliasFromAccountMembership@8
+SampRemoveAliasFromAllAccounts@4
+SampRetrieveAliasMembers@12
+SampDeleteAliasKeys@4
+SampDeleteAliasMembershipKeysForAccount@4
+SampAdjustAliasDomainsCount@4
+SampValidateNewAliasMember@4
+SampChangeAliasAccountName@12
+SampAlSlowQueryAliasMembership@12
+SampAlQueryMembersOfAlias@8
+SampAlAddMembersToAlias@12
+SampAlRemoveMembersFromAlias@12
+SampAlLookupMembersInAlias@16
+SampAlDeleteAlias@4
+SampAlRemoveAccountFromAllAliases@20
+SampAlBuildAliasInformation@0
+SampAlCreateMemberAccount@20
+SampAlAllocateMemberAccount@16
+SampAlGrowMemberAccount@16
+SampAlAddAliasesToMemberAccount@20
+SampAlLookupAliasesInMemberAccount@12
+SampAlRemoveAliasesFromMemberAccount@20
+SampAlDeleteMemberAccount@12
+SampAlCreateMemberDomain@12
+SampAlAllocateMemberDomain@12
+SampAlGrowMemberDomain@12
+SampAlDeleteMemberDomain@8
+SampAlCreateMemberAliasList@12
+SampAlGrowMemberAliasList@8
+SampAlBuildAliasInformationKeyNames@4
+SampAlBuildMemberAliasList@4
+SampAlInfoIsValidForAlias@4
+SampAlFastRestoreList@20
+SampInitObjectInfoAttributes@0
+SampStoreObjectAttributes@8
+SampDeleteAttributeKeys@4
+SampSetFixedAttributes@8
+SampSetUnicodeStringAttribute@12
+SampGetSidAttribute@16
+SampSetSidAttribute@12
+SampSetAccessAttribute@16
+SampGetUlongArrayAttribute@24
+SampSetUlongArrayAttribute@20
+SampSetLargeIntArrayAttribute@16
+SampGetSidArrayAttribute@24
+SampSetSidArrayAttribute@20
+SampSetLogonHoursAttribute@12
+SampSetVariableAttribute@20
+Usage@0
+UnexpectedProblem@0
+Initialize@0
+InitializeSecurityDescriptors@0
+TmppBuildSamProtection@24
+SampGetDomainPolicy@0
+SetCurrentDomain@4
+InitializeSam@0
+SampCreateDatabaseProtection@4
+CreateBuiltinDomain@0
+CreateAccountDomain@0
+PrepDomain@4
+CreateAlias@20
+CreateGroup@20
+CreateUser@32
+BuildPrimaryDomainSid@4
+BuildAccountSid@8
+UpdateAliasXReference@8
+OpenAliasMember@8
+OpenOrCreateAccountRidKey@12
+OpenOrCreateAliasDomainKey@8
+AppendAliasDomainNameToUnicodeString@8
+TmppGetAccountDomainInfo@4
+SampGetServerRole@0
+SampGetPrimaryDomainInfo@0
+SampRestoreDefaultDacl@0
+SampDetermineSetupEnvironment@0
+SampInitializeRegistry@0
+SampGetMessageStrings@20
+SampInvalidateGroupContexts@4
+SampInvalidateAliasContexts@4
+SampInvalidateUserContexts@4
+SampInvalidateContextListKeys@8
+SamrOpenDomain@16
+SamrQueryInformationDomain@12
+SamrSetInformationDomain@12
+SampCreateGroupInDomain@24
+SamrCreateGroupInDomain@20
+SamrEnumerateGroupsInDomain@20
+SampCreateAliasInDomain@24
+SamrCreateAliasInDomain@20
+SamrEnumerateAliasesInDomain@20
+SamrRemoveMemberFromForeignDomain@8
+SamrCreateUserInDomain@20
+SamrEnumerateUsersInDomain@24
+SamrLookupIdsInDomain@20
+SampOpenDomainKey@8
+SampInitializeDomainObject@0
+SampInitializeSingleDomain@4
+SampSetDomainPolicy@0
+SampReInitializeSingleDomain@4
+SampGetAccountDomainInfo@4
+SampCollisionError@4
+SamICreateAccountByRid@28
+SamIGetSerialNumberDomain@12
+SamISetSerialNumberDomain@16
+SamIGetPrivateData@20
+SampSetPrivateUserData@12
+SamISetPrivateData@12
+SamrTestPrivateFunctionsDomain@4
+SamrTestPrivateFunctionsUser@4
+SampBuildDomainKeyName@8
+SamrOpenGroup@16
+SamrQueryInformationGroup@12
+SamrSetInformationGroup@12
+SamrAddMemberToGroup@12
+SamrDeleteGroup@4
+SamrRemoveMemberFromGroup@8
+SamrGetMembersInGroup@8
+SamrSetMemberAttributesOfGroup@12
+SampAddUserToGroup@8
+SampRemoveUserFromGroup@8
+SampRetrieveGroupV1Fixed@8
+SampReplaceGroupV1Fixed@8
+SampRetrieveGroupMembers@12
+SampReplaceGroupMembers@12
+SampDeleteGroupKeys@4
+SampChangeGroupAccountName@12
+SampAddAccountToGroupMembers@8
+SampRemoveAccountFromGroupMembers@8
+SAMPR_HANDLE_rundown@4
+SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR@4
+SamIFree_SAMPR_DOMAIN_INFO_BUFFER@8
+SamIFree_SAMPR_ENUMERATION_BUFFER@4
+SamIFree_SAMPR_PSID_ARRAY@4
+SamIFree_SAMPR_RETURNED_USTRING_ARRAY@4
+SamIFree_SAMPR_GROUP_INFO_BUFFER@8
+SamIFree_SAMPR_ALIAS_INFO_BUFFER@8
+SamIFree_SAMPR_GET_MEMBERS_BUFFER@4
+SamIFree_SAMPR_DISPLAY_INFO_BUFFER@8
+samr_SamrConnect@4
+samr_SamrCloseHandle@4
+samr_SamrSetSecurityObject@4
+samr_SamrQuerySecurityObject@4
+samr_SamrShutdownSamServer@4
+samr_SamrLookupDomainInSamServer@4
+samr_SamrEnumerateDomainsInSamServer@4
+samr_SamrOpenDomain@4
+samr_SamrQueryInformationDomain@4
+samr_SamrSetInformationDomain@4
+samr_SamrCreateGroupInDomain@4
+samr_SamrEnumerateGroupsInDomain@4
+samr_SamrCreateUserInDomain@4
+samr_SamrEnumerateUsersInDomain@4
+samr_SamrCreateAliasInDomain@4
+samr_SamrEnumerateAliasesInDomain@4
+samr_SamrGetAliasMembership@4
+samr_SamrLookupNamesInDomain@4
+samr_SamrLookupIdsInDomain@4
+samr_SamrOpenGroup@4
+samr_SamrQueryInformationGroup@4
+samr_SamrSetInformationGroup@4
+samr_SamrAddMemberToGroup@4
+samr_SamrDeleteGroup@4
+samr_SamrRemoveMemberFromGroup@4
+samr_SamrGetMembersInGroup@4
+samr_SamrSetMemberAttributesOfGroup@4
+samr_SamrOpenAlias@4
+samr_SamrQueryInformationAlias@4
+samr_SamrSetInformationAlias@4
+samr_SamrDeleteAlias@4
+samr_SamrAddMemberToAlias@4
+samr_SamrRemoveMemberFromAlias@4
+samr_SamrGetMembersInAlias@4
+samr_SamrOpenUser@4
+samr_SamrDeleteUser@4
+samr_SamrQueryInformationUser@4
+samr_SamrSetInformationUser@4
+samr_SamrChangePasswordUser@4
+samr_SamrGetGroupsForUser@4
+samr_SamrQueryDisplayInformation@4
+samr_SamrGetDisplayEnumerationIndex@4
+samr_SamrTestPrivateFunctionsDomain@4
+samr_SamrTestPrivateFunctionsUser@4
+samr_SamrGetUserDomainPasswordInformation@4
+samr_SamrRemoveMemberFromForeignDomain@4
+_gns__SID_IDENTIFIER_AUTHORITY@8
+_pns__DOMAIN_SERVER_ROLE_INFORMATION@8
+_gns__DOMAIN_SERVER_ROLE_INFORMATION@8
+_pns__DOMAIN_STATE_INFORMATION@8
+_gns__DOMAIN_STATE_INFORMATION@8
+_gns__CYPHER_BLOCK@8
+_gns__ENCRYPTED_LM_OWF_PASSWORD@8
+_sgs__RPC_UNICODE_STRING@8
+_pgs__RPC_UNICODE_STRING@8
+_ggs__RPC_UNICODE_STRING@12
+_sns__RPC_SID@8
+_pns__RPC_SID@8
+_gns__RPC_SID@12
+_ggs__RPC_SID@16
+_ans__RPC_SID@8
+_ags__RPC_SID@12
+_sgs__SAMPR_RID_ENUMERATION@8
+_pgs__SAMPR_RID_ENUMERATION@8
+_fgs__SAMPR_RID_ENUMERATION@4
+_sgs__SAMPR_ENUMERATION_BUFFER@8
+_pgs__SAMPR_ENUMERATION_BUFFER@8
+_fgs__SAMPR_ENUMERATION_BUFFER@4
+_sgs__SAMPR_SR_SECURITY_DESCRIPTOR@8
+_pgs__SAMPR_SR_SECURITY_DESCRIPTOR@8
+_ggs__SAMPR_SR_SECURITY_DESCRIPTOR@12
+_sgs__SAMPR_GET_GROUPS_BUFFER@8
+_pgs__SAMPR_GET_GROUPS_BUFFER@8
+_sgs__SAMPR_GET_MEMBERS_BUFFER@8
+_pgs__SAMPR_GET_MEMBERS_BUFFER@8
+_fgs__SAMPR_GET_MEMBERS_BUFFER@4
+_sgs__SAMPR_LOGON_HOURS@8
+_pgs__SAMPR_LOGON_HOURS@8
+_ggs__SAMPR_LOGON_HOURS@12
+_sgs__SAMPR_ULONG_ARRAY@8
+_pgs__SAMPR_ULONG_ARRAY@8
+_sgs__SAMPR_SID_INFORMATION@8
+_pgs__SAMPR_SID_INFORMATION@8
+_ggs__SAMPR_SID_INFORMATION@16
+_ags__SAMPR_SID_INFORMATION@8
+_fgs__SAMPR_SID_INFORMATION@4
+_sgs__SAMPR_PSID_ARRAY@8
+_pgs__SAMPR_PSID_ARRAY@8
+_ggs__SAMPR_PSID_ARRAY@12
+_fgs__SAMPR_PSID_ARRAY@4
+_sgs__SAMPR_RETURNED_USTRING_ARRAY@8
+_pgs__SAMPR_RETURNED_USTRING_ARRAY@8
+_fgs__SAMPR_RETURNED_USTRING_ARRAY@4
+_sgs__SAMPR_DOMAIN_GENERAL_INFORMATION@8
+_pgs__SAMPR_DOMAIN_GENERAL_INFORMATION@8
+_ggs__SAMPR_DOMAIN_GENERAL_INFORMATION@12
+_fgs__SAMPR_DOMAIN_GENERAL_INFORMATION@4
+_sgs__SAMPR_DOMAIN_OEM_INFORMATION@8
+_pgs__SAMPR_DOMAIN_OEM_INFORMATION@8
+_ggs__SAMPR_DOMAIN_OEM_INFORMATION@12
+_fgs__SAMPR_DOMAIN_OEM_INFORMATION@4
+_sgs__SAMPR_DOMAIN_NAME_INFORMATION@8
+_pgs__SAMPR_DOMAIN_NAME_INFORMATION@8
+_ggs__SAMPR_DOMAIN_NAME_INFORMATION@12
+_fgs__SAMPR_DOMAIN_NAME_INFORMATION@4
+_sgs_SAMPR_DOMAIN_REPLICATION_INFORMATION@8
+_pgs_SAMPR_DOMAIN_REPLICATION_INFORMATION@8
+_ggs_SAMPR_DOMAIN_REPLICATION_INFORMATION@12
+_fgs_SAMPR_DOMAIN_REPLICATION_INFORMATION@4
+_snu__SAMPR_DOMAIN_INFO_BUFFER@12
+_sgu__SAMPR_DOMAIN_INFO_BUFFER@12
+_pnu__SAMPR_DOMAIN_INFO_BUFFER@12
+_pgu__SAMPR_DOMAIN_INFO_BUFFER@12
+_gnu__SAMPR_DOMAIN_INFO_BUFFER@12
+_ggu__SAMPR_DOMAIN_INFO_BUFFER@16
+_fgu__SAMPR_DOMAIN_INFO_BUFFER@8
+_sgs__SAMPR_GROUP_GENERAL_INFORMATION@8
+_pgs__SAMPR_GROUP_GENERAL_INFORMATION@8
+_ggs__SAMPR_GROUP_GENERAL_INFORMATION@12
+_fgs__SAMPR_GROUP_GENERAL_INFORMATION@4
+_sgs__SAMPR_GROUP_NAME_INFORMATION@8
+_pgs__SAMPR_GROUP_NAME_INFORMATION@8
+_ggs__SAMPR_GROUP_NAME_INFORMATION@12
+_fgs__SAMPR_GROUP_NAME_INFORMATION@4
+_sgs__SAMPR_GROUP_ADM_COMMENT_INFORMATION@8
+_pgs__SAMPR_GROUP_ADM_COMMENT_INFORMATION@8
+_ggs__SAMPR_GROUP_ADM_COMMENT_INFORMATION@12
+_fgs__SAMPR_GROUP_ADM_COMMENT_INFORMATION@4
+_snu__SAMPR_GROUP_INFO_BUFFER@12
+_sgu__SAMPR_GROUP_INFO_BUFFER@12
+_pnu__SAMPR_GROUP_INFO_BUFFER@12
+_pgu__SAMPR_GROUP_INFO_BUFFER@12
+_gnu__SAMPR_GROUP_INFO_BUFFER@12
+_ggu__SAMPR_GROUP_INFO_BUFFER@16
+_fgu__SAMPR_GROUP_INFO_BUFFER@8
+_sgs__SAMPR_ALIAS_GENERAL_INFORMATION@8
+_pgs__SAMPR_ALIAS_GENERAL_INFORMATION@8
+_ggs__SAMPR_ALIAS_GENERAL_INFORMATION@12
+_fgs__SAMPR_ALIAS_GENERAL_INFORMATION@4
+_sgs__SAMPR_ALIAS_NAME_INFORMATION@8
+_pgs__SAMPR_ALIAS_NAME_INFORMATION@8
+_ggs__SAMPR_ALIAS_NAME_INFORMATION@12
+_fgs__SAMPR_ALIAS_NAME_INFORMATION@4
+_sgs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION@8
+_pgs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION@8
+_ggs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION@12
+_fgs__SAMPR_ALIAS_ADM_COMMENT_INFORMATION@4
+_snu__SAMPR_ALIAS_INFO_BUFFER@12
+_sgu__SAMPR_ALIAS_INFO_BUFFER@12
+_pnu__SAMPR_ALIAS_INFO_BUFFER@12
+_pgu__SAMPR_ALIAS_INFO_BUFFER@12
+_gnu__SAMPR_ALIAS_INFO_BUFFER@12
+_ggu__SAMPR_ALIAS_INFO_BUFFER@16
+_fgu__SAMPR_ALIAS_INFO_BUFFER@8
+_sgs__SAMPR_USER_ALL_INFORMATION@8
+_pgs__SAMPR_USER_ALL_INFORMATION@8
+_ggs__SAMPR_USER_ALL_INFORMATION@12
+_sgs__SAMPR_USER_GENERAL_INFORMATION@8
+_pgs__SAMPR_USER_GENERAL_INFORMATION@8
+_ggs__SAMPR_USER_GENERAL_INFORMATION@12
+_fgs__SAMPR_USER_GENERAL_INFORMATION@4
+_sgs__SAMPR_USER_PREFERENCES_INFORMATION@8
+_pgs__SAMPR_USER_PREFERENCES_INFORMATION@8
+_ggs__SAMPR_USER_PREFERENCES_INFORMATION@12
+_fgs__SAMPR_USER_PREFERENCES_INFORMATION@4
+_sgs__SAMPR_USER_PARAMETERS_INFORMATION@8
+_pgs__SAMPR_USER_PARAMETERS_INFORMATION@8
+_ggs__SAMPR_USER_PARAMETERS_INFORMATION@12
+_fgs__SAMPR_USER_PARAMETERS_INFORMATION@4
+_sgs__SAMPR_USER_LOGON_INFORMATION@8
+_pgs__SAMPR_USER_LOGON_INFORMATION@8
+_ggs__SAMPR_USER_LOGON_INFORMATION@12
+_fgs__SAMPR_USER_LOGON_INFORMATION@4
+_sgs__SAMPR_USER_ACCOUNT_INFORMATION@8
+_pgs__SAMPR_USER_ACCOUNT_INFORMATION@8
+_ggs__SAMPR_USER_ACCOUNT_INFORMATION@12
+_fgs__SAMPR_USER_ACCOUNT_INFORMATION@4
+_sgs__SAMPR_USER_A_NAME_INFORMATION@8
+_pgs__SAMPR_USER_A_NAME_INFORMATION@8
+_ggs__SAMPR_USER_A_NAME_INFORMATION@12
+_fgs__SAMPR_USER_A_NAME_INFORMATION@4
+_sgs__SAMPR_USER_F_NAME_INFORMATION@8
+_pgs__SAMPR_USER_F_NAME_INFORMATION@8
+_ggs__SAMPR_USER_F_NAME_INFORMATION@12
+_fgs__SAMPR_USER_F_NAME_INFORMATION@4
+_sgs__SAMPR_USER_NAME_INFORMATION@8
+_pgs__SAMPR_USER_NAME_INFORMATION@8
+_ggs__SAMPR_USER_NAME_INFORMATION@12
+_fgs__SAMPR_USER_NAME_INFORMATION@4
+_sgs__SAMPR_USER_HOME_INFORMATION@8
+_pgs__SAMPR_USER_HOME_INFORMATION@8
+_ggs__SAMPR_USER_HOME_INFORMATION@12
+_fgs__SAMPR_USER_HOME_INFORMATION@4
+_sgs__SAMPR_USER_SCRIPT_INFORMATION@8
+_pgs__SAMPR_USER_SCRIPT_INFORMATION@8
+_ggs__SAMPR_USER_SCRIPT_INFORMATION@12
+_fgs__SAMPR_USER_SCRIPT_INFORMATION@4
+_sgs__SAMPR_USER_PROFILE_INFORMATION@8
+_pgs__SAMPR_USER_PROFILE_INFORMATION@8
+_ggs__SAMPR_USER_PROFILE_INFORMATION@12
+_fgs__SAMPR_USER_PROFILE_INFORMATION@4
+_sgs__SAMPR_USER_ADMIN_COMMENT_INFORMATION@8
+_pgs__SAMPR_USER_ADMIN_COMMENT_INFORMATION@8
+_ggs__SAMPR_USER_ADMIN_COMMENT_INFORMATION@12
+_fgs__SAMPR_USER_ADMIN_COMMENT_INFORMATION@4
+_sgs__SAMPR_USER_WORKSTATIONS_INFORMATION@8
+_pgs__SAMPR_USER_WORKSTATIONS_INFORMATION@8
+_ggs__SAMPR_USER_WORKSTATIONS_INFORMATION@12
+_fgs__SAMPR_USER_WORKSTATIONS_INFORMATION@4
+_sgs__SAMPR_USER_LOGON_HOURS_INFORMATION@8
+_pgs__SAMPR_USER_LOGON_HOURS_INFORMATION@8
+_ggs__SAMPR_USER_LOGON_HOURS_INFORMATION@12
+_fgs__SAMPR_USER_LOGON_HOURS_INFORMATION@4
+_gns__SAMPR_USER_INTERNAL1_INFORMATION@8
+_snu__SAMPR_USER_INFO_BUFFER@12
+_sgu__SAMPR_USER_INFO_BUFFER@12
+_pnu__SAMPR_USER_INFO_BUFFER@12
+_pgu__SAMPR_USER_INFO_BUFFER@12
+_gnu__SAMPR_USER_INFO_BUFFER@12
+_ggu__SAMPR_USER_INFO_BUFFER@16
+_sgs__SAMPR_DOMAIN_DISPLAY_USER@8
+_pgs__SAMPR_DOMAIN_DISPLAY_USER@8
+_fgs__SAMPR_DOMAIN_DISPLAY_USER@4
+_sgs__SAMPR_DOMAIN_DISPLAY_MACHINE@8
+_pgs__SAMPR_DOMAIN_DISPLAY_MACHINE@8
+_fgs__SAMPR_DOMAIN_DISPLAY_MACHINE@4
+_sgs__SAMPR_DOMAIN_DISPLAY_USER_BUFFER@8
+_pgs__SAMPR_DOMAIN_DISPLAY_USER_BUFFER@8
+_fgs__SAMPR_DOMAIN_DISPLAY_USER_BUFFER@4
+_sgs__SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER@8
+_pgs__SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER@8
+_fgs__SAMPR_DOMAIN_DISPLAY_MACHINE_BUFFER@4
+_snu__SAMPR_DISPLAY_INFO_BUFFER@12
+_sgu__SAMPR_DISPLAY_INFO_BUFFER@12
+_pnu__SAMPR_DISPLAY_INFO_BUFFER@12
+_pgu__SAMPR_DISPLAY_INFO_BUFFER@12
+_fgu__SAMPR_DISPLAY_INFO_BUFFER@8
+SamIInitialize@0
+SampInitialize@0
+SampLoadPasswordFilterDll@0
+SampEnableAuditPrivilege@0
+SampInitializeDomainDescriptors@4
+SampBuildSamProtection@40
+SamrSetSecurityObject@12
+SampValidatePassedSD@8
+SamrQuerySecurityObject@12
+SampAuditOnClose@4
+SamrConnect@12
+SamIConnect@16
+SamrShutdownSamServer@4
+SamrLookupDomainInSamServer@12
+SamrEnumerateDomainsInSamServer@20
+SampGetUnicodeStringField@12
+SamrDeleteUser@4
+SampIsUserAccountControlValid@8
+SamrSetInformationUser@12
+SamrChangePasswordUser@44
+SamrGetUserDomainPasswordInformation@8
+SampReplaceUserV1aFixed@8
+SampComputePasswordExpired@8
+SampStorePasswordExpired@8
+SampStoreUserPasswords@24
+SampReplaceUserMembership@12
+SampReplaceUserLogonHours@8
+SampRetrieveUserGroupAttribute@12
+SampAddGroupToUserMembership@16
+SampRemoveMembershipUser@12
+SampSetGroupAttributesOfUser@12
+SampDeleteUserKeys@4
+SampAddPasswordHistory@24
+SampCheckPasswordHistory@28
+SampAddDeltaTime@16
+SampChangeUserAccountName@12
+SampAcquireWriteLock@0
+SampFlushThread@4
+SampCommitChanges@0
+SampRefreshRegistry@0
+SampReleaseWriteLock@4
+SampCommitAndRetainWriteLock@0
+SampRetrieveStringFromRegistry@12
+SampPutStringToRegistry@12
+SampBuildDomainSubKeyName@8
+SampBuildAliasMembersKeyName@12
+SampValidateNewAccountName@4
+SampValidateAccountNameChange@8
+SampRetrieveAccountCounts@12
+SampAdjustAccountCount@8
+SampEnumerateAccountNamesCommon@28
+SampEnumerateAccountNames@28
+SampLookupAccountRid@20
+SampLookupAccountName@12
+SampIsAccountBuiltIn@4
+SampCreateFullSid@12
+SampCreateAccountSid@8
+SampNotifyNetlogonOfDelta@24
+SampDuplicateUnicodeString@8
+SampChangeAccountOperatorAccessToMember@8
+SampChangeAccountOperatorAccessToUser@8
+SamINotifyDelta@28
+SamISetAuditingInformation@4
+SampRtlWellKnownPrivilegeCheck@12
+SampWriteEventLog@32
+SampShutdownNotification@4
+CloseHandle@4
+CreateThread@24
+DbgPrint
+ElfDeregisterEventSource@4
+ElfRegisterEventSourceW@12
+ElfReportEventW@48
+FormatMessageW@28
+GetLastError@0
+GetTimeZoneInformation@4
+NlLoadNetlogonDll@0
+I_NetNotifyDelta@40
+I_NetNotifyRole@4
+I_NetNotifyMachineAccount@20
+I_NetGetAnyDCName@8
+FreeLibrary@4
+GetProcAddress@8
+I_RpcMapWin32Status@4
+LoadLibraryA@4
+LoadLibraryW@4
+LocalFree@4
+LsaClose@4
+LsaFreeMemory@4
+LsaIAuditSamEvent@32
+LsaIFree_LSAPR_POLICY_INFORMATION@8
+LsaIOpenPolicyTrusted@4
+LsaIQueryInformationPolicyTrusted@8
+LsaOpenPolicy@16
+LsaQueryInformationPolicy@12
+LsarClose@4
+LsarQueryInformationPolicy@12
+MIDL_user_allocate@4
+MIDL_user_free@4
+MIDL_user_reallocate@8
+MIDL_user_size@4
+LocalAlloc@8
+LocalHandle@4
+LocalReAlloc@12
+LocalSize@4
+NDRSContextMarshall@12
+NDRSContextUnmarshall@8
+NDRcopy@12
+NtAccessCheckAndAuditAlarm@44
+NtAdjustPrivilegesToken@24
+NtClose@4
+NtCloseObjectAuditAlarm@12
+NtDelayExecution@8
+NtFlushKey@4
+NtOpenEvent@12
+NtOpenProcess@16
+NtOpenProcessToken@12
+NtOpenThread@16
+NtOpenThreadToken@16
+NtPrivilegeCheck@12
+NtQueryInformationToken@20
+NtQuerySystemTime@4
+NtQueryValueKey@24
+NtRestoreKey@12
+NtSetInformationToken@16
+RpcImpersonateClient@4
+RpcMgmtStopServerListening@4
+RpcRaiseException@4
+RpcRevertToSelf@0
+RpcpInitRpcServer@0
+RpcpAddInterface@8
+RpcpStartRpcServer@8
+RpcpDeleteInterface@4
+RpcpStopRpcServer@4
+EnterCriticalSection@4
+InitializeCriticalSection@4
+InitializeSecurityDescriptor@8
+LeaveCriticalSection@4
+RpcServerListen@12
+RpcServerRegisterIf@12
+RpcServerUnregisterIf@12
+RpcServerUseProtseqEpW@16
+RtlAbortRXact@4
+RtlAbsoluteToSelfRelativeSD@12
+RtlAcquireResourceExclusive@8
+RtlAddAccessAllowedAce@16
+RtlAddAce@20
+RtlAddActionToRXact@24
+RtlAddAttributeActionToRXact@32
+RtlAddAuditAccessAce@24
+RtlAdjustPrivilege@16
+RtlAllocateHeap@12
+RtlAppendUnicodeStringToString@8
+RtlAppendUnicodeToString@8
+RtlApplyRXactNoFlush@4
+RtlAreAllAccessesGranted@8
+RtlAreAnyAccessesGranted@8
+RtlAssert@16
+RtlCompareMemory@12
+RtlCompareUnicodeString@12
+RtlConvertSidToUnicodeString@12
+RtlConvertUiListToApiList@12
+RtlCopySid@12
+RtlCopyUnicodeString@8
+RtlCreateAcl@12
+RtlCreateSecurityDescriptor@8
+RtlEqualComputerName@8
+RtlEqualDomainName@8
+RtlEqualSid@8
+RtlEqualUnicodeString@12
+RtlExtendedIntegerMultiply@12
+RtlExtendedLargeIntegerDivide@16
+RtlFreeHeap@12
+RtlFreeUnicodeString@4
+RtlGetAce@12
+RtlGetDaclSecurityDescriptor@16
+RtlGetGroupSecurityDescriptor@12
+RtlGetNtProductType@4
+RtlGetOwnerSecurityDescriptor@12
+RtlGetSaclSecurityDescriptor@16
+RtlInitUnicodeString@8
+RtlInitializeRXact@12
+RtlInitializeResource@4
+RtlInitializeSid@12
+RtlIntegerToUnicodeString@12
+RtlLengthRequiredSid@4
+RtlLengthSecurityDescriptor@4
+RtlLengthSid@4
+RtlMakeSelfRelativeSD@12
+RtlMapGenericMask@8
+RtlRandom@4
+RtlReleaseResource@4
+RtlSetDaclSecurityDescriptor@16
+RtlSetGroupSecurityDescriptor@12
+RtlSetOwnerSecurityDescriptor@12
+RtlSetSaclSecurityDescriptor@16
+RtlSetSecurityObject@20
+RtlStartRXact@4
+RtlSubAuthorityCountSid@4
+RtlSubAuthoritySid@8
+RtlTimeToTimeFields@8
+RtlValidAcl@4
+RtlValidSid@4
+RtlpNtCreateKey@24
+RtlpNtEnumerateSubKey@16
+RtlpNtOpenKey@16
+RtlpNtQueryValueKey@20
+RtlpNtSetValueKey@16
+SetConsoleCtrlHandler@8
+SetProcessShutdownParameters@8
+SetSecurityDescriptorDacl@16
+SystemFunction006@8
+SystemFunction007@8
+SystemFunction013@12
+SystemFunction015@12
+SystemFunction021@12
+SystemFunction023@12
+SystemFunction024@12
+SystemFunction025@12
+SystemFunction026@12
+SystemFunction027@12
+SystemFunction029@8
+SystemFunction030@8
+SystemFunction031@8
+char_array_from_ndr@16
+char_from_ndr@8
+data_from_ndr@16
+long_array_from_ndr@16
+long_from_ndr@8
+long_from_ndr_temp@12
+midl_allocate@4
+short_from_ndr@8
+short_from_ndr_temp@12
diff --git a/private/newsam/server/samsrvp.h b/private/newsam/server/samsrvp.h
new file mode 100644
index 000000000..0dfc28ebf
--- /dev/null
+++ b/private/newsam/server/samsrvp.h
@@ -0,0 +1,3318 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ samsrvp.h
+
+Abstract:
+
+ This file contains definitions private to the SAM server program.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+#ifndef _NTSAMP_
+#define _NTSAMP_
+
+
+#ifndef UNICODE
+#define UNICODE
+#endif // UNICODE
+
+
+////////////////////////////////////////////////////////////////////////
+// //
+// //
+// Diagnostics //
+// //
+////////////////////////////////////////////////////////////////////////
+
+//
+// The following define controls the diagnostic capabilities that
+// are built into SAM.
+//
+
+#if DBG
+#define SAMP_DIAGNOSTICS 1
+#endif // DBG
+
+
+//
+// These definitions are useful diagnostics aids
+//
+
+#if SAMP_DIAGNOSTICS
+
+//
+// Diagnostics included in build
+//
+
+//
+// Test for diagnostics enabled
+//
+
+#define IF_SAMP_GLOBAL( FlagName ) \
+ if (SampGlobalFlag & (SAMP_DIAG_##FlagName))
+
+#define IF_NOT_SAMP_GLOBAL( FlagName ) \
+ if ( !(SampGlobalFlag & (SAMP_DIAG_##FlagName)) )
+
+//
+// Diagnostics print statement
+//
+
+#define SampDiagPrint( FlagName, _Text_ ) \
+ IF_SAMP_GLOBAL( FlagName ) \
+ DbgPrint _Text_
+
+
+#else
+
+//
+// No diagnostics included in build
+//
+
+//
+// Test for diagnostics enabled
+//
+
+#define IF_SAMP_GLOBAL( FlagName ) if (FALSE)
+#define IF_NOT_SAMP_GLOBAL ( FlagName ) if (TRUE)
+
+
+//
+// Diagnostics print statement (nothing)
+//
+
+#define SampDiagPrint( FlagName, Text ) ;
+
+
+#endif // SAMP_DIAGNOSTICS
+
+
+
+
+
+//
+// The following flags enable or disable various diagnostic
+// capabilities within SAM. These flags are set in
+// SampGlobalFlag.
+//
+// DISPLAY_CACHE - print diagnostic messages related
+// to the display cache (additions, deletions,
+// modifications, etc).
+//
+// DISPLAY_LOCKOUT - print diagnostic messages related
+// to account lockout.
+//
+// DISPLAY_ROLE_CHANGES - print diagnostic messages related
+// to primary/backup role changes.
+//
+// DISPLAY_CACHE_ERRORS - print diagnostic messages related to
+// errors when manipulating the display cache.
+//
+// DISPLAY_STORAGE_FAIL - print diagnostic messages related to
+// backing store failures.
+//
+// BREAK_ON_STORAGE_FAIL - breakpoint if an attempt to write
+// to backing store fails.
+//
+// CONTEXT_TRACKING - print diagnostic messages related to
+// object context usage (creation / deletion, etc.).
+//
+// ACTIVATE_DEBUG_PROC - activate a process for use as a diagnostic
+// aid. This is expected to be used only during SETUP testing.
+//
+// DISPLAY_ADMIN_CHANGES - print diagnostic messages related to
+// changing user account protection to allow or dissallow
+// Account Operator access to admin or normal user accounts.
+//
+
+#define SAMP_DIAG_DISPLAY_CACHE ((ULONG) 0x00000001L)
+#define SAMP_DIAG_DISPLAY_LOCKOUT ((ULONG) 0x00000002L)
+#define SAMP_DIAG_DISPLAY_ROLE_CHANGES ((ULONG) 0x00000004L)
+#define SAMP_DIAG_DISPLAY_CACHE_ERRORS ((ULONG) 0x00000008L)
+#define SAMP_DIAG_DISPLAY_STORAGE_FAIL ((ULONG) 0x00000010L)
+#define SAMP_DIAG_BREAK_ON_STORAGE_FAIL ((ULONG) 0x00000020L)
+#define SAMP_DIAG_CONTEXT_TRACKING ((ULONG) 0x00000040L)
+#define SAMP_DIAG_ACTIVATE_DEBUG_PROC ((ULONG) 0x00000080L)
+#define SAMP_DIAG_DISPLAY_ADMIN_CHANGES ((ULONG) 0x00000100L)
+
+
+
+
+//
+// Choose a print type appropriate to how we are building.
+//
+
+#ifdef SAMP_BUILD_CONSOLE_PROGRAM
+
+#define BldPrint printf
+
+#else
+
+#define BldPrint DbgPrint
+
+#endif
+
+#if DBG
+
+#define SUCCESS_ASSERT(Status, Msg) \
+{ \
+ if ( !NT_SUCCESS(Status) ) { \
+ UnexpectedProblem(); \
+ BldPrint(Msg); \
+ BldPrint("Status is: 0x%lx \n", Status); \
+ return(Status); \
+ \
+ } \
+}
+
+#else
+
+#define SUCCESS_ASSERT(Status, Msg) \
+{ \
+ if ( !NT_SUCCESS(Status) ) { \
+ return(Status); \
+ } \
+}
+
+#endif // DBG
+
+
+//
+// Define this symbol to get context tracking messages printed
+// (otherwise, comment it out)
+//
+
+//#define SAMP_DBG_CONTEXT_TRACKING
+
+//
+// Maximum number of digits that may be specified to
+// SampRtlConvertRidToUnicodeString
+//
+
+#define SAMP_MAXIMUM_ACCOUNT_RID_DIGITS ((ULONG) 8)
+
+//
+// Account never expires timestamp (in ULONG form )
+//
+
+#define SAMP_ACCOUNT_NEVER_EXPIRES ((ULONG) 0)
+
+
+
+//
+// SAM's shutdown order level (index).
+// Shutdown notifications are made in the order of highest level
+// to lowest level value.
+//
+
+#define SAMP_SHUTDOWN_LEVEL ((DWORD) 481)
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <nt.h>
+#include <ntrtl.h> // DbgPrint prototype
+#include <nturtl.h> // needed for winbase.h
+#include <rpc.h> // DataTypes and runtime APIs
+
+#include <string.h> // strlen
+#include <stdio.h> // sprintf
+#define UnicodeTerminate(p) ((PUNICODE_STRING)(p))->Buffer[(((PUNICODE_STRING)(p))->Length + 1)/sizeof(WCHAR)] = UNICODE_NULL
+
+#include <ntrpcp.h> // prototypes for MIDL user functions
+#include <samrpc.h> // midl generated SAM RPC definitions
+#include <ntlsa.h>
+#include <samisrv.h> // SamIConnect()
+#include <lsarpc.h>
+#include <lsaisrv.h>
+#include <ntsam.h>
+#include <ntsamp.h>
+#include <samsrv.h> // prototypes available to security process
+#include "sampmsgs.h"
+
+VOID
+UnexpectedProblem( VOID );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// TEMPORARY GenTab2 definitions //
+// These structures should be considered opaque. //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+//
+// Each element in the tree is pointed to from a leaf structure.
+// The leafs are linked together to arrange the elements in
+// ascending sorted order.
+//
+
+typedef struct _GTB_TWO_THREE_LEAF {
+
+ //
+ // Sort order list links
+ //
+
+ LIST_ENTRY SortOrderEntry;
+
+ //
+ // Pointer to element
+ //
+
+ PVOID Element;
+
+} GTB_TWO_THREE_LEAF, *PGTB_TWO_THREE_LEAF;
+
+
+
+typedef struct _GTB_TWO_THREE_NODE {
+
+ //
+ // Pointer to parent node. If this is the root node,
+ // then this pointer is null.
+ //
+
+ struct _GTB_TWO_THREE_NODE *ParentNode;
+
+
+ //
+ // Pointers to child nodes.
+ //
+ // 1) If a pointer is null, then this node does not have
+ // that child. In this case, the control value MUST
+ // indicate that the children are leaves.
+ //
+ // 2) If the children are leaves, then each child pointer
+ // is either NULL (indicating this node doesn't have
+ // that child) or points to a GTB_TWO_THREE_LEAF.
+ // If ThirdChild is Non-Null, then so is SecondChild.
+ // If SecondChild is Non-Null, then so is FirstChild.
+ // (that is, you can't have a third child without a
+ // second child, or a second child without a first
+ // child).
+ //
+
+ struct _GTB_TWO_THREE_NODE *FirstChild;
+ struct _GTB_TWO_THREE_NODE *SecondChild;
+ struct _GTB_TWO_THREE_NODE *ThirdChild;
+
+ //
+ // Flags provding control information about this node
+ //
+
+ ULONG Control;
+
+
+ //
+ // These fields point to the element that has the lowest
+ // value of all elements in the second and third subtrees
+ // (respectively). These fields are only valid if the
+ // corresponding child subtree pointer is non-null.
+ //
+
+ PGTB_TWO_THREE_LEAF LowOfSecond;
+ PGTB_TWO_THREE_LEAF LowOfThird;
+
+} GTB_TWO_THREE_NODE, *PGTB_TWO_THREE_NODE;
+
+
+//
+// The comparison function takes as input pointers to elements containing
+// user defined structures and returns the results of comparing the two
+// elements. The result must indicate whether the FirstElement
+// is GreaterThan, LessThan, or EqualTo the SecondElement.
+//
+
+typedef
+RTL_GENERIC_COMPARE_RESULTS
+(NTAPI *PRTL_GENERIC_2_COMPARE_ROUTINE) (
+ PVOID FirstElement,
+ PVOID SecondElement
+ );
+
+//
+// The allocation function is called by the generic table package whenever
+// it needs to allocate memory for the table.
+//
+
+typedef
+PVOID
+(NTAPI *PRTL_GENERIC_2_ALLOCATE_ROUTINE) (
+ CLONG ByteSize
+ );
+
+//
+// The deallocation function is called by the generic table package whenever
+// it needs to deallocate memory from the table that was allocated by calling
+// the user supplied allocation function.
+//
+
+typedef
+VOID
+(NTAPI *PRTL_GENERIC_2_FREE_ROUTINE) (
+ PVOID Buffer
+ );
+
+
+typedef struct _RTL_GENERIC_TABLE2 {
+
+ //
+ // Pointer to root node.
+ //
+
+ PGTB_TWO_THREE_NODE Root;
+
+ //
+ // Number of elements in table
+ //
+
+ ULONG ElementCount;
+
+ //
+ // Link list of leafs (and thus elements) in sort order
+ //
+
+ LIST_ENTRY SortOrderHead;
+
+
+ //
+ // Caller supplied routines
+ //
+
+ PRTL_GENERIC_2_COMPARE_ROUTINE Compare;
+ PRTL_GENERIC_2_ALLOCATE_ROUTINE Allocate;
+ PRTL_GENERIC_2_FREE_ROUTINE Free;
+
+
+} RTL_GENERIC_TABLE2, *PRTL_GENERIC_TABLE2;
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// //
+// Generic Table2 Routine Definitions... //
+// //
+//////////////////////////////////////////////////////////////////////////
+
+
+
+//NTSYSAPI
+VOID
+//NTAPI
+RtlInitializeGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PRTL_GENERIC_2_COMPARE_ROUTINE CompareRoutine,
+ PRTL_GENERIC_2_ALLOCATE_ROUTINE AllocateRoutine,
+ PRTL_GENERIC_2_FREE_ROUTINE FreeRoutine
+ );
+
+
+//NTSYSAPI
+PVOID
+//NTAPI
+RtlInsertElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PBOOLEAN NewElement
+ );
+
+
+//NTSYSAPI
+BOOLEAN
+//NTAPI
+RtlDeleteElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ );
+
+
+//NTSYSAPI
+PVOID
+//NTAPI
+RtlLookupElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ );
+
+
+//NTSYSAPI
+PVOID
+//NTAPI
+RtlEnumerateGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID *RestartKey
+ );
+
+
+//NTSYSAPI
+PVOID
+//NTAPI
+RtlRestartKeyByIndexGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ ULONG I,
+ PVOID *RestartKey
+ );
+
+//NTSYSAPI
+PVOID
+//NTAPI
+RtlRestartKeyByValueGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PVOID *RestartKey
+ );
+
+//NTSYSAPI
+ULONG
+//NTAPI
+RtlNumberElementsGenericTable2(
+ PRTL_GENERIC_TABLE2 Table
+ );
+
+//
+// The function IsGenericTableEmpty will return to the caller TRUE if
+// the generic table is empty (i.e., does not contain any elements)
+// and FALSE otherwise.
+//
+
+//NTSYSAPI
+BOOLEAN
+//NTAPI
+RtlIsGenericTable2Empty (
+ PRTL_GENERIC_TABLE2 Table
+ );
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Macros //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+//
+// This macro generates TRUE if account auditing is enabled and this
+// server is a PDC. Otherwise, this macro generates FALSE.
+//
+// SampDoAccountAuditing(
+// IN ULONG i
+// )
+//
+// Where:
+//
+// i - is the index of the domain whose state is to be checked.
+//
+
+#define SampDoAccountAuditing( i ) \
+ ((SampAccountAuditingEnabled == TRUE) && \
+ (SampDefinedDomains[i].CurrentFixed.ServerRole == DomainServerRolePrimary))
+
+//
+// VOID
+// SampSetAuditingInformation(
+// IN PPOLICY_AUDIT_EVENTS_INFO PolicyAuditEventsInfo
+// )
+//
+// Routine Description:
+//
+// This macro function sets the Audit Event Information relevant to SAM
+// given LSA Audit Events Information.
+//
+// Arguments:
+//
+// PolicyAuditEventsInfo - Pointer to Audit Events Information
+// structure.
+//
+// Return Values:
+//
+// None.
+//
+
+#define SampSetAuditingInformation( PolicyAuditEventsInfo ) { \
+ \
+ if (PolicyAuditEventsInfo->AuditingMode && \
+ (PolicyAuditEventsInfo->EventAuditingOptions[ AuditCategoryAccountManagement ] & \
+ POLICY_AUDIT_EVENT_SUCCESS) \
+ ) { \
+ \
+ SampAccountAuditingEnabled = TRUE; \
+ \
+ } else { \
+ \
+ SampAccountAuditingEnabled = FALSE; \
+ } \
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Defines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+//
+// Major and minor revision are stored as a single 32-bit
+// value with the major revision in the upper 16-bits and
+// the minor revision in the lower 16-bits.
+//
+// Major Revision: 1 - NT Version 1.0
+// Minor Revisions: 1 - NT Revision 1.0
+// 2 - NT Revision 1.0A
+//
+
+#define SAMP_MAJOR_REVISION (0x00010000)
+#define SAMP_MINOR_REVISION_V1_0 (0x00000001)
+#define SAMP_MINOR_REVISION_V1_0A (0x00000002)
+#define SAMP_MINOR_REVISION (0x00000002)
+#define SAMP_REVISION (SAMP_MAJOR_REVISION + SAMP_MINOR_REVISION)
+#define SAMP_SERVER_REVISION (SAMP_REVISION + 1)
+
+
+//
+// Maximum supported name length (in bytes) for this revision...
+//
+
+#define SAMP_MAXIMUM_NAME_LENGTH (1024)
+
+
+//
+// Maximum amount of memory anyone can ask us to spend on a single
+// request
+//
+
+#define SAMP_MAXIMUM_MEMORY_TO_USE (4096*4096)
+
+
+//
+// Maximum allowable number of object opens.
+// After this, opens will be rejected with INSUFFICIENT_RESOURCES
+//
+
+#define SAMP_MAXIMUM_ACTIVE_CONTEXTS (2048)
+
+
+//
+// The number of SAM Local Domains
+//
+
+#define SAMP_DEFINED_DOMAINS_COUNT ((ULONG) 2)
+
+//
+// Defines the maximum number of well-known (restricted) accounts
+// in the SAM database. Restricted accounts have rids less than this
+// value. User-defined accounts have rids >= this value.
+//
+
+#define SAMP_RESTRICTED_ACCOUNT_COUNT 1000
+
+
+//
+// Maximum password history length. We store OWFs (16 bytes) in
+// a string (up to 64k), so we could have up to 4k. However, that's
+// much larger than necessary, and we'd like to leave room in case
+// OWFs grow or somesuch. So we'll limit it to 1k.
+//
+
+#define SAMP_MAXIMUM_PASSWORD_HISTORY_LENGTH 1024
+
+//
+// The default group attributes to return when anybody asks for them.
+// This saves the expense of looking at the user object every time.
+//
+
+
+#define SAMP_DEFAULT_GROUP_ATTRIBUTES ( SE_GROUP_MANDATORY | \
+ SE_GROUP_ENABLED | \
+ SE_GROUP_ENABLED_BY_DEFAULT )
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// Each object has an associated set of attributes on disk. //
+// These attributes are divided into fixed-length and variable-length. //
+// Each object type defines whether its fixed and variable length //
+// attributes are stored together or separately. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+
+#define SAMP_SERVER_STORED_SEPARATELY (FALSE)
+
+#define SAMP_DOMAIN_STORED_SEPARATELY (TRUE)
+
+#define SAMP_USER_STORED_SEPARATELY (TRUE)
+
+#define SAMP_GROUP_STORED_SEPARATELY (FALSE)
+
+#define SAMP_ALIAS_STORED_SEPARATELY (FALSE)
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// Each object type has a defined set of variable length attributes. //
+// These are arranged within the object as an array of offsets and //
+// lengths (SAMP_VARIABLE_LENGTH_ATTRIBUTE data types). //
+// This section defines the offset of each variable length attribute //
+// for each object type. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+//
+// Variable length attributes common to all objects
+//
+
+#define SAMP_OBJECT_SECURITY_DESCRIPTOR (0L)
+
+
+//
+// Variable length attributes of a SERVER object
+//
+
+#define SAMP_SERVER_SECURITY_DESCRIPTOR (SAMP_OBJECT_SECURITY_DESCRIPTOR)
+
+#define SAMP_SERVER_VARIABLE_ATTRIBUTES (1L)
+
+
+//
+// Variable length attributes of a DOMAIN object
+//
+
+#define SAMP_DOMAIN_SECURITY_DESCRIPTOR (SAMP_OBJECT_SECURITY_DESCRIPTOR)
+#define SAMP_DOMAIN_SID (1L)
+#define SAMP_DOMAIN_OEM_INFORMATION (2L)
+#define SAMP_DOMAIN_REPLICA (3L)
+
+#define SAMP_DOMAIN_VARIABLE_ATTRIBUTES (4L)
+
+
+
+//
+// Variable length attributes of a USER object
+//
+
+#define SAMP_USER_SECURITY_DESCRIPTOR (SAMP_OBJECT_SECURITY_DESCRIPTOR)
+#define SAMP_USER_ACCOUNT_NAME (1L)
+#define SAMP_USER_FULL_NAME (2L)
+#define SAMP_USER_ADMIN_COMMENT (3L)
+#define SAMP_USER_USER_COMMENT (4L)
+#define SAMP_USER_PARAMETERS (5L)
+#define SAMP_USER_HOME_DIRECTORY (6L)
+#define SAMP_USER_HOME_DIRECTORY_DRIVE (7L)
+#define SAMP_USER_SCRIPT_PATH (8L)
+#define SAMP_USER_PROFILE_PATH (9L)
+#define SAMP_USER_WORKSTATIONS (10L)
+#define SAMP_USER_LOGON_HOURS (11L)
+#define SAMP_USER_GROUPS (12L)
+#define SAMP_USER_DBCS_PWD (13L)
+#define SAMP_USER_UNICODE_PWD (14L)
+#define SAMP_USER_NT_PWD_HISTORY (15L)
+#define SAMP_USER_LM_PWD_HISTORY (16L)
+
+#define SAMP_USER_VARIABLE_ATTRIBUTES (17L)
+
+
+//
+// Variable length attributes of a GROUP object
+//
+
+#define SAMP_GROUP_SECURITY_DESCRIPTOR (SAMP_OBJECT_SECURITY_DESCRIPTOR)
+#define SAMP_GROUP_NAME (1L)
+#define SAMP_GROUP_ADMIN_COMMENT (2L)
+#define SAMP_GROUP_MEMBERS (3L)
+
+#define SAMP_GROUP_VARIABLE_ATTRIBUTES (4L)
+
+
+//
+// Variable length attributes of an ALIAS object
+//
+
+#define SAMP_ALIAS_SECURITY_DESCRIPTOR (SAMP_OBJECT_SECURITY_DESCRIPTOR)
+#define SAMP_ALIAS_NAME (1L)
+#define SAMP_ALIAS_ADMIN_COMMENT (2L)
+#define SAMP_ALIAS_MEMBERS (3L)
+
+#define SAMP_ALIAS_VARIABLE_ATTRIBUTES (4L)
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Data structures used for tracking allocated memory
+//
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_MEMORY {
+ struct _SAMP_MEMORY *Next;
+ PVOID Memory;
+} SAMP_MEMORY, *PSAMP_MEMORY;
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Data structures used for enumeration
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct _SAMP_ENUMERATION_ELEMENT {
+ struct _SAMP_ENUMERATION_ELEMENT *Next;
+ SAMPR_RID_ENUMERATION Entry;
+} SAMP_ENUMERATION_ELEMENT, *PSAMP_ENUMERATION_ELEMENT;
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Data structures related to service administration
+//
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// SAM Service operation states.
+// Valid state transition diagram is:
+//
+// Initializing ----> Enabled <====> Disabled ---> Shutdown -->Terminating
+//
+
+typedef enum _SAMP_SERVICE_STATE {
+ SampServiceInitializing = 1,
+ SampServiceEnabled,
+ SampServiceDisabled,
+ SampServiceShutdown,
+ SampServiceTerminating
+} SAMP_SERVICE_STATE, *PSAMP_SERVICE_STATE;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Data structures associated with object types //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef enum _SAMP_OBJECT_TYPE {
+ SampServerObjectType = 0,
+ SampDomainObjectType,
+ SampGroupObjectType,
+ SampAliasObjectType,
+ SampUserObjectType,
+ SampUnknownObjectType // This is used as a max index value
+ // and so must follow the valid object types.
+} SAMP_OBJECT_TYPE, *PSAMP_OBJECT_TYPE;
+
+
+//
+// Object type-dependent information
+//
+
+typedef struct _SAMP_OBJECT_INFORMATION {
+
+ //
+ // Generic mapping for this object type
+ //
+
+ GENERIC_MAPPING GenericMapping;
+
+
+ //
+ // Mask of access types that are not valid for
+ // this object type when the access mask has been
+ // mapped from generic to specific access types.
+ //
+
+ ACCESS_MASK InvalidMappedAccess;
+
+
+ //
+ // Mask of accesses representing write operations. These are
+ // used on a BDC to determine if an operation should be allowed
+ // or not.
+ //
+
+ ACCESS_MASK WriteOperations;
+
+ //
+ // Name of the object type - used for auditing.
+ //
+
+ UNICODE_STRING ObjectTypeName;
+
+
+ //
+ // The following fields provide information about the attributes
+ // of this object and how they are stored on disk. These values
+ // are set at SAM initialization time and are not changed
+ // thereafter. NOTE: changing these values in the build will
+ // result in an on-disk format change - so don't change them just
+ // for the hell-of-it.
+ //
+ // FixedStoredSeparately - When TRUE indicates the fixed and
+ // variable-length attributes of the object are stored
+ // separately (in two registry-key-attributes). When FALSE,
+ // indicates they are stored together (in a single
+ // registry-key-attribute).
+ //
+ //
+ // FixedAttributesOffset - Offset from the beginning of the
+ // on-disk buffer to the beginning of the fixed-length
+ // attributes structure.
+ //
+ // VariableBufferOffset - Offset from the beginning of the
+ // on-disk buffer to the beginning of the Variable-length
+ // data buffer. If fixed and variable-length data are
+ // stored together, this will be zero.
+ //
+ // VariableArrayOffset - Offset from the beginning of the
+ // on-disk buffer to the beginning of the array of
+ // variable-length attributes descriptors.
+ //
+ // VariableDataOffset - Offset from the beginning of the
+ // on-disk buffer to the beginning of the variable-length
+ // attribute data.
+ //
+
+ BOOLEAN FixedStoredSeparately;
+ ULONG FixedAttributesOffset,
+ VariableBufferOffset,
+ VariableArrayOffset,
+ VariableDataOffset;
+
+ //
+ // Indicates the length of the fixed length information
+ // for this object type.
+ //
+
+ ULONG FixedLengthSize;
+
+ //
+ // Indicates the number of variable length attributes
+ // for this object type.
+ //
+
+ ULONG VariableAttributeCount;
+
+
+} SAMP_OBJECT_INFORMATION, *PSAMP_OBJECT_INFORMATION;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// The following structures represent the in-memory body of each //
+// object type. This is typically used to link instances of object //
+// types together, and track dynamic state information related to //
+// the object type. //
+// //
+// This information does not include the on-disk representation of //
+// the object data. That information is kept in a separate structure //
+// both on-disk and when in-memory. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// SERVER object in-memory body //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_SERVER_OBJECT {
+ ULONG Reserved1;
+} SAMP_SERVER_OBJECT, *PSAMP_SERVER_OBJECT;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// DOMAIN object in-memory body //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_DOMAIN_OBJECT {
+ ULONG Reserved1;
+} SAMP_DOMAIN_OBJECT, *PSAMP_DOMAIN_OBJECT;
+
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// USER object in-memory body //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_USER_OBJECT {
+ ULONG Rid;
+ BOOLEAN DomainPasswordInformationAccessible;
+} SAMP_USER_OBJECT, *PSAMP_USER_OBJECT;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// GROUP object in-memory body //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_GROUP_OBJECT {
+ ULONG Rid;
+} SAMP_GROUP_OBJECT, *PSAMP_GROUP_OBJECT;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// ALIAS object in-memory body //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ALIAS_OBJECT {
+ ULONG Rid;
+} SAMP_ALIAS_OBJECT, *PSAMP_ALIAS_OBJECT;
+
+
+
+
+
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// //
+// The following data structure is the in-memory context associated //
+// with an open object. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_OBJECT {
+
+ //
+ // Structure used to link this structure into lists
+ //
+
+ LIST_ENTRY ContextListEntry;
+
+ //
+ // Indicates the type of object stored.
+ // This is used to access an array of object type descriptors.
+ //
+
+ SAMP_OBJECT_TYPE ObjectType;
+
+
+ //
+ // The FixedValid and VariableValid indicate whether the data in
+ // the fixed and variable-length on-disk image buffers are valid
+ // (i.e., were read from disk) or invalid (uninitialized).
+ // TRUE indicates the attribute is valid, FALSE indicates it is not.
+ //
+
+ BOOLEAN FixedValid;
+ BOOLEAN VariableValid;
+
+
+ //
+ // The following flags indicate whether the fixed and/or variable
+ // length attributes portion of this object are dirty (i.e., have
+ // been changed since read from disk). If TRUE, then the data is
+ // dirty and will have to be flushed upon commit. These flags are
+ // only meaningful if the corresponding FixedValid or VariableValid
+ // flag is TRUE.
+ //
+ // When attributes are read from disk, the data is said to be
+ // "clean". If any changes are made to that data, then it is
+ // said to be "dirty". Dirty object attributes will be flushed
+ // to disk when the object is de-referenced at the end of a
+ // client call.
+ //
+
+ BOOLEAN FixedDirty;
+ BOOLEAN VariableDirty;
+
+
+
+ //
+ // This field points to the on-disk attributes of the object. This
+ // is one of:
+ // SAMP_ON_DISK_SERVER_OBJECT
+ // SAMP_ON_DISK_DOMAIN_OBJECT
+ // SAMP_ON_DISK_USER_OBJECT
+ // SAMP_ON_DISK_GROUP_OBJECT
+ // SAMP_ON_DISK_ALIAS_OBJECT
+ //
+ // The memory pointed to by this field is one allocation unit, even
+ // if fixed and variable length attributes are stored as seperate
+ // registry key attributes. This means that any time additions to
+ // the variable length attributes causes a new buffer to be allocated,
+ // both the fixed and variable length portions of the structure must
+ // be copied to the newly allocated memory.
+ //
+
+ PVOID OnDisk;
+
+
+ //
+ // The OnDiskAllocated, OnDiskUsed, and OnDiskFree fields describe the
+ // memory pointed to by the OnDisk field. The OnDiskAllocated field
+ // indicates how many bytes long the block of memory is. The OnDiskUsed
+ // field indicates how much of the allocated memory is already in use.
+ // The variable length attributes are all packed upon any modification
+ // so that all free space is at the end of the block. The OnDiskFree
+ // field indicates how many bytes of the allocated block are available
+ // for use (note that this should be Allocated minus Used ).
+ //
+ // NOTE: The allocated and used values will ALWAYS be rounded up to ensure
+ // they are integral multiples of 4 bytes in length. This ensures
+ // any use of these fields directly will be dword aligned.
+ //
+ // Also note that when the VariableValid flag is FALSE,
+ // then the then OnDiskUsed OnDiskFree do NOT contain valid
+ // values.
+ //
+
+ ULONG OnDiskAllocated;
+ ULONG OnDiskUsed;
+ ULONG OnDiskFree;
+
+
+
+ //
+ // Before a context handle may be used, it must be referenced.
+ // This prevents the data from being deallocated from underneath it.
+ //
+ // Note, this count reflects one reference for the open itself, and
+ // then another reference for each time the object is looked up or
+ // subsequently referenced. Therefore, a handle close should
+ // dereference the object twice - once to counter the Lookup operation
+ // and once to represent elimination of the handle itself.
+ //
+
+ ULONG ReferenceCount;
+
+
+
+ //
+ // This field indicates the accesses that the client has been granted
+ // via this context.
+ //
+
+ ACCESS_MASK GrantedAccess;
+
+
+
+ //
+ // This handle is to the root registry key of the corresponding
+ // object. If this value is NULL, then the corresponding
+ // object was created in a previous call, but has not yet been
+ // opened. This will only occur for USERS, GROUPS, and ALIASES
+ // (and DOMAINS when we support DOMAIN creation).
+ //
+
+ HANDLE RootKey;
+
+ //
+ // This is the registry name of the corresponding object. It is
+ // set when the object is created, when RootKey is null. It is
+ // used to add the attributes changes out to the RXACT in the absence
+ // of the RootKey. After being used once, it is deleted - because
+ // the next time the object is used, the LookupContext() will fill
+ // in the RootKey.
+ //
+
+ UNICODE_STRING RootName;
+
+ //
+ // If the object is other than a Server object, then this field
+ // contains the index of the domain the object is in. This provides
+ // access to things like the domain's name.
+ //
+
+ ULONG DomainIndex;
+
+ //
+ // This field indicates a context block is to be deleted.
+ // Actual deallocation of the memory for the context block
+ // will not occur until the reference count drops to zero.
+ //
+
+ BOOLEAN MarkedForDelete;
+
+ //
+ // This field is used to indicate that the client associated with
+ // this context block is to be fully trusted. When TRUE, no access
+ // checks are performed against the client. This allows a single
+ // interface to be used by both RPC clients and internal procedures.
+ //
+
+ BOOLEAN TrustedClient;
+
+
+ //
+ // This field indicates whether an audit generation routine must be
+ // called when this context block is deleted (which represents a
+ // handle being deleted).
+ //
+
+ BOOLEAN AuditOnClose;
+
+
+ //
+ // This flag is TRUE when this context is valid. It may be necessary
+ // to invalidate before we can eliminate all references to it due to
+ // the way RPC works. RPC will only allow you to invalidate a context
+ // handle when called by the client using an API that has the context
+ // handle as an OUT parameter.
+ //
+ // Since someone may delete a user or group object (which invalidates
+ // all handles to that object), we must have a way of tracking handle
+ // validity independent of RPC's method.
+ //
+
+ BOOLEAN Valid;
+
+
+
+
+
+ //
+ // The body of each object.
+
+ union {
+
+ SAMP_SERVER_OBJECT Server;
+ SAMP_DOMAIN_OBJECT Domain;
+ SAMP_GROUP_OBJECT Group;
+ SAMP_ALIAS_OBJECT Alias;
+ SAMP_USER_OBJECT User;
+
+ } TypeBody;
+
+} SAMP_OBJECT, *PSAMP_OBJECT;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Data structures used to store information in the registry
+//
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Fixed length portion of a revision 1 Server object
+//
+
+typedef struct _SAMP_V1_FIXED_LENGTH_SERVER {
+
+ ULONG RevisionLevel;
+
+} SAMP_V1_FIXED_LENGTH_SERVER, *PSAMP_V1_FIXED_LENGTH_SERVER;
+
+
+//
+// Fixed length portion of a Domain
+// (previous release formats of this structure follow)
+//
+// Note: in version 1.0 of NT, the fixed length portion of
+// a domain was stored separate from the variable length
+// portion. This allows us to compare the size of the
+// data read from disk against the size of a V1_0A form
+// of the fixed length data to determine whether it is
+// a Version 1 format or later format.
+//
+//
+
+typedef struct _SAMP_V1_0A_FIXED_LENGTH_DOMAIN {
+
+ ULONG Revision;
+ ULONG Unused1;
+
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER ModifiedCount;
+ LARGE_INTEGER MaxPasswordAge;
+ LARGE_INTEGER MinPasswordAge;
+ LARGE_INTEGER ForceLogoff;
+ LARGE_INTEGER LockoutDuration;
+ LARGE_INTEGER LockoutObservationWindow;
+ LARGE_INTEGER ModifiedCountAtLastPromotion;
+
+
+ ULONG NextRid;
+ ULONG PasswordProperties;
+
+ USHORT MinPasswordLength;
+ USHORT PasswordHistoryLength;
+
+ USHORT LockoutThreshold;
+
+ DOMAIN_SERVER_ENABLE_STATE ServerState;
+ DOMAIN_SERVER_ROLE ServerRole;
+
+ BOOLEAN UasCompatibilityRequired;
+
+} SAMP_V1_0A_FIXED_LENGTH_DOMAIN, *PSAMP_V1_0A_FIXED_LENGTH_DOMAIN;
+
+typedef struct _SAMP_V1_0_FIXED_LENGTH_DOMAIN {
+
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER ModifiedCount;
+ LARGE_INTEGER MaxPasswordAge;
+ LARGE_INTEGER MinPasswordAge;
+ LARGE_INTEGER ForceLogoff;
+
+ ULONG NextRid;
+
+ DOMAIN_SERVER_ENABLE_STATE ServerState;
+ DOMAIN_SERVER_ROLE ServerRole;
+
+ USHORT MinPasswordLength;
+ USHORT PasswordHistoryLength;
+ ULONG PasswordProperties;
+
+ BOOLEAN UasCompatibilityRequired;
+
+} SAMP_V1_0_FIXED_LENGTH_DOMAIN, *PSAMP_V1_0_FIXED_LENGTH_DOMAIN;
+
+
+
+
+
+
+//
+// Fixed length portion of a revision 1 group account
+//
+// Note: MemberCount could be treated as part of the fixed length
+// data, but it is more convenient to keep it with the Member RID
+// list in the MEMBERS key.
+//
+
+typedef struct _SAMP_V1_FIXED_LENGTH_GROUP {
+
+ ULONG RelativeId;
+ ULONG Attributes;
+ UCHAR AdminGroup;
+
+} SAMP_V1_FIXED_LENGTH_GROUP, *PSAMP_V1_FIXED_LENGTH_GROUP;
+
+typedef struct _SAMP_V1_0A_FIXED_LENGTH_GROUP {
+
+ ULONG Revision;
+ ULONG RelativeId;
+ ULONG Attributes;
+ ULONG Unused1;
+ UCHAR AdminCount;
+ UCHAR OperatorCount;
+
+} SAMP_V1_0A_FIXED_LENGTH_GROUP, *PSAMP_V1_0A_FIXED_LENGTH_GROUP;
+
+
+//
+// Fixed length portion of a revision 1 alias account
+//
+// Note: MemberCount could be treated as part of the fixed length
+// data, but it is more convenient to keep it with the Member RID
+// list in the MEMBERS key.
+//
+
+typedef struct _SAMP_V1_FIXED_LENGTH_ALIAS {
+
+ ULONG RelativeId;
+
+} SAMP_V1_FIXED_LENGTH_ALIAS, *PSAMP_V1_FIXED_LENGTH_ALIAS;
+
+
+
+//
+// Fixed length portion of a user account
+// (previous release formats of this structure follow)
+//
+// Note: GroupCount could be treated as part of the fixed length
+// data, but it is more convenient to keep it with the Group RID
+// list in the GROUPS key.
+//
+// Note: in version 1.0 of NT, the fixed length portion of
+// a user was stored separate from the variable length
+// portion. This allows us to compare the size of the
+// data read from disk against the size of a V1_0A form
+// of the fixed length data to determine whether it is
+// a Version 1 format or later format.
+
+
+//
+// This is the fixed length user from NT3.51 QFE and SUR
+//
+
+
+typedef struct _SAMP_V1_0A_FIXED_LENGTH_USER {
+
+ ULONG Revision;
+ ULONG Unused1;
+
+ LARGE_INTEGER LastLogon;
+ LARGE_INTEGER LastLogoff;
+ LARGE_INTEGER PasswordLastSet;
+ LARGE_INTEGER AccountExpires;
+ LARGE_INTEGER LastBadPasswordTime;
+
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ ULONG UserAccountControl;
+
+ USHORT CountryCode;
+ USHORT CodePage;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ USHORT AdminCount;
+ USHORT Unused2;
+ USHORT OperatorCount;
+
+} SAMP_V1_0A_FIXED_LENGTH_USER, *PSAMP_V1_0A_FIXED_LENGTH_USER;
+
+//
+// This is the fixed length user from NT3.5 and NT3.51
+//
+
+
+typedef struct _SAMP_V1_0_FIXED_LENGTH_USER {
+
+ ULONG Revision;
+ ULONG Unused1;
+
+ LARGE_INTEGER LastLogon;
+ LARGE_INTEGER LastLogoff;
+ LARGE_INTEGER PasswordLastSet;
+ LARGE_INTEGER AccountExpires;
+ LARGE_INTEGER LastBadPasswordTime;
+
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ ULONG UserAccountControl;
+
+ USHORT CountryCode;
+ USHORT CodePage;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ USHORT AdminCount;
+
+} SAMP_V1_0_FIXED_LENGTH_USER, *PSAMP_V1_0_FIXED_LENGTH_USER;
+
+
+//
+// This is the fixed length user from NT3.1
+//
+
+typedef struct _SAMP_V1_FIXED_LENGTH_USER {
+
+ LARGE_INTEGER LastLogon;
+ LARGE_INTEGER LastLogoff;
+ LARGE_INTEGER PasswordLastSet;
+ LARGE_INTEGER AccountExpires;
+
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ ULONG UserAccountControl;
+
+ USHORT CountryCode;
+ USHORT CodePage;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ USHORT AdminCount;
+
+
+} SAMP_V1_FIXED_LENGTH_USER, *PSAMP_V1_FIXED_LENGTH_USER;
+
+
+//
+// Domain account information is cached in memory in a sorted list.
+// This allows fast return of information to user-interactive clients.
+// One of these structures is part of the in-memory information for each domain.
+//
+
+typedef struct _PSAMP_DOMAIN_DISPLAY_INFORMATION {
+
+ RTL_GENERIC_TABLE2 RidTable;
+ ULONG TotalBytesInRidTable;
+
+ RTL_GENERIC_TABLE2 UserTable;
+ ULONG TotalBytesInUserTable;
+
+ RTL_GENERIC_TABLE2 MachineTable;
+ ULONG TotalBytesInMachineTable;
+
+ RTL_GENERIC_TABLE2 InterdomainTable;
+ ULONG TotalBytesInInterdomainTable;
+
+ RTL_GENERIC_TABLE2 GroupTable;
+ ULONG TotalBytesInGroupTable;
+
+
+ //
+ // These fields specify whether the cached information is valid.
+ // If TRUE, the cache contains valid information
+ // If FALSE, trees are empty.
+ //
+
+ BOOLEAN UserAndMachineTablesValid;
+ BOOLEAN GroupTableValid;
+
+} SAMP_DOMAIN_DISPLAY_INFORMATION, *PSAMP_DOMAIN_DISPLAY_INFORMATION;
+
+
+//
+// Domain account information data structure used to pass data to the
+// cache manipulation routines. This structure is the union of the cached
+// data for all the account types that we keep in the cache. Other SAM routines
+// can call fill this structure in without knowing which type of account
+// requires which elements.
+//
+
+typedef struct _SAMP_ACCOUNT_DISPLAY_INFO {
+ ULONG Rid;
+ ULONG AccountControl; // Also used as Attributes for groups
+ UNICODE_STRING Name;
+ UNICODE_STRING Comment;
+ UNICODE_STRING FullName;
+
+} SAMP_ACCOUNT_DISPLAY_INFO, *PSAMP_ACCOUNT_DISPLAY_INFO;
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Alias Membership Lists. //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_AL_REFERENCED_DOMAIN {
+
+ ULONG DomainReference;
+ PSID DomainSid;
+
+} SAMP_AL_REFERENCED_DOMAIN, *PSAMP_AL_REFERENCED_DOMAIN;
+
+typedef struct _SAMP_AL_REFERENCED_DOMAIN_LIST {
+
+ ULONG SRMaximumLength;
+ ULONG SRUsedLength;
+ ULONG MaximumCount;
+ ULONG UsedCount;
+ PSAMP_AL_REFERENCED_DOMAIN Domains;
+
+} SAMP_AL_REFERENCED_DOMAIN_LIST, *PSAMP_AL_REFERENCED_DOMAIN_LIST;
+
+typedef struct _SAMP_AL_SR_REFERENCED_DOMAIN {
+
+ ULONG Length;
+ ULONG DomainReference;
+ SID DomainSid;
+
+} SAMP_AL_SR_REFERENCED_DOMAIN, *PSAMP_AL_SR_REFERENCED_DOMAIN;
+
+typedef struct _SAMP_AL_SR_REFERENCED_DOMAIN_LIST {
+
+ ULONG SRMaximumLength;
+ ULONG SRUsedLength;
+ ULONG MaximumCount;
+ ULONG UsedCount;
+ SAMP_AL_SR_REFERENCED_DOMAIN Domains[ANYSIZE_ARRAY];
+
+} SAMP_AL_SR_REFERENCED_DOMAIN_LIST, *PSAMP_AL_SR_REFERENCED_DOMAIN_LIST;
+
+
+//
+// The Alias Membership Lists are global data structures maintained by SAM
+// to provide rapid retrieval of Alias membership information. There are
+// two types of Lists, the Alias Member List which is used to retrieve members
+// of Aliases and the Member Alias List which is used to retrieve the aliases
+// that members belong to. A pair of these lists exists for each local
+// SAM Domain (currently, the BUILTIN and Accounts domain are the only two)
+//
+// Currently, these lists are used as memory caches. They are generated at
+// system boot from the information stored in the SAM Database in the Registry
+// and SAM keeps them up to date when Alias memberships change. Thus SAM
+// API which perform lookup/read operations can use these lists instead of
+// accessing the Registry keys directly. At a future date, it may be possible
+// to back up the lists directly to the Registry and make obsolete the current
+// information for Alias membership stored there. Because these lists are
+// used as caches, they can be invalidated when the going gets tough, in which
+// case, API will read their information directly from the Registry.
+//
+// Alias Member List
+//
+// This is the 'Alias-to-Member' List. Given one or more Aliases, it is used to
+// find their members. One of these lists exists for each local SAM Domain.
+// The Alias Member List specifies all/ of the information describing aliases
+// in the local SAM Domain. It is designed for fast retrieval of alias
+// membership information for an account given the account's Sid.
+//
+// An Alias Member List is structured. For each Alias in the list, the accounts that
+// are mebers of the Alias are classified by their Referenced Domain. If an
+// account is a member of n aliases in the SAM Local Domain to which an Alias
+// List relates, there will be n entries for the account in the Alias Member List -
+//
+// are classified by domain. If an AccountSid is a member of n aliases in a given SAM
+// Local Domain, there are n entries for it in the Alias Member List.
+//
+// The structure of an Alias Member List consists of three levels. These are, from
+// the top down:
+//
+// * The Alias Member List structure (SAMP_AL_ALIAS_LIST)
+//
+// The Alias Member List structure specifies all aliases in the local SAM Domain.
+// One of these exists per local SAM domain. It contains a list of Alias
+// structures.
+//
+// * The Alias structure
+//
+// One Alias structure exists for each alias in the local SAM Domain. An
+// Alias structure contains an array of Domain structures.
+//
+// * The Domain structure
+//
+// The Domain structure describes a Domain which has one or more accounts
+// belonging to one or more aliases in the local SAM domain. The structure
+// contains a list of these member accounts.
+//
+// The entire Alias Member List is self relative, facilitating easy storage and
+// retrieval from backing storage.
+//
+
+typedef struct _SAMP_AL_DOMAIN {
+
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG DomainReference;
+ ULONG RidCount;
+ ULONG Rids[ANYSIZE_ARRAY];
+
+} SAMP_AL_DOMAIN, *PSAMP_AL_DOMAIN;
+
+typedef struct _SAMP_AL_ALIAS {
+
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG AliasRid;
+ ULONG DomainCount;
+ SAMP_AL_DOMAIN Domains[ANYSIZE_ARRAY];
+
+} SAMP_AL_ALIAS, *PSAMP_AL_ALIAS;
+
+typedef struct _SAMP_AL_ALIAS_MEMBER_LIST {
+
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG AliasCount;
+ ULONG DomainIndex;
+ ULONG Enabled;
+ SAMP_AL_ALIAS Aliases[ANYSIZE_ARRAY];
+
+} SAMP_AL_ALIAS_MEMBER_LIST, *PSAMP_AL_ALIAS_MEMBER_LIST;
+
+//
+// Member Alias List.
+//
+// This is the 'Member to Alias' List. Given one or more member account Sids,
+// this list is used to find all the Aliases to which one or more of the
+// members belongs. One Member Alias List exists for each local SAM Domain.
+// The list contains all of the membership relationships for aliases in the
+// Domain. The member accounts are grouped by sorted Rid within Domain
+// Sid, and for each Rid the list contains an array of the Rids of the Aliases
+// to which it belongs.
+//
+// This list is implemented in a Self-Relative format for easy backup and
+// restore. For now, the list is being used simply as a cache, which is
+// constructed at system load, and updated whenever membership relationships
+// change. When the going gets tough, we just ditch the cache. Later, it
+// may be desirable to save this list to a backing store (e.g. to a Registry
+// Key)
+//
+// The list is implemented as a 3-tier hierarchy. These are described
+// from the top down.
+//
+// Member Alias List (SAMP_AL_MEMBER_ALIAS_LIST)
+//
+// This top-level structure contains the list header. The list header
+// contains a count of the Member Domains and also the DomainIndex of the
+// SAM Local Domain to which the list relates.
+//
+// Member Domain
+//
+// One of these exists for each Domain that contains one or more accounts
+// that are members of one or more Aliases in the SAM local Domain.
+//
+// Member Account
+//
+// One of these exists for each account that is a member of one or more
+// Aliases in the SAM Local Domain. A Member Account structure specifies
+// the Rid of the member and the Rid of the Aliases to which it belongs
+// (only Aliases in the associated local SAM Domain are listed).
+//
+
+typedef struct _SAMP_AL_MEMBER_ACCOUNT {
+
+ ULONG Signature;
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG Rid;
+ ULONG AliasCount;
+ ULONG AliasRids[ ANYSIZE_ARRAY];
+
+} SAMP_AL_MEMBER_ACCOUNT, *PSAMP_AL_MEMBER_ACCOUNT;
+
+typedef struct _SAMP_AL_MEMBER_DOMAIN {
+
+ ULONG Signature;
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG RidCount;
+ SID DomainSid;
+
+} SAMP_AL_MEMBER_DOMAIN, *PSAMP_AL_MEMBER_DOMAIN;
+
+typedef struct _SAMP_AL_MEMBER_ALIAS_LIST {
+
+ ULONG Signature;
+ ULONG MaximumLength;
+ ULONG UsedLength;
+ ULONG DomainIndex;
+ ULONG DomainCount;
+ SAMP_AL_MEMBER_DOMAIN MemberDomains[ANYSIZE_ARRAY];
+
+} SAMP_AL_MEMBER_ALIAS_LIST, *PSAMP_AL_MEMBER_ALIAS_LIST;
+
+//
+// Alias Information
+//
+// This is the top level structure which connects the Lists. One of these
+// appears in the SAMP_DEFINED_DOMAINS structure.
+//
+// The connection between the lists is as follows
+//
+// SAMP_DEFINED_DOMAINS Contains SAMP_AL_ALIAS_INFORMATION
+//
+// SAMP_AL_ALIAS_INFORMATION contains pointers to
+// SAMP_AL_ALIAS_MEMBER_LIST and SAMP_AL_MEMBER_ALIAS_LIST
+//
+// SAMP_AL_ALIAS_MEMBER_LIST and SAMP_AL_MEMBER_ALIAS_LIST contain
+// the DomainIndex of the SAMP_DEFINED_DOMAINS structure.
+//
+// Thus it is possible to navigate from any list to any other.
+//
+
+typedef struct _SAMP_AL_ALIAS_INFORMATION {
+
+ BOOLEAN Valid;
+ UNICODE_STRING AliasMemberListKeyName;
+ UNICODE_STRING MemberAliasListKeyName;
+
+ HANDLE AliasMemberListKeyHandle;
+ HANDLE MemberAliasListKeyHandle;
+
+ PSAMP_AL_ALIAS_MEMBER_LIST AliasMemberList;
+ PSAMP_AL_MEMBER_ALIAS_LIST MemberAliasList;
+
+ SAMP_AL_REFERENCED_DOMAIN_LIST ReferencedDomainList;
+
+} SAMP_AL_ALIAS_INFORMATION, *PSAMP_AL_ALIAS_INFORMATION;
+
+typedef struct _SAMP_AL_SPLIT_MEMBER_SID {
+
+ ULONG Rid;
+ PSID DomainSid;
+ PSAMP_AL_MEMBER_DOMAIN MemberDomain;
+
+} SAMP_AL_SPLIT_MEMBER_SID, *PSAMP_AL_SPLIT_MEMBER_SID;
+
+typedef struct _SAMP_AL_SPLIT_MEMBER_SID_LIST {
+
+ ULONG Count;
+ PSAMP_AL_SPLIT_MEMBER_SID Sids;
+
+} SAMP_AL_SPLIT_MEMBER_SID_LIST, *PSAMP_AL_SPLIT_MEMBER_SID_LIST;
+
+
+/////////////////////////////////////////////////////////////////////////////
+// //
+// Information about each domain that is kept readily available in memory //
+// //
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _PSAMP_DEFINED_DOMAINS {
+
+ //
+ // This field contains a handle to a context open to the domain object.
+ // This handle can be used to reference in-memory copies of all
+ // attributes and is used when writing out changes to the object.
+ //
+
+ PSAMP_OBJECT Context;
+
+ //
+ // (Should keep the domain's security descriptor here)
+ //
+
+
+
+
+ //
+ // This field contains the SID of the domain.
+ //
+
+ PSID Sid;
+
+ //
+ // This field contains the external name of this domain. This is the
+ // name by which the domain is known outside SAM and is the name
+ // recorded by the LSA in the PolicyAccountDomainInformation
+ // information class for the Policy Object.
+ //
+
+ UNICODE_STRING ExternalName;
+
+ //
+ // This field contains the internal name of this domain. This is the
+ // name by which the domain is known inside SAM. It is set at
+ // installation and never changes.
+ //
+
+ UNICODE_STRING InternalName;
+
+ //
+ // These fields contain standard security descriptors for new user,
+ // group, and alias accounts within the corresponding domain.
+ //
+ // The following security descriptors are prepared:
+ //
+ // AdminUserSD - Contains a SD appropriate for applying to
+ // a user object that is a member of the ADMINISTRATORS
+ // alias.
+ //
+ // AdminGroupSD - Contains a SD appropriate for applying to
+ // a group object that is a member of the ADMINISTRATORS
+ // alias.
+ //
+ // NormalUserSD - Contains a SD appropriate for applying to
+ // a user object that is NOT a member of the ADMINISTRATORS
+ // alias.
+ //
+ // NormalGroupSD - Contains a SD appropriate for applying to
+ // a Group object that is NOT a member of the ADMINISTRATORS
+ // alias.
+ //
+ // NormalAliasSD - Contains a SD appropriate for applying to
+ // newly created alias objects.
+ //
+ //
+ //
+ // Additionally, the following related information is provided:
+ //
+ // AdminUserRidPointer
+ // NormalUserRidPointer
+ //
+ // Points to the last RID of the ACE in the corresponding
+ // SD's DACL which grants access to the user. This rid
+ // must be replaced with the user's rid being the SD is
+ // applied to the user object.
+ //
+ //
+ //
+ // AdminUserSDLength
+ // AdminGroupSDLength
+ // NormalUserSDLength
+ // NormalGroupSDLength
+ // NormalAliasSDLength
+ //
+ // The length, in bytes, of the corresponding security
+ // descriptor.
+ //
+
+ PSECURITY_DESCRIPTOR
+ AdminUserSD,
+ AdminGroupSD,
+ NormalUserSD,
+ NormalGroupSD,
+ NormalAliasSD;
+
+ PULONG AdminUserRidPointer,
+ NormalUserRidPointer;
+
+ ULONG AdminUserSDLength,
+ AdminGroupSDLength,
+ NormalUserSDLength,
+ NormalGroupSDLength,
+ NormalAliasSDLength;
+
+
+ //
+ // Context blocks for open objects in this domain are kept in the
+ // following lists. Both valid and invalid objects are kept on the
+ // list. They are removed upon deletion.
+ //
+
+ LIST_ENTRY GroupContextHead,
+ AliasContextHead,
+ UserContextHead;
+
+
+ //
+ // There are two copies of the fixed length domain information.
+ // When a transaction is started, the "UnmodifiedFixed" field is copied
+ // to the "CurrentFixed" field. The CurrentFixed field is the field
+ // all operations should be performed on (like allocating new RIDs).
+ // When a write-lock is released, the CurrentFixed information will
+ // either be automatically written out (if the transaction is to be
+ // committed) or discarded (if the transaction is to be rolled back).
+ // If the transaction is committed, then the CurrentField will also be
+ // copied to the UnmodifiedFixed field, making it available for the next
+ // transaction.
+ //
+ // This allows an operation to proceed, operating on fields
+ // (specifically, the NextRid and ModifiedCount fields) without
+ // regard to whether the operation will ultimately be committed or
+ // rolled back.
+ //
+
+ SAMP_V1_0A_FIXED_LENGTH_DOMAIN
+ CurrentFixed,
+ UnmodifiedFixed;
+
+
+ //
+ // Cached display information
+ //
+
+ SAMP_DOMAIN_DISPLAY_INFORMATION DisplayInformation;
+
+ //
+ // Cached Alias Information
+ //
+
+ SAMP_AL_ALIAS_INFORMATION AliasInformation;
+
+} SAMP_DEFINED_DOMAINS, *PSAMP_DEFINED_DOMAINS;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// This structure is used to describe where the data for //
+// an object's variable length attribute is. This is a //
+// self-relative structure, allowing it to be stored on disk //
+// and later retrieved and used without fixing pointers. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+
+typedef struct _SAMP_VARIABLE_LENGTH_ATTRIBUTE {
+ //
+ // Indicates the offset of data from the address of this data
+ // structure.
+ //
+
+ LONG Offset;
+
+
+ //
+ // Indicates the length of the data.
+ //
+
+ ULONG Length;
+
+
+ //
+ // A 32-bit value that may be associated with each variable
+ // length attribute. This may be used, for example, to indicate
+ // how many elements are in the variable-length attribute.
+ //
+
+ ULONG Qualifier;
+
+} SAMP_VARIABLE_LENGTH_ATTRIBUTE, *PSAMP_VARIABLE_LENGTH_ATTRIBUTE;
+
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// The following structures represent the On-Disk Structure of each //
+// object type. Each object has a fixed length data portion and a //
+// variable length data portion. Information in the object type //
+// descriptor indicates how many variable length attributes the object //
+// has and whether the fixed and variable length data are stored //
+// together in one registry key attribute, or, alternatively, each is //
+// stored in its own registry key attribute. //
+// //
+// //
+// //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// SERVER object on-disk structure //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ON_DISK_SERVER_OBJECT {
+
+
+ //
+ // This field is needed for registry i/o operations.
+ // This marks the beginning of the i/o buffer address.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header1;
+
+
+ //
+ // This field contains the fixed length attributes of the object
+ //
+
+ SAMP_V1_FIXED_LENGTH_SERVER V1Fixed;
+
+
+#if SAMP_SERVER_STORED_SEPARATELY
+
+ //
+ // This header is needed for registry operations if fixed and
+ // variable length attributes are stored separately. This
+ // field marks the beginning of the i/o buffer address for
+ // variable-length attribute i/o.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header2;
+#endif //SAMP_SERVER_STORED_SEPARATELY
+
+ //
+ // Elements of this array point to variable-length attribute
+ // values.
+ //
+
+ SAMP_VARIABLE_LENGTH_ATTRIBUTE Attribute[SAMP_SERVER_VARIABLE_ATTRIBUTES];
+
+
+} SAMP_ON_DISK_SERVER_OBJECT, *PSAMP_ON_DISK_SERVER_OBJECT;
+
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// DOMAIN object on-disk structure //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ON_DISK_DOMAIN_OBJECT {
+
+
+ //
+ // This field is needed for registry i/o operations.
+ // This marks the beginning of the i/o buffer address.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header1;
+
+
+ //
+ // This field contains the fixed length attributes of the object
+ //
+
+ SAMP_V1_0A_FIXED_LENGTH_DOMAIN V1Fixed;
+
+
+#if SAMP_DOMAIN_STORED_SEPARATELY
+
+ //
+ // This header is needed for registry operations if fixed and
+ // variable length attributes are stored separately. This
+ // field marks the beginning of the i/o buffer address for
+ // variable-length attribute i/o.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header2;
+#endif //SAMP_DOMAIN_STORED_SEPARATELY
+
+ //
+ // Elements of this array point to variable-length attribute
+ // values.
+ //
+
+ SAMP_VARIABLE_LENGTH_ATTRIBUTE Attribute[SAMP_DOMAIN_VARIABLE_ATTRIBUTES];
+
+
+} SAMP_ON_DISK_DOMAIN_OBJECT, *PSAMP_ON_DISK_DOMAIN_OBJECT;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// USER object on-disk structure //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ON_DISK_USER_OBJECT {
+
+
+ //
+ // This field is needed for registry i/o operations.
+ // This marks the beginning of the i/o buffer address.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header1;
+
+
+ //
+ // This field contains the fixed length attributes of the object
+ //
+
+ SAMP_V1_0A_FIXED_LENGTH_USER V1Fixed;
+
+
+#if SAMP_USER_STORED_SEPARATELY
+
+ //
+ // This header is needed for registry operations if fixed and
+ // variable length attributes are stored separately. This
+ // field marks the beginning of the i/o buffer address for
+ // variable-length attribute i/o.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header2;
+#endif //SAMP_USER_STORED_SEPARATELY
+
+ //
+ // Elements of this array point to variable-length attribute
+ // values.
+ //
+
+ SAMP_VARIABLE_LENGTH_ATTRIBUTE Attribute[SAMP_USER_VARIABLE_ATTRIBUTES];
+
+
+} SAMP_ON_DISK_USER_OBJECT, *PSAMP_ON_DISK_USER_OBJECT;
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// GROUP object on-disk structure //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ON_DISK_GROUP_OBJECT {
+
+
+ //
+ // This field is needed for registry i/o operations.
+ // This marks the beginning of the i/o buffer address.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header1;
+
+
+ //
+ // This field contains the fixed length attributes of the object
+ //
+
+ SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed;
+
+
+#if SAMP_GROUP_STORED_SEPARATELY
+
+ //
+ // This header is needed for registry operations if fixed and
+ // variable length attributes are stored separately. This
+ // field marks the beginning of the i/o buffer address for
+ // variable-length attribute i/o.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header2;
+#endif //SAMP_GROUP_STORED_SEPARATELY
+
+ //
+ // Elements of this array point to variable-length attribute
+ // values.
+ //
+
+ SAMP_VARIABLE_LENGTH_ATTRIBUTE Attribute[SAMP_GROUP_VARIABLE_ATTRIBUTES];
+
+
+} SAMP_ON_DISK_GROUP_OBJECT, *PSAMP_ON_DISK_GROUP_OBJECT;
+
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// ALIAS object on-disk structure //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+typedef struct _SAMP_ON_DISK_ALIAS_OBJECT {
+
+
+ //
+ // This field is needed for registry i/o operations.
+ // This marks the beginning of the i/o buffer address.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header1;
+
+
+ //
+ // This field contains the fixed length attributes of the object
+ //
+
+ SAMP_V1_FIXED_LENGTH_ALIAS V1Fixed;
+
+
+#if SAMP_ALIAS_STORED_SEPARATELY
+
+ //
+ // This header is needed for registry operations if fixed and
+ // variable length attributes are stored separately. This
+ // field marks the beginning of the i/o buffer address for
+ // variable-length attribute i/o.
+ //
+
+ KEY_VALUE_PARTIAL_INFORMATION Header2;
+#endif //SAMP_ALIAS_STORED_SEPARATELY
+
+ //
+ // Elements of this array point to variable-length attribute
+ // values.
+ //
+
+ SAMP_VARIABLE_LENGTH_ATTRIBUTE Attribute[SAMP_ALIAS_VARIABLE_ATTRIBUTES];
+
+
+} SAMP_ON_DISK_ALIAS_OBJECT, *PSAMP_ON_DISK_ALIAS_OBJECT;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Enumerated types for manipulating group memberships //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum _SAMP_MEMBERSHIP_DELTA {
+ AddToAdmin,
+ NoChange,
+ RemoveFromAdmin
+} SAMP_MEMBERSHIP_DELTA, *PSAMP_MEMBERSHIP_DELTA;
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// The following prototypes are usable throughout the process that SAM //
+// resides in. THESE ROUTINES MUST NOT BE CALLED BY NON-SAM CODE ! //
+// //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// SAM's shutdown notification routine
+//
+
+
+BOOL SampShutdownNotification( DWORD dwCtrlType );
+
+
+//
+// Sub-Component initialization routines
+//
+
+BOOLEAN SampInitializeDomainObject(VOID);
+
+NTSTATUS SampInitializeRegistry ( VOID );
+
+NTSTATUS
+SampReInitializeSingleDomain(
+ ULONG Index
+ );
+
+//
+// database lock related services
+//
+
+VOID
+SampAcquireReadLock(VOID);
+
+VOID
+SampReleaseReadLock(VOID);
+
+
+NTSTATUS
+SampAcquireWriteLock( VOID );
+
+VOID
+SampSetTransactionDomain(
+ IN ULONG DomainIndex
+ );
+
+NTSTATUS
+SampCommitAndRetainWriteLock(
+ VOID
+ );
+
+NTSTATUS
+SampReleaseWriteLock(
+ IN BOOLEAN Commit
+ );
+
+
+//
+// Context block manipulation services
+//
+
+PSAMP_OBJECT
+SampCreateContext(
+ IN SAMP_OBJECT_TYPE Type,
+ IN BOOLEAN TrustedClient
+ );
+
+VOID
+SampDeleteContext(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampLookupContext(
+ IN PSAMP_OBJECT Context,
+ IN ACCESS_MASK DesiredAccess,
+ IN SAMP_OBJECT_TYPE ExpectedType,
+ OUT PSAMP_OBJECT_TYPE FoundType
+ );
+
+VOID
+SampReferenceContext(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampDeReferenceContext(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN Commit
+ );
+
+
+VOID
+SampInvalidateContextAddress(
+ IN PSAMP_OBJECT Context
+ );
+
+VOID
+SampInvalidateGroupContexts(
+ IN ULONG Rid
+ );
+
+VOID
+SampInvalidateAliasContexts(
+ IN ULONG Rid
+ );
+
+
+VOID
+SampInvalidateUserContexts(
+ IN ULONG Rid
+ );
+
+VOID
+SampInvalidateContextListKeys(
+ IN PLIST_ENTRY Head,
+ IN BOOLEAN Close
+ );
+
+#ifdef SAMP_DBG_CONTEXT_TRACKING
+VOID
+SampDumpContexts(
+ VOID
+ );
+#endif
+
+//
+// Unicode String related services - These use MIDL_user_allocate and
+// MIDL_user_free so that the resultant strings can be given to the
+// RPC runtime.
+//
+
+NTSTATUS
+SampInitUnicodeString(
+ OUT PUNICODE_STRING String,
+ IN USHORT MaximumLength
+ );
+
+NTSTATUS
+SampAppendUnicodeString(
+ IN OUT PUNICODE_STRING Target,
+ IN PUNICODE_STRING StringToAdd
+ );
+
+VOID
+SampFreeUnicodeString(
+ IN PUNICODE_STRING String
+ );
+
+VOID
+SampFreeOemString(
+ IN POEM_STRING String
+ );
+
+NTSTATUS
+SampDuplicateUnicodeString(
+ IN PUNICODE_STRING OutString,
+ IN PUNICODE_STRING InString
+ );
+
+NTSTATUS
+SampUnicodeToOemString(
+ IN POEM_STRING OutString,
+ IN PUNICODE_STRING InString
+ );
+
+NTSTATUS
+SampBuildDomainSubKeyName(
+ OUT PUNICODE_STRING KeyName,
+ IN PUNICODE_STRING SubKeyName OPTIONAL
+ );
+
+
+NTSTATUS
+SampRetrieveStringFromRegistry(
+ IN HANDLE ParentKey,
+ IN PUNICODE_STRING SubKeyName,
+ OUT PUNICODE_STRING Body
+ );
+
+
+NTSTATUS
+SampPutStringToRegistry(
+ IN BOOLEAN RelativeToDomain,
+ IN PUNICODE_STRING SubKeyName,
+ IN PUNICODE_STRING Body
+ );
+
+
+//
+// user, group and alias Account services
+//
+
+
+NTSTATUS
+SampBuildAccountKeyName(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ OUT PUNICODE_STRING AccountKeyName,
+ IN PUNICODE_STRING AccountName
+ );
+
+NTSTATUS
+SampBuildAccountSubKeyName(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ OUT PUNICODE_STRING AccountKeyName,
+ IN ULONG AccountRid,
+ IN PUNICODE_STRING SubKeyName OPTIONAL
+ );
+
+NTSTATUS
+SampBuildAliasMembersKeyName(
+ IN PSID AccountSid,
+ OUT PUNICODE_STRING DomainKeyName,
+ OUT PUNICODE_STRING AccountKeyName
+ );
+
+NTSTATUS
+SampValidateNewAccountName(
+ PUNICODE_STRING NewAccountName
+ );
+
+NTSTATUS
+SampValidateAccountNameChange(
+ IN PUNICODE_STRING NewAccountName,
+ IN PUNICODE_STRING OldAccountName
+ );
+
+NTSTATUS
+SampIsAccountBuiltIn(
+ ULONG Rid
+ );
+
+
+
+NTSTATUS
+SampAdjustAccountCount(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Increment
+ );
+
+NTSTATUS
+SampRetrieveAccountCounts(
+ OUT PULONG UserCount,
+ OUT PULONG GroupCount,
+ OUT PULONG AliasCount
+ );
+
+
+NTSTATUS
+SampEnumerateAccountNamesCommon(
+ IN SAMPR_HANDLE DomainHandle,
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ IN ULONG Filter,
+ OUT PULONG CountReturned
+ );
+
+
+NTSTATUS
+SampEnumerateAccountNames(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ IN ULONG Filter,
+ OUT PULONG CountReturned,
+ IN BOOLEAN TrustedClient
+ );
+
+NTSTATUS
+SampLookupAccountRid(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN PUNICODE_STRING Name,
+ IN NTSTATUS NotFoundStatus,
+ OUT PULONG Rid,
+ OUT PSID_NAME_USE Use
+ );
+
+NTSTATUS
+SampLookupAccountName(
+ IN ULONG Rid,
+ OUT PUNICODE_STRING Name OPTIONAL,
+ OUT PSAMP_OBJECT_TYPE ObjectType
+ );
+
+NTSTATUS
+SampOpenAccount(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN SAMPR_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG AccountId,
+ IN BOOLEAN WriteLockHeld,
+ OUT SAMPR_HANDLE *AccountHandle
+ );
+
+NTSTATUS
+SampCreateAccountContext(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN ULONG AccountId,
+ IN BOOLEAN TrustedClient,
+ IN BOOLEAN AccountExists,
+ OUT PSAMP_OBJECT *AccountContext
+ );
+
+
+NTSTATUS
+SampCreateAccountSid(
+ PSAMP_OBJECT AccountContext,
+ PSID *AccountSid
+ );
+
+NTSTATUS
+SampRetrieveGroupV1Fixed(
+ IN PSAMP_OBJECT GroupContext,
+ IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed
+ );
+
+NTSTATUS
+SampReplaceGroupV1Fixed(
+ IN PSAMP_OBJECT Context,
+ IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed
+ );
+
+NTSTATUS
+SampRetrieveUserV1aFixed(
+ IN PSAMP_OBJECT UserContext,
+ OUT PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ );
+
+NTSTATUS
+SampReplaceUserV1aFixed(
+ IN PSAMP_OBJECT Context,
+ IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ );
+
+NTSTATUS
+SampRetrieveGroupMembers(
+ IN PSAMP_OBJECT GroupContext,
+ IN PULONG MemberCount,
+ IN PULONG *Members OPTIONAL
+ );
+
+NTSTATUS
+SampChangeAccountOperatorAccessToMember(
+ IN PRPC_SID MemberSid,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
+ );
+
+NTSTATUS
+SampChangeOperatorAccessToUser(
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
+ );
+
+NTSTATUS
+SampChangeOperatorAccessToUser2(
+ IN PSAMP_OBJECT UserContext,
+ IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
+ IN SAMP_MEMBERSHIP_DELTA AddingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA AddingToOperator
+ );
+
+//
+// Access validation and auditing related services
+//
+
+NTSTATUS
+SampValidateObjectAccess(
+ IN PSAMP_OBJECT Context,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN ObjectCreation
+ );
+
+VOID
+SampAuditOnClose(
+ IN PSAMP_OBJECT Context
+ );
+
+
+NTSTATUS
+SampCreateNullToken(
+ );
+
+//
+// Authenticated RPC and SPX support services
+//
+
+
+ULONG
+SampSecureRpcInit(
+ PVOID Ignored
+ );
+
+BOOLEAN
+SampStartNonNamedPipeTransports(
+ );
+
+
+//
+// Notification package routines.
+//
+
+NTSTATUS
+SampPasswordChangeNotify(
+ PUNICODE_STRING UserName,
+ ULONG RelativeId,
+ PUNICODE_STRING NewPassword
+ );
+
+NTSTATUS
+SampPasswordChangeFilter(
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING FullName,
+ IN PUNICODE_STRING NewPassword,
+ IN BOOLEAN SetOperation
+ );
+
+
+
+NTSTATUS
+SampLoadNotificationPackages(
+ );
+
+NTSTATUS
+SampDeltaChangeNotify(
+ IN PSID DomainSid,
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PUNICODE_STRING ObjectName,
+ IN PLARGE_INTEGER ModifiedCount,
+ IN PSAM_DELTA_DATA DeltaData OPTIONAL
+ );
+
+
+
+//
+// Security Descriptor production services
+//
+
+
+NTSTATUS
+SampInitializeDomainDescriptors(
+ ULONG Index
+ );
+
+NTSTATUS
+SampGetNewAccountSecurity(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Admin,
+ IN BOOLEAN TrustedClient,
+ IN BOOLEAN RestrictCreatorAccess,
+ IN ULONG NewAccountRid,
+ OUT PSECURITY_DESCRIPTOR *NewDescriptor,
+ OUT PULONG DescriptorLength
+ );
+
+NTSTATUS
+SampGetObjectSD(
+ IN PSAMP_OBJECT Context,
+ OUT PULONG SecurityDescriptorLength,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor
+ );
+
+
+NTSTATUS
+SampModifyAccountSecurity(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Admin,
+ IN PSECURITY_DESCRIPTOR OldDescriptor,
+ OUT PSECURITY_DESCRIPTOR *NewDescriptor,
+ OUT PULONG DescriptorLength
+ );
+
+
+//
+// Group related services
+//
+
+NTSTATUS
+SampAddUserToGroup(
+ IN ULONG GroupRid,
+ IN ULONG UserRid
+ );
+
+NTSTATUS
+SampRemoveUserFromGroup(
+ IN ULONG GroupRid,
+ IN ULONG UserRid
+ );
+
+
+//
+// Alias related services
+//
+
+NTSTATUS
+SampAlBuildAliasInformation(
+ );
+
+NTSTATUS
+SampAlQueryAliasMembership(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PSAMPR_PSID_ARRAY SidArray,
+ OUT PSAMPR_ULONG_ARRAY Membership
+ );
+
+NTSTATUS
+SampAlQueryMembersOfAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ OUT PSAMPR_PSID_ARRAY MemberSids
+ );
+
+NTSTATUS
+SampAlAddMembersToAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG Options,
+ IN PSAMPR_PSID_ARRAY MemberSids
+ );
+
+NTSTATUS
+SampAlRemoveMembersFromAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG Options,
+ IN PSAMPR_PSID_ARRAY MemberSids
+ );
+
+NTSTATUS
+SampAlLookupMembersInAlias(
+ IN SAMPR_HANDLE AliasHandle,
+ IN ULONG AliasRid,
+ IN PSAMPR_PSID_ARRAY MemberSids,
+ OUT PULONG MembershipCount
+ );
+
+NTSTATUS
+SampAlDeleteAlias(
+ IN SAMPR_HANDLE *AliasHandle
+ );
+
+NTSTATUS
+SampAlRemoveAccountFromAllAliases(
+ IN PSID AccountSid,
+ IN BOOLEAN CheckAccess,
+ IN SAMPR_HANDLE DomainHandle OPTIONAL,
+ IN PULONG MembershipCount OPTIONAL,
+ IN PULONG *Membership OPTIONAL
+ );
+
+NTSTATUS
+SampRetrieveAliasMembers(
+ IN PSAMP_OBJECT AliasContext,
+ IN PULONG MemberCount,
+ IN PSID **Members OPTIONAL
+ );
+
+
+NTSTATUS
+SampRemoveAccountFromAllAliases(
+ IN PSID AccountSid,
+ IN BOOLEAN CheckAccess,
+ IN SAMPR_HANDLE DomainHandle OPTIONAL,
+ IN PULONG MembershipCount OPTIONAL,
+ IN PULONG *Membership OPTIONAL
+ );
+
+NTSTATUS
+SampAlSlowQueryAliasMembership(
+ IN SAMPR_HANDLE DomainHandle,
+ IN PSAMPR_PSID_ARRAY SidArray,
+ OUT PSAMPR_ULONG_ARRAY Membership
+ );
+
+NTSTATUS
+SampRetrieveAliasMembership(
+ IN PSID Account,
+ OUT PULONG MemberCount OPTIONAL,
+ IN OUT PULONG BufferSize OPTIONAL,
+ OUT PULONG Buffer OPTIONAL
+ );
+
+//
+// User related services
+//
+
+
+NTSTATUS
+SampGetPrivateUserData(
+ PSAMP_OBJECT UserContext,
+ OUT PULONG DataLength,
+ OUT PVOID *Data
+ );
+
+NTSTATUS
+SampSetPrivateUserData(
+ PSAMP_OBJECT UserContext,
+ IN ULONG DataLength,
+ IN PVOID Data
+ );
+NTSTATUS
+SampRetrieveUserGroupAttribute(
+ IN ULONG UserRid,
+ IN ULONG GroupRid,
+ OUT PULONG Attribute
+ );
+
+NTSTATUS
+SampAddGroupToUserMembership(
+ IN ULONG GroupRid,
+ IN ULONG Attributes,
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA AdminGroup,
+ IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
+ OUT PBOOLEAN UserActive
+ );
+
+NTSTATUS
+SampSetGroupAttributesOfUser(
+ IN ULONG GroupRid,
+ IN ULONG Attributes,
+ IN ULONG UserRid
+ );
+
+NTSTATUS
+SampRemoveMembershipUser(
+ IN ULONG GroupRid,
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA AdminGroup,
+ IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
+ OUT PBOOLEAN UserActive
+ );
+
+BOOLEAN
+SampStillInLockoutObservationWindow(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ );
+
+
+//
+// Cached display information services
+//
+
+NTSTATUS
+SampInitializeDisplayInformation (
+ ULONG DomainIndex
+ );
+
+NTSTATUS
+SampMarkDisplayInformationInvalid (
+ SAMP_OBJECT_TYPE ObjectType
+ );
+
+NTSTATUS
+SampUpdateDisplayInformation (
+ PSAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo OPTIONAL,
+ PSAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo OPTIONAL,
+ SAMP_OBJECT_TYPE ObjectType
+ );
+
+
+//
+// Miscellaneous services
+//
+
+LARGE_INTEGER
+SampAddDeltaTime(
+ IN LARGE_INTEGER Time,
+ IN LARGE_INTEGER DeltaTime
+ );
+
+NTSTATUS
+SampCreateFullSid(
+ PSID DomainSid,
+ ULONG Rid,
+ PSID *AccountSid
+ );
+
+NTSTATUS
+SampSplitSid(
+ IN PSID AccountSid,
+ OUT PSID *DomainSid,
+ OUT ULONG *Rid
+ );
+
+VOID
+SampNotifyNetlogonOfDelta(
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PUNICODE_STRING ObjectName,
+ IN DWORD ReplicateImmediately,
+ IN PSAM_DELTA_DATA DeltaData OPTIONAL
+ );
+
+VOID
+SampWriteEventLog (
+ IN USHORT EventType,
+ IN USHORT EventCategory OPTIONAL,
+ IN ULONG EventID,
+ IN PSID UserSid OPTIONAL,
+ IN USHORT NumStrings,
+ IN ULONG DataSize,
+ IN PUNICODE_STRING *Strings OPTIONAL,
+ IN PVOID Data OPTIONAL
+ );
+
+NTSTATUS
+SampGetAccountDomainInfo(
+ PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo
+ );
+
+NTSTATUS
+SampUpgradeSamDatabase(
+ ULONG Revision
+ );
+
+
+//
+// Old RPC stub routine definitions used in SamIFree()
+//
+
+void _fgs__RPC_UNICODE_STRING (RPC_UNICODE_STRING * _source);
+void _fgs__SAMPR_RID_ENUMERATION (SAMPR_RID_ENUMERATION * _source);
+void _fgs__SAMPR_ENUMERATION_BUFFER (SAMPR_ENUMERATION_BUFFER * _source);
+void _fgs__SAMPR_SR_SECURITY_DESCRIPTOR (SAMPR_SR_SECURITY_DESCRIPTOR * _source);
+void _fgs__SAMPR_GET_GROUPS_BUFFER (SAMPR_GET_GROUPS_BUFFER * _source);
+void _fgs__SAMPR_GET_MEMBERS_BUFFER (SAMPR_GET_MEMBERS_BUFFER * _source);
+void _fgs__SAMPR_LOGON_HOURS (SAMPR_LOGON_HOURS * _source);
+void _fgs__SAMPR_ULONG_ARRAY (SAMPR_ULONG_ARRAY * _source);
+void _fgs__SAMPR_SID_INFORMATION (SAMPR_SID_INFORMATION * _source);
+void _fgs__SAMPR_PSID_ARRAY (SAMPR_PSID_ARRAY * _source);
+void _fgs__SAMPR_RETURNED_USTRING_ARRAY (SAMPR_RETURNED_USTRING_ARRAY * _source);
+void _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION (SAMPR_DOMAIN_GENERAL_INFORMATION * _source);
+void _fgs__SAMPR_DOMAIN_GENERAL_INFORMATION2 (SAMPR_DOMAIN_GENERAL_INFORMATION2 * _source);
+void _fgs__SAMPR_DOMAIN_OEM_INFORMATION (SAMPR_DOMAIN_OEM_INFORMATION * _source);
+void _fgs__SAMPR_DOMAIN_NAME_INFORMATION (SAMPR_DOMAIN_NAME_INFORMATION * _source);
+void _fgs_SAMPR_DOMAIN_REPLICATION_INFORMATION (SAMPR_DOMAIN_REPLICATION_INFORMATION * _source);
+void _fgu__SAMPR_DOMAIN_INFO_BUFFER (SAMPR_DOMAIN_INFO_BUFFER * _source, DOMAIN_INFORMATION_CLASS _branch);
+void _fgu__SAMPR_GROUP_INFO_BUFFER (SAMPR_GROUP_INFO_BUFFER * _source, GROUP_INFORMATION_CLASS _branch);
+void _fgu__SAMPR_ALIAS_INFO_BUFFER (SAMPR_ALIAS_INFO_BUFFER * _source, ALIAS_INFORMATION_CLASS _branch);
+void _fgu__SAMPR_USER_INFO_BUFFER (SAMPR_USER_INFO_BUFFER * _source, USER_INFORMATION_CLASS _branch);
+void _fgu__SAMPR_DISPLAY_INFO_BUFFER (SAMPR_DISPLAY_INFO_BUFFER * _source, DOMAIN_DISPLAY_INFORMATION _branch);
+
+
+
+//
+// SAM object attribute manipulation services
+//
+
+
+
+VOID
+SampInitObjectInfoAttributes();
+
+NTSTATUS
+SampStoreObjectAttributes(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN UseKeyHandle
+ );
+
+NTSTATUS
+SampDeleteAttributeKeys(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampGetFixedAttributes(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN MakeCopy,
+ OUT PVOID *FixedData
+ );
+
+NTSTATUS
+SampSetFixedAttributes(
+ IN PSAMP_OBJECT Context,
+ IN PVOID FixedData
+ );
+
+NTSTATUS
+SampGetUnicodeStringAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PUNICODE_STRING UnicodeAttribute
+ );
+
+NTSTATUS
+SampSetUnicodeStringAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PUNICODE_STRING Attribute
+ );
+
+NTSTATUS
+SampGetSidAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PSID *Sid
+ );
+
+NTSTATUS
+SampSetSidAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSID Attribute
+ );
+
+NTSTATUS
+SampGetAccessAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG Revision,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor
+ );
+
+NTSTATUS
+SampSetAccessAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSECURITY_DESCRIPTOR Attribute,
+ IN ULONG Length
+ );
+
+NTSTATUS
+SampGetUlongArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG *UlongArray,
+ OUT PULONG UsedCount,
+ OUT PULONG LengthCount
+ );
+
+NTSTATUS
+SampSetUlongArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PULONG Attribute,
+ IN ULONG UsedCount,
+ IN ULONG LengthCount
+ );
+
+NTSTATUS
+SampGetLargeIntArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PLARGE_INTEGER *LargeIntArray,
+ OUT PULONG Count
+ );
+
+NTSTATUS
+SampSetLargeIntArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PLARGE_INTEGER Attribute,
+ IN ULONG Count
+ );
+
+NTSTATUS
+SampGetSidArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PSID *SidArray,
+ OUT PULONG Length,
+ OUT PULONG Count
+ );
+
+NTSTATUS
+SampSetSidArrayAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PSID Attribute,
+ IN ULONG Length,
+ IN ULONG Count
+ );
+
+NTSTATUS
+SampGetLogonHoursAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN BOOLEAN MakeCopy,
+ OUT PLOGON_HOURS LogonHours
+ );
+
+NTSTATUS
+SampSetLogonHoursAttribute(
+ IN PSAMP_OBJECT Context,
+ IN ULONG AttributeIndex,
+ IN PLOGON_HOURS Attribute
+ );
+
+VOID
+SampFreeAttributeBuffer(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampRtlConvertUlongToUnicodeString(
+ IN ULONG Value,
+ IN ULONG Base OPTIONAL,
+ IN ULONG DigitCount,
+ IN BOOLEAN AllocateDestinationString,
+ OUT PUNICODE_STRING UnicodeString
+ );
+
+NTSTATUS
+SampRtlWellKnownPrivilegeCheck(
+ BOOLEAN ImpersonateClient,
+ IN ULONG PrivilegeId,
+ IN OPTIONAL PCLIENT_ID ClientId
+ );
+
+//
+// Functions to upgrade the SAM database and fix SAM bugs
+//
+
+NTSTATUS
+SampUpgradeSamDatabase(
+ ULONG Revision
+ );
+
+/////////////////////////////////////////////////////////////////////////
+// //
+// 2-3 tree generic table routines //
+// These should be moved to RTL directory if a general need arises. //
+// //
+/////////////////////////////////////////////////////////////////////////
+
+
+VOID
+RtlInitializeGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PRTL_GENERIC_2_COMPARE_ROUTINE CompareRoutine,
+ PRTL_GENERIC_2_ALLOCATE_ROUTINE AllocateRoutine,
+ PRTL_GENERIC_2_FREE_ROUTINE FreeRoutine
+ );
+
+PVOID
+RtlInsertElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PBOOLEAN NewElement
+ );
+
+BOOLEAN
+RtlDeleteElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ );
+
+PVOID
+RtlLookupElementGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element
+ );
+
+PVOID
+RtlEnumerateGenericTable2 (
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID *RestartKey
+ );
+
+PVOID
+RtlRestartKeyByIndexGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ ULONG I,
+ PVOID *RestartKey
+ );
+
+PVOID
+RtlRestartKeyByValueGenericTable2(
+ PRTL_GENERIC_TABLE2 Table,
+ PVOID Element,
+ PVOID *RestartKey
+ );
+
+ULONG
+RtlNumberElementsGenericTable2(
+ PRTL_GENERIC_TABLE2 Table
+ );
+
+BOOLEAN
+RtlIsGenericTable2Empty (
+ PRTL_GENERIC_TABLE2 Table
+ );
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Shared global variables //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+extern NT_PRODUCT_TYPE SampProductType;
+
+extern RTL_RESOURCE SampLock;
+extern BOOLEAN SampTransactionWithinDomain;
+extern ULONG SampTransactionDomainIndex;
+
+extern SAMP_SERVICE_STATE SampServiceState;
+
+extern BOOLEAN SampAccountAuditingEnabled;
+
+extern HANDLE SampKey;
+extern PRTL_RXACT_CONTEXT SampRXactContext;
+
+extern SAMP_OBJECT_INFORMATION SampObjectInformation[ SampUnknownObjectType ];
+
+extern ULONG SampActiveContextCount;
+extern LIST_ENTRY SampContextListHead;
+
+extern ULONG SampDefinedDomainsCount;
+extern PSAMP_DEFINED_DOMAINS SampDefinedDomains;
+extern UNICODE_STRING SampFixedAttributeName;
+extern UNICODE_STRING SampVariableAttributeName;
+extern UNICODE_STRING SampCombinedAttributeName;
+
+extern UNICODE_STRING SampNameDomains;
+extern UNICODE_STRING SampNameDomainGroups;
+extern UNICODE_STRING SampNameDomainAliases;
+extern UNICODE_STRING SampNameDomainAliasesMembers;
+extern UNICODE_STRING SampNameDomainUsers;
+extern UNICODE_STRING SampNameDomainAliasesNames;
+extern UNICODE_STRING SampNameDomainGroupsNames;
+extern UNICODE_STRING SampNameDomainUsersNames;
+
+extern UNICODE_STRING SampBackSlash;
+extern UNICODE_STRING SampNullString;
+extern UNICODE_STRING SampSamSubsystem;
+extern UNICODE_STRING SampServerObjectName;
+
+
+extern LARGE_INTEGER SampImmediatelyDeltaTime;
+extern LARGE_INTEGER SampNeverDeltaTime;
+extern LARGE_INTEGER SampHasNeverTime;
+extern LARGE_INTEGER SampWillNeverTime;
+
+extern LM_OWF_PASSWORD SampNullLmOwfPassword;
+extern NT_OWF_PASSWORD SampNullNtOwfPassword;
+
+extern TIME LastUnflushedChange;
+extern BOOLEAN FlushThreadCreated;
+extern BOOLEAN FlushImmediately;
+
+extern LONG SampFlushThreadMinWaitSeconds;
+extern LONG SampFlushThreadMaxWaitSeconds;
+extern LONG SampFlushThreadExitDelaySeconds;
+
+//
+// Warning: these SIDs are only defined during the first boot of setup,
+// when the code in bldsam3.c for building the SAM database, has been
+// run. On a normal build they are both NULL.
+//
+
+extern PSID SampBuiltinDomainSid;
+extern PSID SampAccountDomainSid;
+
+extern PSID SampWorldSid;
+extern PSID SampAnonymousSid;
+extern PSID SampAdministratorUserSid;
+extern PSID SampAdministratorsAliasSid;
+extern HANDLE SampNullSessionToken;
+extern BOOLEAN SampNetwareServerInstalled;
+extern BOOLEAN SampIpServerInstalled;
+extern BOOLEAN SampAppletalkServerInstalled;
+extern BOOLEAN SampVinesServerInstalled;
+
+#if SAMP_DIAGNOSTICS
+
+extern ULONG SampGlobalFlag;
+
+#endif //SAMP_DIAGNOSTICS
+
+#endif // _NTSAMP_
diff --git a/private/newsam/server/samss.c b/private/newsam/server/samss.c
new file mode 100644
index 000000000..1f0d56be8
--- /dev/null
+++ b/private/newsam/server/samss.c
@@ -0,0 +1,1701 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ samss.c
+
+Abstract:
+
+ This is the main routine for the Security Account Manager Server process.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Module Private defines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#define SAM_AUTO_BUILD
+
+//
+// Enable this define to compile in code to SAM that allows for the
+// simulation of SAM initialization/installation failures. See
+// SampInitializeForceError() below for details.
+//
+
+// #define SAMP_SETUP_FAILURE_TEST 1
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampInitialize(
+ OUT PULONG Revision
+ );
+
+NTSTATUS
+SampInitializeWellKnownSids( VOID );
+
+VOID
+SampLoadPasswordFilterDll( VOID );
+
+NTSTATUS
+SampEnableAuditPrivilege( VOID );
+
+NTSTATUS
+SampFixGroupCount( VOID );
+
+
+
+#ifdef SAMP_SETUP_FAILURE_TEST
+
+NTSTATUS
+SampInitializeForceError(
+ OUT PNTSTATUS ForcedStatus
+ );
+
+#endif //SAMP_SETUP_FAILURE_TEST
+
+
+
+#if SAMP_DIAGNOSTICS
+VOID
+SampActivateDebugProcess( VOID );
+
+NTSTATUS
+SampActivateDebugProcessWrkr(
+ IN PVOID ThreadParameter
+ );
+#endif //SAMP_DIAGNOSTICS
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+NTSTATUS
+SamIInitialize (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This is the initialization control routine for the Security Account
+ Manager Server. A mechanism is provided for simulating initialization
+ errors.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NTSTATUS - Standard Nt Result Code
+
+ STATUS_SUCCESS - The call completed successfully
+
+ Simulated errors
+
+ Errors from called routines.
+
+--*/
+
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ NTSTATUS IgnoreStatus;
+ HANDLE EventHandle = NULL;
+ ULONG Revision = 0;
+
+//
+// The following conditional code is used to generate artifical errors
+// during SAM installation for the purpose of testing setup.exe error
+// handling. This code should remain permanently, since it provides a
+// way of testing against regressions in the setup error handling code.
+//
+
+#ifdef SAMP_SETUP_FAILURE_TEST
+ NTSTATUS ForcedStatus;
+
+ //
+ // Read an error code from the Registry.
+ //
+
+ NtStatus = SampInitializeForceError( &ForcedStatus);
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ KdPrint(("SAMSS: Attempt to force error failed 0x%lx\n", NtStatus));
+ KdPrint(("SAM will try to initialize normally\n"));
+
+ NtStatus = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // Use the status returned
+ //
+
+ NtStatus = ForcedStatus;
+ }
+
+#endif // SAMP_SETUP_FAILURE_TEST
+
+ //
+ // Initialize SAM if no error was forced.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampInitialize( &Revision );
+ }
+
+ //
+ // Register our shutdown routine
+ //
+
+ if (!SetConsoleCtrlHandler(SampShutdownNotification, TRUE)) {
+ KdPrint(("SAM Server: SetConsoleCtrlHandler call failed %d\n",GetLastError()));
+ }
+
+ if (!SetProcessShutdownParameters(SAMP_SHUTDOWN_LEVEL,SHUTDOWN_NORETRY)) {
+ KdPrint(("SAM Server: SetProcessShutdownParameters call failed %d\n",GetLastError()));
+ }
+
+
+ //
+ // Try to load the cached Alias Membership information and turn on caching.
+ // If unsuccessful, caching remains disabled forever.
+ //
+
+ IgnoreStatus = SampAlBuildAliasInformation();
+
+ if (!NT_SUCCESS(IgnoreStatus)) {
+
+ KdPrint(("SAM Server: Build Alias Cache access violation handled"));
+ KdPrint(("SAM Server: Alias Caching turned off\n"));
+ }
+
+ //
+ // Perform any necessary upgrades.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampUpgradeSamDatabase(
+ Revision
+ );
+ if (!NT_SUCCESS(IgnoreStatus)) {
+ KdPrint(("SAM Server: Failed to upgrade SAM database: 0x%x\n",IgnoreStatus));
+ }
+ }
+
+
+ //
+ // Everyone is initialized, start processing calls.
+ //
+
+ SampServiceState = SampServiceEnabled;
+
+
+ //
+ // If requested, activate a diagnostic process.
+ // This is a debug aid expected to be used for SETUP testing.
+ //
+
+#if SAMP_DIAGNOSTICS
+ IF_SAMP_GLOBAL( ACTIVATE_DEBUG_PROC ) {
+
+ SampActivateDebugProcess();
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampInitialize(
+ OUT PULONG Revision
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does the actual initialization of the SAM server. This includes:
+
+ - Initializing well known global variable values
+
+ - Creating the registry exclusive access lock,
+
+ - Opening the registry and making sure it includes a SAM database
+ with a known revision level,
+
+ - Starting the RPC server,
+
+ - Add the SAM services to the list of exported RPC interfaces
+
+
+
+Arguments:
+
+ Revision - receives the revision of the database.
+
+Return Value:
+
+ STATUS_SUCCESS - Initialization has successfully completed.
+
+ STATUS_UNKNOWN_REVISION - The SAM database has an unknown revision.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ LPWSTR ServiceName;
+
+ PSAMP_OBJECT ServerContext;
+ OBJECT_ATTRIBUTES SamAttributes;
+ UNICODE_STRING SamNameU;
+ PULONG RevisionLevel;
+ BOOLEAN ProductExplicitlySpecified;
+ PPOLICY_AUDIT_EVENTS_INFO PolicyAuditEventsInfo = NULL;
+
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+
+ CHAR NullLmPassword = 0;
+ RPC_STATUS RpcStatus;
+ HANDLE ThreadHandle;
+ ULONG ThreadId;
+
+ //
+ // Set the state of our service to "initializing" until everything
+ // is initialized.
+ //
+
+ SampServiceState = SampServiceInitializing;
+
+
+ //
+ // Set up some useful well-known sids
+ //
+
+ NtStatus = SampInitializeWellKnownSids();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the product type
+ //
+
+ ProductExplicitlySpecified = RtlGetNtProductType(&SampProductType);
+
+
+ //
+ // Set the number of currently active opens
+ //
+
+ SampActiveContextCount = 0;
+
+ //
+ // Initialize the server/domain context list
+ //
+
+ InitializeListHead(&SampContextListHead);
+
+ //
+ // Initialize the attribute field information of the object
+ // information structures.
+ //
+
+ SampInitObjectInfoAttributes();
+
+ //
+ // Set up the generic mappings for the SAM object types
+ //
+
+ SampObjectInformation[ SampServerObjectType ].GenericMapping.GenericRead
+ = SAM_SERVER_READ;
+ SampObjectInformation[ SampServerObjectType ].GenericMapping.GenericWrite
+ = SAM_SERVER_WRITE;
+ SampObjectInformation[ SampServerObjectType ].GenericMapping.GenericExecute
+ = SAM_SERVER_EXECUTE;
+ SampObjectInformation[ SampServerObjectType ].GenericMapping.GenericAll
+ = SAM_SERVER_ALL_ACCESS;
+
+ SampObjectInformation[ SampDomainObjectType ].GenericMapping.GenericRead
+ = DOMAIN_READ;
+ SampObjectInformation[ SampDomainObjectType ].GenericMapping.GenericWrite
+ = DOMAIN_WRITE;
+ SampObjectInformation[ SampDomainObjectType ].GenericMapping.GenericExecute
+ = DOMAIN_EXECUTE;
+ SampObjectInformation[ SampDomainObjectType ].GenericMapping.GenericAll
+ = DOMAIN_ALL_ACCESS;
+
+ SampObjectInformation[ SampGroupObjectType ].GenericMapping.GenericRead
+ = GROUP_READ;
+ SampObjectInformation[ SampGroupObjectType ].GenericMapping.GenericWrite
+ = GROUP_WRITE;
+ SampObjectInformation[ SampGroupObjectType ].GenericMapping.GenericExecute
+ = GROUP_EXECUTE;
+ SampObjectInformation[ SampGroupObjectType ].GenericMapping.GenericAll
+ = GROUP_ALL_ACCESS;
+
+ SampObjectInformation[ SampAliasObjectType ].GenericMapping.GenericRead
+ = ALIAS_READ;
+ SampObjectInformation[ SampAliasObjectType ].GenericMapping.GenericWrite
+ = ALIAS_WRITE;
+ SampObjectInformation[ SampAliasObjectType ].GenericMapping.GenericExecute
+ = ALIAS_EXECUTE;
+ SampObjectInformation[ SampAliasObjectType ].GenericMapping.GenericAll
+ = ALIAS_ALL_ACCESS;
+
+ SampObjectInformation[ SampUserObjectType ].GenericMapping.GenericRead
+ = USER_READ;
+ SampObjectInformation[ SampUserObjectType ].GenericMapping.GenericWrite
+ = USER_WRITE;
+ SampObjectInformation[ SampUserObjectType ].GenericMapping.GenericExecute
+ = USER_EXECUTE;
+ SampObjectInformation[ SampUserObjectType ].GenericMapping.GenericAll
+ = USER_ALL_ACCESS;
+
+ //
+ // Set mask of INVALID accesses for an access mask that is already mapped.
+ //
+
+ SampObjectInformation[ SampServerObjectType ].InvalidMappedAccess
+ = (ULONG)(~(SAM_SERVER_ALL_ACCESS | ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED));
+ SampObjectInformation[ SampDomainObjectType ].InvalidMappedAccess
+ = (ULONG)(~(DOMAIN_ALL_ACCESS | ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED));
+ SampObjectInformation[ SampGroupObjectType ].InvalidMappedAccess
+ = (ULONG)(~(GROUP_ALL_ACCESS | ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED));
+ SampObjectInformation[ SampAliasObjectType ].InvalidMappedAccess
+ = (ULONG)(~(ALIAS_ALL_ACCESS | ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED));
+ SampObjectInformation[ SampUserObjectType ].InvalidMappedAccess
+ = (ULONG)(~(USER_ALL_ACCESS | ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED));
+
+ //
+ // Set a mask of write operations for the object types. Strip
+ // out READ_CONTROL, which doesn't allow writing but is defined
+ // in all of the standard write accesses.
+ // This is used to enforce correct role semantics (e.g., only
+ // trusted clients can perform write operations when a domain
+ // role isn't Primary).
+ //
+ // Note that USER_WRITE isn't good enough for user objects. That's
+ // because USER_WRITE allows users to modify portions of their
+ // account information, but other portions can only be modified by
+ // an administrator.
+ //
+
+ SampObjectInformation[ SampServerObjectType ].WriteOperations
+ = (SAM_SERVER_WRITE & ~READ_CONTROL) | DELETE;
+ SampObjectInformation[ SampDomainObjectType ].WriteOperations
+ = (DOMAIN_WRITE & ~READ_CONTROL) | DELETE;
+ SampObjectInformation[ SampGroupObjectType ].WriteOperations
+ = (GROUP_WRITE & ~READ_CONTROL) | DELETE;
+ SampObjectInformation[ SampAliasObjectType ].WriteOperations
+ = (ALIAS_WRITE & ~READ_CONTROL) | DELETE;
+ SampObjectInformation[ SampUserObjectType ].WriteOperations
+ = ( USER_WRITE & ~READ_CONTROL ) | USER_WRITE_ACCOUNT |
+ USER_FORCE_PASSWORD_CHANGE | USER_WRITE_GROUP_INFORMATION | DELETE;
+
+ //
+ // Set up the names of the SAM defined object types.
+ // These names are used for auditing purposes.
+ //
+
+ RtlInitUnicodeString( &SamNameU, L"SAM_SERVER" );
+ SampObjectInformation[ SampServerObjectType ].ObjectTypeName = SamNameU;
+ RtlInitUnicodeString( &SamNameU, L"SAM_DOMAIN" );
+ SampObjectInformation[ SampDomainObjectType ].ObjectTypeName = SamNameU;
+ RtlInitUnicodeString( &SamNameU, L"SAM_GROUP" );
+ SampObjectInformation[ SampGroupObjectType ].ObjectTypeName = SamNameU;
+ RtlInitUnicodeString( &SamNameU, L"SAM_ALIAS" );
+ SampObjectInformation[ SampAliasObjectType ].ObjectTypeName = SamNameU;
+ RtlInitUnicodeString( &SamNameU, L"SAM_USER" );
+ SampObjectInformation[ SampUserObjectType ].ObjectTypeName = SamNameU;
+
+ //
+ // Set up the name of the SAM server object itself (rather than its type)
+ //
+
+ RtlInitUnicodeString( &SampServerObjectName, L"SAM" );
+
+ //
+ // Set up the name of the SAM server for auditing purposes
+ //
+
+ RtlInitUnicodeString( &SampSamSubsystem, L"Security Account Manager" );
+
+ //
+ // Set up the names of well known registry keys
+ //
+
+ RtlInitUnicodeString( &SampFixedAttributeName, L"F" );
+ RtlInitUnicodeString( &SampVariableAttributeName, L"V" );
+ RtlInitUnicodeString( &SampCombinedAttributeName, L"C" );
+
+ RtlInitUnicodeString(&SampNameDomains, L"DOMAINS" );
+ RtlInitUnicodeString(&SampNameDomainGroups, L"Groups" );
+ RtlInitUnicodeString(&SampNameDomainAliases, L"Aliases" );
+ RtlInitUnicodeString(&SampNameDomainAliasesMembers, L"Members" );
+ RtlInitUnicodeString(&SampNameDomainUsers, L"Users" );
+ RtlInitUnicodeString(&SampNameDomainAliasesNames, L"Names" );
+ RtlInitUnicodeString(&SampNameDomainGroupsNames, L"Names" );
+ RtlInitUnicodeString(&SampNameDomainUsersNames, L"Names" );
+
+
+
+ //
+ // Initialize other useful characters and strings
+ //
+
+ RtlInitUnicodeString(&SampBackSlash, L"\\");
+ RtlInitUnicodeString(&SampNullString, L"");
+
+
+ //
+ // Initialize some useful time values
+ //
+
+ SampImmediatelyDeltaTime.LowPart = 0;
+ SampImmediatelyDeltaTime.HighPart = 0;
+
+ SampNeverDeltaTime.LowPart = 0;
+ SampNeverDeltaTime.HighPart = MINLONG;
+
+ SampHasNeverTime.LowPart = 0;
+ SampHasNeverTime.HighPart = 0;
+
+ SampWillNeverTime.LowPart = MAXULONG;
+ SampWillNeverTime.HighPart = MAXLONG;
+
+ //
+ // Initialize useful encryption constants
+ //
+
+ NtStatus = RtlCalculateLmOwfPassword(&NullLmPassword, &SampNullLmOwfPassword);
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ RtlInitUnicodeString(&SamNameU, NULL);
+ NtStatus = RtlCalculateNtOwfPassword(&SamNameU, &SampNullNtOwfPassword);
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+
+ //
+ // Initialize variables for the hive flushing thread
+ //
+
+ LastUnflushedChange.LowPart = 0;
+ LastUnflushedChange.HighPart = 0;
+
+ FlushThreadCreated = FALSE;
+ FlushImmediately = FALSE;
+
+ SampFlushThreadMinWaitSeconds = 30;
+ SampFlushThreadMaxWaitSeconds = 600;
+ SampFlushThreadExitDelaySeconds = 120;
+
+
+ //
+ // Enable the audit privilege (needed to use NtAccessCheckAndAuditAlarm)
+ //
+
+ NtStatus = SampEnableAuditPrivilege();
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ KdPrint((" SAM SERVER: The SAM Server could not enable the audit Privilege.\n"
+ " Failing to initialize SAM.\n"));
+ return( NtStatus );
+ }
+
+ //
+ // Get Auditing Information from the LSA and save information
+ // relevant to SAM.
+ //
+
+ NtStatus = LsaIQueryInformationPolicyTrusted(
+ PolicyAuditEventsInformation,
+ (PLSAPR_POLICY_INFORMATION *) &PolicyAuditEventsInfo
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ SampSetAuditingInformation( PolicyAuditEventsInfo );
+
+ } else {
+
+ //
+ // Failed to query Audit Information from LSA. Allow SAM to
+ // continue initializing wuth SAM Account auditing turned off.
+ //
+
+ KdPrint((" SAM SERVER: Query Audit Info from LSA returned 0x%lX\n",
+ NtStatus));
+ KdPrint((" SAM SERVER: Sam Account Auditing is not enabled"));
+
+ SampAccountAuditingEnabled = FALSE;
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ //
+ // We no longer need the Lsa Audit Events Info data.
+ //
+
+ if (PolicyAuditEventsInfo != NULL) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAuditEventsInformation,
+ (PLSAPR_POLICY_INFORMATION) PolicyAuditEventsInfo
+ );
+ }
+
+ //
+ // Create the internal data structure and backstore lock ...
+ //
+
+ RtlInitializeResource(&SampLock);
+
+ //
+ // Open the registry and make sure it includes a SAM database.
+ // Also make sure this SAM database has been initialized and is
+ // at a revision level we understand.
+ //
+
+ RtlInitUnicodeString( &SamNameU, L"\\Registry\\Machine\\Security\\SAM" );
+ ASSERT( NT_SUCCESS(NtStatus) );
+
+ InitializeObjectAttributes(
+ &SamAttributes,
+ &SamNameU,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &SampKey,
+ (KEY_READ | KEY_WRITE),
+ &SamAttributes,
+ 0
+ );
+
+ if ( NtStatus == STATUS_OBJECT_NAME_NOT_FOUND ) {
+#ifndef SAM_AUTO_BUILD
+
+ KdPrint((" NEWSAM\\SERVER: Sam database not found in registry.\n"
+ " Failing to initialize\n"));
+ return(NtStatus);
+
+#endif //SAM_AUTO_BUILD
+
+#if DBG
+ KdPrint((" NEWSAM\\SERVER: Initializing SAM registry database for\n"));
+ if (SampProductType == NtProductWinNt) {
+ DbgPrint(" WinNt product.\n");
+ } else if ( SampProductType == NtProductLanManNt ) {
+ DbgPrint(" LanManNt product.\n");
+ } else {
+ DbgPrint(" Dedicated Server product.\n");
+ }
+#endif //DBG
+
+ //
+ // Change the flush thread timeouts. This is necessary because
+ // the reboot following an installation does not call
+ // ExitWindowsEx() and so our shutdown notification routine does
+ // not get called. Consequently, it does not have a chance to
+ // flush any changes that were obtained by syncing with a PDC.
+ // If there are a large number of accounts, it could be
+ // extremely expensive to do another full re-sync. So, close
+ // the flush thread wait times so that it is pretty sure to
+ // have time to flush.
+ //
+
+ SampFlushThreadMinWaitSeconds = 5;
+
+
+ NtStatus = SampInitializeRegistry();
+
+
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ return(NtStatus);
+ }
+
+ NtStatus = RtlpNtOpenKey(
+ &SampKey,
+ (KEY_READ | KEY_WRITE),
+ &SamAttributes,
+ 0
+ );
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ KdPrint(("SAM Server: Could not access the SAM database.\n"
+ " Status is 0x%lx \n", NtStatus));
+ KdPrint((" Failing to initialize SAM.\n"));
+ return(NtStatus);
+ }
+
+ //
+ // The following subroutine may be removed from the code
+ // following the Daytona release. By then it will have fixed
+ // the group count.
+ //
+
+ NtStatus = SampFixGroupCount();
+
+
+ //
+ // We need to read the fixed attributes of the server objects.
+ // Create a context to do that.
+ //
+
+ ServerContext = SampCreateContext( SampServerObjectType, TRUE );
+
+ if ( ServerContext == NULL ) {
+
+ KdPrint(("SAM Server: Could not create server context.\n"
+ " Failing to initialize SAM.\n"));
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ //
+ // The RootKey for a SERVER object is the root of the SAM database.
+ // This key should not be closed when the context is deleted.
+ //
+
+ ServerContext->RootKey = SampKey;
+
+ //
+ // Get the FIXED attributes, which just consists of the revision level.
+ //
+
+ //
+ // BUGBUG: this does not actually return the fixed attributes. Find
+ // out why. MMS 9/10/95
+ //
+
+
+ NtStatus = SampGetFixedAttributes(
+ ServerContext,
+ FALSE,
+ (PVOID *)&RevisionLevel
+ );
+
+ if (NtStatus != STATUS_SUCCESS) {
+
+ KdPrint(("SAM Server: Could not access the SAM database revision level.\n"));
+ KdPrint((" Status is 0x%lx \n", NtStatus));
+ KdPrint((" Failing to initialize SAM.\n"));
+ return(NtStatus);
+ }
+
+ *Revision = *RevisionLevel;
+
+ if ( ((*Revision && 0xFFFF0000) > SAMP_MAJOR_REVISION) ||
+ (*Revision > SAMP_SERVER_REVISION) ) {
+
+ KdPrint(("SAM Server: The SAM database revision level is not one supported\n"));
+ KdPrint((" by this version of the SAM server code. The highest revision\n"));
+ KdPrint((" level supported is 0x%lx. The SAM Database revision is 0x%lx \n",
+ (ULONG)SAMP_SERVER_REVISION, *Revision));
+ KdPrint((" Failing to initialize SAM.\n"));
+ return(STATUS_UNKNOWN_REVISION);
+ }
+
+ SampDeleteContext( ServerContext );
+
+ //
+ // If necessary, commit a partially commited transaction.
+ //
+
+ NtStatus = RtlInitializeRXact( SampKey, TRUE, &SampRXactContext );
+
+ if ( NtStatus == STATUS_RXACT_STATE_CREATED ) {
+
+ KdPrint((" SAM SERVER: RXACT state of the SAM database didn't yet exist.\n"
+ " Failing to initialize SAM.\n"));
+ return(NtStatus);
+ } else if (!NT_SUCCESS(NtStatus)) {
+
+ KdPrint((" SAM SERVER: RXACT state of the SAM database didn't initialize properly.\n"));
+ KdPrint((" Status is 0x%lx \n", NtStatus));
+ KdPrint((" Failing to initialize SAM.\n"));
+ return(NtStatus);
+ }
+
+ if ( NtStatus == STATUS_RXACT_COMMITTED ) {
+
+ KdPrint((" SAM SERVER: Previously aborted backstore commit was completed\n"
+ " during SAM initialization. This is not a cause\n"
+ " for alarm.\n"
+ " Continuing with SAM initialization.\n"));
+ }
+
+ //
+ // Start the RPC server...
+ //
+
+ //
+ // Publish the sam server interface package...
+ //
+ // NOTE: Now all RPC servers in lsass.exe (now winlogon) share the same
+ // pipe name. However, in order to support communication with
+ // version 1.0 of WinNt, it is necessary for the Client Pipe name
+ // to remain the same as it was in version 1.0. Mapping to the new
+ // name is performed in the Named Pipe File System code.
+ //
+
+ ServiceName = L"lsass";
+ NtStatus = RpcpAddInterface( ServiceName, samr_ServerIfHandle);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAMSS: Could Not Start RPC Server.\n"
+ " Failing to initialize SAM Server.\n"
+ " Status is: 0x%lx\n", NtStatus));
+ return(NtStatus);
+ }
+
+ //
+ // If we are running as a netware server, for Small World or FPNW,
+ // register an SPX endpoint and some authentication info.
+ //
+
+
+ //
+ // Build null session token handle if a Netware server is
+ // installed.
+ //
+
+
+
+ if (SampStartNonNamedPipeTransports()) {
+
+ NtStatus = SampCreateNullToken();
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAMSS: Unable to create NULL token: 0x%x\n",
+ NtStatus));
+ return(NtStatus);
+ }
+
+ }
+
+ //
+ // Create a thread to start authenticated RPC.
+ //
+
+ ThreadHandle = CreateThread(
+ NULL,
+ 0,
+ (LPTHREAD_START_ROUTINE) SampSecureRpcInit,
+ NULL,
+ 0,
+ &ThreadId
+ );
+
+
+ if (ThreadHandle == NULL) {
+ KdPrint(("SAMSS: Unable to create thread: %d\n",
+ GetLastError()));
+
+ return(STATUS_INVALID_HANDLE);
+
+ }
+
+ //
+ // Load the password-change notification packages.
+ //
+
+ NtStatus = SampLoadNotificationPackages( );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ KdPrint(("SAMSS: Failed to load notification packagees: 0x%x.\n"
+ " Failing to initialize SAM Server.\n", NtStatus));
+ return(NtStatus);
+ }
+
+ //
+ // Allow each sub-component of SAM a chance to initialize
+ //
+
+ // SampInitializeServerObject();
+ if (!SampInitializeDomainObject()) {
+
+ KdPrint(("SAMSS: Domain Object Intialization Failed.\n"
+ " Failing to initialize SAM Server.\n"));
+ return(STATUS_INVALID_DOMAIN_STATE);
+ }
+
+ // SampInitializeGroupObject();
+ // SampInitializeUserObject();
+
+
+
+ //
+ // Load the password filter DLL if there is one
+ //
+
+ SampLoadPasswordFilterDll();
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampInitializeWellKnownSids( VOID )
+
+/*++
+
+Routine Description:
+
+ This routine initializes some global well-known sids.
+
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS - Initialization has successfully completed.
+
+ STATUS_NO_MEMORY - Couldn't allocate memory for the sids.
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ PPOLICY_ACCOUNT_DOMAIN_INFO
+ DomainInfo;
+
+ //
+ // WORLD is s-1-1-0
+ // ANONYMOUS is s-1-5-7
+ //
+
+ SID_IDENTIFIER_AUTHORITY
+ WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY,
+ NtAuthority = SECURITY_NT_AUTHORITY;
+
+
+ NtStatus = RtlAllocateAndInitializeSid(
+ &NtAuthority,
+ 1,
+ SECURITY_ANONYMOUS_LOGON_RID,
+ 0, 0, 0, 0, 0, 0, 0,
+ &SampAnonymousSid
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlAllocateAndInitializeSid(
+ &WorldSidAuthority,
+ 1, //Sub authority count
+ SECURITY_WORLD_RID, //Sub authorities (up to 8)
+ 0, 0, 0, 0, 0, 0, 0,
+ &SampWorldSid
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = RtlAllocateAndInitializeSid(
+ &NtAuthority,
+ 2,
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &SampAdministratorsAliasSid
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampGetAccountDomainInfo( &DomainInfo );
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampCreateFullSid( DomainInfo->DomainSid,
+ DOMAIN_USER_RID_ADMIN,
+ &SampAdministratorUserSid
+ );
+ MIDL_user_free( DomainInfo );
+ }
+ }
+
+ }
+ }
+
+ return(NtStatus);
+}
+
+
+
+VOID
+SampLoadPasswordFilterDll(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This function loads a DLL to do password filtering. This DLL is
+ optional and is expected to be used by ISVs or customers to do
+ things like dictionary lookups and other simple algorithms to
+ reject any password deemed too risky to allow a user to use.
+
+ For example, user initials or easily guessed password might be
+ rejected.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+
+--*/
+
+{
+
+
+#if NOT_YET_SUPPORTED
+ NTSTATUS Status, IgnoreStatus, MsProcStatus;
+ PVOID ModuleHandle;
+ STRING ProcedureName;
+
+ UNICODE_STRING FileName;
+
+ PSAM_PF_INITIALIZE InitializeRoutine;
+
+
+
+ //
+ // Indicate the dll has not yet been loaded.
+ //
+
+ SampPasswordFilterDllRoutine = NULL;
+
+
+
+ RtlInitUnicodeString( &FileName, L"PwdFiltr" );
+ Status = LdrLoadDll( NULL, NULL, &FileName, &ModuleHandle );
+
+
+ if (!NT_SUCCESS(Status)) {
+ return;
+ }
+
+ KdPrint(("Samss: Loading Password Filter DLL - %Z\n", &FileName ));
+
+
+
+
+ //
+ // Now get the address of the password filter DLL routines
+ //
+
+ RtlInitString( &ProcedureName, SAM_PF_NAME_INITIALIZE );
+ Status = LdrGetProcedureAddress(
+ ModuleHandle,
+ &ProcedureName,
+ 0,
+ (PVOID *)&InitializeRoutine
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // We found the DLL, but couldn't get its initialization routine
+ // address
+ //
+
+ // FIX, FIX - Log an error
+
+ KdPrint(("Samss: Couldn't get password filter DLL init routine address.\n"
+ " Status is: 0x%lx\n", Status));
+
+ IgnoreStatus = LdrUnloadDll( ModuleHandle );
+ return;
+ }
+
+
+ RtlInitString( &ProcedureName, SAM_PF_NAME_PASSWORD_FILTER );
+ Status = LdrGetProcedureAddress(
+ ModuleHandle,
+ &ProcedureName,
+ 0,
+ (PVOID *)&SampPasswordFilterDllRoutine
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // We found the DLL, but couldn't get its password filter routine
+ // address
+ //
+
+ // FIX, FIX - Log an error
+
+ KdPrint(("Samss: Couldn't get password filter routine address from loaded DLL.\n"
+ " Status is: 0x%lx\n", Status));
+
+ IgnoreStatus = LdrUnloadDll( ModuleHandle );
+ return;
+ }
+
+
+
+
+ //
+ // Now initialize the DLL
+ //
+
+ Status = (InitializeRoutine)();
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // We found the DLL and loaded its routine addresses, but it returned
+ // and error from its initialize routine.
+ //
+
+ // FIX, FIX - Log an error
+
+ KdPrint(("Samss: Password filter DLL returned error from initialization routine.\n");
+ " Status is: 0x%lx\n", Status));
+
+ SampPasswordFilterDllRoutine = NULL;
+ IgnoreStatus = LdrUnloadDll( ModuleHandle );
+ return;
+ }
+
+#endif // NOT_YET_SUPPORTED
+ return;
+
+
+}
+
+
+NTSTATUS
+SampEnableAuditPrivilege( VOID )
+
+/*++
+
+Routine Description:
+
+ This routine enables the SAM process's AUDIT privilege.
+ This privilege is necessary to use the NtAccessCheckAndAuditAlarm()
+ service.
+
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+
+
+--*/
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ HANDLE Token;
+ LUID AuditPrivilege;
+ PTOKEN_PRIVILEGES NewState;
+ ULONG ReturnLength;
+
+ //
+ // Open our own token
+ //
+
+ NtStatus = NtOpenProcessToken(
+ NtCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES,
+ &Token
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Initialize the adjustment structure
+ //
+
+ AuditPrivilege =
+ RtlConvertLongToLuid(SE_AUDIT_PRIVILEGE);
+
+ ASSERT( (sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)) < 100);
+ NewState = RtlAllocateHeap(RtlProcessHeap(), 0, 100 );
+
+ NewState->PrivilegeCount = 1;
+ NewState->Privileges[0].Luid = AuditPrivilege;
+ NewState->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ //
+ // Set the state of the privilege to ENABLED.
+ //
+
+ NtStatus = NtAdjustPrivilegesToken(
+ Token, // TokenHandle
+ FALSE, // DisableAllPrivileges
+ NewState, // NewState
+ 0, // BufferLength
+ NULL, // PreviousState (OPTIONAL)
+ &ReturnLength // ReturnLength
+ );
+
+ //
+ // Clean up some stuff before returning
+ //
+
+ RtlFreeHeap( RtlProcessHeap(), 0, NewState );
+ IgnoreStatus = NtClose( Token );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ return NtStatus;
+}
+
+
+NTSTATUS
+SampFixGroupCount( VOID )
+
+/*++
+
+Routine Description:
+
+ This routine fixes the group count of the account domain.
+ A bug in early Daytona beta systems left the group count
+ too low (by one). This routine fixes that problem by
+ setting the value according to however many groups are found
+ in the registry.
+
+
+Arguments:
+
+ None - uses the gobal variable "SampKey".
+
+
+Return Value:
+
+ The status value of the registry services needed to query
+ and set the group count.
+
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ OBJECT_ATTRIBUTES
+ ObjectAttributes;
+
+ UNICODE_STRING
+ KeyName,
+ NullName;
+
+ HANDLE
+ AccountHandle;
+
+ ULONG
+ ResultLength,
+ GroupCount;
+
+ PKEY_FULL_INFORMATION
+ KeyInfo;
+
+
+ RtlInitUnicodeString( &KeyName,
+ L"DOMAINS\\Account\\Groups"
+ );
+
+
+ //
+ // Open this key.
+ // Query the number of sub-keys in the key.
+ // The number of groups is one less than the number
+ // of values (because there is one key called "Names").
+ //
+
+ InitializeObjectAttributes( &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &AccountHandle,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = NtQueryKey(
+ AccountHandle,
+ KeyFullInformation,
+ NULL, // Buffer
+ 0, // Length
+ &ResultLength
+ );
+
+ if (NtStatus == STATUS_BUFFER_OVERFLOW ||
+ NtStatus == STATUS_BUFFER_TOO_SMALL) {
+
+ KeyInfo = RtlAllocateHeap( RtlProcessHeap(), 0, ResultLength);
+ if (KeyInfo == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ NtStatus = NtQueryKey(
+ AccountHandle,
+ KeyFullInformation,
+ KeyInfo, // Buffer
+ ResultLength, // Length
+ &ResultLength
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ GroupCount = (KeyInfo->SubKeys - 1);
+ }
+
+ RtlFreeHeap( RtlProcessHeap(), 0, KeyInfo );
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RtlInitUnicodeString( &NullName, NULL );
+ NtStatus = NtSetValueKey(
+ AccountHandle,
+ &NullName, // Null value name
+ 0, // Title Index
+ GroupCount, // Count goes in Type field
+ NULL, // No data
+ 0
+ );
+ }
+
+
+ IgnoreStatus = NtClose( AccountHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ return(NtStatus);
+
+
+}
+
+
+#ifdef SAMP_SETUP_FAILURE_TEST
+
+NTSTATUS
+SampInitializeForceError(
+ OUT PNTSTATUS ForcedStatus
+ )
+
+/*++
+
+Routine Description:
+
+ This function forces an error to occur in the SAM initialization/installation.
+ The error to be simulated is specified by storing the desired Nt Status
+ value to be simulated in the REG_DWORD registry key valie PhonyLsaError
+ in HKEY_LOCAL_MACHINE\System\Setup.
+
+Arguments:
+
+ ForcedStatus - Receives the Nt status code to be simulated. If set to a
+ non-success status, SAM initialization is bypassed and the specified
+ status code is set instead. If STATUS_SUCCESS is returned, no
+ simulation takes place and SAM initializes as it would normally.
+
+Return Values:
+
+ NTSTATUS - Standard Nt Result Code
+
+--*/
+
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ NTSTATUS OutputForcedStatus = STATUS_SUCCESS;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE KeyHandle = NULL;
+ PKEY_VALUE_FULL_INFORMATION KeyValueInformation = NULL;
+ ULONG KeyValueInfoLength;
+ ULONG ResultLength;
+ UNICODE_STRING KeyPath;
+ UNICODE_STRING ValueName;
+
+
+ RtlInitUnicodeString( &KeyPath, L"\\Registry\\Machine\\System\\Setup" );
+ RtlInitUnicodeString( &ValueName, L"PhonyLsaError" );
+
+ InitializeObjectAttributes( &ObjectAttributes,
+ &KeyPath,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL
+ );
+ NtStatus = NtOpenKey( &KeyHandle, MAXIMUM_ALLOWED, &ObjectAttributes);
+
+ if (!NT_SUCCESS( NtStatus )) {
+
+ //
+ // If the error is simply that the registry key does not exist,
+ // do not simulate an error and allow SAM initialization to
+ // proceed.
+ //
+
+ if (NtStatus != STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ KdPrint(("SAMSS: NtOpenKey for Phony Lsa Error failed 0x%lx\n", NtStatus));
+ goto InitializeForceErrorError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ goto InitializeForceErrorFinish;
+ }
+
+ KeyValueInfoLength = 256;
+
+ NtStatus = STATUS_NO_MEMORY;
+
+ KeyValueInformation = RtlAllocateHeap(
+ RtlProcessHeap(),
+ 0,
+ KeyValueInfoLength
+ );
+
+ if (KeyValueInformation == NULL) {
+
+ goto InitializeForceErrorError;
+ }
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &ValueName,
+ KeyValueFullInformation,
+ KeyValueInformation,
+ KeyValueInfoLength,
+ &ResultLength
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the error is simply that that the PhonyLsaError value has not
+ // been set, do not simulate an error and instead allow SAM initialization
+ // to proceed.
+ //
+
+ if (NtStatus != STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ KdPrint(("SAMSS: NtQueryValueKey for Phony Lsa Error failed 0x%lx\n", NtStatus));
+ goto InitializeForceErrorError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+ goto InitializeForceErrorFinish;
+ }
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+
+ if (KeyValueInformation->Type != REG_DWORD) {
+
+ KdPrint(("SAMSS: Key for Phony Lsa Error is not REG_DWORD type"));
+ goto InitializeForceErrorError;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ //
+ // Obtain the error code stored as the registry key value
+ //
+
+ OutputForcedStatus = *((NTSTATUS *)((PCHAR)KeyValueInformation + KeyValueInformation->DataOffset));
+
+InitializeForceErrorFinish:
+
+ //
+ // Clean up our resources.
+ //
+
+ if (KeyValueInformation != NULL) {
+
+ RtlFreeHeap( RtlProcessHeap(), 0, KeyValueInformation );
+ }
+
+ if (KeyHandle != NULL) {
+
+ NtClose( KeyHandle );
+ }
+
+ *ForcedStatus = OutputForcedStatus;
+ return(NtStatus);
+
+InitializeForceErrorError:
+
+ goto InitializeForceErrorFinish;
+}
+
+#endif // SAMP_SETUP_FAILURE_TEST
+
+
+
+#if SAMP_DIAGNOSTICS
+
+VOID
+SampActivateDebugProcess( VOID )
+
+/*++
+
+Routine Description:
+
+ This function activates a process with a time delay.
+ The point of this action is to provide some diagnostic capabilities
+ during SETUP. This originated out of the need to run dh.exe (to get
+ a heap dump of LSASS.exe) during setup.
+
+
+
+Arguments:
+
+ Arguments are provided via global variables. The debug user is
+ given an opportunity to change these string values before the
+ process is activated.
+
+Return Values:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+ HANDLE
+ Thread;
+
+ DWORD
+ ThreadId;
+
+ IF_NOT_SAMP_GLOBAL( ACTIVATE_DEBUG_PROC ) {
+ return;
+ }
+
+ //
+ // Do all the work in another thread so that it can wait before
+ // activating the debug process.
+ //
+
+ Thread = CreateThread(
+ NULL,
+ 0L,
+ (LPTHREAD_START_ROUTINE)SampActivateDebugProcessWrkr,
+ 0L,
+ 0L,
+ &ThreadId
+ );
+ if (Thread != NULL) {
+ (VOID) CloseHandle( Thread );
+ }
+
+
+ return;
+}
+
+
+NTSTATUS
+SampActivateDebugProcessWrkr(
+ IN PVOID ThreadParameter
+ )
+
+/*++
+
+Routine Description:
+
+ This function activates a process with a time delay.
+ The point of this action is to provide some diagnostic capabilities
+ during SETUP. This originated out of the need to run dh.exe (to get
+ a heap dump of LSASS.exe) during setup.
+
+ The user is given the opportunity to change any or all of the
+ following values before the process is activated (and before
+ we wait):
+
+ Seconds until activation
+ Image to activate
+ Command line to image
+
+
+Arguments:
+
+ ThreadParameter - Not used.
+
+Return Values:
+
+ STATUS_SUCCESS
+
+--*/
+
+{
+ NTSTATUS
+ NtStatus;
+
+ UNICODE_STRING
+ CommandLine;
+
+ ULONG
+ Delay = 30; // Number of seconds
+
+ SECURITY_ATTRIBUTES
+ ProcessSecurityAttributes;
+
+ STARTUPINFO
+ StartupInfo;
+
+ PROCESS_INFORMATION
+ ProcessInformation;
+
+ SECURITY_DESCRIPTOR
+ SD;
+
+ BOOL
+ Result;
+
+
+ RtlInitUnicodeString( &CommandLine,
+ TEXT("dh.exe -p 33") );
+
+
+ //
+ // Give the user an opportunity to change parameter strings...
+ //
+
+ SampDiagPrint( ACTIVATE_DEBUG_PROC,
+ ("SAM: Diagnostic flags are set to activate a debug process...\n"
+ " The following parameters are being used:\n\n"
+ " Command Line [0x%lx]: *%wZ*\n"
+ " Seconds to activation [address: 0x%lx]: %d\n\n"
+ " Change parameters if necessary and then proceed.\n"
+ " Use |# command at the ntsd prompt to see the process ID\n"
+ " of lsass.exe\n",
+ &CommandLine, &CommandLine,
+ &Delay, Delay) );
+
+ DbgBreakPoint();
+
+ //
+ // Wait for Delay seconds ...
+ //
+
+ Sleep( Delay*1000 );
+
+ SampDiagPrint( ACTIVATE_DEBUG_PROC,
+ ("SAM: Activating debug process %wZ\n",
+ &CommandLine) );
+ //
+ // Initialize process security info
+ //
+
+ InitializeSecurityDescriptor( &SD ,SECURITY_DESCRIPTOR_REVISION1 );
+ ProcessSecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+ ProcessSecurityAttributes.lpSecurityDescriptor = &SD;
+ ProcessSecurityAttributes.bInheritHandle = FALSE;
+
+ //
+ // Initialize process startup info
+ //
+
+ RtlZeroMemory( &StartupInfo, sizeof(StartupInfo) );
+ StartupInfo.cb = sizeof(STARTUPINFO);
+ StartupInfo.lpReserved = CommandLine.Buffer;
+ StartupInfo.lpTitle = CommandLine.Buffer;
+ StartupInfo.dwX =
+ StartupInfo.dwY =
+ StartupInfo.dwXSize =
+ StartupInfo.dwYSize = 0L;
+ StartupInfo.dwFlags = STARTF_FORCEOFFFEEDBACK;
+ StartupInfo.wShowWindow = SW_SHOW; // let it be seen if possible
+ StartupInfo.lpReserved2 = NULL;
+ StartupInfo.cbReserved2 = 0;
+
+
+ //
+ // Now create the diagnostic process...
+ //
+
+ Result = CreateProcess(
+ NULL, // Image name
+ CommandLine.Buffer,
+ &ProcessSecurityAttributes,
+ NULL, // ThreadSecurityAttributes
+ FALSE, // InheritHandles
+ CREATE_UNICODE_ENVIRONMENT, //Flags
+ NULL, //Environment,
+ NULL, //CurrentDirectory,
+ &StartupInfo,
+ &ProcessInformation);
+
+ if (!Result) {
+ SampDiagPrint( ACTIVATE_DEBUG_PROC,
+ ("SAM: Couldn't activate diagnostic process.\n"
+ " Error: 0x%lx (%d)\n\n",
+ GetLastError(), GetLastError()) );
+ }
+
+ return(STATUS_SUCCESS); // Exit this thread
+}
+#endif // SAMP_DIAGNOSTICS
diff --git a/private/newsam/server/secdescr.c b/private/newsam/server/secdescr.c
new file mode 100644
index 000000000..2bf37e8e1
--- /dev/null
+++ b/private/newsam/server/secdescr.c
@@ -0,0 +1,3069 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ SecDescr.c
+
+Abstract:
+
+ This file contains services related to the establishment of and modification
+ of security descriptors for SAM objects.
+
+ Note that there are a couple of special security descriptors that this routine
+ does not build. These are the security descriptors for the DOMAIN_ADMIN group,
+ the ADMIN user account, and the SAM object. For the first release, in which
+ creation of domains is not supported, the DOMAIN object's security descriptor
+ is also not created here.
+
+ These security descriptors are built by the program that initializes a SAM
+ database.
+
+
+Author:
+
+ Jim Kelly (JimK) 14-Oct-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampValidatePassedSD(
+ IN ULONG Length,
+ IN PISECURITY_DESCRIPTOR PassedSD
+ );
+
+NTSTATUS
+SampCheckForDescriptorRestrictions(
+ IN PSAMP_OBJECT Context,
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PISECURITY_DESCRIPTOR PassedSD
+ );
+
+NTSTATUS
+SampBuildSamProtection(
+ IN PSID WorldSid,
+ IN PSID AdminsAliasSid,
+ IN ULONG AceCount,
+ IN PSID AceSid[],
+ IN ACCESS_MASK AceMask[],
+ IN PGENERIC_MAPPING GenericMap,
+ IN BOOLEAN UserObject,
+ OUT PULONG DescriptorLength,
+ OUT PSECURITY_DESCRIPTOR *Descriptor,
+ OUT PULONG *RidToReplace OPTIONAL
+ );
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services available for use throughout SAM //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampInitializeDomainDescriptors(
+ ULONG Index
+ )
+/*++
+
+
+Routine Description:
+
+ This routine initializes security descriptors needed to protect
+ user, group, and alias objects.
+
+ These security descriptors are placed in the SampDefinedDomains[] array.
+
+ This routine expects all SIDs to be previously initialized.
+
+ The following security descriptors are prepared:
+
+ AdminUserSD - Contains a SD appropriate for applying to
+ a user object that is a member of the ADMINISTRATORS
+ alias.
+
+ AdminGroupSD - Contains a SD appropriate for applying to
+ a group object that is a member of the ADMINISTRATORS
+ alias.
+
+ NormalUserSD - Contains a SD appropriate for applying to
+ a user object that is NOT a member of the ADMINISTRATORS
+ alias.
+
+ NormalGroupSD - Contains a SD appropriate for applying to
+ a Group object that is NOT a member of the ADMINISTRATORS
+ alias.
+
+ NormalAliasSD - Contains a SD appropriate for applying to
+ newly created alias objects.
+
+
+
+ Additionally, the following related information is provided:
+
+ AdminUserRidPointer
+ NormalUserRidPointer
+
+ Points to the last RID of the ACE in the corresponding
+ SD's DACL which grants access to the user. This rid
+ must be replaced with the user's rid being the SD is
+ applied to the user object.
+
+
+
+ AdminUserSDLength
+ AdminGroupSDLength
+ NormalUserSDLength
+ NormalGroupSDLength
+ NormalAliasSDLength
+
+ The length, in bytes, of the corresponding security
+ descriptor.
+
+
+
+
+Arguments:
+
+ Index - The index of the domain whose security descriptors are being
+ created. The Sid field of this domain's data structure is already
+ expected to be set.
+
+Return Value:
+
+ STATUS_SUCCESS - The security descriptors have been successfully initialized.
+
+ STATUS_INSUFFICIENT_RESOURCES - Heap could not be allocated to produce the needed
+ security descriptors.
+
+--*/
+{
+
+ NTSTATUS Status;
+ ULONG Size;
+
+ PSID AceSid[10]; // Don't expect more than 10 ACEs in any of these.
+ ACCESS_MASK AceMask[10]; // Access masks corresponding to Sids
+
+ GENERIC_MAPPING AliasMap = {ALIAS_READ,
+ ALIAS_WRITE,
+ ALIAS_EXECUTE,
+ ALIAS_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING GroupMap = {GROUP_READ,
+ GROUP_WRITE,
+ GROUP_EXECUTE,
+ GROUP_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING UserMap = {USER_READ,
+ USER_WRITE,
+ USER_EXECUTE,
+ USER_ALL_ACCESS
+ };
+
+
+ SID_IDENTIFIER_AUTHORITY
+ BuiltinAuthority = SECURITY_NT_AUTHORITY;
+
+
+ ULONG AdminsSidBuffer[8],
+ AccountSidBuffer[8];
+
+ PSID AdminsAliasSid = &AdminsSidBuffer[0],
+ AccountAliasSid = &AccountSidBuffer[0],
+ AnySidInAccountDomain = NULL;
+
+
+ //
+ // Make sure the buffer we've alloted for the simple sids above
+ // are large enough.
+ //
+ //
+ // ADMINISTRATORS and ACCOUNT_OPERATORS aliases
+ // are is S-1-5-20-x (2 sub-authorities)
+ //
+ ASSERT( RtlLengthRequiredSid(2) <= ( 8 * sizeof(ULONG) ) );
+
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // //
+ // Initialize the SIDs we'll need.
+ // //
+ ////////////////////////////////////////////////////////////////////////////////////
+
+
+ RtlInitializeSid( AdminsAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AdminsAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AdminsAliasSid, 1 )) = DOMAIN_ALIAS_RID_ADMINS;
+
+ RtlInitializeSid( AccountAliasSid, &BuiltinAuthority, 2 );
+ *(RtlSubAuthoritySid( AccountAliasSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AccountAliasSid, 1 )) = DOMAIN_ALIAS_RID_ACCOUNT_OPS;
+
+ //
+ // Initialize a SID that can be used to represent accounts
+ // in this domain.
+ //
+ // This is the same as the domain sid found in the DefinedDomains[]
+ // array except it has one more sub-authority.
+ // It doesn't matter what the value of the last RID is because it
+ // is always replaced before use.
+ //
+
+ Size = RtlLengthSid( SampDefinedDomains[Index].Sid ) + sizeof(ULONG);
+ AnySidInAccountDomain = RtlAllocateHeap( RtlProcessHeap(), 0, Size);
+ ASSERT( AnySidInAccountDomain != NULL );
+ Status = RtlCopySid(
+ Size,
+ AnySidInAccountDomain,
+ SampDefinedDomains[Index].Sid );
+ ASSERT(NT_SUCCESS(Status));
+ (*RtlSubAuthorityCountSid( AnySidInAccountDomain )) += 1;
+
+
+
+
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////
+ //
+ //
+ //
+ //
+ // The following security is assigned to groups that are made
+ // members of the ADMINISTRATORS alias.
+ //
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant
+ // WORLD Administrators
+ // (Execute | Read) GenericAll
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ // All other aliases and groups must be assigned the following
+ // security:
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant Grant
+ // WORLD Administrators AccountOperators Alias
+ // (Execute | Read) GenericAll GenericAll
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ //
+ //
+ // The following security is assigned to users that are made a
+ // member of the ADMINISTRATORS alias. This includes direct
+ // inclusion or indirect inclusion through group membership.
+ //
+ //
+ // Owner: Administrators Alias
+ // Group: Administrators Alias
+ //
+ // Dacl: Grant Grant Grant
+ // WORLD Administrators User's SID
+ // (Execute | Read) GenericAll GenericWrite
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ //
+ //
+ // All other users must be assigned the following
+ // security:
+ //
+ // Owner: AccountOperators Alias
+ // Group: AccountOperators Alias
+ //
+ // Dacl: Grant Grant Grant Grant
+ // WORLD Administrators Account Operators Alias User's SID
+ // (Execute | Read) GenericAll GenericAll GenericWrite
+ //
+ // Sacl: Audit
+ // Success | Fail
+ // WORLD
+ // (Write | Delete | WriteDacl | AccessSystemSecurity)
+ //
+ //
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //
+ // Note that because we are going to cram these ACLs directly
+ // into the backing store, we must map the generic accesses
+ // beforehand.
+ //
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ //
+ ///////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////
+
+
+
+
+
+ //
+ // We're not particularly good about freeing memory on error
+ // conditions below. Generally speaking, if this doens't
+ // initialize correctly, the system is hosed.
+ //
+
+
+ //
+ // Normal Alias SD
+ //
+
+ AceSid[0] = SampWorldSid;
+ AceMask[0] = (ALIAS_EXECUTE | ALIAS_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (ALIAS_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (ALIAS_ALL_ACCESS);
+
+
+ Status = SampBuildSamProtection(
+ SampWorldSid, // WorldSid
+ AdminsAliasSid, // AdminsAliasSid
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &AliasMap, // GenericMap
+ FALSE, // Not user object
+ &SampDefinedDomains[Index].NormalAliasSDLength, // Descriptor
+ &SampDefinedDomains[Index].NormalAliasSD, // Descriptor
+ NULL // RidToReplace
+ );
+ if (!NT_SUCCESS(Status)) {
+ goto done;
+ }
+
+
+
+
+
+ //
+ // Admin Group SD
+ //
+
+ AceSid[0] = SampWorldSid;
+ AceMask[0] = (GROUP_EXECUTE | GROUP_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (GROUP_ALL_ACCESS);
+
+
+ Status = SampBuildSamProtection(
+ SampWorldSid, // WorldSid
+ AdminsAliasSid, // AdminsAliasSid
+ 2, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &GroupMap, // GenericMap
+ FALSE, // Not user object
+ &SampDefinedDomains[Index].AdminGroupSDLength, // Descriptor
+ &SampDefinedDomains[Index].AdminGroupSD, // Descriptor
+ NULL // RidToReplace
+ );
+ if (!NT_SUCCESS(Status)) {
+ goto done;
+ }
+
+
+
+ //
+ // Normal GROUP SD
+ //
+
+ AceSid[0] = SampWorldSid;
+ AceMask[0] = (GROUP_EXECUTE | GROUP_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (GROUP_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (GROUP_ALL_ACCESS);
+
+
+ Status = SampBuildSamProtection(
+ SampWorldSid, // WorldSid
+ AdminsAliasSid, // AdminsAliasSid
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &GroupMap, // GenericMap
+ FALSE, // Not user object
+ &SampDefinedDomains[Index].NormalGroupSDLength, // Descriptor
+ &SampDefinedDomains[Index].NormalGroupSD, // Descriptor
+ NULL // RidToReplace
+ );
+ if (!NT_SUCCESS(Status)) {
+ goto done;
+ }
+
+
+
+
+ //
+ // Admin User SD
+ //
+
+ AceSid[0] = SampWorldSid;
+ AceMask[0] = (USER_EXECUTE | USER_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (USER_ALL_ACCESS);
+
+ AceSid[2] = AnySidInAccountDomain;
+ AceMask[2] = (USER_WRITE);
+
+
+ Status = SampBuildSamProtection(
+ SampWorldSid, // WorldSid
+ AdminsAliasSid, // AdminsAliasSid
+ 3, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &UserMap, // GenericMap
+ TRUE, // Not user object
+ &SampDefinedDomains[Index].AdminUserSDLength, // Descriptor
+ &SampDefinedDomains[Index].AdminUserSD, // Descriptor
+ &SampDefinedDomains[Index].AdminUserRidPointer // RidToReplace
+ );
+ if (!NT_SUCCESS(Status)) {
+ goto done;
+ }
+
+
+
+ //
+ // Normal User SD
+ //
+
+ AceSid[0] = SampWorldSid;
+ AceMask[0] = (USER_EXECUTE | USER_READ);
+
+ AceSid[1] = AdminsAliasSid;
+ AceMask[1] = (USER_ALL_ACCESS);
+
+ AceSid[2] = AccountAliasSid;
+ AceMask[2] = (USER_ALL_ACCESS);
+
+ AceSid[3] = AnySidInAccountDomain;
+ AceMask[3] = (USER_WRITE);
+
+
+ Status = SampBuildSamProtection(
+ SampWorldSid, // WorldSid
+ AdminsAliasSid, // AdminsAliasSid
+ 4, // AceCount
+ &AceSid[0], // AceSid array
+ &AceMask[0], // Ace Mask array
+ &UserMap, // GenericMap
+ TRUE, // Not user object
+ &SampDefinedDomains[Index].NormalUserSDLength, // Descriptor
+ &SampDefinedDomains[Index].NormalUserSD, // Descriptor
+ &SampDefinedDomains[Index].NormalUserRidPointer // RidToReplace
+ );
+ if (!NT_SUCCESS(Status)) {
+ goto done;
+ }
+
+done:
+
+
+ RtlFreeHeap( RtlProcessHeap(), 0, AnySidInAccountDomain );
+
+
+ return(Status);
+
+}
+
+
+NTSTATUS
+SampBuildSamProtection(
+ IN PSID WorldSid,
+ IN PSID AdminsAliasSid,
+ IN ULONG AceCount,
+ IN PSID AceSid[],
+ IN ACCESS_MASK AceMask[],
+ IN PGENERIC_MAPPING GenericMap,
+ IN BOOLEAN UserObject,
+ OUT PULONG DescriptorLength,
+ OUT PSECURITY_DESCRIPTOR *Descriptor,
+ OUT PULONG *RidToReplace OPTIONAL
+ )
+
+/*++
+
+
+Routine Description:
+
+ This routine builds a self-relative security descriptor ready
+ to be applied to one of the SAM objects.
+
+ If so indicated, a pointer to the last RID of the SID in the last
+ ACE of the DACL is returned and a flag set indicating that the RID
+ must be replaced before the security descriptor is applied to an object.
+ This is to support USER object protection, which must grant some
+ access to the user represented by the object.
+
+ The owner and group of each security descriptor will be set
+ to:
+
+ Owner: Administrators Alias
+ Group: Administrators Alias
+
+
+ The SACL of each of these objects will be set to:
+
+
+ Audit
+ Success | Fail
+ WORLD
+ (Write | Delete | WriteDacl | AccessSystemSecurity)
+
+
+
+Arguments:
+
+ AceCount - The number of ACEs to be included in the DACL.
+
+ AceSid - Points to an array of SIDs to be granted access by the DACL.
+ If the target SAM object is a User object, then the last entry
+ in this array is expected to be the SID of an account within the
+ domain with the last RID not yet set. The RID will be set during
+ actual account creation.
+
+ AceMask - Points to an array of accesses to be granted by the DACL.
+ The n'th entry of this array corresponds to the n'th entry of
+ the AceSid array. These masks should not include any generic
+ access types.
+
+ GenericMap - Points to a generic mapping for the target object type.
+
+
+ UserObject - Indicates whether the target SAM object is a User object
+ or not. If TRUE (it is a User object), then the resultant
+ protection will be set up indicating Rid replacement is necessary.
+
+
+ DescriptorLength - Receives the length of the resultant SD.
+
+ Descriptor - Receives a pointer to the resultant SD.
+
+ RidToReplace - Is required aif userObject is TRUE and will be set
+ to point to the user's RID.
+
+
+Return Value:
+
+ TBS.
+
+--*/
+{
+
+ NTSTATUS Status;
+
+ SECURITY_DESCRIPTOR Absolute;
+ PSECURITY_DESCRIPTOR Relative;
+ PACL TmpAcl;
+ PACCESS_ALLOWED_ACE TmpAce;
+ PSID TmpSid;
+ ULONG Length, i;
+ PULONG RidLocation;
+ BOOLEAN IgnoreBoolean;
+ ACCESS_MASK MappedMask;
+
+ //
+ // The approach is to set up an absolute security descriptor that
+ // looks like what we want and then copy it to make a self-relative
+ // security descriptor.
+ //
+
+
+ Status = RtlCreateSecurityDescriptor(
+ &Absolute,
+ SECURITY_DESCRIPTOR_REVISION1
+ );
+ ASSERT( NT_SUCCESS(Status) );
+
+
+
+ //
+ // Owner
+ //
+
+ Status = RtlSetOwnerSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+ //
+ // Group
+ //
+
+ Status = RtlSetGroupSecurityDescriptor (&Absolute, AdminsAliasSid, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Discretionary ACL
+ //
+ // Calculate its length,
+ // Allocate it,
+ // Initialize it,
+ // Add each ACE
+ // Add it to the security descriptor
+ //
+
+ Length = (ULONG)sizeof(ACL);
+ for (i=0; i<AceCount; i++) {
+
+ Length += RtlLengthSid( AceSid[i] ) +
+ (ULONG)sizeof(ACCESS_ALLOWED_ACE) -
+ (ULONG)sizeof(ULONG); //Subtract out SidStart field length
+ }
+
+ TmpAcl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(TmpAcl != NULL);
+
+
+ Status = RtlCreateAcl( TmpAcl, Length, ACL_REVISION2);
+ ASSERT( NT_SUCCESS(Status) );
+
+ for (i=0; i<AceCount; i++) {
+ MappedMask = AceMask[i];
+ RtlMapGenericMask( &MappedMask, GenericMap );
+ Status = RtlAddAccessAllowedAce (
+ TmpAcl,
+ ACL_REVISION2,
+ MappedMask,
+ AceSid[i]
+ );
+ ASSERT( NT_SUCCESS(Status) );
+ }
+
+ Status = RtlSetDaclSecurityDescriptor (&Absolute, TRUE, TmpAcl, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+ //
+ // Sacl
+ //
+
+
+ Length = (ULONG)sizeof(ACL) +
+ RtlLengthSid( WorldSid ) +
+ (ULONG)sizeof(SYSTEM_AUDIT_ACE) -
+ (ULONG)sizeof(ULONG); //Subtract out SidStart field length
+ TmpAcl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(TmpAcl != NULL);
+
+ Status = RtlCreateAcl( TmpAcl, Length, ACL_REVISION2);
+ ASSERT( NT_SUCCESS(Status) );
+
+ Status = RtlAddAuditAccessAce (
+ TmpAcl,
+ ACL_REVISION2,
+ GenericMap->GenericWrite | DELETE | WRITE_DAC | ACCESS_SYSTEM_SECURITY,
+ WorldSid,
+ TRUE, //AuditSuccess,
+ TRUE //AuditFailure
+ );
+ ASSERT( NT_SUCCESS(Status) );
+
+ Status = RtlSetSaclSecurityDescriptor (&Absolute, TRUE, TmpAcl, FALSE );
+ ASSERT(NT_SUCCESS(Status));
+
+
+
+
+
+
+ //
+ // Convert the Security Descriptor to Self-Relative
+ //
+ // Get the length needed
+ // Allocate that much memory
+ // Copy it
+ // Free the generated absolute ACLs
+ //
+
+ Length = 0;
+ Status = RtlAbsoluteToSelfRelativeSD( &Absolute, NULL, &Length );
+ ASSERT(Status == STATUS_BUFFER_TOO_SMALL);
+
+ Relative = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+ ASSERT(Relative != NULL);
+ Status = RtlAbsoluteToSelfRelativeSD(&Absolute, Relative, &Length );
+ ASSERT(NT_SUCCESS(Status));
+
+
+ RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Dacl );
+ RtlFreeHeap( RtlProcessHeap(), 0, Absolute.Sacl );
+
+
+
+
+ //
+ // If the object is a user object, then get the address of the
+ // last RID of the SID in the last ACE in the DACL.
+ //
+
+ if (UserObject == TRUE) {
+
+ Status = RtlGetDaclSecurityDescriptor(
+ Relative,
+ &IgnoreBoolean,
+ &TmpAcl,
+ &IgnoreBoolean
+ );
+ ASSERT(NT_SUCCESS(Status));
+ Status = RtlGetAce ( TmpAcl, AceCount-1, (PVOID *)&TmpAce );
+ ASSERT(NT_SUCCESS(Status));
+ TmpSid = (PSID)(&TmpAce->SidStart),
+
+ RidLocation = RtlSubAuthoritySid(
+ TmpSid,
+ (ULONG)(*RtlSubAuthorityCountSid( TmpSid ) - 1)
+ );
+ }
+
+
+
+
+
+
+
+ //
+ // Set the result information
+ //
+
+ (*DescriptorLength) = Length;
+ (*Descriptor) = Relative;
+ if (ARGUMENT_PRESENT(RidToReplace)) {
+ (*RidToReplace) = RidLocation;
+ }
+
+
+
+ return(Status);
+
+}
+
+
+
+NTSTATUS
+SampGetNewAccountSecurity(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Admin,
+ IN BOOLEAN TrustedClient,
+ IN BOOLEAN RestrictCreatorAccess,
+ IN ULONG NewAccountRid,
+ OUT PSECURITY_DESCRIPTOR *NewDescriptor,
+ OUT PULONG DescriptorLength
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service creates a standard self-relative security descriptor
+ for a new USER, GROUP or ALIAS account.
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+Arguments:
+
+ ObjectType - Indicates the type of account for which a new security
+ descriptor is required. This must be either SampGroupObjectType
+ or SampUserObjectType.
+
+ Admin - if TRUE, indicates the security descriptor will be protecting
+ an object that is an admin object (e.g., is a member, directly
+ or indirectly, of the ADMINISTRATORS alias).
+
+ TrustedClient - Indicates whether the client is a trusted client
+ or not. TRUE indicates the client is trusted, FALSE indicates
+ the client is not trusted.
+
+ RestrictCreatorAccess - Indicates whether or not the creator's
+ access to the object is to be restricted according to
+ specific rules. Also indicates whether or not the account
+ is to be given any access to itself. An account will only
+ be given access to itself if there are no creator access
+ restrictions.
+
+ The following ObjectTypes have restriction rules that may
+ be requested:
+
+ User:
+ - Admin is assigned as owner of the object.
+ - Creator is given (DELETE | USER_WRITE) access.
+
+
+ NewAccountRid - The relative ID of the new account.
+
+ NewDescriptor - Receives a pointer to the new account's self-relative
+ security descriptor. Be sure to free this descriptor with
+ MIDL_user_free() when done.
+
+ DescriptorLength - Receives the length (in bytes) of the returned
+ security descriptor
+
+
+Return Value:
+
+ STATUS_SUCCESS - A new security descriptor has been produced.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to
+ produce the security descriptor.
+
+
+
+--*/
+
+{
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+ ULONG AccountSidBuffer[8];
+ PSID AccountAliasSid = &AccountSidBuffer[0];
+
+ SECURITY_DESCRIPTOR DaclDescriptor;
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ NTSTATUS IgnoreStatus;
+ HANDLE ClientToken = INVALID_HANDLE_VALUE;
+ ULONG DataLength = 0;
+ ACCESS_ALLOWED_ACE *NewAce = NULL;
+ PACL NewDacl = NULL;
+ PACL OldDacl = NULL;
+ PSECURITY_DESCRIPTOR StaticDescriptor = NULL;
+ PSECURITY_DESCRIPTOR LocalDescriptor = NULL;
+ PTOKEN_GROUPS ClientGroups = NULL;
+ PTOKEN_OWNER SubjectOwner = NULL;
+ PSID SubjectSid = NULL;
+ ULONG AceLength = 0;
+ ULONG i;
+ BOOLEAN AdminAliasFound = FALSE;
+ BOOLEAN AccountAliasFound = FALSE;
+ BOOLEAN DaclPresent, DaclDefaulted;
+
+ GENERIC_MAPPING GenericMapping;
+ GENERIC_MAPPING AliasMap = {ALIAS_READ,
+ ALIAS_WRITE,
+ ALIAS_EXECUTE,
+ ALIAS_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING GroupMap = {GROUP_READ,
+ GROUP_WRITE,
+ GROUP_EXECUTE,
+ GROUP_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING UserMap = {USER_READ,
+ USER_WRITE,
+ USER_EXECUTE,
+ USER_ALL_ACCESS
+ };
+
+ //
+ // Security account objects don't pick up security in the normal
+ // fashion in the release 1 timeframe. They are assigned a well-known
+ // security descriptor based upon their object type.
+ //
+ // Notice that all the accounts with tricky security are created when
+ // the domain is created (e.g., admin groups and admin user account).
+ //
+
+ switch (ObjectType) {
+
+ case SampGroupObjectType:
+
+ ASSERT(RestrictCreatorAccess == FALSE);
+
+ //
+ // NewAccountRid parameter is ignored for groups.
+ //
+
+ if (Admin == TRUE) {
+
+ StaticDescriptor =
+ SampDefinedDomains[SampTransactionDomainIndex].AdminGroupSD;
+ (*DescriptorLength) =
+ SampDefinedDomains[SampTransactionDomainIndex].AdminGroupSDLength;
+ } else {
+
+ StaticDescriptor =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalGroupSD;
+ (*DescriptorLength) =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalGroupSDLength;
+ }
+
+ GenericMapping = GroupMap;
+
+ break;
+
+
+ case SampAliasObjectType:
+
+ ASSERT(RestrictCreatorAccess == FALSE);
+
+ //
+ // Admin and NewAccountRid parameters are ignored for aliases.
+ //
+
+ StaticDescriptor =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalAliasSD;
+ (*DescriptorLength) =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalAliasSDLength;
+
+ GenericMapping = AliasMap;
+
+ break;
+
+
+ case SampUserObjectType:
+
+ if (Admin == TRUE) {
+
+ StaticDescriptor =
+ SampDefinedDomains[SampTransactionDomainIndex].AdminUserSD;
+ (*DescriptorLength) =
+ SampDefinedDomains[SampTransactionDomainIndex].AdminUserSDLength;
+ (*SampDefinedDomains[SampTransactionDomainIndex].AdminUserRidPointer)
+ = NewAccountRid;
+
+ } else {
+
+ StaticDescriptor =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalUserSD;
+ (*DescriptorLength) =
+ SampDefinedDomains[SampTransactionDomainIndex].NormalUserSDLength;
+ (*SampDefinedDomains[SampTransactionDomainIndex].NormalUserRidPointer)
+ = NewAccountRid;
+ }
+
+ GenericMapping = UserMap;
+
+ break;
+
+ }
+
+ //
+ // We have a pointer to SAM's static security descriptor. Copy it
+ // into a heap buffer that RtlSetSecurityObject() will like.
+ //
+
+ LocalDescriptor = RtlAllocateHeap( RtlProcessHeap(), 0, (*DescriptorLength) );
+
+ if ( LocalDescriptor == NULL ) {
+
+ (*NewDescriptor) = NULL;
+ (*DescriptorLength) = 0;
+
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlCopyMemory(
+ LocalDescriptor,
+ StaticDescriptor,
+ (*DescriptorLength)
+ );
+
+ //
+ // If the caller is to have restricted access to this account,
+ // then remove the last ACE from the ACL (the one intended for
+ // the account itself).
+ //
+
+ if (RestrictCreatorAccess) {
+ NtStatus = RtlGetDaclSecurityDescriptor(
+ LocalDescriptor,
+ &DaclPresent,
+ &OldDacl,
+ &DaclDefaulted
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+ ASSERT(DaclPresent);
+ ASSERT(OldDacl->AceCount >= 1);
+
+ OldDacl->AceCount -= 1; // Remove the last ACE from the ACL.
+ }
+
+
+ //
+ // If the caller is not a trusted client, see if the caller is an
+ // administrator or an account operator. If not, add an ACCESS_ALLOWED
+ // ACE to the DACL that gives full access to the creator (or restricted
+ // access, if so specified).
+ //
+
+ if ( !TrustedClient ) {
+
+ NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
+
+ if (NT_SUCCESS(NtStatus)) { // if (ImpersonatingClient)
+
+ NtStatus = NtOpenThreadToken(
+ NtCurrentThread(),
+ TOKEN_QUERY,
+ TRUE, //OpenAsSelf
+ &ClientToken
+ );
+
+ //
+ // Stop impersonating the client
+ //
+
+ IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf());
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ if (NT_SUCCESS(NtStatus)) { // if (TokenOpened)
+
+
+
+
+ //
+ // See if the caller is an administrator or an account
+ // operator. First, see how big
+ // a buffer we need to hold the caller's groups.
+ //
+
+ NtStatus = NtQueryInformationToken(
+ ClientToken,
+ TokenGroups,
+ NULL,
+ 0,
+ &DataLength
+ );
+
+ if ( ( NtStatus == STATUS_BUFFER_TOO_SMALL ) &&
+ ( DataLength > 0 ) ) {
+
+ ClientGroups = MIDL_user_allocate( DataLength );
+
+ if ( ClientGroups == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Now get a list of the caller's groups.
+ //
+
+ NtStatus = NtQueryInformationToken(
+ ClientToken,
+ TokenGroups,
+ ClientGroups,
+ DataLength,
+ &DataLength
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+
+ //
+ // Build the SID of the ACCOUNT_OPS alias, so we
+ // can see if the user is included in it.
+ //
+
+ RtlInitializeSid(
+ AccountAliasSid,
+ &BuiltinAuthority,
+ 2 );
+
+ *(RtlSubAuthoritySid( AccountAliasSid, 0 )) =
+ SECURITY_BUILTIN_DOMAIN_RID;
+
+ *(RtlSubAuthoritySid( AccountAliasSid, 1 )) =
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS;
+
+ //
+ // See if the ADMIN or ACCOUNT_OPS alias is in
+ // the caller's groups.
+ //
+
+ for ( i = 0; i < ClientGroups->GroupCount; i++ ) {
+
+ SubjectSid = ClientGroups->Groups[i].Sid;
+ ASSERT( SubjectSid != NULL );
+
+ if ( RtlEqualSid( SubjectSid, SampAdministratorsAliasSid ) ) {
+
+ AdminAliasFound = TRUE;
+ break;
+ }
+ if ( RtlEqualSid( SubjectSid, AccountAliasSid ) ) {
+
+ AccountAliasFound = TRUE;
+ break;
+ }
+ }
+
+ //
+ // If the callers groups did not include the admins
+ // alias, add an ACCESS_ALLOWED ACE for the owner.
+ //
+
+ if ( !AdminAliasFound && !AccountAliasFound ) {
+
+ //
+ // First, find out what size buffer we need
+ // to get the owner.
+ //
+
+ NtStatus = NtQueryInformationToken(
+ ClientToken,
+ TokenOwner,
+ NULL,
+ 0,
+ &DataLength
+ );
+
+ if ( ( NtStatus == STATUS_BUFFER_TOO_SMALL ) &&
+ ( DataLength > 0 ) ) {
+
+ SubjectOwner = MIDL_user_allocate( DataLength );
+
+ if ( SubjectOwner == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Now, query the owner that will be
+ // given access to the object
+ // created.
+ //
+
+ NtStatus = NtQueryInformationToken(
+ ClientToken,
+ TokenOwner,
+ SubjectOwner,
+ DataLength,
+ &DataLength
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Create an ACE that gives the
+ // owner full access.
+ //
+
+ AceLength = sizeof( ACE_HEADER ) +
+ sizeof( ACCESS_MASK ) +
+ RtlLengthSid(
+ SubjectOwner->Owner );
+
+ NewAce = (ACCESS_ALLOWED_ACE *)
+ MIDL_user_allocate( AceLength );
+
+ if ( NewAce == NULL ) {
+
+ NtStatus =
+ STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ NewAce->Header.AceType =
+ ACCESS_ALLOWED_ACE_TYPE;
+
+ NewAce->Header.AceSize = (USHORT) AceLength;
+ NewAce->Header.AceFlags = 0;
+ NewAce->Mask = USER_ALL_ACCESS;
+
+ //
+ // If the creator's access is
+ // to be restricted, change the
+ // AccessMask.
+ //
+
+ if (RestrictCreatorAccess) {
+ NewAce->Mask = DELETE |
+ USER_WRITE |
+ USER_FORCE_PASSWORD_CHANGE;
+ }
+
+ RtlCopySid(
+ RtlLengthSid(
+ SubjectOwner->Owner ),
+ (PSID)( &NewAce->SidStart ),
+ SubjectOwner->Owner );
+
+ //
+ // Allocate a new, larger ACL and
+ // copy the old one into it.
+ //
+
+ NtStatus =
+ RtlGetDaclSecurityDescriptor(
+ LocalDescriptor,
+ &DaclPresent,
+ &OldDacl,
+ &DaclDefaulted
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NewDacl = MIDL_user_allocate(
+ OldDacl->AclSize +
+ AceLength );
+
+ if ( NewDacl == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ RtlCopyMemory(
+ NewDacl,
+ OldDacl,
+ OldDacl->AclSize
+ );
+
+ NewDacl->AclSize =
+ OldDacl->AclSize +
+ (USHORT) AceLength;
+
+ //
+ // Add the new ACE
+ // to the new ACL.
+ //
+
+ NtStatus = RtlAddAce(
+ NewDacl,
+ ACL_REVISION2,
+ 1, // add after first ACE (world)
+ (PVOID)NewAce,
+ AceLength
+ );
+ } // end_if (allocated NewDacl)
+ } // end_if (get DACL from SD)
+ } // end_if (allocated NewAce)
+ } // end_if (Query TokenOwner Succeeded)
+ } // end_if (Allocated TokenOwner buffer)
+ } // end_if (Query TokenOwner size Succeeded)
+ } // end_if (not admin)
+ } // end_if (Query TokenGroups Succeeded)
+ } // end_if (Allocated TokenGroups buffer)
+ } // end_if (Query TokenGroups size Succeeded)
+
+ IgnoreStatus = NtClose( ClientToken );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } // end_if (TokenOpened)
+ } // end_if (ImpersonatingClient)
+ } // end_if (TrustedClient)
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we created a new DACL above, stick it on the security
+ // descriptor.
+ //
+
+ if ( NewDacl != NULL ) {
+
+ NtStatus = RtlCreateSecurityDescriptor(
+ &DaclDescriptor,
+ SECURITY_DESCRIPTOR_REVISION1
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Set the DACL on the LocalDescriptor. Note that this
+ // call will RtlFreeHeap() the old descriptor, and allocate
+ // a new one.
+ //
+
+ DaclDescriptor.Control = SE_DACL_PRESENT;
+ DaclDescriptor.Dacl = NewDacl;
+
+ NtStatus = RtlSetSecurityObject(
+ DACL_SECURITY_INFORMATION,
+ &DaclDescriptor,
+ &LocalDescriptor,
+ &GenericMapping,
+ NULL
+ );
+ }
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Copy the security descriptor and length into buffers for the
+ // caller. AceLength is 0 if we didn't add an ACE to the DACL
+ // above.
+ //
+
+ (*DescriptorLength) = (*DescriptorLength) + AceLength;
+
+ (*NewDescriptor) = MIDL_user_allocate( (*DescriptorLength) );
+
+ if ( (*NewDescriptor) == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ RtlCopyMemory(
+ (*NewDescriptor),
+ LocalDescriptor,
+ (*DescriptorLength)
+ );
+ }
+ }
+
+ //
+ // Free up local items that may have been allocated.
+ //
+
+ if ( LocalDescriptor != NULL ) {
+ RtlFreeHeap( RtlProcessHeap(), 0, LocalDescriptor );
+ }
+
+ if ( ClientGroups != NULL ) {
+ MIDL_user_free( ClientGroups );
+ }
+
+ if ( SubjectOwner != NULL ) {
+ MIDL_user_free( SubjectOwner );
+ }
+
+ if ( NewAce != NULL ) {
+ MIDL_user_free( NewAce );
+ }
+
+ if ( NewDacl != NULL ) {
+ MIDL_user_free( NewDacl );
+ }
+
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ (*NewDescriptor) = NULL;
+ (*DescriptorLength) = 0;
+ }
+
+ return( NtStatus );
+}
+
+
+NTSTATUS
+SampModifyAccountSecurity(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Admin,
+ IN PSECURITY_DESCRIPTOR OldDescriptor,
+ OUT PSECURITY_DESCRIPTOR *NewDescriptor,
+ OUT PULONG DescriptorLength
+ )
+/*++
+
+Routine Description:
+
+ This service modifies a self-relative security descriptor
+ for a USER or GROUP to add or remove account operator access.
+
+
+Arguments:
+
+ ObjectType - Indicates the type of account for which a new security
+ descriptor is required. This must be either SampGroupObjectType
+ or SampUserObjectType.
+
+ Admin - if TRUE, indicates the security descriptor will be protecting
+ an object that is an admin object (e.g., is a member, directly
+ or indirectly, of the ADMINISTRATORS or an operator alias).
+
+ NewDescriptor - Receives a pointer to the new account's self-relative
+ security descriptor. Be sure to free this descriptor with
+ MIDL_user_free() when done.
+
+ DescriptorLength - Receives the length (in bytes) of the returned
+ security descriptor
+
+
+Return Value:
+
+ STATUS_SUCCESS - A new security descriptor has been produced.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to
+ produce the security descriptor.
+
+
+
+--*/
+
+{
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+ ULONG AccountSidBuffer[8];
+ PSID AccountAliasSid = &AccountSidBuffer[0];
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ NTSTATUS IgnoreStatus;
+ ULONG Length;
+ ULONG i,j;
+ ULONG AccountOpAceIndex;
+ ULONG AceCount;
+ PACL OldDacl;
+ PACL NewDacl = NULL;
+ BOOLEAN DaclDefaulted;
+ BOOLEAN DaclPresent;
+ ACL_SIZE_INFORMATION AclSizeInfo;
+ PACCESS_ALLOWED_ACE Ace;
+ PGENERIC_MAPPING GenericMapping;
+ ACCESS_MASK AccountOpAccess;
+ SECURITY_DESCRIPTOR AbsoluteDescriptor;
+ PSECURITY_DESCRIPTOR LocalDescriptor = NULL;
+
+ GENERIC_MAPPING GroupMap = {GROUP_READ,
+ GROUP_WRITE,
+ GROUP_EXECUTE,
+ GROUP_ALL_ACCESS
+ };
+
+ GENERIC_MAPPING UserMap = {USER_READ,
+ USER_WRITE,
+ USER_EXECUTE,
+ USER_ALL_ACCESS
+ };
+
+
+ NtStatus = RtlCopySecurityDescriptor(
+ OldDescriptor,
+ &LocalDescriptor
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ //
+ // Build the SID of the ACCOUNT_OPS alias, so we
+ // can see if is in the DACL or we can add it to the DACL.
+ //
+
+ RtlInitializeSid(
+ AccountAliasSid,
+ &BuiltinAuthority,
+ 2
+ );
+
+ *(RtlSubAuthoritySid( AccountAliasSid, 0 )) =
+ SECURITY_BUILTIN_DOMAIN_RID;
+
+ *(RtlSubAuthoritySid( AccountAliasSid, 1 )) =
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS;
+
+ //
+ // The approach is to set up an absolute security descriptor that
+ // contains the new DACL, and then merge that into the existing
+ // security descriptor.
+ //
+
+
+ IgnoreStatus = RtlCreateSecurityDescriptor(
+ &AbsoluteDescriptor,
+ SECURITY_DESCRIPTOR_REVISION1
+ );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ //
+ // Figure out the access granted to account operators and the
+ // generic mask to use.
+ //
+
+ if (ObjectType == SampUserObjectType) {
+ AccountOpAccess = USER_ALL_ACCESS;
+ GenericMapping = &UserMap;
+ } else if (ObjectType == SampGroupObjectType) {
+ AccountOpAccess = GROUP_ALL_ACCESS;
+ GenericMapping = &GroupMap;
+ } else {
+ //
+ // This doesn't apply to aliases, domains, or servers.
+ //
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Get the old DACL off the passed in security descriptor.
+ //
+
+ IgnoreStatus = RtlGetDaclSecurityDescriptor(
+ OldDescriptor,
+ &DaclPresent,
+ &OldDacl,
+ &DaclDefaulted
+ );
+
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // We will only modify the DACL if it is present
+ //
+
+ if (!DaclPresent) {
+ *NewDescriptor = LocalDescriptor;
+ *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor);
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // Get the count of ACEs
+ //
+
+ IgnoreStatus = RtlQueryInformationAcl(
+ OldDacl,
+ &AclSizeInfo,
+ sizeof(AclSizeInfo),
+ AclSizeInformation
+ );
+
+
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Calculate the lenght of the new ACL.
+ //
+
+ Length = (ULONG)sizeof(ACL);
+ AccountOpAceIndex = 0xffffffff;
+
+
+ for (i = 0; i < AclSizeInfo.AceCount; i++) {
+ IgnoreStatus = RtlGetAce(
+ OldDacl,
+ i,
+ (PVOID *) &Ace
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Check if this is an access allowed ACE, and the ACE is for
+ // the Account Operators alias.
+ //
+
+ if ( (Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) &&
+ RtlEqualSid( AccountAliasSid,
+ &Ace->SidStart ) ) {
+
+ AccountOpAceIndex = i;
+ continue;
+ }
+ Length += Ace->Header.AceSize;
+ }
+
+
+ if (!Admin) {
+
+ //
+ // If we are making this account not be an admin account and it already
+ // has an account operator ace, we are done.
+ //
+
+ if ( AccountOpAceIndex != 0xffffffff ) {
+
+ *NewDescriptor = LocalDescriptor;
+ *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor);
+ return(STATUS_SUCCESS);
+ } else {
+
+ //
+ // Add the size of an account operator ace to the required length
+ //
+
+ Length += sizeof(ACCESS_ALLOWED_ACE) +
+ RtlLengthSid(AccountAliasSid) -
+ sizeof(ULONG);
+ }
+
+ }
+
+ NewDacl = RtlAllocateHeap( RtlProcessHeap(), 0, Length );
+
+ if (NewDacl == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto Cleanup;
+ }
+
+ IgnoreStatus = RtlCreateAcl( NewDacl, Length, ACL_REVISION2);
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+
+ //
+ // Add the old ACEs back into this ACL.
+ //
+
+ for (i = 0, j = 0; i < AclSizeInfo.AceCount; i++) {
+ if (i == AccountOpAceIndex) {
+ ASSERT(Admin);
+ continue;
+ }
+ //
+ // Add back in the old ACEs
+ //
+
+ IgnoreStatus = RtlGetAce(
+ OldDacl,
+ i,
+ (PVOID *) &Ace
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ IgnoreStatus = RtlAddAce (
+ NewDacl,
+ ACL_REVISION2,
+ j,
+ Ace,
+ Ace->Header.AceSize
+ );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ //
+ // If we are making this account not be an administrator, add the
+ // access allowed ACE for the account operator. This ACE is always
+ // the second to last one.
+ //
+
+ if (!Admin) {
+ IgnoreStatus = RtlAddAccessAllowedAce(
+ NewDacl,
+ ACL_REVISION2,
+ AccountOpAccess,
+ AccountAliasSid
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Insert this DACL into the security descriptor.
+ //
+
+ IgnoreStatus = RtlSetDaclSecurityDescriptor (
+ &AbsoluteDescriptor,
+ TRUE, // DACL present
+ NewDacl,
+ FALSE // DACL not defaulted
+ );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Now call RtlSetSecurityObject to merge the existing security descriptor
+ // with the new DACL we just created.
+ //
+
+
+ NtStatus = RtlSetSecurityObject(
+ DACL_SECURITY_INFORMATION,
+ &AbsoluteDescriptor,
+ &LocalDescriptor,
+ GenericMapping,
+ NULL
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+ *NewDescriptor = LocalDescriptor;
+ *DescriptorLength = RtlLengthSecurityDescriptor(LocalDescriptor);
+ LocalDescriptor = NULL;
+Cleanup:
+
+ if ( NewDacl != NULL ) {
+ RtlFreeHeap(RtlProcessHeap(),0, NewDacl );
+ }
+ if (LocalDescriptor != NULL) {
+ RtlDeleteSecurityObject(&LocalDescriptor);
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampGetObjectSD(
+ IN PSAMP_OBJECT Context,
+ OUT PULONG SecurityDescriptorLength,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor
+ )
+
+/*++
+
+Routine Description:
+
+ This retrieves a security descriptor from a SAM object's backing store.
+
+
+
+
+Arguments:
+
+ Context - The object to which access is being requested.
+
+ SecurityDescriptorLength - Receives the length of the security descriptor.
+
+ SecurityDescriptor - Receives a pointer to the security descriptor.
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - The security descriptor has been retrieved.
+
+ STATUS_INTERNAL_DB_CORRUPTION - The object does not have a security descriptor.
+ This is bad.
+
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to retrieve the
+ security descriptor.
+
+ STATUS_UNKNOWN_REVISION - The security descriptor retrieved is no one known by
+ this revision of SAM.
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG Revision;
+
+
+ (*SecurityDescriptorLength) = 0;
+
+ NtStatus = SampGetAccessAttribute(
+ Context,
+ SAMP_OBJECT_SECURITY_DESCRIPTOR,
+ TRUE, // Make copy
+ &Revision,
+ SecurityDescriptor
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( ((Revision && 0xFFFF0000) > SAMP_MAJOR_REVISION) ||
+ (Revision > SAMP_REVISION) ) {
+
+ NtStatus = STATUS_UNKNOWN_REVISION;
+ }
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ MIDL_user_free( (*SecurityDescriptor) );
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+ *SecurityDescriptorLength = RtlLengthSecurityDescriptor(
+ (*SecurityDescriptor) );
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrSetSecurityObject(
+ IN SAMPR_HANDLE ObjectHandle,
+ IN SECURITY_INFORMATION SecurityInformation,
+ IN PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor
+ )
+
+/*++
+
+Routine Description:
+
+ This function (SamrSetSecurityObject) takes a well formed Security
+ Descriptor provided by the caller and assigns specified portions of
+ it to an object. Based on the flags set in the SecurityInformation
+ parameter and the caller's access rights, this procedure will
+ replace any or all of the security information associated with an
+ object.
+
+ This is the only function available to users and applications for
+ changing security information, including the owner ID, group ID, and
+ the discretionary and system ACLs of an object. The caller must
+ have WRITE_OWNER access to the object to change the owner or primary
+ group of the object. The caller must have WRITE_DAC access to the
+ object to change the discretionary ACL. The caller must have
+ ACCESS_SYSTEM_SECURITY access to an object to assign a system ACL
+ to the object.
+
+ This API is modelled after the NtSetSecurityObject() system service.
+
+
+Parameters:
+
+ ObjectHandle - A handle to an existing object.
+
+ SecurityInformation - Indicates which security information is to
+ be applied to the object. The value(s) to be assigned are
+ passed in the SecurityDescriptor parameter.
+
+
+ SecurityDescriptor - A pointer to a well formed self-relative Security
+ Descriptor and corresponding length.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ either WRITE_OWNER, WRITE_DAC, or ACCESS_SYSTEM_SECURITY
+ access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened SAM object.
+
+ STATUS_BAD_DESCRIPTOR_FORMAT - Indicates something about security descriptor
+ is not valid. This may indicate that the structure of the descriptor is
+ not valid or that a component of the descriptor specified via the
+ SecurityInformation parameter is not present in the security descriptor.
+
+ STATUS_INVALID_PARAMETER - Indicates no security information was specified.
+
+ STATUS_LAST_ADMIN - Indicates the new SD could potentially lead
+ to the administrator account being unusable and therefore
+ the new protection is being rejected.
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus, TmpStatus;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+ SECURITY_DB_OBJECT_TYPE SecurityDbObjectType;
+ ACCESS_MASK DesiredAccess;
+ PSECURITY_DESCRIPTOR RetrieveSD, SetSD;
+ PISECURITY_DESCRIPTOR PassedSD;
+ ULONG RetrieveSDLength;
+ ULONG ObjectRid;
+ ULONG SecurityDescriptorIndex;
+ HANDLE ClientToken;
+ BOOLEAN NotificationType = TRUE;
+
+
+
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ if (SecurityDescriptor == NULL) {
+ return(STATUS_BAD_DESCRIPTOR_FORMAT);
+ }
+ if (SecurityDescriptor->SecurityDescriptor == NULL) {
+ return(STATUS_BAD_DESCRIPTOR_FORMAT);
+ }
+
+ PassedSD = (PISECURITY_DESCRIPTOR)(SecurityDescriptor->SecurityDescriptor);
+
+
+ //
+ // Validate the passed security descriptor
+ //
+
+ NtStatus = SampValidatePassedSD( SecurityDescriptor->Length, PassedSD );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Set the desired access based upon the specified SecurityInformation
+ //
+
+ DesiredAccess = 0;
+ if ( SecurityInformation & SACL_SECURITY_INFORMATION) {
+ DesiredAccess |= ACCESS_SYSTEM_SECURITY;
+ }
+ if ( SecurityInformation & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) {
+ DesiredAccess |= WRITE_OWNER;
+ }
+ if ( SecurityInformation & DACL_SECURITY_INFORMATION ) {
+ DesiredAccess |= WRITE_DAC;
+ }
+
+
+ //
+ // If no information was specified, then return invalid parameter.
+ //
+ if (DesiredAccess == 0) {
+
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ //
+ // Make sure the specified fields are present in the provided security descriptor.
+ // You can't screw up an SACL or DACL, but you can screw up an owner or group.
+ // Security descriptors must have owner and group fields.
+ //
+
+
+ if ( (SecurityInformation & OWNER_SECURITY_INFORMATION) ) {
+ if (PassedSD->Owner == NULL) {
+ return(STATUS_BAD_DESCRIPTOR_FORMAT);
+ }
+ }
+
+
+ if ( (SecurityInformation & GROUP_SECURITY_INFORMATION) ) {
+ if (PassedSD->Group == NULL) {
+ return(STATUS_BAD_DESCRIPTOR_FORMAT);
+ }
+ }
+
+
+
+
+ //
+ // See if the handle is valid and opened for the requested access
+ //
+
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ Context = (PSAMP_OBJECT)ObjectHandle;
+ NtStatus = SampLookupContext(
+ Context,
+ DesiredAccess,
+ SampUnknownObjectType, // ExpectedType
+ &FoundType
+ );
+
+ switch ( FoundType ) {
+
+ case SampServerObjectType: {
+
+ SecurityDescriptorIndex = SAMP_SERVER_SECURITY_DESCRIPTOR;
+ ObjectRid = 0L;
+ NotificationType = FALSE;
+ break;
+ }
+
+ case SampDomainObjectType: {
+
+ //
+ // BUGBUG Bug #4388 - allow account operators to access pre-340
+ // databases
+ //
+ // This fix should NEVER be checked in. It is for one-time
+ // internal use only.
+ //
+ // Pre-340 databases had security descriptors on the domain
+ // objects that didn't allow account operators to operate on
+ // accounts. Replicating one of these old databases to a
+ // newer release copies the bad security descriptors. To avoid
+ // this, do the following ONE TIME only:
+ //
+ // Uncomment the code immediately below. Build a new
+ // SAMSRV.DLL. Put it on a BDC running the target version
+ // of the system. Replicate the pre-340 database from the
+ // PDC (which may be running a more recent version of the
+ // system, but still has the bad pre-340 descriptors from
+ // earlier replications).
+ //
+ // That's it; you've got a fixed database that can be safely
+ // replicated in the future. Get rid of this temporary code.
+ // (And put the real SAMSRV.DLL back on the BDC).
+ //
+ // IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+ // IgnoreStatus = SampReleaseWriteLock( FALSE );
+ // return( STATUS_SUCCESS );
+ //
+
+ SecurityDbObjectType = SecurityDbObjectSamDomain;
+ SecurityDescriptorIndex = SAMP_DOMAIN_SECURITY_DESCRIPTOR;
+ ObjectRid = 0L;
+ break;
+ }
+
+ case SampUserObjectType: {
+
+ SecurityDbObjectType = SecurityDbObjectSamUser;
+ SecurityDescriptorIndex = SAMP_USER_SECURITY_DESCRIPTOR;
+ ObjectRid = Context->TypeBody.User.Rid;
+ break;
+ }
+
+ case SampGroupObjectType: {
+
+ SecurityDbObjectType = SecurityDbObjectSamGroup;
+ SecurityDescriptorIndex = SAMP_GROUP_SECURITY_DESCRIPTOR;
+ ObjectRid = Context->TypeBody.Group.Rid;
+ break;
+ }
+
+ case SampAliasObjectType: {
+
+ SecurityDbObjectType = SecurityDbObjectSamAlias;
+ SecurityDescriptorIndex = SAMP_ALIAS_SECURITY_DESCRIPTOR;
+ ObjectRid = Context->TypeBody.Alias.Rid;
+ break;
+ }
+
+ default: {
+
+ NotificationType = FALSE;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Get the security descriptor
+ //
+
+
+ RetrieveSD = NULL;
+ RetrieveSDLength = 0;
+ NtStatus = SampGetObjectSD( Context, &RetrieveSDLength, &RetrieveSD);
+
+ //
+ // Make sure the descriptor does not break any Administrator
+ // restrictions.
+ //
+
+ NtStatus = SampCheckForDescriptorRestrictions( Context,
+ FoundType,
+ ObjectRid,
+ PassedSD );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // copy the retrieved descriptor into process heap so we can use RTL routines.
+ //
+
+ SetSD = NULL;
+ if (NT_SUCCESS(NtStatus)) {
+
+ SetSD = RtlAllocateHeap( RtlProcessHeap(), 0, RetrieveSDLength );
+ if ( SetSD == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ RtlCopyMemory( SetSD, RetrieveSD, RetrieveSDLength );
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // if the caller is replacing the owner and he is not
+ // trusted, then a handle to the impersonation token is
+ // necessary. If the caller is trusted then take process
+ // token.
+ //
+
+ ClientToken = 0;
+ if ( (SecurityInformation & OWNER_SECURITY_INFORMATION) ) {
+
+ if(!Context->TrustedClient) {
+
+ NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = NtOpenThreadToken(
+ NtCurrentThread(),
+ TOKEN_QUERY,
+ TRUE, //OpenAsSelf
+ &ClientToken
+ );
+ ASSERT( (ClientToken == 0) || NT_SUCCESS(NtStatus) );
+
+
+
+ //
+ // Stop impersonating the client
+ //
+
+ IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf());
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+ }
+ else {
+
+ //
+ // trusted client
+ //
+
+ NtStatus = NtOpenProcessToken(
+ NtCurrentProcess(),
+ TOKEN_QUERY,
+ &ClientToken );
+
+ ASSERT( (ClientToken == 0) || NT_SUCCESS(NtStatus) );
+
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Build the replacement security descriptor.
+ // This must be done in process heap to satisfy the needs of the RTL
+ // routine.
+ //
+
+
+ NtStatus = RtlSetSecurityObject(
+ SecurityInformation,
+ PassedSD,
+ &SetSD,
+ &SampObjectInformation[FoundType].GenericMapping,
+ ClientToken
+ );
+ if (ClientToken != 0) {
+ IgnoreStatus = NtClose( ClientToken );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Apply the security descriptor back onto the object.
+ //
+
+ NtStatus = SampSetAccessAttribute(
+ Context,
+ SecurityDescriptorIndex,
+ SetSD,
+ RtlLengthSecurityDescriptor(SetSD)
+ );
+ }
+
+ }
+
+
+
+ }
+
+
+
+
+ //
+ // Free up allocated memory
+ //
+
+ if (RetrieveSD != NULL) {
+ MIDL_user_free( RetrieveSD );
+ }
+ if (SetSD != NULL) {
+ RtlFreeHeap( RtlProcessHeap(), 0, SetSD );
+ }
+
+
+
+
+ //
+ // De-reference the object
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampDeReferenceContext( Context, TRUE );
+
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+ }
+ }
+
+ } //end_if
+
+
+
+ //
+ // Commit the changes to disk.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NotificationType && NT_SUCCESS( NtStatus ) ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChange,
+ SecurityDbObjectType,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+
+
+
+ //
+ // Release lock and propagate errors
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+
+ return(NtStatus);
+
+
+}
+
+NTSTATUS
+SampValidatePassedSD(
+ IN ULONG Length,
+ IN PISECURITY_DESCRIPTOR PassedSD
+ )
+
+/*++
+
+Routine Description:
+
+ This routine validates that a passed security descriptor is valid and does
+ not extend beyond its expressed length.
+
+
+Parameters:
+
+ Length - The length of the security descriptor. This should be what RPC
+ used to allocate memory to receive the security descriptor.
+
+ PassedSD - Points to the security descriptor to inspect.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The security descriptor is valid.
+
+ STATUS_BAD_DESCRIPTOR_FORMAT - Something was wrong with the security
+ descriptor. It might have extended beyond its limits or had an
+ invalid component.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ PACL Acl;
+ PSID Sid;
+ PUCHAR SDEnd;
+ BOOLEAN Present, IgnoreBoolean;
+
+
+ if (Length < SECURITY_DESCRIPTOR_MIN_LENGTH) {
+ return(STATUS_BAD_DESCRIPTOR_FORMAT);
+ }
+
+ SDEnd = (PUCHAR)PassedSD + Length;
+
+
+ try {
+
+
+ //
+ // Make sure the DACL is within the SD
+ //
+
+ NtStatus = RtlGetDaclSecurityDescriptor(
+ (PSECURITY_DESCRIPTOR)PassedSD,
+ &Present,
+ &Acl,
+ &IgnoreBoolean
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ if (Present) {
+ if (Acl != NULL) {
+
+ //
+ // Make sure the ACl header is in the buffer.
+ //
+
+ if ( (((PUCHAR)Acl)+sizeof(ACL) > SDEnd) ||
+ (((PUCHAR)Acl) < (PUCHAR)PassedSD) ) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ //
+ // Make sure the rest of the ACL is within the buffer
+ //
+
+ if ( ((PUCHAR)Acl)+Acl->AclSize > SDEnd) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ //
+ // Make sure the rest of the ACL is valid
+ //
+
+ if (!RtlValidAcl( Acl )) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+ }
+ }
+
+
+
+ //
+ // Make sure the SACL is within the SD
+ //
+
+ NtStatus = RtlGetSaclSecurityDescriptor(
+ (PSECURITY_DESCRIPTOR)PassedSD,
+ &Present,
+ &Acl,
+ &IgnoreBoolean
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ if (Present) {
+ if (Acl != NULL) {
+
+ //
+ // Make sure the ACl header is in the buffer.
+ //
+
+ if ( (((PUCHAR)Acl)+sizeof(ACL) > SDEnd) ||
+ (((PUCHAR)Acl) < (PUCHAR)PassedSD) ) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ //
+ // Make sure the rest of the ACL is within the buffer
+ //
+
+ if ( ((PUCHAR)Acl)+Acl->AclSize > SDEnd) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ //
+ // Make sure the rest of the ACL is valid
+ //
+
+ if (!RtlValidAcl( Acl )) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+ }
+ }
+
+
+ //
+ // Make sure the Owner SID is within the SD
+ //
+
+ NtStatus = RtlGetOwnerSecurityDescriptor(
+ (PSECURITY_DESCRIPTOR)PassedSD,
+ &Sid,
+ &IgnoreBoolean
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ if (Sid != NULL) {
+
+ //
+ // Make sure the SID header is in the SD
+ //
+
+ if ( (((PUCHAR)Sid)+sizeof(SID)-(ANYSIZE_ARRAY*sizeof(ULONG)) > SDEnd) ||
+ (((PUCHAR)Sid) < (PUCHAR)PassedSD) ) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+
+ //
+ // Make sure there aren't too many sub-authorities
+ //
+
+ if (((PISID)Sid)->SubAuthorityCount > SID_MAX_SUB_AUTHORITIES) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+
+ //
+ // Make sure the rest of the SID is within the SD
+ //
+
+ if ( ((PUCHAR)Sid)+RtlLengthSid(Sid) > SDEnd) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ }
+
+
+
+ //
+ // Make sure the Group SID is within the SD
+ //
+
+ NtStatus = RtlGetGroupSecurityDescriptor(
+ (PSECURITY_DESCRIPTOR)PassedSD,
+ &Sid,
+ &IgnoreBoolean
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ if (Sid != NULL) {
+
+ //
+ // Make sure the SID header is in the SD
+ //
+
+ if ( (((PUCHAR)Sid)+sizeof(SID)-(ANYSIZE_ARRAY*sizeof(ULONG)) > SDEnd) ||
+ (((PUCHAR)Sid) < (PUCHAR)PassedSD) ) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+
+ //
+ // Make sure there aren't too many sub-authorities
+ //
+
+ if (((PISID)Sid)->SubAuthorityCount > SID_MAX_SUB_AUTHORITIES) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+
+ //
+ // Make sure the rest of the SID is within the SD
+ //
+
+ if ( ((PUCHAR)Sid)+RtlLengthSid(Sid) > SDEnd) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ }
+
+ }
+
+
+
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+ return( STATUS_BAD_DESCRIPTOR_FORMAT );
+ } // end_try
+
+
+
+
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+SampCheckForDescriptorRestrictions(
+ IN PSAMP_OBJECT Context,
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PISECURITY_DESCRIPTOR PassedSD
+ )
+
+/*++
+
+Routine Description:
+
+ This function ensures that the passed security descriptor,
+ which is being applied to an object of type 'FoundType' with
+ a Rid of value 'ObjectRid', does not violate any policies.
+ For example, you can not set protection on the Administrator
+ user account such that the administrator is unable to change
+ her password.
+
+
+
+Parameters:
+
+ Context - The caller's context. This is used to determine
+ whether the caller is trusted or not. If the caller is
+ trusted, then there are no restrictions.
+
+ ObjectType - The type of object the new security descriptor
+ is being applied to.
+
+ ObjectRid - The RID of the object the new security descriptor
+ is being applied to.
+
+ PassedSD - The security descriptor passed by the client.
+
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_LAST_ADMIN - Indicates the new SD could potentially lead
+ to the administrator account being unusable and therefore
+ the new protection is being rejected.
+
+
+
+--*/
+{
+
+ NTSTATUS
+ NtStatus;
+
+ BOOLEAN
+ DaclPresent,
+ AdminSid,
+ Done,
+ IgnoreBoolean;
+
+ PACL
+ Dacl;
+
+ ACL_SIZE_INFORMATION
+ DaclInfo;
+
+ PACCESS_ALLOWED_ACE
+ Ace;
+
+ ACCESS_MASK
+ Accesses,
+ Remaining;
+
+ ULONG
+ AceIndex;
+
+ GENERIC_MAPPING
+ UserMap = {USER_READ,
+ USER_WRITE,
+ USER_EXECUTE,
+ USER_ALL_ACCESS};
+
+ //
+ // No checking for trusted client operations
+ //
+
+ if (Context->TrustedClient) {
+ return(STATUS_SUCCESS);
+ }
+
+
+
+ NtStatus = RtlGetDaclSecurityDescriptor ( (PSECURITY_DESCRIPTOR)PassedSD,
+ &DaclPresent,
+ &Dacl,
+ &IgnoreBoolean //DaclDefaulted
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ if (!DaclPresent) {
+
+ //
+ // Not replacing the DACL
+ //
+
+ return(STATUS_SUCCESS);
+ }
+
+ if (Dacl == NULL) {
+
+ //
+ // Assigning "World all access"
+ //
+
+ return(STATUS_SUCCESS);
+ }
+
+ if (!RtlValidAcl(Dacl)) {
+ return(STATUS_INVALID_ACL);
+ }
+
+ NtStatus = RtlQueryInformationAcl ( Dacl,
+ &DaclInfo,
+ sizeof(ACL_SIZE_INFORMATION),
+ AclSizeInformation
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+
+
+ //
+ // Enforce Administrator user policies
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ if (ObjectRid == DOMAIN_USER_RID_ADMIN) {
+
+ ASSERT(ObjectType == SampUserObjectType);
+
+ //
+ // For the administrator account, the ACL must grant
+ // these accesses:
+ //
+
+ Remaining = USER_READ_GENERAL |
+ USER_READ_PREFERENCES |
+ USER_WRITE_PREFERENCES |
+ USER_READ_LOGON |
+ USER_READ_ACCOUNT |
+ USER_WRITE_ACCOUNT |
+ USER_CHANGE_PASSWORD |
+ USER_FORCE_PASSWORD_CHANGE |
+ USER_LIST_GROUPS |
+ USER_READ_GROUP_INFORMATION |
+ USER_WRITE_GROUP_INFORMATION;
+
+ //
+ // to these SIDs:
+ //
+ // <domain>\Administrator
+ // <builtin>\Administrators
+ //
+ // It doesn't matter which accesses are granted to which SIDs,
+ // as long as collectively all the accesses are granted.
+ //
+
+ //
+ // Walk the ACEs collecting accesses that are granted to these
+ // SIDs. Make sure there are no DENYs that prevent them from
+ // being granted.
+ //
+
+ Done = FALSE;
+ for ( AceIndex=0;
+ (AceIndex < DaclInfo.AceCount) && !Done;
+ AceIndex++) {
+
+ NtStatus = RtlGetAce ( Dacl, AceIndex, &((PVOID)Ace) );
+
+ //
+ // Don't do anything with inherit-only ACEs
+ //
+
+ if ((Ace->Header.AceFlags & INHERIT_ONLY_ACE) == 0) {
+
+ //
+ // Note that we expect ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE
+ // to be identical structures in the following switch statement.
+ //
+
+ switch (Ace->Header.AceType) {
+ case ACCESS_ALLOWED_ACE_TYPE:
+ case ACCESS_DENIED_ACE_TYPE:
+ {
+ //
+ // Is this an interesting SID
+ //
+
+ AdminSid =
+ RtlEqualSid( ((PSID)(&Ace->SidStart)),
+ SampAdministratorUserSid)
+ ||
+ RtlEqualSid( ((PSID)(&Ace->SidStart)),
+ SampAdministratorsAliasSid);
+ if (AdminSid) {
+
+ //
+ // Map the accesses granted or denied
+ //
+
+ Accesses = Ace->Mask;
+ RtlMapGenericMask( &Accesses, &UserMap );
+
+ if (Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) {
+
+ Remaining &= ~Accesses;
+ if (Remaining == 0) {
+
+ //
+ // All necessary accesses granted
+ //
+
+ Done = TRUE;
+ }
+
+ } else {
+ ASSERT(Ace->Header.AceType == ACCESS_DENIED_ACE_TYPE);
+
+ if (Remaining & Accesses) {
+
+ //
+ // We've just been denied some necessary
+ // accesses that haven't yet been granted.
+ //
+
+ Done = TRUE;
+ }
+ }
+
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ } // end_switch
+
+ if (Done) {
+ break;
+ }
+ }
+
+ } // end_for
+
+ if (Remaining != 0) {
+ NtStatus = STATUS_LAST_ADMIN;
+ }
+
+
+ } // end_if (Administrator Account)
+
+
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrQuerySecurityObject(
+ IN SAMPR_HANDLE ObjectHandle,
+ IN SECURITY_INFORMATION SecurityInformation,
+ OUT PSAMPR_SR_SECURITY_DESCRIPTOR *SecurityDescriptor
+ )
+
+/*++
+
+Routine Description:
+
+ This function (SamrQuerySecurityObject) returns to the caller requested
+ security information currently assigned to an object.
+
+ Based on the caller's access rights this procedure
+ will return a security descriptor containing any or all of the
+ object's owner ID, group ID, discretionary ACL or system ACL. To
+ read the owner ID, group ID, or the discretionary ACL the caller
+ must be granted READ_CONTROL access to the object. To read the
+ system ACL the caller must be granted ACCESS_SYSTEM_SECURITY
+ access.
+
+ This API is modelled after the NtQuerySecurityObject() system
+ service.
+
+
+Parameters:
+
+ ObjectHandle - A handle to an existing object.
+
+ SecurityInformation - Supplies a value describing which pieces of
+ security information are being queried.
+
+ SecurityDescriptor - Provides a pointer to a structure to be filled
+ in with a security descriptor containing the requested security
+ information. This information is returned in the form of a
+ self-relative security descriptor.
+
+Return Values:
+
+ STATUS_SUCCESS - normal, successful completion.
+
+ STATUS_ACCESS_DENIED - The specified handle was not opened for
+ either READ_CONTROL or ACCESS_SYSTEM_SECURITY
+ access.
+
+ STATUS_INVALID_HANDLE - The specified handle is not that of an
+ opened SAM object.
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ PSAMPR_SR_SECURITY_DESCRIPTOR RpcSD;
+ PSECURITY_DESCRIPTOR RetrieveSD, ReturnSD;
+ ULONG RetrieveSDLength, ReturnSDLength;
+
+
+
+ ReturnSD = NULL;
+
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (*SecurityDescriptor == NULL);
+
+
+
+
+
+
+
+ //
+ // Set the desired access based upon the requested SecurityInformation
+ //
+
+ DesiredAccess = 0;
+ if ( SecurityInformation & SACL_SECURITY_INFORMATION) {
+ DesiredAccess |= ACCESS_SYSTEM_SECURITY;
+ }
+ if ( SecurityInformation & (DACL_SECURITY_INFORMATION |
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION)
+ ) {
+ DesiredAccess |= READ_CONTROL;
+ }
+
+
+
+
+
+ //
+ // Allocate the first block of returned memory
+ //
+
+ RpcSD = MIDL_user_allocate( sizeof(SAMPR_SR_SECURITY_DESCRIPTOR) );
+ if (RpcSD == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ RpcSD->Length = 0;
+ RpcSD->SecurityDescriptor = NULL;
+
+
+
+ //
+ // See if the handle is valid and opened for the requested access
+ //
+
+
+ SampAcquireReadLock();
+ Context = (PSAMP_OBJECT)ObjectHandle;
+ NtStatus = SampLookupContext(
+ Context,
+ DesiredAccess,
+ SampUnknownObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Get the security descriptor
+ //
+
+
+ RetrieveSDLength = 0;
+ NtStatus = SampGetObjectSD( Context, &RetrieveSDLength, &RetrieveSD);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // blank out the parts that aren't to be returned
+ //
+
+ if ( !(SecurityInformation & SACL_SECURITY_INFORMATION) ) {
+ ((PISECURITY_DESCRIPTOR)RetrieveSD)->Control &= ~SE_SACL_PRESENT;
+ }
+
+
+ if ( !(SecurityInformation & DACL_SECURITY_INFORMATION) ) {
+ ((PISECURITY_DESCRIPTOR)RetrieveSD)->Control &= ~SE_DACL_PRESENT;
+ }
+
+
+ if ( !(SecurityInformation & OWNER_SECURITY_INFORMATION) ) {
+ ((PISECURITY_DESCRIPTOR)RetrieveSD)->Owner = NULL;
+ }
+
+
+ if ( !(SecurityInformation & GROUP_SECURITY_INFORMATION) ) {
+ ((PISECURITY_DESCRIPTOR)RetrieveSD)->Group = NULL;
+ }
+
+
+ //
+ // Determine how much memory is needed for a self-relative
+ // security descriptor containing just this information.
+ //
+
+
+ ReturnSDLength = 0;
+ NtStatus = RtlMakeSelfRelativeSD(
+ RetrieveSD,
+ NULL,
+ &ReturnSDLength
+ );
+ ASSERT(!NT_SUCCESS(NtStatus));
+
+ if (NtStatus == STATUS_BUFFER_TOO_SMALL) {
+
+
+ ReturnSD = MIDL_user_allocate( ReturnSDLength );
+ if (ReturnSD == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+
+ //
+ // make an appropriate self-relative security descriptor
+ //
+
+ NtStatus = RtlMakeSelfRelativeSD(
+ RetrieveSD,
+ ReturnSD,
+ &ReturnSDLength
+ );
+ }
+
+ }
+
+
+ //
+ // Free up the retrieved SD
+ //
+
+ MIDL_user_free( RetrieveSD );
+ }
+
+
+
+ //
+ // De-reference the object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+ //
+ // If we succeeded, set up the return buffer.
+ // Otherwise, free any allocated memory.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RpcSD->Length = ReturnSDLength;
+ RpcSD->SecurityDescriptor = (PUCHAR)ReturnSD;
+ (*SecurityDescriptor) = RpcSD;
+
+ } else {
+
+ MIDL_user_free( RpcSD );
+ if (ReturnSD != NULL) {
+ MIDL_user_free(ReturnSD);
+ }
+ (*SecurityDescriptor) = NULL;
+ }
+
+
+
+ return(NtStatus);
+
+
+}
diff --git a/private/newsam/server/security.c b/private/newsam/server/security.c
new file mode 100644
index 000000000..e6b0fd8fe
--- /dev/null
+++ b/private/newsam/server/security.c
@@ -0,0 +1,1063 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ security.c
+
+Abstract:
+
+ This file contains services which perform access validation on
+ attempts to access SAM objects. It also performs auditing on
+ both open and close operations.
+
+
+Author:
+
+ Jim Kelly (JimK) 6-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <ntseapi.h>
+#include <seopaque.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+VOID
+SampRemoveAnonymousChangePasswordAccess(
+ IN OUT PSECURITY_DESCRIPTOR Sd
+ );
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampImpersonateNullSession(
+ )
+/*++
+
+Routine Description:
+
+ Impersonates the null session token
+
+Arguments:
+
+ None
+
+Return Value:
+
+ STATUS_CANNOT_IMPERSONATE - there is no null session token to imperonate
+
+--*/
+{
+ if (SampNullSessionToken == NULL) {
+ return(STATUS_CANNOT_IMPERSONATE);
+ }
+ return( NtSetInformationThread(
+ NtCurrentThread(),
+ ThreadImpersonationToken,
+ (PVOID) &SampNullSessionToken,
+ sizeof(HANDLE)
+ ) );
+
+}
+
+NTSTATUS
+SampRevertNullSession(
+ )
+/*++
+
+Routine Description:
+
+ Reverts a thread from impersonating the null session token.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ STATUS_CANNOT_IMPERSONATE - there was no null session token to be
+ imperonating.
+
+--*/
+{
+
+ HANDLE NullHandle = NULL;
+
+ if (SampNullSessionToken == NULL) {
+ return(STATUS_CANNOT_IMPERSONATE);
+ }
+
+ return( NtSetInformationThread(
+ NtCurrentThread(),
+ ThreadImpersonationToken,
+ (PVOID) &NullHandle,
+ sizeof(HANDLE)
+ ) );
+
+}
+
+
+
+
+NTSTATUS
+SampValidateObjectAccess(
+ IN PSAMP_OBJECT Context,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN ObjectCreation
+ )
+
+/*++
+
+Routine Description:
+
+ This service performs access validation on the specified object.
+ The security descriptor of the object is expected to be in a sub-key
+ of the ObjectRootKey named "SecurityDescriptor".
+
+
+ This service:
+
+ 1) Retrieves the target object's SecurityDescriptor from the
+ the ObjectRootKey,
+
+ 2) Impersonates the client. If this fails, and we have a
+ null session token to use, imperonate that.
+
+ 3) Uses NtAccessCheckAndAuditAlarm() to validate access to the
+ object,
+
+ 4) Stops impersonating the client.
+
+ Upon successful completion, the passed context's GrantedAccess mask
+ and AuditOnClose fields will be properly set to represent the results
+ of the access validation. If the AuditOnClose field is set to TRUE,
+ then the caller is responsible for calling SampAuditOnClose() when
+ the object is closed.
+
+
+Arguments:
+
+ Context - The handle value that will be assigned if the access validation
+ is successful.
+
+ DesiredAccess - Specifies the accesses being requested to the target
+ object.
+
+ ObjectCreation - A boolean flag indicated whether the access will
+ result in a new object being created if granted. A value of TRUE
+ indicates an object will be created, FALSE indicates an existing
+ object will be opened.
+
+
+
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates access has been granted.
+
+ Other values that may be returned are those returned by:
+
+ NtAccessCheckAndAuditAlarm()
+
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus, AccessStatus;
+ ULONG SecurityDescriptorLength;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ACCESS_MASK MappedDesiredAccess;
+ BOOLEAN TrustedClient;
+ SAMP_OBJECT_TYPE ObjectType;
+ PUNICODE_STRING ObjectName;
+ ULONG DomainIndex;
+ BOOLEAN ImpersonatingNullSession = FALSE;
+
+
+ //
+ // Extract various fields from the account context
+ //
+
+ TrustedClient = Context->TrustedClient;
+ ObjectType = Context->ObjectType;
+ DomainIndex = Context->DomainIndex;
+
+
+
+
+ //
+ // Map the desired access
+ //
+
+
+ MappedDesiredAccess = DesiredAccess;
+ RtlMapGenericMask(
+ &MappedDesiredAccess,
+ &SampObjectInformation[ ObjectType ].GenericMapping
+ );
+
+ // This doesn't take ACCESS_SYSTEM_SECURITY into account.
+ //
+ //if ((SampObjectInformation[ObjectType].InvalidMappedAccess &
+ // MappedDesiredAccess) != 0) {
+ // return(STATUS_ACCESS_DENIED);
+ //}
+
+ if (TrustedClient) {
+ Context->GrantedAccess = MappedDesiredAccess;
+ Context->AuditOnClose = FALSE;
+ return(STATUS_SUCCESS);
+ }
+
+
+
+ //
+ // Calculate the string to use as an object name for auditing
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+ switch (ObjectType) {
+
+ case SampServerObjectType:
+ ObjectName = &SampServerObjectName;
+ break;
+
+ case SampDomainObjectType:
+ ObjectName = &SampDefinedDomains[DomainIndex].ExternalName;
+ break;
+
+ case SampUserObjectType:
+ case SampGroupObjectType:
+ case SampAliasObjectType:
+ ObjectName = &Context->RootName;
+ break;
+
+ default:
+ ASSERT(FALSE);
+ break;
+ }
+
+
+
+
+ if ( NT_SUCCESS(NtStatus)) {
+
+ //
+ // Fetch the object security descriptor so we can validate
+ // the access against it
+ //
+
+ NtStatus = SampGetObjectSD( Context, &SecurityDescriptorLength, &SecurityDescriptor);
+ if ( NT_SUCCESS(NtStatus)) {
+
+ //
+ // If this is a USER object, then we may have to mask the
+ // ability for Anonymous logons to change passwords.
+ //
+
+ if ( (ObjectType == SampUserObjectType) &&
+ (SampDefinedDomains[DomainIndex].UnmodifiedFixed.PasswordProperties
+ & DOMAIN_PASSWORD_NO_ANON_CHANGE) ) {
+
+ //
+ // Change our (local) copy of the object's DACL
+ // so that it doesn't grant CHANGE_PASSWORD to
+ // either WORLD or ANONYMOUS
+ //
+
+ SampRemoveAnonymousChangePasswordAccess(SecurityDescriptor);
+ }
+
+
+
+ //
+ // Impersonate the client. If RPC impersonation fails because
+ // it is not supported (came in unauthenticated), then impersonate
+ // the null session.
+ //
+
+ NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
+
+ if (NtStatus == RPC_NT_CANNOT_SUPPORT) {
+ NtStatus = SampImpersonateNullSession();
+ ImpersonatingNullSession = TRUE;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Access validate the client
+ //
+
+ NtStatus = NtAccessCheckAndAuditAlarm(
+ &SampSamSubsystem,
+ (PVOID)Context,
+ &SampObjectInformation[ ObjectType ].ObjectTypeName,
+ ObjectName,
+ SecurityDescriptor,
+ MappedDesiredAccess,
+ &SampObjectInformation[ ObjectType ].GenericMapping,
+ ObjectCreation,
+ &Context->GrantedAccess,
+ &AccessStatus,
+ &Context->AuditOnClose
+ );
+
+
+ //
+ // Stop impersonating the client
+ //
+
+ if (ImpersonatingNullSession) {
+ IgnoreStatus = SampRevertNullSession();
+ }
+ IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf());
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ //
+ // Free up the security descriptor
+ //
+
+ MIDL_user_free( SecurityDescriptor );
+
+ }
+ }
+
+
+
+ //
+ // If we got an error back from the access check, return that as
+ // status. Otherwise, return the access check status.
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ return(AccessStatus);
+}
+
+
+VOID
+SampAuditOnClose(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This service performs auditing necessary during a handle close operation.
+
+ This service may ONLY be called if the corresponding call to
+ SampValidateObjectAccess() during openned returned TRUE.
+
+
+
+Arguments:
+
+ Context - This must be the same value that was passed to the corresponding
+ SampValidateObjectAccess() call. This value is used for auditing
+ purposes only.
+
+Return Value:
+
+ None.
+
+
+--*/
+{
+
+ //FIX, FIX - Call NtAuditClose() (or whatever it is).
+
+ return;
+
+ DBG_UNREFERENCED_PARAMETER( Context );
+
+}
+
+
+VOID
+SampRemoveAnonymousChangePasswordAccess(
+ IN OUT PSECURITY_DESCRIPTOR Sd
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes USER_CHANGE_PASSWORD access from
+ any GRANT aces in the discretionary acl that have either
+ the WORLD or ANONYMOUS SIDs in the ACE.
+
+Parameters:
+
+ Sd - Is a pointer to a security descriptor of a SAM USER
+ object.
+
+Returns:
+
+ None.
+
+--*/
+{
+ PACL
+ Dacl;
+
+ ULONG
+ i,
+ AceCount;
+
+ PACE_HEADER
+ Ace;
+
+ BOOLEAN
+ DaclPresent,
+ DaclDefaulted;
+
+
+ RtlGetDaclSecurityDescriptor( Sd,
+ &DaclPresent,
+ &Dacl,
+ &DaclDefaulted
+ );
+
+ if ( !DaclPresent || (Dacl == NULL)) {
+ return;
+ }
+
+ if ((AceCount = Dacl->AceCount) == 0) {
+ return;
+ }
+
+ for ( i = 0, Ace = FirstAce( Dacl ) ;
+ i < AceCount ;
+ i++, Ace = NextAce( Ace )
+ ) {
+
+ if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
+
+ if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
+
+ if ( (RtlEqualSid( SampWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart )) ||
+ (RtlEqualSid( SampAnonymousSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ))) {
+
+ //
+ // Turn off CHANGE_PASSWORD access
+ //
+
+ ((PACCESS_ALLOWED_ACE)Ace)->Mask &= ~USER_CHANGE_PASSWORD;
+ }
+ }
+ }
+ }
+
+ return;
+}
+
+
+NTSTATUS
+SampCreateNullToken(
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates a token representing a null logon.
+
+Arguments:
+
+
+Return Value:
+
+ The status value of the NtCreateToken() call.
+
+
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ TOKEN_USER UserId;
+ TOKEN_PRIMARY_GROUP PrimaryGroup;
+ TOKEN_GROUPS GroupIds;
+ TOKEN_PRIVILEGES Privileges;
+ TOKEN_SOURCE SourceContext;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ SECURITY_QUALITY_OF_SERVICE ImpersonationQos;
+ LARGE_INTEGER ExpirationTime;
+ LUID LogonId = SYSTEM_LUID;
+
+
+
+ UserId.User.Sid = SampWorldSid;
+ UserId.User.Attributes = 0;
+ GroupIds.GroupCount = 0;
+ Privileges.PrivilegeCount = 0;
+ PrimaryGroup.PrimaryGroup = SampWorldSid;
+ ExpirationTime.LowPart = 0xfffffff;
+ ExpirationTime.LowPart = 0x7ffffff;
+
+
+ //
+ // Build a token source for SAM.
+ //
+
+ Status = NtAllocateLocallyUniqueId( &SourceContext.SourceIdentifier );
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+ strncpy(SourceContext.SourceName,"SamSS ",sizeof(SourceContext.SourceName));
+
+
+ //
+ // Set the object attributes to specify an Impersonation impersonation
+ // level.
+ //
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
+ ImpersonationQos.ImpersonationLevel = SecurityImpersonation;
+ ImpersonationQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
+ ImpersonationQos.EffectiveOnly = TRUE;
+ ImpersonationQos.Length = (ULONG)sizeof(SECURITY_QUALITY_OF_SERVICE);
+ ObjectAttributes.SecurityQualityOfService = &ImpersonationQos;
+
+ Status = NtCreateToken(
+ &SampNullSessionToken, // Handle
+ (TOKEN_ALL_ACCESS), // DesiredAccess
+ &ObjectAttributes, // ObjectAttributes
+ TokenImpersonation, // TokenType
+ &LogonId, // Authentication LUID
+ &ExpirationTime, // Expiration Time
+ &UserId, // User ID
+ &GroupIds, // Group IDs
+ &Privileges, // Privileges
+ NULL, // Owner
+ &PrimaryGroup, // Primary Group
+ NULL, // Default Dacl
+ &SourceContext // TokenSource
+ );
+
+ return Status;
+
+}
+
+ULONG
+SampSecureRpcInit(
+ PVOID Ignored
+ )
+/*++
+
+Routine Description:
+
+ This routine waits for the NTLMSSP service to start and then registers
+ security information with RPC to allow authenticated RPC to be used to
+ SAM. It also registers an SPX endpoint if FPNW is installed.
+
+Arguments:
+
+ Ignored - required parameter for starting a thread.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+#define MAX_RPC_RETRIES 30
+
+ ULONG LogStatus = ERROR_SUCCESS;
+ ULONG RpcStatus;
+ ULONG RpcRetry;
+ ULONG RpcSleepTime = 10 * 1000; // retry every ten seconds
+ RPC_BINDING_VECTOR * BindingVector = NULL;
+ BOOLEAN AdditionalTransportStarted = FALSE;
+
+
+
+ RpcStatus = RpcServerRegisterAuthInfoW(
+ NULL, // server principal name
+ RPC_C_AUTHN_WINNT,
+ NULL, // no get key function
+ NULL // no get key argument
+ );
+
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register auth. info: %d\n",
+ RpcStatus ));
+ goto ErrorReturn;
+ }
+
+ //
+ // If the Netware server is installed, register the SPX protocol.
+ // Since the transport may not be loaded yet, retry a couple of times
+ // if we get a CANT_CREATE_ENDPOINT error (meaning the transport isn't
+ // there).
+ //
+
+ if (SampNetwareServerInstalled) {
+
+ RpcRetry = MAX_RPC_RETRIES;
+ while (RpcRetry != 0) {
+
+ RpcStatus = RpcServerUseProtseqW(
+ L"ncacn_spx",
+ 10,
+ NULL // no security descriptor
+ );
+
+ //
+ // If it succeded break out of the loop.
+ //
+ if (RpcStatus == ERROR_SUCCESS) {
+ break;
+ }
+ Sleep(RpcSleepTime);
+ RpcRetry--;
+ continue;
+
+ }
+
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register SPX endpoint: %d\n", RpcStatus ));
+ LogStatus = RpcStatus;
+ } else {
+ AdditionalTransportStarted = TRUE;
+ }
+ }
+
+ //
+ // do the same thing all over again with TcpIp
+ //
+
+ if (SampIpServerInstalled) {
+
+ RpcRetry = MAX_RPC_RETRIES;
+ while (RpcRetry != 0) {
+
+ RpcStatus = RpcServerUseProtseqW(
+ L"ncacn_ip_tcp",
+ 10,
+ NULL // no security descriptor
+ );
+
+ //
+ // If it succeeded, break out of the loop.
+ //
+
+ if (RpcStatus == ERROR_SUCCESS) {
+ break;
+ }
+ Sleep(RpcSleepTime);
+ RpcRetry--;
+ continue;
+
+ }
+
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register TCP endpoint: %d\n", RpcStatus ));
+ LogStatus = RpcStatus;
+ } else {
+ AdditionalTransportStarted = TRUE;
+ }
+
+ }
+
+ //
+ // do the same thing all over again with apple talk
+ //
+
+ if (SampAppletalkServerInstalled) {
+
+ RpcRetry = MAX_RPC_RETRIES;
+ while (RpcRetry != 0) {
+
+ RpcStatus = RpcServerUseProtseqW(
+ L"ncacn_at_dsp",
+ 10,
+ NULL // no security descriptor
+ );
+
+ //
+ // If it succeeded, break out of the loop.
+ //
+
+ if (RpcStatus == ERROR_SUCCESS) {
+ break;
+ }
+ Sleep(RpcSleepTime);
+ RpcRetry--;
+ continue;
+
+ }
+
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register Appletalk endpoint: %d\n", RpcStatus ));
+ LogStatus = RpcStatus;
+ } else {
+ AdditionalTransportStarted = TRUE;
+ }
+
+ }
+
+ //
+ // do the same thing all over again with Vines
+ //
+
+ if (SampVinesServerInstalled) {
+
+ RpcRetry = MAX_RPC_RETRIES;
+ while (RpcRetry != 0) {
+
+ RpcStatus = RpcServerUseProtseqW(
+ L"ncacn_vns_spp",
+ 10,
+ NULL // no security descriptor
+ );
+
+ //
+ // If it succeeded, break out of the loop.
+ //
+
+ if (RpcStatus == ERROR_SUCCESS) {
+ break;
+ }
+ Sleep(RpcSleepTime);
+ RpcRetry--;
+ continue;
+
+ }
+
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register Vines endpoint: %d\n", RpcStatus ));
+ LogStatus = RpcStatus;
+ } else {
+ AdditionalTransportStarted = TRUE;
+ }
+
+
+ }
+
+ //
+ // If we started Tcp/Ip or Spx, go on to register the endpoints
+ //
+
+ if (AdditionalTransportStarted) {
+ RpcStatus = RpcServerInqBindings(&BindingVector);
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not inq bindings: %d\n",RpcStatus));
+ goto ErrorReturn;
+ }
+ RpcStatus = RpcEpRegister(
+ samr_ServerIfHandle,
+ BindingVector,
+ NULL, // no uuid vector
+ L"" // no annotation
+ );
+
+ RpcBindingVectorFree(&BindingVector);
+ if (RpcStatus != 0) {
+ KdPrint(("SAMSS: Could not register endpoints: %d\n",RpcStatus));
+ LogStatus = RpcStatus;
+ goto ErrorReturn;
+ }
+ }
+
+ if (LogStatus != ERROR_SUCCESS)
+ {
+ goto ErrorReturn;
+ }
+ return(ERROR_SUCCESS);
+
+ErrorReturn:
+
+ SampWriteEventLog(
+ EVENTLOG_ERROR_TYPE,
+ 0, // Category
+ SAMMSG_RPC_INIT_FAILED,
+ NULL, // User Sid
+ 0, // Num strings
+ sizeof(NTSTATUS), // Data size
+ NULL, // String array
+ (PVOID)&LogStatus // Data
+ );
+
+ return(LogStatus);
+}
+
+
+BOOLEAN
+SampStartNonNamedPipeTransports(
+ )
+/*++
+
+Routine Description:
+
+ This routine checks to see if we should listen on a non-named pipe
+ transport. We check the registry for flags indicating that we should
+ listen on Tcp/Ip and SPX. There is a flag
+ in the registry under system\currentcontrolset\Control\Lsa\
+ NetwareClientSupport and TcpipClientSupport indicating whether or not
+ to setup the endpoint.
+
+
+Arguments:
+
+
+Return Value:
+
+ TRUE - Netware (FPNW or SmallWorld) is installed and the SPX endpoint
+ should be started.
+
+ FALSE - Either Netware is not installed, or an error occurred while
+ checking for it.
+--*/
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE KeyHandle;
+ UCHAR Buffer[100];
+ PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION) Buffer;
+ ULONG KeyValueLength = 100;
+ ULONG ResultLength;
+ PULONG SpxFlag;
+
+
+ //
+ // Open the Lsa key in the registry
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa"
+ );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+
+ NtStatus = NtOpenKey(
+ &KeyHandle,
+ KEY_READ,
+ &ObjectAttributes
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(FALSE);
+ }
+
+ //
+ // Query the NetwareClientSupport value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"NetWareClientSupport"
+ );
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &KeyName,
+ KeyValuePartialInformation,
+ KeyValueInformation,
+ KeyValueLength,
+ &ResultLength
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check that the data is the correct size and type - a ULONG.
+ //
+
+ if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
+ (KeyValueInformation->Type == REG_DWORD)) {
+
+
+ SpxFlag = (PULONG) KeyValueInformation->Data;
+
+ if (*SpxFlag == 1) {
+ SampNetwareServerInstalled = TRUE;
+ }
+ }
+
+ }
+
+ //
+ // Query the Tcp/IpClientSupport value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"TcpipClientSupport"
+ );
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &KeyName,
+ KeyValuePartialInformation,
+ KeyValueInformation,
+ KeyValueLength,
+ &ResultLength
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check that the data is the correct size and type - a ULONG.
+ //
+
+ if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
+ (KeyValueInformation->Type == REG_DWORD)) {
+
+
+ SpxFlag = (PULONG) KeyValueInformation->Data;
+
+ if (*SpxFlag == 1) {
+ SampIpServerInstalled = TRUE;
+ }
+ }
+
+ }
+
+ //
+ // Query the AppletalkClientSupport value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"AppletalkClientSupport"
+ );
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &KeyName,
+ KeyValuePartialInformation,
+ KeyValueInformation,
+ KeyValueLength,
+ &ResultLength
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check that the data is the correct size and type - a ULONG.
+ //
+
+ if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
+ (KeyValueInformation->Type == REG_DWORD)) {
+
+
+ SpxFlag = (PULONG) KeyValueInformation->Data;
+
+ if (*SpxFlag == 1) {
+ SampAppletalkServerInstalled = TRUE;
+ }
+ }
+
+ }
+
+ //
+ // Query the VinesClientSupport value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"VinesClientSupport"
+ );
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &KeyName,
+ KeyValuePartialInformation,
+ KeyValueInformation,
+ KeyValueLength,
+ &ResultLength
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Check that the data is the correct size and type - a ULONG.
+ //
+
+ if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
+ (KeyValueInformation->Type == REG_DWORD)) {
+
+
+ SpxFlag = (PULONG) KeyValueInformation->Data;
+
+ if (*SpxFlag == 1) {
+ SampVinesServerInstalled = TRUE;
+ }
+ }
+
+ }
+
+ NtClose(KeyHandle);
+
+ if (SampNetwareServerInstalled || SampIpServerInstalled ||
+ SampVinesServerInstalled || SampAppletalkServerInstalled)
+ {
+ return(TRUE);
+ }
+ else
+ {
+ return(FALSE);
+ };
+}
+
+
diff --git a/private/newsam/server/server.c b/private/newsam/server/server.c
new file mode 100644
index 000000000..3b4ad2097
--- /dev/null
+++ b/private/newsam/server/server.c
@@ -0,0 +1,920 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ server.c
+
+Abstract:
+
+ This file contains services related to the SAM "server" object.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+NTSTATUS
+SamrConnect2(
+ IN PSAMPR_SERVER_NAME ServerName,
+ OUT SAMPR_HANDLE * ServerHandle,
+ IN ACCESS_MASK DesiredAccess
+ )
+
+/*++
+
+Routine Description:
+
+ This service is the dispatch routine for SamConnect. It performs
+ an access validation to determine whether the caller may connect
+ to SAM for the access specified. If so, a context block is established.
+ This is different from the SamConnect call in that the entire server
+ name is passed instead of just the first character.
+
+
+Arguments:
+
+ ServerName - Name of the node this SAM reside on. Ignored by this
+ routine.
+
+ ServerHandle - If the connection is successful, the value returned
+ via this parameter serves as a context handle to the openned
+ SERVER object.
+
+ DesiredAccess - Specifies the accesses desired to the SERVER object.
+
+
+Return Value:
+
+ Status values returned by SamIConnect().
+
+
+--*/
+{
+ BOOLEAN TrustedClient;
+
+
+ //
+ // If we ever want to support trusted remote clients, then the test
+ // for whether or not the client is trusted can be made here and
+ // TrustedClient set appropriately. For now, all remote clients are
+ // considered untrusted.
+
+ TrustedClient = FALSE;
+
+ return SamIConnect(ServerName, ServerHandle, DesiredAccess, TrustedClient );
+
+}
+
+
+NTSTATUS
+SamrConnect(
+ IN PSAMPR_SERVER_NAME ServerName,
+ OUT SAMPR_HANDLE * ServerHandle,
+ IN ACCESS_MASK DesiredAccess
+ )
+
+/*++
+
+Routine Description:
+
+ This service is the dispatch routine for SamConnect. It performs
+ an access validation to determine whether the caller may connect
+ to SAM for the access specified. If so, a context block is established
+
+
+Arguments:
+
+ ServerName - Name of the node this SAM reside on. Ignored by this
+ routine. The name contains only a single character.
+
+ ServerHandle - If the connection is successful, the value returned
+ via this parameter serves as a context handle to the openned
+ SERVER object.
+
+ DesiredAccess - Specifies the accesses desired to the SERVER object.
+
+
+Return Value:
+
+ Status values returned by SamIConnect().
+
+
+--*/
+{
+ BOOLEAN TrustedClient;
+
+
+ //
+ // If we ever want to support trusted remote clients, then the test
+ // for whether or not the client is trusted can be made here and
+ // TrustedClient set appropriately. For now, all remote clients are
+ // considered untrusted.
+
+ TrustedClient = FALSE;
+
+ return SamIConnect(NULL, ServerHandle, DesiredAccess, TrustedClient );
+
+}
+
+
+NTSTATUS
+SamIConnect(
+ IN PSAMPR_SERVER_NAME ServerName,
+ OUT SAMPR_HANDLE * ServerHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN TrustedClient
+ )
+
+/*++
+
+Routine Description:
+
+ This service is the dispatch routine for SamConnect. It performs
+ an access validation to determine whether the caller may connect
+ to SAM for the access specified. If so, a context block is established
+
+
+ NOTE: If the caller is trusted, then the DesiredAccess parameter may
+ NOT contain any Generic access types or MaximumAllowed. All
+ mapping must be done by the caller.
+
+Arguments:
+
+ ServerName - Name of the node this SAM reside on. Ignored by this
+ routine.
+
+ ServerHandle - If the connection is successful, the value returned
+ via this parameter serves as a context handle to the openned
+ SERVER object.
+
+ DesiredAccess - Specifies the accesses desired to the SERVER object.
+
+ TrustedClient - Indicates whether the client is known to be part of
+ the trusted computer base (TCB). If so (TRUE), no access validation
+ is performed and all requested accesses are granted. If not
+ (FALSE), then the client is impersonated and access validation
+ performed against the SecurityDescriptor on the SERVER object.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The SERVER 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;
+ PSAMP_OBJECT Context;
+
+ UNREFERENCED_PARAMETER( ServerName ); //Ignored by this routine
+
+ //
+ // If the SAM server is not initialized, reject the connection.
+ //
+
+ if (SampServiceState != SampServiceEnabled) {
+
+ return(STATUS_INVALID_SERVER_STATE);
+ }
+
+ SampAcquireReadLock();
+
+
+ Context = SampCreateContext( SampServerObjectType, TrustedClient );
+
+ if (Context != NULL) {
+
+ //
+ // The RootKey for a SERVER object is the root of the SAM database.
+ // This key should not be closed when the context is deleted.
+ //
+
+ Context->RootKey = SampKey;
+
+ //
+ // The rootkeyname has been initialized to NULL inside CreateContext.
+ //
+
+ //
+ // Perform access validation ...
+ //
+
+ NtStatus = SampValidateObjectAccess(
+ Context, //Context
+ DesiredAccess, //DesiredAccess
+ FALSE //ObjectCreation
+ );
+
+
+
+ //
+ // 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( Context );
+ } else {
+ (*ServerHandle) = Context;
+ }
+
+ } else {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrShutdownSamServer(
+ IN SAMPR_HANDLE ServerHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This service shuts down the SAM server.
+
+ In the long run, this routine will perform an orderly shutdown.
+ In the short term, it is useful for debug purposes to shutdown
+ in a brute force un-orderly fashion.
+
+Arguments:
+
+ ServerHandle - Received from a previous call to SamIConnect().
+
+Return Value:
+
+ STATUS_SUCCESS - The services completed successfully.
+
+
+ STATUS_ACCESS_DENIED - The caller doesn't have the appropriate access
+ to perform the requested operation.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT ServerContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Validate type of, and access to server object.
+ //
+
+ ServerContext = (PSAMP_OBJECT)ServerHandle;
+ NtStatus = SampLookupContext(
+ ServerContext,
+ SAM_SERVER_SHUTDOWN, // DesiredAccess
+ SampServerObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Signal the event that will cut loose the main thread.
+ // The main thread will then exit - causing the walls to
+ // come tumbling down.
+ //
+
+ IgnoreStatus = RpcMgmtStopServerListening(0);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+
+ //
+ // De-reference the server object
+ //
+
+ IgnoreStatus = SampDeReferenceContext( ServerContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the write lock and roll-back the transaction
+ //
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SamrLookupDomainInSamServer(
+ IN SAMPR_HANDLE ServerHandle,
+ IN PRPC_UNICODE_STRING Name,
+ OUT PRPC_SID *DomainId
+ )
+
+/*++
+
+Routine Description:
+
+ This service
+
+Arguments:
+
+ ServerHandle - A context handle returned by a previous call
+ to SamConnect().
+
+ Name - contains the name of the domain to look up.
+
+ DomainSid - Receives a pointer to a buffer containing the SID of
+ the domain. The buffer pointed to must be deallocated by the
+ caller using MIDL_user_free() when no longer needed.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The services completed successfully.
+
+ STATUS_ACCESS_DENIED - The caller doesn't have the appropriate access
+ to perform the requested operation.
+
+ STATUS_NO_SUCH_DOMAIN - The specified domain does not exist at this
+ server.
+
+
+ STATUS_INVALID_SERVER_STATE - Indicates the SAM server is currently
+ disabled.
+
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT ServerContext;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG i, SidLength;
+ BOOLEAN DomainFound;
+ PSID FoundSid;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (DomainId != NULL);
+ ASSERT ((*DomainId) == NULL);
+
+
+
+ ASSERT( Name != NULL );
+ if (Name->Buffer == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ ServerContext = (PSAMP_OBJECT)ServerHandle;
+ NtStatus = SampLookupContext(
+ ServerContext,
+ SAM_SERVER_LOOKUP_DOMAIN,
+ SampServerObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+
+ //
+ // Set our default completion status
+ //
+
+ NtStatus = STATUS_NO_SUCH_DOMAIN;
+
+
+ //
+ // Search the list of defined domains for a match.
+ //
+
+ DomainFound = FALSE;
+ for (i = 0;
+ (i<SampDefinedDomainsCount && (!DomainFound));
+ i++ ) {
+
+ if (RtlEqualDomainName(&SampDefinedDomains[i].ExternalName, (PUNICODE_STRING)Name) ) {
+
+
+ DomainFound = TRUE;
+
+
+ //
+ // Allocate and fill in the return buffer
+ //
+
+ SidLength = RtlLengthSid( SampDefinedDomains[i].Sid );
+ FoundSid = MIDL_user_allocate( SidLength );
+ if (FoundSid != NULL) {
+ NtStatus =
+ RtlCopySid( SidLength, FoundSid, SampDefinedDomains[i].Sid );
+
+ if (!NT_SUCCESS(NtStatus) ) {
+ MIDL_user_free( FoundSid );
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+
+ (*DomainId) = FoundSid;
+ }
+
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ }
+
+
+
+ //
+ // De-reference the object
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampDeReferenceContext( ServerContext, FALSE );
+
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext( ServerContext, FALSE );
+ }
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrEnumerateDomainsInSamServer(
+ IN SAMPR_HANDLE ServerHandle,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ OUT PULONG CountReturned
+ )
+
+/*++
+
+Routine Description:
+
+ This API lists all the domains defined in the account database.
+ Since there may be more domains 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 SAM_SERVER_ENUMERATE_DOMAINS access to the
+ SamServer object.
+
+Arguments:
+
+ ConnectHandle - Handle obtained from a previous SamConnect call.
+
+ EnumerationContext - API specific handle to allow multiple calls
+ (see below). This is a zero based index.
+
+ Buffer - Receives a pointer to the buffer where the information
+ is placed. The information returned is contiguous
+ SAM_RID_ENUMERATION data structures. However, the
+ RelativeId field of each of these structures is not valid.
+ This buffer must be freed when no longer needed 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 the access required
+ to enumerate the domains.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+ STATUS_INVALID_SERVER_STATE - Indicates the SAM server is
+ currently disabled.
+
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ ULONG i;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+ ULONG TotalLength = 0;
+ ULONG NewTotalLength;
+ PSAMP_ENUMERATION_ELEMENT SampHead, NextEntry, NewEntry;
+ BOOLEAN LengthLimitReached = FALSE;
+ PSAMPR_RID_ENUMERATION ArrayBuffer;
+ ULONG ArrayBufferLength;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (ServerHandle != NULL);
+ ASSERT (EnumerationContext != NULL);
+ ASSERT ( Buffer != NULL);
+ ASSERT ((*Buffer) == NULL);
+ ASSERT (CountReturned != NULL);
+
+
+ //
+ // Initialize the list of names being returned.
+ // This is a singly linked list.
+ //
+
+ SampHead = NULL;
+
+
+ //
+ // Initialize the count returned
+ //
+
+ (*CountReturned) = 0;
+
+
+
+
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ Context = (PSAMP_OBJECT)ServerHandle;
+ NtStatus = SampLookupContext(
+ Context,
+ SAM_SERVER_ENUMERATE_DOMAINS,
+ SampServerObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Enumerating domains is easy. We keep a list in memory.
+ // All we have to do is use the enumeration context as an
+ // index into the defined domains array.
+ //
+
+
+
+ //
+ // Set our default completion status
+ // Note that this is a SUCCESS status code.
+ // That is NT_SUCCESS(STATUS_MORE_ENTRIES) will return TRUE.
+
+ //
+
+ NtStatus = STATUS_MORE_ENTRIES;
+
+
+
+ //
+ // Search the list of defined domains for a match.
+ //
+
+ for ( i = (ULONG)(*EnumerationContext);
+ ( (i < SampDefinedDomainsCount) &&
+ (NT_SUCCESS(NtStatus)) &&
+ (!LengthLimitReached) );
+ i++ ) {
+
+
+ //
+ // See if there is room for the next name. If TotalLength
+ // is still zero then we haven't yet even gotten one name.
+ // We have to return at least one name even if it exceeds
+ // the length request.
+ //
+
+
+ NewTotalLength = TotalLength +
+ sizeof(UNICODE_STRING) +
+ (ULONG)SampDefinedDomains[i].ExternalName.Length +
+ sizeof(UNICODE_NULL);
+
+ if ( (NewTotalLength < PreferedMaximumLength) ||
+ (TotalLength == 0) ) {
+
+ if (NewTotalLength > SAMP_MAXIMUM_MEMORY_TO_USE) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+
+ TotalLength = NewTotalLength;
+ (*CountReturned) += 1;
+
+ //
+ // Room for this name as well.
+ // Allocate a new return list entry, and a buffer for the
+ // name.
+ //
+
+ NewEntry = MIDL_user_allocate(sizeof(SAMP_ENUMERATION_ELEMENT));
+ if (NewEntry == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ NewEntry->Entry.Name.Buffer =
+ MIDL_user_allocate(
+ (ULONG)SampDefinedDomains[i].ExternalName.Length +
+ sizeof(UNICODE_NULL)
+ );
+
+ if (NewEntry->Entry.Name.Buffer == NULL) {
+ MIDL_user_free(NewEntry);
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ //
+ // Copy the name into the return buffer
+ //
+
+ RtlCopyMemory( NewEntry->Entry.Name.Buffer,
+ SampDefinedDomains[i].ExternalName.Buffer,
+ SampDefinedDomains[i].ExternalName.Length
+ );
+ NewEntry->Entry.Name.Length = SampDefinedDomains[i].ExternalName.Length;
+ NewEntry->Entry.Name.MaximumLength = NewEntry->Entry.Name.Length + (USHORT)sizeof(UNICODE_NULL);
+ UnicodeTerminate((PUNICODE_STRING)(&NewEntry->Entry.Name));
+
+
+ //
+ // The Rid field of the ENUMERATION_INFORMATION is not
+ // filled in for domains.
+ // Just for good measure, set it to zero.
+ //
+
+ NewEntry->Entry.RelativeId = 0;
+
+
+
+ //
+ // Now add this to the list of names to be returned.
+ //
+
+ NewEntry->Next = (PSAMP_ENUMERATION_ELEMENT)SampHead;
+ SampHead = NewEntry;
+ }
+
+ }
+ }
+
+ } else {
+
+ LengthLimitReached = TRUE;
+
+ }
+
+ }
+
+
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Set the enumeration context
+ //
+
+ (*EnumerationContext) = (*EnumerationContext) + (*CountReturned);
+
+
+
+ //
+ // If we are returning the last of the names, then change our
+ // status code to indicate this condition.
+ //
+
+ if ( ((*EnumerationContext) >= SampDefinedDomainsCount) ) {
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+
+
+
+ //
+ // Build a return buffer containing an array of the
+ // SAM_RID_ENUMERATIONs pointed to by another
+ // buffer containing the number of elements in that
+ // array.
+ //
+
+ (*Buffer) = MIDL_user_allocate( sizeof(SAMPR_ENUMERATION_BUFFER) );
+
+ if ( (*Buffer) == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ (*Buffer)->EntriesRead = (*CountReturned);
+
+ ArrayBufferLength = sizeof( SAM_RID_ENUMERATION ) *
+ (*CountReturned);
+ ArrayBuffer = MIDL_user_allocate( ArrayBufferLength );
+ (*Buffer)->Buffer = ArrayBuffer;
+
+ if ( ArrayBuffer == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ MIDL_user_free( (*Buffer) );
+
+ } else {
+
+ //
+ // Walk the list of return entries, copying
+ // them into the return buffer
+ //
+
+ NextEntry = SampHead;
+ i = 0;
+ while (NextEntry != NULL) {
+
+ NewEntry = NextEntry;
+ NextEntry = NewEntry->Next;
+
+ ArrayBuffer[i] = NewEntry->Entry;
+ i += 1;
+
+ MIDL_user_free( NewEntry );
+ }
+
+ }
+
+ }
+ }
+
+
+
+
+ if ( !NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Free the memory we've allocated
+ //
+
+ NextEntry = SampHead;
+ while (NextEntry != NULL) {
+
+ NewEntry = NextEntry;
+ NextEntry = NewEntry->Next;
+
+ MIDL_user_free( NewEntry->Entry.Name.Buffer );
+ MIDL_user_free( NewEntry );
+ }
+
+ (*EnumerationContext) = 0;
+ (*CountReturned) = 0;
+ (*Buffer) = NULL;
+
+ }
+
+ //
+ // De-reference the object
+ // Note that NtStatus could be STATUS_MORE_ENTRIES, which is a
+ // successful return code - we want to make sure we return that,
+ // without wiping it out here.
+ //
+
+ if ( NtStatus == STATUS_SUCCESS ) {
+
+ NtStatus = SampDeReferenceContext( Context, FALSE );
+
+ } else {
+
+ IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+ }
+ }
+
+
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+
+ return(NtStatus);
+
+}
diff --git a/private/newsam/server/sources b/private/newsam/server/sources
new file mode 100644
index 000000000..fedc4c82e
--- /dev/null
+++ b/private/newsam/server/sources
@@ -0,0 +1,89 @@
+
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+MAJORCOMP=sam
+MINORCOMP=server
+
+TARGETNAME=samsrv
+TARGETPATH=\nt\public\sdk\lib
+TARGETTYPE=DYNLINK
+TARGETLIBS= \
+ $(BASEDIR)\public\sdk\lib\*\nlrepl.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcrt4.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcndr.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcutil.lib \
+ $(BASEDIR)\public\sdk\lib\*\kernel32.lib \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\lsasrv.lib \
+ ..\..\lsa\crypt\engine\obj\*\rc4c.obj
+
+
+INCLUDES=.;..;..\inc;..\..\inc;..\..\lsa\crypt\engine
+
+NTPROFILEINPUT=yes
+
+SOURCES= \
+ display.c \
+ alias.c \
+ almember.c \
+ attr.c \
+ bldsam3.c \
+ close.c \
+ context.c \
+ domain.c \
+ gentab2.c \
+ global.c \
+ group.c \
+ notify.c \
+ oldstub.c \
+ rundown.c \
+ samifree.c \
+ samrpc_s.c \
+ sam_rev.rc \
+ samss.c \
+ secdescr.c \
+ security.c \
+ server.c \
+ user.c \
+ utility.c \
+ upgrade.c
+
+# string.c \
+
+C_DEFINES=-DRPC_NO_WINDOWS_H
+
+
+#
+# Defining the NTTARGETFILES variable causes MAKEFILE.DEF to attempt to
+# include .\makefile.inc immediately after it specifies the top
+# level targets (all, clean and loc) and their dependencies. MAKEFILE.DEF
+# also expands the value of the NTTARGETFILES variable at the end of the
+# list of dependencies for the all target. Useful for specifying additional
+# targets and dependencies that don't fit the general case covered by
+# MAKEFILE.DEF
+#
+
+UMRES=obj\*\sam_rev.res
+NTTARGETFILE0=sampmsgs.h
diff --git a/private/newsam/server/string.c b/private/newsam/server/string.c
new file mode 100644
index 000000000..d2b2dd552
--- /dev/null
+++ b/private/newsam/server/string.c
@@ -0,0 +1,252 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ string.c
+
+Abstract:
+
+ This file contains services for retrieving and replacing string field
+ values.
+
+
+Author:
+
+ Jim Kelly (JimK) 10-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampGetUnicodeStringField(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING SubKeyName,
+ OUT PUNICODE_STRING *String
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves a unicode string from a named sub-key of
+ the root key provided in the Context argument.
+
+ The returned unicode string is returned in two buffers allocated
+ using MIDL_user_allocate() and are therefore suitable for returning as
+ [out] parameters of an RPC call. The first buffer will be the unicode
+ string body. The second buffer will contain the unicode string
+ characters and will include 2 bytes of zeros.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ Context - Pointer to an active context block whose RootKey is valid.
+
+ SubKeyName - The name of the sub-key containing the unicode string
+ to retrieve.
+
+ String - Receives a pointer to a set of allocated buffers containing
+ the unicode string. The buffers are allocated using
+ MIDL_userAllocate(). If any errors are returned, these buffers
+ will not be allocated.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The string value has been successfully retrieved.
+
+ STATUS_NO_MEMORY - There was insufficient memory to allocate a
+ buffer to read the unicode string into.
+
+ STATUS_INTERNAL_ERROR - The value of the sub-key seems to have changed
+ during the execution of this service. This should not happen since
+ the service must be called with the WRITE LOCK held.
+
+ Other error values are those returned by:
+
+ NtQueryValueKey()
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ HANDLE SubKeyHandle;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ ULONG StringLength, ActualStringLength, IgnoreKeyValueType;
+ PUNICODE_STRING StringBody;
+ PCHAR CharacterBuffer;
+ LARGE_INTEGER LastWriteTime;
+
+ //
+ // Prepare for failure
+ //
+
+ *String = NULL;
+
+
+ //
+ // Open the named sub-key ...
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes, // Resultant object attributes
+ SubKeyName, // Relative Name
+ OBJ_CASE_INSENSITIVE, // Attributes
+ Context->RootKey, // Parent key handle
+ NULL // SecurityDescriptor
+ );
+
+ NtStatus = RtlpNtOpenKey( // Don't use NtCreateKey() - it must already exist
+ &SubKeyHandle,
+ KEY_READ,
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Query the length of the unicode string in the sub-key
+ //
+
+ NtStatus = RtlpNtQueryValueKey(
+ SubKeyHandle,
+ &IgnoreKeyValueType,
+ NULL, // No buffer yet
+ &StringLength,
+ &LastWriteTime
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = NtClose( SubKeyHandle );
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Allocate buffers for both the string body and the
+ // character buffer.
+ //
+
+ CharacterBuffer = MIDL_user_allocate( StringLength + sizeof(UNICODE_NULL) );
+ StringBody = MIDL_user_allocate( sizeof(UNICODE_STRING) );
+
+ if ((CharacterBuffer == NULL) || (StringBody == NULL)) {
+
+ //
+ // We couldn't allocate pool ...
+ //
+
+ IgnoreStatus = NtClose( SubKeyHandle );
+
+ if (CharacterBuffer != NULL) {
+ MIDL_user_free( CharacterBuffer );
+ }
+ if (StringBody != NULL) {
+ MIDL_user_free( StringBody );
+ }
+
+ return(STATUS_NO_MEMORY);
+ }
+
+
+
+ //
+ // Initialize the string body
+ //
+
+ StringBody->Length = (USHORT)StringLength;
+ StringBody->MaximumLength = (USHORT)StringLength + (USHORT)sizeof(UNICODE_NULL);
+ StringBody->Buffer = (PWSTR)CharacterBuffer;
+
+ //
+ // Read the string value into the character buffer.
+ //
+
+ NtStatus = RtlpNtQueryValueKey(
+ SubKeyHandle,
+ &IgnoreKeyValueType,
+ CharacterBuffer,
+ &ActualStringLength,
+ &LastWriteTime
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (ActualStringLength != StringLength) {
+
+ //
+ // Hmmm - we just queuried the length and got StringLength.
+ // Then we read the buffer and its different, yet the
+ // whole time we've held the write lock. Something
+ // has screwed up our database.
+ //
+
+ NtStatus = STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ IgnoreStatus = NtClose( SubKeyHandle );
+
+ MIDL_user_free( CharacterBuffer );
+ MIDL_user_free( StringBody );
+
+ return(NtStatus);
+ }
+
+
+ //
+ // Null terminate the string
+ //
+
+ UnicodeTerminate(StringBody);
+ *String = StringBody;
+
+ return(STATUS_SUCCESS);
+}
diff --git a/private/newsam/server/upgrade.c b/private/newsam/server/upgrade.c
new file mode 100644
index 000000000..554777b61
--- /dev/null
+++ b/private/newsam/server/upgrade.c
@@ -0,0 +1,1580 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ tconnect.c
+
+Abstract:
+
+ This is the file for a simple connection test to SAM.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <msaudite.h>
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Global data structures //
+// //
+///////////////////////////////////////////////////////////////////////////////
+ULONG AdministrativeRids[] = {
+ DOMAIN_ALIAS_RID_ADMINS,
+ DOMAIN_ALIAS_RID_SYSTEM_OPS,
+ DOMAIN_ALIAS_RID_PRINT_OPS,
+ DOMAIN_ALIAS_RID_BACKUP_OPS,
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS
+ };
+
+#define ADMINISTRATIVE_ALIAS_COUNT (sizeof(AdministrativeRids)/sizeof(ULONG))
+
+#define RTLP_RXACT_KEY_NAME L"RXACT"
+#define RTLP_RXACT_KEY_NAME_SIZE (sizeof(RTLP_RXACT_KEY_NAME) - sizeof(WCHAR))
+
+#define SAMP_FIX_18471_KEY_NAME L"\\Registry\\Machine\\Security\\SAM\\Fix18471"
+#define SAMP_FIX_18471_SHORT_KEY_NAME L"Fix18471"
+#define SAMP_LSA_KEY_NAME L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa"
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+BOOLEAN
+SampMatchDomainPrefix(
+ IN PSID AccountSid,
+ IN PSID DomainSid
+ )
+
+/*++
+
+Routine Description:
+
+ This function compares the domain sid to the domain prefix of an
+ account sid.
+
+Arguments:
+
+ AccountSid - Specifies the account Sid to be compared. The Sid is assumed to be
+ syntactically valid.
+
+ DomainSid - Specifies the domain Sid to compare against.
+
+
+Return Value:
+
+ TRUE - The account Sid is from the Domain specified by the domain Sid
+
+ FALSE - The domain prefix of the account Sid did not match the domain.
+
+--*/
+
+{
+ //
+ // Check if the account Sid has one more subauthority than the
+ // domain Sid.
+ //
+
+ if (*RtlSubAuthorityCountSid(DomainSid) + 1 !=
+ *RtlSubAuthorityCountSid(AccountSid)) {
+ return(FALSE);
+ }
+
+ if (memcmp(
+ RtlIdentifierAuthoritySid(DomainSid),
+ RtlIdentifierAuthoritySid(AccountSid),
+ sizeof(SID_IDENTIFIER_AUTHORITY) ) ) {
+
+ return(FALSE);
+ }
+
+ //
+ // Compare the sub authorities
+ //
+
+ if (memcmp(
+ RtlSubAuthoritySid(DomainSid, 0) ,
+ RtlSubAuthoritySid(AccountSid, 0) ,
+ *RtlSubAuthorityCountSid(DomainSid)
+ ))
+ {
+ return(FALSE);
+ }
+
+ return(TRUE);
+
+}
+
+
+
+NTSTATUS
+SampCreate18471Key(
+ )
+/*++
+
+Routine Description:
+
+ This routine creates the 18471 key used to transaction this fix.
+
+Arguments:
+
+
+Return Value:
+
+ Codes from the NT registry APIs
+
+--*/
+{
+ NTSTATUS Status;
+ UNICODE_STRING KeyName;
+
+
+ //
+ // Open the 18471 key in the registry to see if an upgrade is in
+ // progress
+ //
+
+
+ //
+ // Start a transaction with to create this key.
+ //
+
+ Status = SampAcquireWriteLock();
+
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+ SampSetTransactionDomain(0);
+ SampTransactionWithinDomain = FALSE;
+
+ //
+ // Create the fix18471 key in the registry
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ SAMP_FIX_18471_SHORT_KEY_NAME
+ );
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ 0, // no value type
+ NULL, // no value
+ 0 // no value length
+ );
+
+ //
+ // Commit this change
+ //
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampReleaseWriteLock( TRUE );
+ } else {
+ (void) SampReleaseWriteLock( FALSE );
+ }
+
+ return(Status);
+}
+
+NTSTATUS
+SampAddAliasTo18471Key(
+ IN ULONG AliasRid
+ )
+/*++
+
+Routine Description:
+
+ This routine creates the 18471 key used to transaction this fix.
+
+Arguments:
+
+
+Return Value:
+
+ Codes from the NT registry APIs
+
+--*/
+{
+ NTSTATUS Status;
+ WCHAR KeyName[100];
+ WCHAR AliasName[15]; // big enough for 4 billion
+ UNICODE_STRING KeyNameString;
+ UNICODE_STRING AliasString;
+
+ //
+ // Build the key name. It will be "fix18471\rid_in_hex"
+ //
+
+ wcscpy(
+ KeyName,
+ SAMP_FIX_18471_SHORT_KEY_NAME L"\\"
+ );
+
+ AliasString.Buffer = AliasName;
+ AliasString.MaximumLength = sizeof(AliasName);
+ Status = RtlIntegerToUnicodeString(
+ AliasRid,
+ 16,
+ &AliasString
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ wcscat(
+ KeyName,
+ AliasString.Buffer
+ );
+
+ RtlInitUnicodeString(
+ &KeyNameString,
+ KeyName
+ );
+
+
+ Status = SampAcquireWriteLock();
+
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+ SampSetTransactionDomain(0);
+ SampTransactionWithinDomain = FALSE;
+
+ //
+ // Open the Lsa key in the registry
+ //
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameString,
+ 0, // no value type
+ NULL, // no value
+ 0 // no value length
+ );
+
+ //
+ // Commit this change
+ //
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampReleaseWriteLock( TRUE );
+
+ } else {
+ (void) SampReleaseWriteLock( FALSE );
+ }
+
+ return(Status);
+}
+
+
+
+NTSTATUS
+SampAddMemberRidTo18471Key(
+ IN ULONG AliasRid,
+ IN ULONG MemberRid
+ )
+/*++
+
+Routine Description:
+
+ This routine adds a key for this member under the key for this alias
+ to the current registry transaction.
+
+Arguments:
+
+ AliasRid - the rid of the alias
+
+ MemberRid - The rid of the member of the alias
+
+Returns:
+
+ Errors from the RtlRXact APIs
+
+--*/
+{
+ NTSTATUS Status;
+ WCHAR KeyName[100];
+ WCHAR AliasName[15]; // big enough for 4 billion
+ UNICODE_STRING KeyNameString;
+ UNICODE_STRING AliasString;
+
+
+ //
+ // Build the full key name. It is of the form:
+ // "fix18471\alias_rid\member_rid"
+ //
+
+ wcscpy(
+ KeyName,
+ SAMP_FIX_18471_SHORT_KEY_NAME L"\\"
+ );
+
+ AliasString.Buffer = AliasName;
+ AliasString.MaximumLength = sizeof(AliasName);
+ Status = RtlIntegerToUnicodeString(
+ AliasRid,
+ 16,
+ &AliasString
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ wcscat(
+ KeyName,
+ AliasString.Buffer
+ );
+
+ wcscat(
+ KeyName,
+ L"\\"
+ );
+
+ AliasString.MaximumLength = sizeof(AliasName);
+ Status = RtlIntegerToUnicodeString(
+ MemberRid,
+ 16,
+ &AliasString
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ wcscat(
+ KeyName,
+ AliasString.Buffer
+ );
+
+ RtlInitUnicodeString(
+ &KeyNameString,
+ KeyName
+ );
+
+ //
+ // Add this action to the RXact
+ //
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyNameString,
+ 0, // no value type
+ NULL, // no value
+ 0 // no value length
+ );
+
+ return(Status);
+
+}
+
+NTSTATUS
+SampCheckMemberUpgradedFor18471(
+ IN ULONG AliasRid,
+ IN ULONG MemberRid
+ )
+/*++
+
+Routine Description:
+
+ This routine checks if the SAM upgrade flag is set. The upgrade
+ flag is:
+
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\lsa
+ UpgradeSam = REG_DWORD 1
+
+
+Arguments:
+
+
+Return Value:
+
+ TRUE - The flag was set
+
+ FALSE - The flag was not set or the value was not present
+
+--*/
+{
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE KeyHandle;
+ NTSTATUS Status;
+ WCHAR KeyName[100];
+ WCHAR AliasName[15]; // big enough for 4 billion
+ UNICODE_STRING KeyNameString;
+ UNICODE_STRING AliasString;
+
+
+ //
+ // Build the full key name. It is of the form:
+ // "fix18471\alias_rid\member_rid"
+ //
+
+ wcscpy(
+ KeyName,
+ SAMP_FIX_18471_KEY_NAME L"\\"
+ );
+
+ AliasString.Buffer = AliasName;
+ AliasString.MaximumLength = sizeof(AliasName);
+ Status = RtlIntegerToUnicodeString(
+ AliasRid,
+ 16,
+ &AliasString
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ wcscat(
+ KeyName,
+ AliasString.Buffer
+ );
+
+ wcscat(
+ KeyName,
+ L"\\"
+ );
+
+ AliasString.MaximumLength = sizeof(AliasName);
+ Status = RtlIntegerToUnicodeString(
+ MemberRid,
+ 16,
+ &AliasString
+ );
+ ASSERT(NT_SUCCESS(Status));
+
+ wcscat(
+ KeyName,
+ AliasString.Buffer
+ );
+
+ RtlInitUnicodeString(
+ &KeyNameString,
+ KeyName
+ );
+
+
+ //
+ // Open the member key in the registry
+ //
+
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyNameString,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+
+ Status = NtOpenKey(
+ &KeyHandle,
+ KEY_READ,
+ &ObjectAttributes
+ );
+
+ NtClose(KeyHandle);
+ return(Status);
+
+}
+
+VOID
+SampBuild18471CleanupKey(
+ OUT PUNICODE_STRING KeyName,
+ IN PWCHAR AliasName,
+ IN ULONG AliasNameLength,
+ IN PWCHAR MemberName,
+ IN ULONG MemberNameLength
+ )
+/*++
+
+Routine Description:
+
+ Builds the key "Fix18471\alias_rid\member_rid"
+
+Arguments:
+
+
+Return Value:
+
+ None
+
+--*/
+{
+ PUCHAR Where = (PUCHAR) KeyName->Buffer;
+
+ RtlCopyMemory(
+ Where,
+ SAMP_FIX_18471_SHORT_KEY_NAME L"\\",
+ sizeof(SAMP_FIX_18471_SHORT_KEY_NAME) // terminating NULL used for '\'
+ );
+
+ Where += sizeof(SAMP_FIX_18471_SHORT_KEY_NAME);
+
+ RtlCopyMemory(
+ Where,
+ AliasName,
+ AliasNameLength
+ );
+ Where += AliasNameLength;
+
+ //
+ // If there is a member name to this alias, add it now.
+ //
+
+ if (MemberName != NULL) {
+ RtlCopyMemory(
+ Where,
+ L"\\",
+ sizeof(WCHAR)
+ );
+ Where += sizeof(WCHAR);
+
+ RtlCopyMemory(
+ Where,
+ MemberName,
+ MemberNameLength
+ );
+ Where += MemberNameLength;
+
+ }
+
+ KeyName->Length = (USHORT) (Where - (PUCHAR) KeyName->Buffer);
+ ASSERT(KeyName->Length <= KeyName->MaximumLength);
+}
+
+
+NTSTATUS
+SampCleanup18471(
+ )
+/*++
+
+Routine Description:
+
+ Cleans up the transaction log left by fixing bug 18471. This routine
+ builds a transaction with all the keys in the log and then commits
+ the transaction
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Status codes from the NT registry APIs and NT RXact APIs
+
+--*/
+{
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ NTSTATUS Status;
+ HANDLE RootKey = NULL;
+ HANDLE AliasKey = NULL;
+ UCHAR Buffer[sizeof(KEY_BASIC_INFORMATION) + 15 * sizeof(WCHAR)];
+ UCHAR Buffer2[sizeof(KEY_BASIC_INFORMATION) + 15 * sizeof(WCHAR)];
+ UNICODE_STRING KeyName;
+ WCHAR KeyBuffer[100];
+ PKEY_BASIC_INFORMATION BasicInfo = (PKEY_BASIC_INFORMATION) Buffer;
+ PKEY_BASIC_INFORMATION BasicInfo2 = (PKEY_BASIC_INFORMATION) Buffer2;
+ ULONG BasicInfoLength;
+ ULONG Index, Index2;
+
+ //
+ // Open the 18471 key in the registry
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ SAMP_FIX_18471_KEY_NAME
+ );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+
+ Status = NtOpenKey(
+ &RootKey,
+ KEY_READ | DELETE,
+ &ObjectAttributes
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // If the error was that the key did not exist, then there
+ // is nothing to cleanup, so return success.
+ //
+
+ if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
+ return(STATUS_SUCCESS);
+ }
+ return(Status);
+ }
+
+ //
+ // Create a transaction to add all the keys to delete to
+ //
+
+ Status = SampAcquireWriteLock();
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ SampSetTransactionDomain(0);
+ SampTransactionWithinDomain = FALSE;
+
+ //
+ // Now enumerate all the subkeys of the root 18471 key
+ //
+
+ Index = 0;
+ do
+ {
+
+ Status = NtEnumerateKey(
+ RootKey,
+ Index,
+ KeyBasicInformation,
+ BasicInfo,
+ sizeof(Buffer),
+ &BasicInfoLength
+ );
+ //
+ //
+ // Check if this is the RXACT key. If it is, we don't want
+ // to add it to the delete log.
+ //
+ // Otherwise open this key and enumerate all the subkeys of it.
+ //
+
+ if (NT_SUCCESS(Status) &&
+ ((BasicInfo->NameLength != RTLP_RXACT_KEY_NAME_SIZE) ||
+ memcmp(
+ BasicInfo->Name,
+ RTLP_RXACT_KEY_NAME,
+ RTLP_RXACT_KEY_NAME_SIZE
+ ) ) ) {
+
+ KeyName.Buffer = BasicInfo->Name;
+ KeyName.Length = (USHORT) BasicInfo->NameLength;
+ KeyName.MaximumLength = KeyName.Length;
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ RootKey,
+ NULL
+ );
+
+ //
+ // Open the key for the alias rid. This really should
+ // succeed
+ //
+
+ Status = NtOpenKey(
+ &AliasKey,
+ KEY_READ,
+ &ObjectAttributes
+ );
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ //
+ // Enumerate all the subkeys (the alias members) and add them
+ // to the transaction
+ //
+
+ Index2 = 0;
+ do
+ {
+ Status = NtEnumerateKey(
+ AliasKey,
+ Index2,
+ KeyBasicInformation,
+ BasicInfo2,
+ sizeof(Buffer2),
+ &BasicInfoLength
+ );
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // Build the name of this key from the alias rid and the
+ // member rid
+ //
+
+ KeyName.Buffer = KeyBuffer;
+ KeyName.MaximumLength = sizeof(KeyBuffer);
+
+ SampBuild18471CleanupKey(
+ &KeyName,
+ BasicInfo->Name,
+ BasicInfo->NameLength,
+ BasicInfo2->Name,
+ BasicInfo2->NameLength
+ );
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+
+ }
+ Index2++;
+
+ } while (NT_SUCCESS(Status));
+
+ NtClose(AliasKey);
+ AliasKey = NULL;
+
+ //
+ // If we suffered a serious error, get out of here now
+ //
+
+ if (!NT_SUCCESS(Status)) {
+ if (Status != STATUS_NO_MORE_ENTRIES) {
+ break;
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ //
+ // Add the alias RID key to the RXact now - we need to add it
+ // after deleting all the children
+ //
+
+ KeyName.Buffer = KeyBuffer;
+ KeyName.MaximumLength = sizeof(KeyBuffer);
+ SampBuild18471CleanupKey(
+ &KeyName,
+ BasicInfo->Name,
+ BasicInfo->NameLength,
+ NULL,
+ 0
+ );
+
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ }
+
+ Index++;
+ } while (NT_SUCCESS(Status));
+
+ if (Status == STATUS_NO_MORE_ENTRIES) {
+ Status = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+
+ RtlInitUnicodeString(
+ &KeyName,
+ SAMP_FIX_18471_SHORT_KEY_NAME
+ );
+
+ Status = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // Write the new server revision to indicate that this
+ // upgrade has been performed
+ //
+
+ ULONG Revision = SAMP_SERVER_REVISION;
+ PSAMP_OBJECT ServerContext;
+
+ //
+ // We need to read the fixed attributes of the server objects.
+ // Create a context to do that.
+ //
+
+ ServerContext = SampCreateContext( SampServerObjectType, TRUE );
+
+ if ( ServerContext != NULL ) {
+
+ ServerContext->RootKey = SampKey;
+
+ Status = SampSetFixedAttributes(
+ ServerContext,
+ &Revision
+ );
+ if (NT_SUCCESS(Status)) {
+ Status = SampStoreObjectAttributes(
+ ServerContext,
+ TRUE
+ );
+ }
+
+ SampDeleteContext( ServerContext );
+ } else {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+
+ //
+ // Apply the RXACT and delete the remaining keys.
+ //
+
+Cleanup:
+
+ //
+ // Cleanup any floating bits from above.
+ //
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampReleaseWriteLock( TRUE );
+ } else {
+ (VOID) SampReleaseWriteLock( FALSE );
+ }
+
+ if (RootKey != NULL) {
+ NtClose(RootKey);
+ }
+
+ ASSERT(AliasKey == NULL);
+
+
+ return(Status);
+
+}
+
+NTSTATUS
+SampFixBug18471 (
+ IN ULONG Revision
+ )
+/*++
+
+Routine Description:
+
+ This routine fixes bug 18471, that SAM does not adjust the protection
+ on groups that are members of administrative aliases in the builtin
+ domain. It fixes this by opening a fixed set of known aliases
+ (Administrators, Account Operators, Backup Operators, Print Operators,
+ and Server Operators), and enumerating their members. To fix this,
+ we will remove all the members of these aliases (except the
+ Administrator user account) and re-add them.
+
+Arguments:
+
+ Revision - Revision of the Sam server.
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS Status;
+ ULONG Index, Index2;
+ PSID BuiltinDomainSid = NULL;
+ SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
+ PSID AccountDomainSid;
+ ULONG AccountDomainIndex = 0xffffffff;
+ ULONG BuiltinDomainIndex = 0xffffffff;
+ SAMPR_PSID_ARRAY AliasMembership;
+ ULONG MemberRid;
+ ULONG SdRevision;
+ PSECURITY_DESCRIPTOR OldDescriptor;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+ SAMP_OBJECT_TYPE MemberType;
+ PSAMP_OBJECT MemberContext;
+ PSAMP_OBJECT AliasContext;
+ SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed;
+ SAMP_V1_0A_FIXED_LENGTH_USER UserV1Fixed;
+
+ //
+ // Check the revision on the server to see if this upgrade has
+ // already been performed.
+ //
+
+
+ if (Revision >= SAMP_SERVER_REVISION) {
+
+ //
+ // This upgrade has already been performed.
+ //
+
+ goto Cleanup;
+ }
+
+
+ //
+ // Build a the BuiltIn domain SID.
+ //
+
+ BuiltinDomainSid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
+
+ if ( BuiltinDomainSid == NULL ) {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Cleanup;
+ }
+
+ RtlInitializeSid( BuiltinDomainSid, &BuiltinAuthority, 1 );
+ *(RtlSubAuthoritySid( BuiltinDomainSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+
+
+ //
+ // Lookup the index of the account domain
+ //
+
+ for (Index = 0;
+ Index < SampDefinedDomainsCount ;
+ Index++ ) {
+
+ if (RtlEqualSid( BuiltinDomainSid, SampDefinedDomains[Index].Sid)) {
+ BuiltinDomainIndex = Index;
+ } else {
+ AccountDomainIndex = Index;
+ }
+ }
+
+ ASSERT(AccountDomainIndex < SampDefinedDomainsCount);
+ ASSERT(BuiltinDomainIndex < SampDefinedDomainsCount);
+
+ AccountDomainSid = SampDefinedDomains[AccountDomainIndex].Sid;
+
+ //
+ // Create out transaction log
+ //
+
+ Status = SampCreate18471Key();
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+
+
+
+ //
+ // Now loop through and open the aliases we are intersted in
+ //
+
+ for (Index = 0;
+ Index < ADMINISTRATIVE_ALIAS_COUNT ;
+ Index++ )
+ {
+
+ SampSetTransactionDomain( BuiltinDomainIndex );
+
+ SampAcquireReadLock();
+
+ Status = SampCreateAccountContext(
+ SampAliasObjectType,
+ AdministrativeRids[Index],
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AliasContext
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ SampReleaseReadLock();
+ if (Status == STATUS_NO_SUCH_ALIAS) {
+ Status = STATUS_SUCCESS;
+ continue;
+ } else {
+
+ goto Cleanup;
+ }
+ }
+
+
+ //
+ // Get the members in the alias so we can remove and re-add them
+ //
+
+ Status = SampRetrieveAliasMembers(
+ AliasContext,
+ &(AliasMembership.Count),
+ (PSID **)&(AliasMembership.Sids)
+ );
+
+ SampDeleteContext(AliasContext);
+ SampReleaseReadLock();
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ //
+ // Write that we are opening this alias to the log. We don't need
+ // to do this for administrators, since for them we the update is
+ // idempotent.
+ //
+
+ if (AdministrativeRids[Index] != DOMAIN_ALIAS_RID_ADMINS) {
+ Status = SampAddAliasTo18471Key(
+ AdministrativeRids[Index]
+ );
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+ }
+
+
+ //
+ // Loop through the members and split each sid. For every
+ // member in the account domain , remove it and re-add it from
+ // this alias.
+ //
+
+
+
+
+ for (Index2 = 0; Index2 < AliasMembership.Count ; Index2++ )
+ {
+ //
+ // Check to see if this account is in the account domain
+ //
+
+ if ( SampMatchDomainPrefix(
+ (PSID) AliasMembership.Sids[Index2].SidPointer,
+ AccountDomainSid
+ ) )
+ {
+
+ //
+ // Get the RID for this member
+ //
+
+ MemberRid = *RtlSubAuthoritySid(
+ AliasMembership.Sids[Index2].SidPointer,
+ *RtlSubAuthorityCountSid(
+ AliasMembership.Sids[Index2].SidPointer
+ ) - 1
+ );
+
+ //
+ // Now remove and re-add the administratie nature of this
+ // membership
+ //
+
+ if (AdministrativeRids[Index] == DOMAIN_ALIAS_RID_ADMINS) {
+
+ Status = SampAcquireWriteLock();
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ SampSetTransactionDomain( AccountDomainIndex );
+
+ //
+ // Try to create a context for the account as a group.
+ //
+
+ Status = SampCreateAccountContext(
+ SampGroupObjectType,
+ MemberRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &MemberContext
+ );
+
+ if (!NT_SUCCESS( Status ) ) {
+
+ //
+ // If this ID does not exist as a group, that's fine -
+ // it might be a user or might have been deleted.
+ //
+
+ SampReleaseWriteLock( FALSE );
+ if (Status == STATUS_NO_SUCH_GROUP) {
+ Status = STATUS_SUCCESS;
+ continue;
+ }
+ break;
+ }
+
+ //
+ // Now set a flag in the group itself,
+ // so that when users are added and removed
+ // in the future it is known whether this
+ // group is in an ADMIN alias or not.
+ //
+
+ Status = SampRetrieveGroupV1Fixed(
+ MemberContext,
+ &GroupV1Fixed
+ );
+
+ if ( NT_SUCCESS(Status)) {
+
+ GroupV1Fixed.AdminCount = 1;
+
+ Status = SampReplaceGroupV1Fixed(
+ MemberContext,
+ &GroupV1Fixed
+ );
+ //
+ // Modify the security descriptor to
+ // prevent account operators from adding
+ // anybody to this group
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ Status = SampGetAccessAttribute(
+ MemberContext,
+ SAMP_GROUP_SECURITY_DESCRIPTOR,
+ FALSE, // don't make copy
+ &SdRevision,
+ &OldDescriptor
+ );
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = SampModifyAccountSecurity(
+ SampGroupObjectType,
+ TRUE, // this is an admin
+ OldDescriptor,
+ &SecurityDescriptor,
+ &SecurityDescriptorLength
+ );
+ }
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Write the new security descriptor into the object
+ //
+
+ Status = SampSetAccessAttribute(
+ MemberContext,
+ SAMP_USER_SECURITY_DESCRIPTOR,
+ SecurityDescriptor,
+ SecurityDescriptorLength
+ );
+
+ MIDL_user_free( SecurityDescriptor );
+ }
+
+
+
+ }
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // Add the modified group to the current transaction
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ Status = SampStoreObjectAttributes(MemberContext, FALSE);
+
+ }
+
+ }
+
+ //
+ // Clean up the group context
+ //
+
+ SampDeleteContext(MemberContext);
+
+ //
+ // we don't want the modified count to change
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampReleaseWriteLock( TRUE );
+ } else {
+ (VOID) SampReleaseWriteLock( FALSE );
+ }
+
+ }
+ else
+ {
+
+
+ //
+ // Check to see if we've already upgraded this member
+ //
+
+ Status = SampCheckMemberUpgradedFor18471(
+ AdministrativeRids[Index],
+ MemberRid);
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // This member already was upgraded.
+ //
+
+ continue;
+ } else {
+
+ //
+ // We continue on with the upgrade
+ //
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Change the operator account for the other
+ // aliases.
+ //
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = SampAcquireWriteLock();
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ SampSetTransactionDomain( AccountDomainIndex );
+
+ Status = SampChangeAccountOperatorAccessToMember(
+ AliasMembership.Sids[Index2].SidPointer,
+ NoChange,
+ AddToAdmin
+ );
+
+ //
+ // If that succeeded, add this member to the log
+ // as one that was upgraded.
+ //
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampAddMemberRidTo18471Key(
+ AdministrativeRids[Index],
+ MemberRid
+ );
+
+ }
+
+ //
+ // We don't want the modified count to be updated so
+ // make this not a domain transaction
+ //
+
+ SampTransactionWithinDomain = FALSE;
+ if (NT_SUCCESS(Status)) {
+ Status = SampReleaseWriteLock( TRUE );
+ } else {
+ (VOID) SampReleaseWriteLock( FALSE );
+ }
+
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ }
+ }
+ }
+
+ SamIFree_SAMPR_PSID_ARRAY(
+ &AliasMembership
+ );
+ AliasMembership.Sids = NULL;
+
+
+ //
+ // If something up above failed or the upgrade was already done,
+ // exit now.
+ //
+
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+ }
+
+Cleanup:
+
+ if (BuiltinDomainSid != NULL) {
+ RtlFreeHeap(
+ RtlProcessHeap(),
+ 0,
+ BuiltinDomainSid
+ );
+ }
+
+ if (NT_SUCCESS(Status)) {
+ Status = SampCleanup18471();
+ }
+ return(Status);
+}
+
+#if 0
+
+BOOLEAN
+SampUpgradeFlagSet(
+ )
+/*++
+
+Routine Description:
+
+ This routine checks if the SAM upgrade flag is set. The upgrade
+ flag is:
+
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\lsa
+ UpgradeSam = REG_DWORD 1
+
+
+Arguments:
+
+
+Return Value:
+
+ TRUE - The flag was set
+
+ FALSE - The flag was not set or the value was not present
+
+--*/
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE KeyHandle;
+ UCHAR Buffer[100];
+ PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION) Buffer;
+ ULONG KeyValueLength = 100;
+ ULONG ResultLength;
+ PULONG UpgradeFlag;
+
+ //
+ // Open the Lsa key in the registry
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ SAMP_LSA_KEY_NAME
+ );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+
+ NtStatus = NtOpenKey(
+ &KeyHandle,
+ KEY_READ,
+ &ObjectAttributes
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(FALSE);
+ }
+
+ //
+ // Query the Notification Packages value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"UpgradeSam"
+ );
+
+ NtStatus = NtQueryValueKey(
+ KeyHandle,
+ &KeyName,
+ KeyValuePartialInformation,
+ KeyValueInformation,
+ KeyValueLength,
+ &ResultLength
+ );
+
+ NtClose(KeyHandle);
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ return(FALSE);
+ }
+
+ //
+ // Check that the data is the correct size and type - a ULONG.
+ //
+
+ if ((KeyValueInformation->DataLength < sizeof(ULONG)) ||
+ (KeyValueInformation->Type != REG_DWORD)) {
+
+ return(FALSE);
+ }
+
+ //
+ // Check the flag.
+ //
+
+ UpgradeFlag = (PULONG) KeyValueInformation->Data;
+
+ if (*UpgradeFlag == 1) {
+ return(TRUE);
+ }
+
+ return(FALSE);
+
+
+}
+
+BOOLEAN
+SampSetUpgradeFlag(
+ )
+/*++
+
+Routine Description:
+
+ This routine sets SAM upgrade flag is set. The upgrade
+ flag is:
+
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\lsa
+ UpgradeSam = REG_DWORD 1
+
+ and the value will be deleted.
+
+
+Arguments:
+
+
+Return Value:
+
+ TRUE - The flag was set
+
+ FALSE - The flag was not set or the value was not present
+
+--*/
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE KeyHandle;
+ UCHAR Buffer[100];
+ PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION) Buffer;
+ ULONG KeyValueLength = 100;
+ ULONG ResultLength;
+ PULONG UpgradeFlag;
+
+ //
+ // Open the Lsa key in the registry
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ SAMP_LSA_KEY_NAME
+ );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+
+ NtStatus = NtOpenKey(
+ &KeyHandle,
+ KEY_SET_VALUE,
+ &ObjectAttributes
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(FALSE);
+ }
+
+ //
+ // Query the Notification Packages value
+ //
+
+ RtlInitUnicodeString(
+ &KeyName,
+ L"UpgradeSam"
+ );
+
+ NtStatus = NtDeleteValueKey(
+ KeyHandle,
+ &KeyName
+ );
+
+ NtClose(KeyHandle);
+
+
+}
+#endif
+
+NTSTATUS
+SampUpgradeSamDatabase(
+ IN ULONG Revision
+ )
+/*++
+
+Routine Description:
+
+ Upgrades the SAM database.
+
+Arguments:
+
+ Revision - The revision stored in the Server fixed length attributes
+
+Return Value:
+
+
+ Note:
+
+
+--*/
+{
+ NTSTATUS Status;
+ Status = SampFixBug18471(Revision);
+
+ return(Status);
+}
diff --git a/private/newsam/server/upgrade.txt b/private/newsam/server/upgrade.txt
new file mode 100644
index 000000000..2b0a828d9
--- /dev/null
+++ b/private/newsam/server/upgrade.txt
@@ -0,0 +1,13 @@
+NOTE TO DEVELOPERS ---
+
+
+------- Jimk, 1/13/94 ----------------------------------------------
+ There is a mechanism in place to upgrade one version of SAM to
+ another while upgrading from one release of NT to another.
+
+ The file SAMUPGRD.C in SETUP\SETUPDLL project has this code
+ in it. At the time I wrote this mail, this file was at
+ \\orville\razzle\src\setup\setupdll\samupgrd.c.
+
+======================================================================
+
diff --git a/private/newsam/server/user.c b/private/newsam/server/user.c
new file mode 100644
index 000000000..c882a37f9
--- /dev/null
+++ b/private/newsam/server/user.c
@@ -0,0 +1,11229 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ user.c
+
+Abstract:
+
+ This file contains services related to the SAM "user" object.
+
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <lmcons.h>
+#include <nturtl.h>
+#include <ntlsa.h> // need for nlrepl.h
+#include <nlrepl.h> // I_NetNotifyMachineAccount prototype
+#include <msaudite.h>
+#include <rc4.h> // rc4_key(), rc4()
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+LARGE_INTEGER
+SampGetPasswordMustChange(
+ IN ULONG UserAccountControl,
+ IN LARGE_INTEGER PasswordLastSet,
+ IN LARGE_INTEGER MaxPasswordAge
+ );
+
+NTSTATUS
+SampComputePasswordExpired(
+ IN BOOLEAN PasswordExpired,
+ OUT PLARGE_INTEGER PasswordLastSet
+ );
+
+NTSTATUS
+SampStorePasswordExpired(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN PasswordExpired
+ );
+
+NTSTATUS
+SampStoreUserPasswords(
+ IN PSAMP_OBJECT Context,
+ IN PLM_OWF_PASSWORD LmOwfPassword,
+ IN BOOLEAN LmPasswordPresent,
+ IN PNT_OWF_PASSWORD NtOwfPassword,
+ IN BOOLEAN NtPasswordPresent,
+ IN BOOLEAN CheckHistory
+ );
+
+NTSTATUS
+SampRetrieveUserPasswords(
+ IN PSAMP_OBJECT Context,
+ OUT PLM_OWF_PASSWORD LmOwfPassword,
+ OUT PBOOLEAN LmPasswordNonNull,
+ OUT PNT_OWF_PASSWORD NtOwfPassword,
+ OUT PBOOLEAN NtPasswordPresent,
+ OUT PBOOLEAN NtPasswordNonNull
+ );
+
+NTSTATUS
+SampRetrieveUserMembership(
+ IN PSAMP_OBJECT UserContext,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG MembershipCount,
+ OUT PGROUP_MEMBERSHIP *Membership OPTIONAL
+ );
+
+NTSTATUS
+SampReplaceUserMembership(
+ IN PSAMP_OBJECT UserContext,
+ IN ULONG MembershipCount,
+ IN PGROUP_MEMBERSHIP Membership
+ );
+
+NTSTATUS
+SampRetrieveUserLogonHours(
+ IN PSAMP_OBJECT Context,
+ OUT PLOGON_HOURS LogonHours
+ );
+
+NTSTATUS
+SampReplaceUserLogonHours(
+ IN PSAMP_OBJECT Context,
+ IN PLOGON_HOURS LogonHours
+ );
+
+
+NTSTATUS
+SampAssignPrimaryGroup(
+ IN PSAMP_OBJECT Context,
+ IN ULONG GroupRid
+ );
+
+NTSTATUS
+SampDeleteUserKeys(
+ IN PSAMP_OBJECT Context
+ );
+
+NTSTATUS
+SampCheckPasswordHistory(
+ IN PVOID EncryptedPassword,
+ IN ULONG EncryptedPasswordLength,
+ IN USHORT PasswordHistoryLength,
+ IN ULONG HistoryAttributeIndex,
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN CheckHistory,
+ OUT PUNICODE_STRING OwfHistoryBuffer
+ );
+
+NTSTATUS
+SampAddPasswordHistory(
+ IN PSAMP_OBJECT Context,
+ IN ULONG HistoryAttributeIndex,
+ IN PUNICODE_STRING NtOwfHistoryBuffer,
+ IN PVOID EncryptedPassword,
+ IN ULONG EncryptedPasswordLength,
+ IN USHORT PasswordHistoryLength
+ );
+
+NTSTATUS
+SampMatchworkstation(
+ IN PUNICODE_STRING LogonWorkStation,
+ IN PUNICODE_STRING WorkStations
+ );
+
+NTSTATUS
+SampChangeUserAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ );
+
+USHORT
+SampQueryBadPasswordCount(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ );
+BOOLEAN
+SampIncrementBadPasswordCount(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ );
+VOID
+SampUpdateAccountLockedOutFlag(
+ PSAMP_OBJECT Context,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
+ PBOOLEAN IsLocked
+ );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Routines //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+
+NTSTATUS
+SamrOpenUser(
+ IN SAMPR_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG UserId,
+ OUT SAMPR_HANDLE *UserHandle
+ )
+
+
+/*++
+
+ This API opens an existing user in the account database. The user
+ is specified by a ID value that is relative to the SID of the
+ domain. The operations that will be performed on the user must be
+ declared at this time.
+
+ This call returns a handle to the newly opened user that may be
+ used for successive operations on the user. This handle may be
+ closed with the SamCloseHandle API.
+
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the user. These access types are reconciled
+ with the Discretionary Access Control list of the user to
+ determine whether the accesses will be granted or denied.
+
+ UserId - Specifies the relative ID value of the user to be
+ opened.
+
+ UserHandle - Receives a handle referencing the newly opened
+ user. This handle will be required in successive calls to
+ operate on the user.
+
+Return Values:
+
+ STATUS_SUCCESS - The user was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_NO_SUCH_USER - The specified user does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ SAMP_OBJECT_TYPE FoundType;
+
+ NtStatus = SampOpenAccount(
+ SampUserObjectType,
+ DomainHandle,
+ DesiredAccess,
+ UserId,
+ FALSE,
+ UserHandle
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+ NTSTATUS NtStatus1;
+
+ //
+ // If the domain handle allows reading the password
+ // parameters, note that in the context to make life
+ // easy for SampGetUserDomainPasswordInformation().
+ //
+
+ SampAcquireReadLock();
+
+ NtStatus1 = SampLookupContext(
+ DomainHandle,
+ DOMAIN_READ_PASSWORD_PARAMETERS, // DesiredAccess
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if ( NT_SUCCESS( NtStatus1 ) ) {
+
+ ((PSAMP_OBJECT)(*UserHandle))->TypeBody.User.DomainPasswordInformationAccessible = TRUE;
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainHandle, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ } else {
+
+ ((PSAMP_OBJECT)(*UserHandle))->TypeBody.User.DomainPasswordInformationAccessible = FALSE;
+ }
+
+ //
+ // Release the lock
+ //
+
+ SampReleaseReadLock();
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrDeleteUser(
+ IN OUT SAMPR_HANDLE *UserHandle
+ )
+
+
+/*++
+
+Routine Description:
+
+ This API deletes a user from the account database. If the account
+ being deleted is the last account in the database in the ADMIN
+ group, then STATUS_LAST_ADMIN is returned, and the Delete fails.
+
+ Note that following this call, the UserHandle is no longer valid.
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on. The handle must be
+ openned for DELETE access.
+
+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_LAST_ADMIN - Cannot delete the last enabled administrator account
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+--*/
+
+{
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ UNICODE_STRING UserName;
+ NTSTATUS NtStatus, IgnoreStatus, TmpStatus;
+ PSAMP_OBJECT AccountContext = NULL;
+ PSAMP_DEFINED_DOMAINS Domain = NULL;
+ SAMP_OBJECT_TYPE FoundType;
+ PSID AccountSid = NULL;
+ PGROUP_MEMBERSHIP Groups = NULL;
+ ULONG ObjectRid,
+ GroupCount,
+ DomainIndex,
+ i;
+
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)(*UserHandle);
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DELETE,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ ObjectRid = AccountContext->TypeBody.User.Rid;
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // built-in accounts can't be deleted, unless the caller is trusted
+ //
+
+ if ( !AccountContext->TrustedClient ) {
+
+ NtStatus = SampIsAccountBuiltIn( ObjectRid );
+ }
+
+ //
+ // Get the list of groups this user is a member of.
+ // Remove the user from each group.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveUserMembership(
+ AccountContext,
+ FALSE, // Make copy
+ &GroupCount,
+ &Groups
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ASSERT( GroupCount > 0);
+ ASSERT( Groups != NULL );
+
+
+ //
+ // Remove the user from each group.
+ //
+
+ for ( i=0; i<GroupCount && NT_SUCCESS(NtStatus); i++) {
+
+ NtStatus = SampRemoveUserFromGroup(
+ Groups[i].RelativeId,
+ ObjectRid
+ );
+ }
+ }
+ }
+
+ //
+ // So far, so good. The user has been removed from all groups.
+ // Now remove the user from all aliases
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCreateAccountSid(AccountContext, &AccountSid);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRemoveAccountFromAllAliases(
+ AccountSid,
+ FALSE,
+ NULL,
+ NULL,
+ NULL
+ );
+ }
+ }
+
+ //
+ // Get the AccountControl flags for when we update
+ // the display cache, and to let Netlogon know if this
+ // is a machine account that is going away.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ //
+ // Now we just need to clean up the user keys themselves.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // First get and save the account name for
+ // I_NetNotifyLogonOfDelta.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &UserName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // This must be done before we invalidate contexts, because our
+ // own handle to the group gets closed as well.
+ //
+
+ NtStatus = SampDeleteUserKeys( AccountContext );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We must invalidate any open contexts to this user.
+ // This will close all handles to the user's keys.
+ // THIS IS AN IRREVERSIBLE PROCESS.
+ //
+
+ SampInvalidateUserContexts( ObjectRid );
+
+ //
+ // Commit the whole mess
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
+
+ //
+ // Update the cached Alias Information
+ //
+
+ IgnoreStatus = SampAlRemoveAccountFromAllAliases(
+ AccountSid,
+ FALSE,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ //
+ // Update the display information
+ //
+
+ AccountInfo.Name = UserName;
+ AccountInfo.Rid = ObjectRid;
+ AccountInfo.AccountControl = V1aFixed.UserAccountControl;
+ RtlInitUnicodeString(&AccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&AccountInfo.FullName, NULL);
+
+ IgnoreStatus = SampUpdateDisplayInformation(&AccountInfo,
+ NULL,
+ SampUserObjectType);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+
+ //
+ // Audit the deletion before we free the write lock
+ // so that we have access to the context block.
+ //
+
+ if (SampDoAccountAuditing(DomainIndex) &&
+ NT_SUCCESS(NtStatus) ) {
+
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_USER_DELETED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &UserName, // Account Name
+ &Domain->ExternalName, // Domain
+ &ObjectRid, // Account Rid
+ NULL // Privileges used
+ );
+
+ }
+
+ //
+ // Notify netlogon of the change
+ //
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbDelete,
+ SecurityDbObjectSamUser,
+ ObjectRid,
+ &UserName,
+ (DWORD) FALSE, // Replicate immediately
+ NULL // Delta data
+ );
+
+ //
+ // Do delete auditing
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ (VOID) NtDeleteObjectAuditAlarm(
+ &SampSamSubsystem,
+ *UserHandle,
+ AccountContext->AuditOnClose
+ );
+ }
+
+
+ if ( ( V1aFixed.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) != 0 ) {
+
+ //
+ // This was a machine account. Let
+ // NetLogon know of the change.
+ //
+
+ IgnoreStatus = I_NetNotifyMachineAccount(
+ ObjectRid,
+ SampDefinedDomains[SampTransactionDomainIndex].Sid,
+ V1aFixed.UserAccountControl,
+ 0,
+ &UserName
+ );
+ }
+ }
+ }
+
+ SampFreeUnicodeString( &UserName );
+ }
+ }
+
+ //
+ // De-reference the object, discarding changes, and delete the context
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we actually deleted the user, delete the context and
+ // let RPC know that the handle is invalid.
+ //
+
+ SampDeleteContext( AccountContext );
+
+ (*UserHandle) = NULL;
+ }
+
+ } //end_if
+
+ //
+ // Free the lock -
+ //
+ // Everything has already been committed above, so we must indicate
+ // no additional changes have taken place.
+ //
+ //
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+
+ if (NtStatus == STATUS_SUCCESS) {
+ NtStatus = TmpStatus;
+ }
+
+ //
+ // If necessary, free the AccountSid.
+ //
+
+ if (AccountSid != NULL) {
+
+ MIDL_user_free(AccountSid);
+ AccountSid = NULL;
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrQueryInformationUser(
+ IN SAMPR_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ OUT PSAMPR_USER_INFO_BUFFER *Buffer
+ )
+{
+ //
+ // This is a thin veil to SamrQueryInformationUser2().
+ // 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( SamrQueryInformationUser2(UserHandle, UserInformationClass, Buffer ) );
+}
+
+
+NTSTATUS
+SamrQueryInformationUser2(
+ IN SAMPR_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ OUT PSAMPR_USER_INFO_BUFFER *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ User object QUERY information routine.
+
+Arguments:
+
+ UserHandle - RPC context handle for an open user object.
+
+ UserInformationClass - Type of information being queried.
+
+ Buffer - To receive the output (queried) information.
+
+
+Return Value:
+
+
+ STATUS_INVALID_INFO_CLASS - An unknown information class was requested.
+ No information has been returned.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to
+ return(the requested information in.
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ PUSER_ALL_INFORMATION All;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+ ULONG i, WhichFields;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ BOOLEAN NoErrorsYet;
+ LM_OWF_PASSWORD LmOwfPassword;
+ NT_OWF_PASSWORD NtOwfPassword;
+ BOOLEAN NtPasswordNonNull, LmPasswordNonNull;
+ BOOLEAN NtPasswordPresent;
+
+ //
+ // 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[40];
+ ULONG AllocatedBufferCount = 0;
+ LARGE_INTEGER TempTime;
+
+ #define RegisterBuffer(Buffer) \
+ { \
+ if ((Buffer) != NULL) { \
+ \
+ ASSERT(AllocatedBufferCount < \
+ sizeof(AllocatedBuffer) / sizeof(*AllocatedBuffer)); \
+ \
+ AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
+ } \
+ }
+
+ #define AllocateBuffer(NewBuffer, Size) \
+ { \
+ (NewBuffer) = MIDL_user_allocate(Size); \
+ RegisterBuffer(NewBuffer); \
+ } \
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (Buffer != NULL);
+ ASSERT ((*Buffer) == NULL);
+
+
+
+ //
+ // Set the desired access based upon information class.
+ //
+
+ switch (UserInformationClass) {
+
+ case UserInternal3Information:
+ case UserAllInformation:
+
+ //
+ // For trusted clients, we will return everything. For
+ // others, we will return everything that they have access to.
+ // In either case, we'll have to look at some variables in the
+ // context so we'll do the work after the SampLookupContext()
+ // below.
+ //
+
+ DesiredAccess = 0;
+ break;
+
+ case UserAccountInformation:
+
+ DesiredAccess = (USER_READ_GENERAL |
+ USER_READ_PREFERENCES |
+ USER_READ_LOGON |
+ USER_READ_ACCOUNT);
+ break;
+
+ case UserGeneralInformation:
+ case UserPrimaryGroupInformation:
+ case UserNameInformation:
+ case UserAccountNameInformation:
+ case UserFullNameInformation:
+ case UserAdminCommentInformation:
+
+ DesiredAccess = USER_READ_GENERAL;
+ break;
+
+
+ case UserPreferencesInformation:
+
+ DesiredAccess = (USER_READ_PREFERENCES |
+ USER_READ_GENERAL);
+ break;
+
+
+ case UserLogonInformation:
+
+ DesiredAccess = (USER_READ_GENERAL |
+ USER_READ_PREFERENCES |
+ USER_READ_LOGON |
+ USER_READ_ACCOUNT);
+ break;
+
+ case UserLogonHoursInformation:
+ case UserHomeInformation:
+ case UserScriptInformation:
+ case UserProfileInformation:
+ case UserWorkStationsInformation:
+
+ DesiredAccess = USER_READ_LOGON;
+ break;
+
+
+ case UserControlInformation:
+ case UserExpiresInformation:
+ case UserParametersInformation:
+
+ DesiredAccess = USER_READ_ACCOUNT;
+ break;
+
+
+ case UserInternal1Information:
+ case UserInternal2Information:
+
+ //
+ // These levels are only queryable by trusted clients. The code
+ // below will check AccountContext->TrustedClient after calling
+ // SampLookupContext, and only return the data if it is TRUE.
+ //
+
+ DesiredAccess = (ACCESS_MASK)0; // Trusted client; no need to verify
+ break;
+
+
+ case UserSetPasswordInformation: // Can't query password
+ default:
+
+ return(STATUS_INVALID_INFO_CLASS);
+
+ } // end_switch
+
+
+
+
+
+ //
+ // Allocate the info structure
+ //
+
+ AllocateBuffer(*Buffer, sizeof(SAMPR_USER_INFO_BUFFER) );
+ if ((*Buffer) == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the information level requires, retrieve the V1_FIXED record
+ // from the registry.
+ //
+
+ switch (UserInformationClass) {
+
+ case UserInternal3Information:
+ //
+ // Only trusted clients may query for this class.
+ //
+
+ if ( !AccountContext->TrustedClient ) {
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ //
+ // Drop through to the UserAll case
+ //
+
+ case UserAllInformation: {
+
+ //
+ // We weren't able to check the security stuff above, so do
+ // it now.
+ //
+
+ if ( AccountContext->TrustedClient ) {
+
+ //
+ // Give everything to trusted clients, except fields that
+ // can't be queried at all.
+ //
+
+ 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;
+
+ } else {
+
+
+ //
+ // Only return fields that the caller has access to.
+ //
+
+ WhichFields = 0;
+
+ if ( RtlAreAllAccessesGranted(
+ AccountContext->GrantedAccess,
+ USER_READ_GENERAL ) ) {
+
+ WhichFields |= USER_ALL_READ_GENERAL_MASK;
+ }
+
+ if ( RtlAreAllAccessesGranted(
+ AccountContext->GrantedAccess,
+ USER_READ_LOGON ) ) {
+
+ WhichFields |= USER_ALL_READ_LOGON_MASK;
+ }
+
+ if ( RtlAreAllAccessesGranted(
+ AccountContext->GrantedAccess,
+ USER_READ_ACCOUNT ) ) {
+
+ WhichFields |= USER_ALL_READ_ACCOUNT_MASK;
+ }
+
+ if ( RtlAreAllAccessesGranted(
+ AccountContext->GrantedAccess,
+ USER_READ_PREFERENCES ) ) {
+
+ WhichFields |= USER_ALL_READ_PREFERENCES_MASK;
+ }
+
+ if ( WhichFields == 0 ) {
+
+ //
+ // Caller doesn't have access to ANY fields.
+ //
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ break;
+ }
+ }
+ }
+
+ //
+ // fall through to pick up the V1aFixed information
+ //
+
+ case UserGeneralInformation:
+ case UserPrimaryGroupInformation:
+ case UserPreferencesInformation:
+ case UserLogonInformation:
+ case UserAccountInformation:
+ case UserControlInformation:
+ case UserExpiresInformation:
+ case UserInternal2Information:
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ break;
+
+ default:
+
+ NtStatus = STATUS_SUCCESS;
+
+ } // end_switch
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // case on the type information requested
+ //
+
+ switch (UserInformationClass) {
+
+ case UserInternal3Information:
+ case UserAllInformation:
+
+ //
+ // All and Internal3 are the same except Internal3 has
+ // an extra field.
+
+ All = (PUSER_ALL_INFORMATION)(*Buffer);
+
+ RtlZeroMemory( (PVOID)All, sizeof(SAMPR_USER_INFO_BUFFER) );
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ if ( WhichFields & ( USER_ALL_PASSWORDMUSTCHANGE |
+ USER_ALL_NTPASSWORDPRESENT ) ) {
+
+ //
+ // These fields will need some info from
+ // SampRetrieveUserPasswords().
+ //
+
+ NtStatus = SampRetrieveUserPasswords(
+ AccountContext,
+ &LmOwfPassword,
+ &LmPasswordNonNull,
+ &NtOwfPassword,
+ &NtPasswordPresent,
+ &NtPasswordNonNull
+ );
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_USERNAME ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->All.UserName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->UserName.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_FULLNAME ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->FullName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->FullName.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_USERID ) ) {
+
+ All->UserId = V1aFixed.UserId;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PRIMARYGROUPID ) ) {
+
+ All->PrimaryGroupId = V1aFixed.PrimaryGroupId;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_ADMINCOMMENT ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->AdminComment)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->AdminComment.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_USERCOMMENT ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_USER_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->UserComment) // Body
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->UserComment.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_HOMEDIRECTORY ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->HomeDirectory)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->HomeDirectory.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_HOMEDIRECTORYDRIVE ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->HomeDirectoryDrive)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->HomeDirectoryDrive.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_SCRIPTPATH ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->ScriptPath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->ScriptPath.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PROFILEPATH ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->ProfilePath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->ProfilePath.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_WORKSTATIONS ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->WorkStations)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->WorkStations.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_LASTLOGON ) ) {
+
+ All->LastLogon = V1aFixed.LastLogon;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_LASTLOGOFF ) ) {
+
+ All->LastLogoff = V1aFixed.LastLogoff;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_LOGONHOURS ) ) {
+
+ NtStatus = SampRetrieveUserLogonHours(
+ AccountContext,
+ (PLOGON_HOURS)&(All->LogonHours)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (All->LogonHours.LogonHours != NULL) {
+
+ RegisterBuffer(All->LogonHours.LogonHours);
+ }
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_BADPASSWORDCOUNT ) ) {
+
+ All->BadPasswordCount = SampQueryBadPasswordCount( AccountContext, &V1aFixed );
+
+ if (UserInformationClass == UserInternal3Information) {
+ (*Buffer)->Internal3.LastBadPasswordTime = V1aFixed.LastBadPasswordTime;
+ }
+
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_LOGONCOUNT ) ) {
+
+ All->LogonCount = V1aFixed.LogonCount;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PASSWORDCANCHANGE ) ) {
+
+ if ( !NtPasswordNonNull && !LmPasswordNonNull ) {
+
+ //
+ // Null passwords can be changed immediately.
+ //
+
+ All->PasswordCanChange = SampHasNeverTime;
+
+ } else {
+
+ All->PasswordCanChange = SampAddDeltaTime(
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MinPasswordAge);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields &
+ (USER_ALL_PASSWORDMUSTCHANGE|USER_ALL_PASSWORDEXPIRED) ) ) {
+
+ All->PasswordMustChange = SampGetPasswordMustChange(
+ V1aFixed.UserAccountControl,
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MaxPasswordAge);
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PASSWORDEXPIRED ) ) {
+
+ LARGE_INTEGER TimeNow;
+
+ NtStatus = NtQuerySystemTime( &TimeNow );
+ if (NT_SUCCESS(NtStatus)) {
+ if ( TimeNow.QuadPart >= All->PasswordMustChange.QuadPart) {
+
+ All->PasswordExpired = TRUE;
+
+ } else {
+
+ All->PasswordExpired = FALSE;
+ }
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PASSWORDLASTSET ) ) {
+
+ All->PasswordLastSet = V1aFixed.PasswordLastSet;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_ACCOUNTEXPIRES ) ) {
+
+ All->AccountExpires = V1aFixed.AccountExpires;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_USERACCOUNTCONTROL ) ) {
+
+ All->UserAccountControl = V1aFixed.UserAccountControl;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PARAMETERS ) ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PARAMETERS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&(All->Parameters)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->Parameters.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_COUNTRYCODE ) ) {
+
+ All->CountryCode = V1aFixed.CountryCode;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_CODEPAGE ) ) {
+
+ All->CodePage = V1aFixed.CodePage;
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_NTPASSWORDPRESENT ) ) {
+
+ ASSERT( WhichFields & USER_ALL_LMPASSWORDPRESENT);
+
+ All->LmPasswordPresent = LmPasswordNonNull;
+ All->NtPasswordPresent = NtPasswordNonNull;
+
+ RtlInitUnicodeString(&All->LmPassword, NULL);
+ RtlInitUnicodeString(&All->NtPassword, NULL);
+
+ if ( LmPasswordNonNull ) {
+
+ All->LmPassword.Buffer =
+ MIDL_user_allocate( LM_OWF_PASSWORD_LENGTH );
+
+ if ( All->LmPassword.Buffer == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ RegisterBuffer(All->LmPassword.Buffer);
+
+ All->LmPassword.Length = LM_OWF_PASSWORD_LENGTH;
+ All->LmPassword.MaximumLength =
+ LM_OWF_PASSWORD_LENGTH;
+ RtlCopyMemory(
+ All->LmPassword.Buffer,
+ &LmOwfPassword,
+ LM_OWF_PASSWORD_LENGTH
+ );
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( NtPasswordPresent ) {
+
+ All->NtPassword.Buffer =
+ MIDL_user_allocate( NT_OWF_PASSWORD_LENGTH );
+
+ if ( All->NtPassword.Buffer == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ RegisterBuffer(All->NtPassword.Buffer);
+
+ All->NtPassword.Length = NT_OWF_PASSWORD_LENGTH;
+ All->NtPassword.MaximumLength =
+ NT_OWF_PASSWORD_LENGTH;
+ RtlCopyMemory(
+ All->NtPassword.Buffer,
+ &NtOwfPassword,
+ NT_OWF_PASSWORD_LENGTH
+ );
+ }
+ }
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_PRIVATEDATA ) ) {
+
+ All->PrivateDataSensitive = TRUE;
+
+ NtStatus = SampGetPrivateUserData(
+ AccountContext,
+ (PULONG)
+ (&(All->PrivateData.Length)),
+ (PVOID *)
+ (&(All->PrivateData.Buffer))
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ All->PrivateData.MaximumLength =
+ All->PrivateData.Length;
+
+ RegisterBuffer(All->PrivateData.Buffer);
+ }
+ }
+
+ if ( (NT_SUCCESS( NtStatus )) &&
+ ( WhichFields & USER_ALL_SECURITYDESCRIPTOR ) ) {
+
+ NtStatus = SampGetObjectSD(
+ AccountContext,
+ &(All->SecurityDescriptor.Length),
+ (PSECURITY_DESCRIPTOR *)
+ &(All->SecurityDescriptor.SecurityDescriptor)
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer(All->SecurityDescriptor.SecurityDescriptor);
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ All->WhichFields = WhichFields;
+ }
+
+ break;
+
+ case UserAccountInformation:
+
+ NoErrorsYet = TRUE;
+
+
+ (*Buffer)->Account.UserId = V1aFixed.UserId;
+ (*Buffer)->Account.PrimaryGroupId = V1aFixed.PrimaryGroupId;
+
+ (*Buffer)->Account.LastLogon =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
+
+ (*Buffer)->Account.LastLogoff =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
+
+
+ (*Buffer)->Account.BadPasswordCount = SampQueryBadPasswordCount( AccountContext, &V1aFixed );
+ (*Buffer)->Account.LogonCount = V1aFixed.LogonCount;
+
+ (*Buffer)->Account.PasswordLastSet =
+ *((POLD_LARGE_INTEGER)&V1aFixed.PasswordLastSet);
+
+ (*Buffer)->Account.AccountExpires =
+ *((POLD_LARGE_INTEGER)&V1aFixed.AccountExpires);
+
+ (*Buffer)->Account.UserAccountControl = V1aFixed.UserAccountControl;
+
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.UserName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.UserName.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.FullName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.FullName.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.HomeDirectory)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.HomeDirectory.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.HomeDirectoryDrive)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.HomeDirectoryDrive.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.ScriptPath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.ScriptPath.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.ProfilePath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.ProfilePath.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.AdminComment) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.AdminComment.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Account.WorkStations)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Account.WorkStations.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+
+ //
+ // Now get the logon hours
+ //
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampRetrieveUserLogonHours(
+ AccountContext,
+ (PLOGON_HOURS)&((*Buffer)->Account.LogonHours)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ((*Buffer)->Account.LogonHours.LogonHours != NULL) {
+
+ RegisterBuffer((*Buffer)->Account.LogonHours.LogonHours);
+ }
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+ break;
+
+
+ case UserGeneralInformation:
+
+
+ (*Buffer)->General.PrimaryGroupId = V1aFixed.PrimaryGroupId;
+
+
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.UserName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.UserName.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.FullName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.FullName.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.AdminComment) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.AdminComment.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_USER_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->General.UserComment) // Body
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->General.UserComment.Buffer);
+ }
+ }
+ }
+ }
+
+
+ break;
+
+
+ case UserNameInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Name.UserName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Name.UserName.Buffer);
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Name.FullName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Name.FullName.Buffer);
+ }
+ }
+
+
+ break;
+
+
+ case UserAccountNameInformation:
+
+ //
+ // Get copy of the string we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->AccountName.UserName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->AccountName.UserName.Buffer);
+ }
+
+
+ break;
+
+
+ case UserFullNameInformation:
+
+ //
+ // Get copy of the string we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->FullName.FullName) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->FullName.FullName.Buffer);
+ }
+
+
+ break;
+
+
+ case UserAdminCommentInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->AdminComment.AdminComment) // Body
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->AdminComment.AdminComment.Buffer);
+ }
+
+
+ break;
+
+
+ case UserPrimaryGroupInformation:
+
+
+ (*Buffer)->PrimaryGroup.PrimaryGroupId = V1aFixed.PrimaryGroupId;
+
+ break;
+
+
+ case UserPreferencesInformation:
+
+
+ (*Buffer)->Preferences.CountryCode = V1aFixed.CountryCode;
+ (*Buffer)->Preferences.CodePage = V1aFixed.CodePage;
+
+
+
+ //
+ // Read the UserComment field from the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_USER_COMMENT,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Preferences.UserComment) // Body
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Preferences.UserComment.Buffer);
+
+ //
+ // This field isn't used, but make sure RPC doesn't
+ // choke on it.
+ //
+
+ (*Buffer)->Preferences.Reserved1.Length = 0;
+ (*Buffer)->Preferences.Reserved1.MaximumLength = 0;
+ (*Buffer)->Preferences.Reserved1.Buffer = NULL;
+ }
+
+
+ break;
+
+
+ case UserParametersInformation:
+
+
+ //
+ // Read the Parameters field from the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PARAMETERS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Parameters.Parameters)
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Parameters.Parameters.Buffer);
+ }
+
+
+ break;
+
+
+ case UserLogonInformation:
+
+ NoErrorsYet = TRUE;
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ (*Buffer)->Logon.UserId = V1aFixed.UserId;
+ (*Buffer)->Logon.PrimaryGroupId = V1aFixed.PrimaryGroupId;
+
+ (*Buffer)->Logon.LastLogon =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
+
+ (*Buffer)->Logon.LastLogoff =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
+
+ (*Buffer)->Logon.BadPasswordCount = V1aFixed.BadPasswordCount;
+
+ (*Buffer)->Logon.PasswordLastSet =
+ *((POLD_LARGE_INTEGER)&V1aFixed.PasswordLastSet);
+
+ TempTime = SampAddDeltaTime(
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MinPasswordAge );
+
+ (*Buffer)->Logon.PasswordCanChange =
+ *((POLD_LARGE_INTEGER)&TempTime);
+
+
+ TempTime = SampGetPasswordMustChange(
+ V1aFixed.UserAccountControl,
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MaxPasswordAge);
+
+ (*Buffer)->Logon.PasswordMustChange =
+ *((POLD_LARGE_INTEGER)&TempTime);
+
+
+ (*Buffer)->Logon.LogonCount = V1aFixed.LogonCount;
+ (*Buffer)->Logon.UserAccountControl = V1aFixed.UserAccountControl;
+
+
+ //
+ // If there is no password on the account then
+ // modify the password can/must change times
+ // so that the password never expires and can
+ // be changed immediately.
+ //
+
+ NtStatus = SampRetrieveUserPasswords(
+ AccountContext,
+ &LmOwfPassword,
+ &LmPasswordNonNull,
+ &NtOwfPassword,
+ &NtPasswordPresent,
+ &NtPasswordNonNull
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( !NtPasswordNonNull && !LmPasswordNonNull ) {
+
+ //
+ // The password is NULL.
+ // It can be changed immediately.
+ //
+
+ (*Buffer)->Logon.PasswordCanChange =
+ *((POLD_LARGE_INTEGER)&SampHasNeverTime);
+
+ }
+ } else {
+ NoErrorsYet = FALSE;
+ }
+
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.UserName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.UserName.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.FullName)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.FullName.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.HomeDirectory)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.HomeDirectory.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.HomeDirectoryDrive)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.HomeDirectoryDrive.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.ScriptPath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.ScriptPath.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.ProfilePath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.ProfilePath.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Logon.WorkStations)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Logon.WorkStations.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+
+
+ //
+ // Now get the logon hours
+ //
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampRetrieveUserLogonHours(
+ AccountContext,
+ (PLOGON_HOURS)&((*Buffer)->Logon.LogonHours)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ((*Buffer)->Logon.LogonHours.LogonHours != NULL) {
+
+ RegisterBuffer((*Buffer)->Logon.LogonHours.LogonHours);
+ }
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+ break;
+
+
+ case UserLogonHoursInformation:
+
+ NtStatus = SampRetrieveUserLogonHours(
+ AccountContext,
+ (PLOGON_HOURS)&((*Buffer)->LogonHours.LogonHours)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ((*Buffer)->LogonHours.LogonHours.LogonHours != NULL) {
+
+ RegisterBuffer((*Buffer)->LogonHours.LogonHours.LogonHours);
+ }
+ }
+
+ break;
+
+
+ case UserHomeInformation:
+
+ NoErrorsYet = TRUE;
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ if (NoErrorsYet == TRUE) {
+
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Home.HomeDirectory)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Home.HomeDirectory.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+
+ if (NoErrorsYet == TRUE) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Home.HomeDirectoryDrive)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Home.HomeDirectoryDrive.Buffer);
+
+ } else {
+ NoErrorsYet = FALSE;
+ }
+ }
+
+ break;
+
+
+ case UserScriptInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Script.ScriptPath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Script.ScriptPath.Buffer);
+ }
+
+ break;
+
+
+ case UserProfileInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->Profile.ProfilePath)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->Profile.ProfilePath.Buffer);
+ }
+
+ break;
+
+
+ case UserWorkStationsInformation:
+
+ //
+ // Get copies of the strings we must retrieve from
+ // the registry.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ TRUE, // Make copy
+ (PUNICODE_STRING)&((*Buffer)->WorkStations.WorkStations)
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ RegisterBuffer((*Buffer)->WorkStations.WorkStations.Buffer);
+ }
+
+ break;
+
+
+ case UserControlInformation:
+
+ (*Buffer)->Control.UserAccountControl = V1aFixed.UserAccountControl;
+ break;
+
+
+ case UserExpiresInformation:
+
+ (*Buffer)->Expires.AccountExpires = V1aFixed.AccountExpires;
+
+ break;
+
+
+ case UserInternal1Information:
+
+ if ( AccountContext->TrustedClient ) {
+
+ //
+ // PasswordExpired is a 'write only' flag.
+ // We always return FALSE on read.
+ //
+
+ (*Buffer)->Internal1.PasswordExpired = FALSE;
+
+ //
+ // Retrieve the OWF passwords.
+ // Since this is a trusted client, we don't need to
+ // reencrypt the OWFpasswords we return - so we stuff
+ // the OWFs into the structure that holds encryptedOWFs.
+ //
+
+ ASSERT( ENCRYPTED_LM_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH );
+ ASSERT( ENCRYPTED_NT_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH );
+
+ NtStatus = SampRetrieveUserPasswords(
+ AccountContext,
+ (PLM_OWF_PASSWORD)&(*Buffer)->Internal1.
+ EncryptedLmOwfPassword,
+ &(*Buffer)->Internal1.
+ LmPasswordPresent,
+ (PNT_OWF_PASSWORD)&(*Buffer)->Internal1.
+ EncryptedNtOwfPassword,
+ &NtPasswordPresent,
+ &(*Buffer)->Internal1.NtPasswordPresent // Return the Non-NULL flag here
+ );
+
+ } else {
+
+ //
+ // This information is only queryable by trusted
+ // clients.
+ //
+
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+
+
+ case UserInternal2Information:
+
+ if ( AccountContext->TrustedClient ) {
+
+ (*Buffer)->Internal2.LastLogon =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
+
+ (*Buffer)->Internal2.LastLogoff =
+ *((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
+
+ (*Buffer)->Internal2.BadPasswordCount = V1aFixed.BadPasswordCount;
+ (*Buffer)->Internal2.LogonCount = V1aFixed.LogonCount;
+
+ } else {
+
+ //
+ // This information is only queryable by trusted
+ // clients.
+ //
+
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+
+ }
+
+ }
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+ //
+ // 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
+SampIsUserAccountControlValid(
+ IN PSAMP_OBJECT Context,
+ IN ULONG UserAccountControl
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks a UserAccountControl field to make sure that
+ the bits set make sense.
+
+ NOTE: if the set operation is also setting passwords, it must set the
+ passwords BEFORE calling this routine!
+
+
+Parameters:
+
+ Context - the context of the account being changed.
+
+ UserAccountControl - the field that is about to be set.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The UserAccountControl field is valid.
+
+ STATUS_SPECIAL_ACCOUNT - The administrator account can't be disabled.
+
+ STATUS_INVALID_PARAMETER - an undefined bit is set, or more than one
+ account type bit is set.
+
+ STATUS_INVALID_PARAMETER_MIX - USER_PASSWORD_NOT_REQUIRED has been
+ turned off, but there isn't a bonafide password on the account.
+
+--*/
+
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+
+#if DBG
+ //
+ // Make sure that undefined bits aren't set.
+ //
+
+ if ( ( UserAccountControl & 0xfffff800 ) != 0 ) {
+
+ DbgPrint("SAM: Setting undefined AccountControl flag(s): 0x%lx for user %d\n",
+ UserAccountControl, Context->TypeBody.User.Rid);
+ }
+#endif //DBG
+
+ //
+ // Make sure that the administrator isn't being disabled.
+ //
+
+ if ( UserAccountControl & USER_ACCOUNT_DISABLED ) {
+
+ if ( Context->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN ) {
+
+ return( STATUS_SPECIAL_ACCOUNT );
+ }
+ }
+
+ //
+ // Make sure that exactly one of the account type bits is set.
+ //
+
+ switch ( UserAccountControl & USER_ACCOUNT_TYPE_MASK ) {
+
+ case USER_TEMP_DUPLICATE_ACCOUNT:
+ case USER_NORMAL_ACCOUNT:
+ case USER_SERVER_TRUST_ACCOUNT:
+ case USER_WORKSTATION_TRUST_ACCOUNT:
+ case USER_INTERDOMAIN_TRUST_ACCOUNT:
+
+ break;
+
+ default:
+
+ return( STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // If USER_PASSWORD_NOT_REQUIRED is turned off, make sure that there
+ // already is a password. Note that this requires that the password
+ // be set before calling this routine, if both are being done at once.
+ //
+
+ if ( ( UserAccountControl & USER_PASSWORD_NOT_REQUIRED ) == 0 ) {
+
+ NT_OWF_PASSWORD NtOwfPassword;
+ LM_OWF_PASSWORD LmOwfPassword;
+ BOOLEAN LmPasswordNonNull, NtPasswordPresent, NtPasswordNonNull;
+
+ NtStatus = SampRetrieveUserPasswords(
+ Context,
+ &LmOwfPassword,
+ &LmPasswordNonNull,
+ &NtOwfPassword,
+ &NtPasswordPresent,
+ &NtPasswordNonNull
+ );
+
+ if ( NT_SUCCESS( NtStatus ) &&
+ ( (!LmPasswordNonNull) && (!NtPasswordNonNull) ) ) {
+
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampCalculateLmPassword(
+ IN PUNICODE_STRING NtPassword,
+ OUT PCHAR *LmPasswordBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This service converts an NT password into a LM password.
+
+Parameters:
+
+ NtPassword - The Nt password to be converted.
+
+ LmPasswordBuffer - On successful return, points at the LM password
+ The buffer should be freed using MIDL_user_free
+
+Return Values:
+
+ STATUS_SUCCESS - LMPassword contains the LM version of the password.
+
+ STATUS_NULL_LM_PASSWORD - The password is too complex to be represented
+ by a LM password. The LM password returned is a NULL string.
+
+
+--*/
+{
+
+#define LM_BUFFER_LENGTH (LM20_PWLEN + 1)
+
+ NTSTATUS NtStatus;
+ ANSI_STRING LmPassword;
+
+ //
+ // Prepare for failure
+ //
+
+ *LmPasswordBuffer = NULL;
+
+
+ //
+ // Compute the Ansi version to the Unicode password.
+ //
+ // The Ansi version of the Cleartext password is at most 14 bytes long,
+ // exists in a trailing zero filled 15 byte buffer,
+ // is uppercased.
+ //
+
+ LmPassword.Buffer = MIDL_user_allocate(LM_BUFFER_LENGTH);
+ if (LmPassword.Buffer == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ LmPassword.MaximumLength = LmPassword.Length = LM_BUFFER_LENGTH;
+ RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
+
+ NtStatus = RtlUpcaseUnicodeStringToOemString( &LmPassword, NtPassword, FALSE );
+
+
+ if ( !NT_SUCCESS(NtStatus) ) {
+
+ //
+ // The password is longer than the max LM password length
+ //
+
+ NtStatus = STATUS_NULL_LM_PASSWORD; // Informational return code
+ RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
+
+ }
+
+
+
+
+ //
+ // Return a pointer to the allocated LM password
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ *LmPasswordBuffer = LmPassword.Buffer;
+
+ } else {
+
+ MIDL_user_free(LmPassword.Buffer);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampCalculateLmAndNtOwfPasswords(
+ IN PUNICODE_STRING ClearNtPassword,
+ OUT PBOOLEAN LmPasswordPresent,
+ OUT PLM_OWF_PASSWORD LmOwfPassword,
+ OUT PNT_OWF_PASSWORD NtOwfPassword
+ )
+/*++
+
+Routine Description:
+
+ This routine calculates the LM and NT OWF passwordw from the cleartext
+ password.
+
+Arguments:
+
+ ClearNtPassword - A Cleartext unicode password
+
+ LmPasswordPresent - indicates whether an LM OWF password could be
+ calculated
+
+ LmOwfPassword - Gets the LM OWF hash of the cleartext password.
+
+ NtOwfPassword - Gets the NT OWF hash of the cleartext password.
+
+
+Return Value:
+
+--*/
+{
+ PCHAR LmPassword = NULL;
+ NTSTATUS NtStatus;
+
+ //
+ // First compute the LM password. If the password is too complex
+ // this may not be possible.
+ //
+
+
+ NtStatus = SampCalculateLmPassword(
+ ClearNtPassword,
+ &LmPassword
+ );
+
+ //
+ // If it faield because the LM password could not be calculated, that
+ // is o.k.
+ //
+
+ if (NtStatus != STATUS_SUCCESS) {
+
+ if (NtStatus == STATUS_NULL_LM_PASSWORD) {
+ *LmPasswordPresent = FALSE;
+ NtStatus = STATUS_SUCCESS;
+
+ }
+
+ } else {
+
+ //
+ // Now compute the OWF passwords
+ //
+
+ *LmPasswordPresent = TRUE;
+
+ NtStatus = RtlCalculateLmOwfPassword(
+ LmPassword,
+ LmOwfPassword
+ );
+
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlCalculateNtOwfPassword(
+ ClearNtPassword,
+ NtOwfPassword
+ );
+ }
+
+ if (LmPassword != NULL) {
+ MIDL_user_free(LmPassword);
+ }
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampDecryptPasswordWithKey(
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
+ IN PBYTE Key,
+ IN ULONG KeySize,
+ IN BOOLEAN UnicodePasswords,
+ OUT PUNICODE_STRING ClearNtPassword
+ )
+/*++
+
+Routine Description:
+
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ struct RC4_KEYSTRUCT Rc4Key;
+ NTSTATUS NtStatus;
+ OEM_STRING OemPassword;
+ PSAMPR_USER_PASSWORD Password = (PSAMPR_USER_PASSWORD) EncryptedPassword;
+
+ //
+ // Decrypt the key.
+ //
+
+ rc4_key(
+ &Rc4Key,
+ KeySize,
+ Key
+ );
+
+ rc4(&Rc4Key,
+ sizeof(SAMPR_ENCRYPTED_USER_PASSWORD),
+ (PUCHAR) Password
+ );
+
+ //
+ // Check that the length is valid. If it isn't bail here.
+ //
+
+ if (Password->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) {
+ return(STATUS_WRONG_PASSWORD);
+ }
+
+
+ //
+ // Convert the password into a unicode string.
+ //
+
+ if (UnicodePasswords) {
+ NtStatus = SampInitUnicodeString(
+ ClearNtPassword,
+ (USHORT) (Password->Length + sizeof(WCHAR))
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ ClearNtPassword->Length = (USHORT) Password->Length;
+
+ RtlCopyMemory(
+ ClearNtPassword->Buffer,
+ ((PCHAR) Password->Buffer) +
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ Password->Length,
+ Password->Length
+ );
+ NtStatus = STATUS_SUCCESS;
+ }
+ } else {
+
+ //
+ // The password is in the OEM character set. Convert it to Unicode
+ // and then copy it into the ClearNtPassword structure.
+ //
+
+ OemPassword.Buffer = ((PCHAR)Password->Buffer) +
+ (SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
+ Password->Length;
+
+ OemPassword.Length = (USHORT) Password->Length;
+
+
+ NtStatus = RtlOemStringToUnicodeString(
+ ClearNtPassword,
+ &OemPassword,
+ TRUE // allocate destination
+ );
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampDecryptPasswordWithSessionKey(
+ IN SAMPR_HANDLE UserHandle,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
+ OUT PUNICODE_STRING ClearNtPassword
+ )
+/*++
+
+Routine Description:
+
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USER_SESSION_KEY UserSessionKey;
+
+ NtStatus = RtlGetUserSessionKeyServer(
+ (RPC_BINDING_HANDLE)UserHandle,
+ &UserSessionKey
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ return(SampDecryptPasswordWithKey(
+ EncryptedPassword,
+ (PUCHAR) &UserSessionKey,
+ sizeof(USER_SESSION_KEY),
+ TRUE,
+ ClearNtPassword
+ ) );
+}
+
+
+
+NTSTATUS
+SampCheckPasswordRestrictions(
+ IN SAMPR_HANDLE UserHandle,
+ PUNICODE_STRING NewNtPassword
+ )
+
+/*++
+
+Routine Description:
+
+ This service is called to make sure that the password presented meets
+ our quality requirements.
+
+
+Arguments:
+
+ UserHandle - Handle to a user.
+
+ NewNtPassword - Pointer to the UNICODE_STRING containing the new
+ password.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The password is acceptable.
+
+ STATUS_PASSWORD_RESTRICTION - The password is too short, or is not
+ complex enough, etc.
+
+ STATUS_INVALID_RESOURCES - There was not enough memory to do the
+ password checking.
+
+
+--*/
+{
+ USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation;
+ NTSTATUS NtStatus;
+ PWORD CharInfoBuffer = NULL;
+ ULONG i;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ PSAMP_OBJECT AccountContext = (PSAMP_OBJECT) UserHandle;
+
+
+
+ //
+ // Query information domain to get password length and
+ // complexity requirements.
+ //
+
+ //
+ // BUGBUG: this code was copied from SamrGetUserDomainPasswordInformation
+ //
+
+ //
+ // When the user was opened, we checked to see if the domain handle
+ // allowed access to the domain password information. Check that here.
+ //
+
+ if ( !( AccountContext->TypeBody.User.DomainPasswordInformationAccessible ) ) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+
+ } else {
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ //
+ // If the user account is a machine account,
+ // then restrictions are generally not enforced.
+ // This is so that simple initial passwords can be
+ // established. IT IS EXPECTED THAT COMPLEX PASSWORDS,
+ // WHICH MEET THE MOST STRINGENT RESTRICTIONS, WILL BE
+ // AUTOMATICALLY ESTABLISHED AND MAINTAINED ONCE THE MACHINE
+ // JOINS THE DOMAIN. It is the UI's responsibility to
+ // maintain this level of complexity.
+ //
+
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (V1aFixed.UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
+ != 0 ) {
+
+ PasswordInformation.MinPasswordLength = 0;
+ PasswordInformation.PasswordProperties = 0;
+ } else {
+
+ PasswordInformation.MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
+ PasswordInformation.PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
+ }
+ }
+ }
+
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( (USHORT)( NewNtPassword->Length / sizeof(WCHAR) ) < PasswordInformation.MinPasswordLength ) {
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+
+ } else {
+
+ //
+ // Check password complexity.
+ //
+
+ if ( PasswordInformation.PasswordProperties & DOMAIN_PASSWORD_COMPLEX ) {
+
+ //
+ // Make sure that the password meets our requirements for
+ // complexity. If it's got an odd byte count, it's
+ // obviously not a hand-entered UNICODE string so we'll
+ // consider it complex by default.
+ //
+
+ if ( !( NewNtPassword->Length & 1 ) ) {
+
+ USHORT NumsInPassword = 0;
+ USHORT UppersInPassword = 0;
+ USHORT LowersInPassword = 0;
+ USHORT OthersInPassword = 0;
+
+ CharInfoBuffer = MIDL_user_allocate( NewNtPassword->Length );
+
+ if ( CharInfoBuffer == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ if ( GetStringTypeW(
+ CT_CTYPE1,
+ NewNtPassword->Buffer,
+ NewNtPassword->Length / 2,
+ CharInfoBuffer ) ) {
+
+ for ( i = 0; i < (ULONG)( NewNtPassword->Length / sizeof(WCHAR) ); i++ ) {
+
+ if ( CharInfoBuffer[i] & C1_DIGIT ) {
+
+ NumsInPassword = 1;
+ }
+
+ if ( CharInfoBuffer[i] & C1_UPPER ) {
+
+ UppersInPassword = 1;
+ }
+
+ if ( CharInfoBuffer[i] & C1_LOWER ) {
+
+ LowersInPassword = 1;
+ }
+
+ if ( !( CharInfoBuffer[i] & ( C1_ALPHA | C1_DIGIT ) ) ) {
+
+ //
+ // Having any "other" characters is
+ // sufficient to make the password
+ // complex.
+ //
+
+ OthersInPassword = 2;
+ }
+ }
+
+ if ( ( NumsInPassword + UppersInPassword +
+ LowersInPassword + OthersInPassword ) < 2 ) {
+
+ //
+ // It didn't have at least two of the four
+ // types of characters, so it's not complex
+ // enough.
+ //
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+ }
+
+ } else {
+
+ //
+ // GetStringTypeW failed; dunno why. Perhaps the
+ // password is binary. Consider it complex by
+ // default.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ MIDL_user_free( CharInfoBuffer );
+ }
+ }
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamrSetInformationUser2(
+ IN SAMPR_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ IN PSAMPR_USER_INFO_BUFFER Buffer
+ )
+{
+ //
+ // This is a thin veil to SamrSetInformationUser().
+ // 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( SamrSetInformationUser(
+ UserHandle,
+ UserInformationClass,
+ Buffer
+ ) );
+}
+
+NTSTATUS
+SamrSetInformationUser(
+ IN SAMPR_HANDLE UserHandle,
+ IN USER_INFORMATION_CLASS UserInformationClass,
+ IN PSAMPR_USER_INFO_BUFFER Buffer
+ )
+
+
+/*++
+
+Routine Description:
+
+
+ This API modifies information in a user record. The data modified
+ is determined by the UserInformationClass parameter.
+ In general, a user may call GetInformation with class
+ UserLogonInformation, but may only call SetInformation with class
+ UserPreferencesInformation. Access type USER_WRITE_ACCOUNT allows
+ changes to be made to all fields.
+
+ NOTE: If the password is set to a new password then the password-
+ set timestamp is reset as well.
+
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ UserInformationClass - Class of information provided. The
+ accesses required for each class is shown below:
+
+ Info Level Required Access Type
+ ----------------------- ------------------------
+ UserGeneralInformation USER_WRITE_ACCOUNT and
+ USER_WRITE_PREFERENCES
+
+ UserPreferencesInformation USER_WRITE_PREFERENCES
+
+ UserParametersInformation USER_WRITE_ACCOUNT
+
+ UserLogonInformation (Can't set)
+
+ UserLogonHoursInformation USER_WRITE_ACCOUNT
+
+ UserAccountInformation (Can't set)
+
+ UserNameInformation USER_WRITE_ACCOUNT
+ UserAccountNameInformation USER_WRITE_ACCOUNT
+ UserFullNameInformation USER_WRITE_ACCOUNT
+ UserPrimaryGroupInformation USER_WRITE_ACCOUNT
+ UserHomeInformation USER_WRITE_ACCOUNT
+ UserScriptInformation USER_WRITE_ACCOUNT
+ UserProfileInformation USER_WRITE_ACCOUNT
+ UserAdminCommentInformation USER_WRITE_ACCOUNT
+ UserWorkStationsInformation USER_WRITE_ACCOUNT
+ UserSetPasswordInformation USER_FORCE_PASSWORD_CHANGE
+ UserControlInformation USER_WRITE_ACCOUNT
+ UserExpiresInformation USER_WRITE_ACCOUNT
+
+ UserInternal1Information USER_FORCE_PASSWORD_CHANGE
+ UserInternal2Information (Trusted client only)
+ UserInternal3Information (Trusted client only) -
+ UserInternal4Information Similar to All Information
+ UserInternal5Information Similar to SetPassword
+ UserAllInformation Will set fields that are
+ requested by caller. Access
+ to fields to be set must be
+ held as defined above.
+
+
+ Buffer - Buffer containing a user info struct.
+
+
+
+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_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 enabled for this
+ operation
+
+ 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 AccountContext = (PSAMP_OBJECT) UserHandle;
+
+ PUSER_ALL_INFORMATION All;
+
+ SAMP_OBJECT_TYPE FoundType;
+
+ PSAMP_DEFINED_DOMAINS Domain;
+
+ ACCESS_MASK DesiredAccess;
+
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+
+ UNICODE_STRING OldAccountName,
+ ApiList,
+ NewAdminComment,
+ NewAccountName,
+ NewFullName;
+
+ NT_OWF_PASSWORD NtOwfPassword;
+
+ LM_OWF_PASSWORD LmOwfPassword;
+
+ USER_SESSION_KEY UserSessionKey;
+
+ BOOLEAN LmPresent;
+ BOOLEAN NtPresent;
+ BOOLEAN PasswordExpired;
+
+ ULONG ObjectRid,
+ OldUserAccountControl,
+ DomainIndex;
+
+ BOOLEAN UserAccountControlChanged = FALSE,
+ MustUpdateAccountDisplay = FALSE,
+ MustQueryV1aFixed = FALSE,
+ ReplicateImmediately = FALSE,
+ TellNetlogon = TRUE,
+ AccountLockedOut,
+ CurrentlyLocked,
+ Unlocking;
+
+ SECURITY_DB_DELTA_TYPE DeltaType = SecurityDbChange;
+ UNICODE_STRING ClearTextPassword;
+ UNICODE_STRING AccountName;
+ ULONG UserRid = 0;
+
+#if DBG
+
+ TIME_FIELDS
+ T1;
+
+#endif //DBG
+
+ //
+ // Initialization.
+ //
+
+ ClearTextPassword.Buffer = NULL;
+ ClearTextPassword.Length = 0;
+ AccountName.Buffer = NULL;
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ if (Buffer == NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ //
+ // Reset any strings that we'll be freeing in clean-up code
+ //
+
+ RtlInitUnicodeString(&OldAccountName, NULL);
+ RtlInitUnicodeString(&NewAccountName, NULL);
+ RtlInitUnicodeString(&NewFullName, NULL);
+ RtlInitUnicodeString(&NewAdminComment, NULL);
+
+
+ //
+ // Set the desired access based upon the Info class
+ //
+
+ switch (UserInformationClass) {
+
+ case UserPreferencesInformation:
+
+ DesiredAccess = USER_WRITE_PREFERENCES;
+ break;
+
+ case UserParametersInformation:
+ case UserLogonHoursInformation:
+ case UserNameInformation:
+ case UserAccountNameInformation:
+ case UserFullNameInformation:
+ case UserPrimaryGroupInformation:
+ case UserHomeInformation:
+ case UserScriptInformation:
+ case UserProfileInformation:
+ case UserAdminCommentInformation:
+ case UserWorkStationsInformation:
+ case UserControlInformation:
+ case UserExpiresInformation:
+
+ DesiredAccess = USER_WRITE_ACCOUNT;
+ break;
+
+ case UserSetPasswordInformation:
+ case UserInternal1Information:
+ case UserInternal5Information:
+
+ DeltaType = SecurityDbChangePassword;
+ DesiredAccess = USER_FORCE_PASSWORD_CHANGE;
+ break;
+
+
+
+ case UserAllInformation:
+ case UserInternal3Information:
+ case UserInternal4Information:
+
+ //////////////////////////////////////////////////////////////
+ // //
+ // !!!! WARNING !!!! //
+ // //
+ // Be warned that the buffer structure for //
+ // UserInternal3/4Information MUST begin with the same //
+ // structure as UserAllInformation. //
+ // //
+ //////////////////////////////////////////////////////////////
+
+ DesiredAccess = 0;
+
+ All = (PUSER_ALL_INFORMATION)Buffer;
+
+ if ( ( All->WhichFields == 0 ) ||
+ ( All->WhichFields & USER_ALL_WRITE_CANT_MASK ) ) {
+
+ //
+ // Passed in something silly (no fields to set), or is
+ // trying to set fields that can't be set.
+ //
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // If the user is the special account Administrator, return an
+ // error if trying to set the expiry information, except to the value
+ // that means that the account never expires.
+ //
+
+ if ( (All->WhichFields & USER_ALL_ACCOUNTEXPIRES) &&
+ (!(AccountContext->TrustedClient)) &&
+ ( AccountContext->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN )) {
+
+ LARGE_INTEGER AccountNeverExpires, Temp;
+
+ AccountNeverExpires = RtlConvertUlongToLargeInteger(
+ SAMP_ACCOUNT_NEVER_EXPIRES
+ );
+
+ OLD_TO_NEW_LARGE_INTEGER(All->AccountExpires, Temp);
+
+ if (!( Temp.QuadPart == AccountNeverExpires.QuadPart)) {
+
+ return( STATUS_SPECIAL_ACCOUNT );
+ }
+ }
+
+ //
+ // If the caller is trying to set trusted values, assume the
+ // caller is trusted, leave DesiredAccess = 0, and proceed.
+ // We'll check to make sure caller is trusted later.
+ //
+
+ if ( !(All->WhichFields & USER_ALL_WRITE_TRUSTED_MASK) ) {
+
+ //
+ // Set desired access based on which fields the caller is
+ // trying to change.
+ //
+
+ if ( All->WhichFields & USER_ALL_WRITE_ACCOUNT_MASK ) {
+
+ DesiredAccess |= USER_WRITE_ACCOUNT;
+ }
+
+ if ( All->WhichFields & USER_ALL_WRITE_PREFERENCES_MASK ) {
+
+ DesiredAccess |= USER_WRITE_PREFERENCES;
+ }
+
+ if ( All->WhichFields & USER_ALL_WRITE_FORCE_PASSWORD_CHANGE_MASK ) {
+
+ DesiredAccess |= USER_FORCE_PASSWORD_CHANGE;
+ }
+
+ ASSERT( DesiredAccess != 0 );
+ }
+
+ break;
+
+ case UserInternal2Information:
+
+ //
+ // These levels are only setable by trusted clients. The code
+ // below will check AccountContext->TrustedClient after calling
+ // SampLookupContext, and only set the data if it is TRUE.
+ //
+
+ DesiredAccess = (ACCESS_MASK)0; // trusted client; no need to verify
+ break;
+
+ case UserGeneralInformation:
+ case UserAccountInformation:
+ case UserLogonInformation:
+ default:
+
+ return(STATUS_INVALID_INFO_CLASS);
+
+ } // end_switch
+
+
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+ ObjectRid = AccountContext->TypeBody.User.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ DesiredAccess,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get a pointer to the domain this object is in.
+ // This is used for auditing.
+ //
+
+ DomainIndex = AccountContext->DomainIndex;
+ Domain = &SampDefinedDomains[ DomainIndex ];
+
+ //
+ // Get the user's rid. This is used for notifying other
+ // packages of a password change.
+ //
+
+ UserRid = AccountContext->TypeBody.User.Rid;
+
+
+ //
+ // If this information level contains reversibly encrypted passwords
+ // it is not allowed if the DOMAIN_PASSWORD_NO_CLEAR_CHANGE bit is
+ // set. If that happens, return an error indicating that
+ // the older information level should be used.
+ //
+
+ if ((UserInformationClass == UserInternal4Information) ||
+ (UserInformationClass == UserInternal5Information)) {
+
+ if (Domain->UnmodifiedFixed.PasswordProperties &
+ DOMAIN_PASSWORD_NO_CLEAR_CHANGE) {
+
+ NtStatus = RPC_NT_INVALID_TAG;
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the information level requires, retrieve the V1_FIXED
+ // record from the registry. We need to fetch V1_FIXED if we
+ // are going to change it or if we need the AccountControl
+ // flags for display cache updating.
+ //
+ // The following information levels change data that is in the cached
+ // display list.
+ //
+
+ switch (UserInformationClass) {
+
+ case UserAllInformation:
+ case UserInternal3Information:
+ case UserInternal4Information:
+
+ if ( ( All->WhichFields &
+ ( USER_ALL_USERNAME | USER_ALL_FULLNAME |
+ USER_ALL_ADMINCOMMENT | USER_ALL_USERACCOUNTCONTROL ) )
+ == 0 ) {
+
+ //
+ // We're not changing any of the fields in the display
+ // info, we don't update the account display.
+ //
+
+ break;
+ }
+
+ case UserControlInformation:
+ case UserNameInformation:
+ case UserAccountNameInformation:
+ case UserFullNameInformation:
+ case UserAdminCommentInformation:
+
+ MustUpdateAccountDisplay = TRUE;
+ }
+
+ //
+ // These levels involve updating the V1aFixed structure
+ //
+
+ switch (UserInformationClass) {
+
+ case UserAllInformation:
+ case UserInternal3Information:
+ case UserInternal4Information:
+
+ //
+ // Earlier, we might have just trusted that the caller
+ // was a trusted client. Check it out here.
+ //
+
+ if ( ( DesiredAccess == 0 ) &&
+ ( !AccountContext->TrustedClient ) ) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+ break;
+ }
+
+ //
+ // Otherwise fall through
+ //
+
+ case UserPreferencesInformation:
+ case UserPrimaryGroupInformation:
+ case UserControlInformation:
+ case UserExpiresInformation:
+ case UserSetPasswordInformation:
+ case UserInternal1Information:
+ case UserInternal2Information:
+ case UserInternal5Information:
+
+ MustQueryV1aFixed = TRUE;
+
+ break;
+
+ default:
+
+ NtStatus = STATUS_SUCCESS;
+
+ } // end_switch
+
+
+ }
+
+ if ( NT_SUCCESS( NtStatus ) &&
+ ( MustQueryV1aFixed || MustUpdateAccountDisplay ) ) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Store away the old account control flags for cache update
+ //
+
+ OldUserAccountControl = V1aFixed.UserAccountControl;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // case on the type information requested
+ //
+
+ switch (UserInformationClass) {
+
+ case UserAllInformation:
+ case UserInternal3Information:
+ case UserInternal4Information:
+
+ //
+ // Set the string data
+ //
+
+ if ( All->WhichFields & USER_ALL_WORKSTATIONS ) {
+
+ if ( !AccountContext->TrustedClient ) {
+
+ //
+ // Convert the workstation list, which is given
+ // to us in UI/Service format, to API list format
+ // before storing it. Note that we don't do this
+ // for trusted clients, since they're just
+ // propogating data that has already been
+ // converted.
+ //
+
+ NtStatus = RtlConvertUiListToApiList(
+ &(All->WorkStations),
+ &ApiList,
+ FALSE );
+ } else {
+ ApiList = All->WorkStations;
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ &ApiList
+ );
+ }
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_USERNAME ) ) {
+
+ NtStatus = SampChangeUserAccountName(
+ AccountContext,
+ &(All->UserName),
+ &OldAccountName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ OldAccountName.Buffer = NULL;
+ }
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_FULLNAME ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ &(All->FullName)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_HOMEDIRECTORY ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ &(All->HomeDirectory)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_HOMEDIRECTORYDRIVE ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ &(All->HomeDirectoryDrive)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_SCRIPTPATH ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ &(All->ScriptPath)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_PROFILEPATH ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ &(All->ProfilePath)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_ADMINCOMMENT ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ &(All->AdminComment)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_USERCOMMENT ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_USER_COMMENT,
+ &(All->UserComment)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_PARAMETERS ) ) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PARAMETERS,
+ &(All->Parameters)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_LOGONHOURS ) ) {
+
+ //
+ // Set the LogonHours
+ //
+
+ NtStatus = SampReplaceUserLogonHours(
+ AccountContext,
+ &(All->LogonHours)
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) && (
+ ( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) ||
+ ( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) ) ) {
+
+ NT_OWF_PASSWORD NtOwfBuffer;
+ LM_OWF_PASSWORD LmOwfBuffer;
+ PLM_OWF_PASSWORD TmpLmBuffer;
+ PNT_OWF_PASSWORD TmpNtBuffer;
+ BOOLEAN TmpLmPresent;
+ BOOLEAN TmpNtPresent;
+
+
+ //
+ // Get copy of the account name to pass to
+ // notification packages.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &AccountName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+ if (UserInformationClass != UserInternal4Information) {
+
+ //
+ // Hashed passwords were sent.
+ //
+
+ if ( AccountContext->TrustedClient ) {
+
+ //
+ // Set password buffers as trusted client has
+ // indicated.
+ //
+
+ if ( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) {
+
+ TmpLmBuffer = (PLM_OWF_PASSWORD)All->LmPassword.Buffer;
+ TmpLmPresent = All->LmPasswordPresent;
+
+ } else {
+
+ TmpLmBuffer = (PLM_OWF_PASSWORD)NULL;
+ TmpLmPresent = FALSE;
+ }
+
+ if ( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) {
+
+ TmpNtBuffer = (PNT_OWF_PASSWORD)All->NtPassword.Buffer;
+ TmpNtPresent = All->NtPasswordPresent;
+
+ } else {
+
+ TmpNtBuffer = (PNT_OWF_PASSWORD)NULL;
+ TmpNtPresent = FALSE;
+ }
+
+ } else {
+
+ //
+ // This call came from the client-side.
+ // The OWFs will have been encrypted with the session
+ // key across the RPC link.
+ //
+ // Get the session key and decrypt both OWFs
+ //
+
+ NtStatus = RtlGetUserSessionKeyServer(
+ (RPC_BINDING_HANDLE)UserHandle,
+ &UserSessionKey
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+
+ //
+ // Decrypt the LM OWF Password with the session key
+ //
+
+ if ( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) {
+
+ NtStatus = RtlDecryptLmOwfPwdWithUserKey(
+ (PENCRYPTED_LM_OWF_PASSWORD)
+ All->LmPassword.Buffer,
+ &UserSessionKey,
+ &LmOwfBuffer
+ );
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+
+ TmpLmBuffer = &LmOwfBuffer;
+ TmpLmPresent = All->LmPasswordPresent;
+
+ } else {
+
+ TmpLmBuffer = (PLM_OWF_PASSWORD)NULL;
+ TmpLmPresent = FALSE;
+ }
+
+ //
+ // Decrypt the NT OWF Password with the session key
+ //
+
+ if ( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) {
+
+ NtStatus = RtlDecryptNtOwfPwdWithUserKey(
+ (PENCRYPTED_NT_OWF_PASSWORD)
+ All->NtPassword.Buffer,
+ &UserSessionKey,
+ &NtOwfBuffer
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+
+ TmpNtBuffer = &NtOwfBuffer;
+ TmpNtPresent = All->NtPasswordPresent;
+
+ } else {
+
+ TmpNtBuffer = (PNT_OWF_PASSWORD)NULL;
+ TmpNtPresent = FALSE;
+ }
+
+ }
+
+ } else {
+
+ //
+ // The clear text password was sent, so use that.
+ //
+
+ NtStatus = SampDecryptPasswordWithSessionKey(
+ UserHandle,
+ &Buffer->Internal4.UserPassword,
+ &ClearTextPassword
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ //
+ // The caller might be simultaneously setting
+ // the password and changing the account to be
+ // a machine or trust account. In this case,
+ // we don't validate the password (e.g., length).
+ //
+ // If the account is already a workstation or server
+ // trust account, don't check restrictions then either.
+ //
+
+ if (!((All->WhichFields & USER_ALL_USERACCOUNTCONTROL) &&
+ (All->UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
+ ) &&
+ ((V1aFixed.UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)) == 0 )
+ ) {
+
+ UNICODE_STRING FullName;
+
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ &ClearTextPassword
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ //
+ // Get the account name and full name to pass
+ // to the password filter.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext, // Context
+ SAMP_USER_FULL_NAME, // AttributeIndex
+ FALSE, // MakeCopy
+ &FullName // UnicodeAttribute
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampPasswordChangeFilter(
+ &AccountName,
+ &FullName,
+ &ClearTextPassword,
+ TRUE // set operation
+ );
+
+ }
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+ }
+
+
+ //
+ // Compute the hashed passwords.
+ //
+
+ NtStatus = SampCalculateLmAndNtOwfPasswords(
+ &ClearTextPassword,
+ &TmpLmPresent,
+ &LmOwfBuffer,
+ &NtOwfBuffer
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+ TmpNtPresent = TRUE;
+ TmpLmBuffer = &LmOwfBuffer;
+ TmpNtBuffer = &NtOwfBuffer;
+ }
+
+
+ //
+ // Set the password data
+ //
+
+ NtStatus = SampStoreUserPasswords(
+ AccountContext,
+ TmpLmBuffer,
+ TmpLmPresent,
+ TmpNtBuffer,
+ TmpNtPresent,
+ FALSE
+ );
+
+ //
+ // If we set the password,
+ // set the PasswordLastSet time to now.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+ NtStatus = SampComputePasswordExpired(
+ FALSE, // Password doesn't expire now
+ &V1aFixed.PasswordLastSet
+ );
+ }
+
+
+ //
+ // Replicate immediately if this is a machine account
+ //
+
+ if ( (V1aFixed.UserAccountControl & USER_MACHINE_ACCOUNT_MASK) ||
+ ((All->WhichFields & USER_ALL_USERACCOUNTCONTROL ) &&
+ (All->UserAccountControl & USER_MACHINE_ACCOUNT_MASK) )) {
+ ReplicateImmediately = TRUE;
+ }
+ DeltaType = SecurityDbChangePassword;
+
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_PASSWORDEXPIRED ) ) {
+
+ //
+ // If the PasswordExpired field is passed in,
+ // Only update PasswordLastSet if the password is being
+ // forced to expire or if the password is currently forced
+ // to expire.
+ //
+ // Avoid setting the PasswordLastSet field to the current
+ // time if it is already non-zero. Otherwise, the field
+ // will slowly creep forward each time this function is
+ // called and the password will never expire.
+ //
+ if ( All->PasswordExpired ||
+ (SampHasNeverTime.QuadPart == V1aFixed.PasswordLastSet.QuadPart) ) {
+
+ NtStatus = SampComputePasswordExpired(
+ All->PasswordExpired,
+ &V1aFixed.PasswordLastSet
+ );
+ }
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_PRIVATEDATA ) ) {
+
+ //
+ // Set the private data
+ //
+
+ NtStatus = SampSetPrivateUserData(
+ AccountContext,
+ All->PrivateData.Length,
+ All->PrivateData.Buffer
+ );
+ }
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_SECURITYDESCRIPTOR ) ) {
+
+ //
+ // Set the security descriptor
+ //
+
+ NtStatus = SampSetAccessAttribute(
+ AccountContext,
+ SAMP_USER_SECURITY_DESCRIPTOR,
+ All->SecurityDescriptor.SecurityDescriptor,
+ All->SecurityDescriptor.Length
+ );
+ }
+
+ //
+ // Set the fixed data
+ //
+ // Note that PasswordCanChange and PasswordMustChange
+ // aren't stored; they're calculated when needed.
+ //
+
+ if ( ( NT_SUCCESS( NtStatus ) ) &&
+ ( All->WhichFields & USER_ALL_USERACCOUNTCONTROL ) ) {
+
+ //
+ // If passwords were passed in, we've already set them,
+ // so it's OK to call this now.
+ //
+
+ NtStatus = SampIsUserAccountControlValid(
+ AccountContext,
+ All->UserAccountControl
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( ( V1aFixed.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) !=
+ ( All->UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) ) {
+
+ //
+ // One or more of the machine account bits has
+ // changed; we'll notify netlogon below.
+ //
+
+ UserAccountControlChanged = TRUE;
+
+ IgnoreStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &OldAccountName
+ );
+ }
+
+ //
+ // Untrusted clients can:
+ //
+ // 1) leave the the ACCOUNT_AUTO_LOCK flag set.
+ // 2) Clear the ACCOUNT_AUTO_LOCK flag.
+ //
+ // They can't set it. So, we must AND the user's
+ // flag value with the current value and set that
+ // in the UserAccountControl field.
+ //
+
+ if (!(AccountContext->TrustedClient)) {
+
+ //
+ // Minimize the passed in AccountControl
+ // with the currently set value.
+ //
+
+ All->UserAccountControl |=
+ (USER_ACCOUNT_AUTO_LOCKED &
+ All->UserAccountControl &
+ V1aFixed.UserAccountControl);
+
+ //
+ // If an untrusted client is unlocking the account,
+ // then we also need to re-set the BadPasswordCount.
+ // Trusted clients are expected to explicitly set
+ // the BadPasswordCount.
+ //
+
+ CurrentlyLocked = (V1aFixed.UserAccountControl &
+ USER_ACCOUNT_AUTO_LOCKED) != 0;
+ Unlocking = (All->UserAccountControl &
+ USER_ACCOUNT_AUTO_LOCKED) == 0;
+
+ if (CurrentlyLocked && Unlocking) {
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: Administrator unlocking account.\n"
+ " User Account : 0x%lx\n"
+ " Clearing lockout flag.\n"
+ " Resetting BadPassowrdCount.\n",
+ V1aFixed.UserId) );
+ V1aFixed.BadPasswordCount = 0;
+ }
+
+ }
+
+ //
+ // Now set the account control flags
+ //
+
+ V1aFixed.UserAccountControl = All->UserAccountControl;
+
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( All->WhichFields & USER_ALL_LASTLOGON ) {
+
+ V1aFixed.LastLogon = All->LastLogon;
+ }
+
+ if ( All->WhichFields & USER_ALL_LASTLOGOFF ) {
+
+ V1aFixed.LastLogoff = All->LastLogoff;
+ }
+
+ if ( All->WhichFields & USER_ALL_PASSWORDLASTSET ) {
+
+ V1aFixed.PasswordLastSet = All->PasswordLastSet;
+ }
+
+ if ( All->WhichFields & USER_ALL_ACCOUNTEXPIRES ) {
+
+ V1aFixed.AccountExpires = All->AccountExpires;
+ }
+
+ if ( All->WhichFields & USER_ALL_PRIMARYGROUPID ) {
+
+ //
+ // Make sure the primary group is legitimate
+ // (it must be one the user is a member of)
+ //
+
+ NtStatus = SampAssignPrimaryGroup(
+ AccountContext,
+ All->PrimaryGroupId
+ );
+ if (NT_SUCCESS(NtStatus)) {
+ V1aFixed.PrimaryGroupId = All->PrimaryGroupId;
+ } else {
+ break;
+ }
+ }
+
+ if ( All->WhichFields & USER_ALL_COUNTRYCODE ) {
+
+ V1aFixed.CountryCode = All->CountryCode;
+ }
+
+ if ( All->WhichFields & USER_ALL_CODEPAGE ) {
+
+ V1aFixed.CodePage = All->CodePage;
+ }
+
+ if ( All->WhichFields & USER_ALL_BADPASSWORDCOUNT ) {
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: \n"
+ " User Account : 0x%lx\n"
+ " Setting BadPasswordCount: %ld\n",
+ V1aFixed.UserId,
+ All->BadPasswordCount)
+ );
+
+
+
+ V1aFixed.BadPasswordCount = All->BadPasswordCount;
+
+ if (UserInformationClass == UserInternal3Information) {
+ //
+ // Also set LastBadPasswordTime;
+ //
+ V1aFixed.LastBadPasswordTime =
+ Buffer->Internal3.LastBadPasswordTime;
+
+#if DBG
+ RtlTimeToTimeFields(
+ &Buffer->Internal3.LastBadPasswordTime,
+ &T1);
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" LastBadPasswordTime : [0x%lx, 0x%lx] %d:%d:%d\n",
+ Buffer->Internal3.LastBadPasswordTime.HighPart,
+ Buffer->Internal3.LastBadPasswordTime.LowPart,
+ T1.Hour, T1.Minute, T1.Second )
+ );
+#endif //DBG
+ }
+ }
+
+ if ( All->WhichFields & USER_ALL_LOGONCOUNT ) {
+
+ V1aFixed.LogonCount = All->LogonCount;
+ }
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ break;
+
+ case UserPreferencesInformation:
+
+ V1aFixed.CountryCode = Buffer->Preferences.CountryCode;
+ V1aFixed.CodePage = Buffer->Preferences.CodePage;
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+
+ //
+ // replace the user comment
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_USER_COMMENT,
+ (PUNICODE_STRING)&(Buffer->Preferences.UserComment)
+ );
+ }
+
+
+ break;
+
+
+ case UserParametersInformation:
+
+
+ //
+ // replace the parameters
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PARAMETERS,
+ (PUNICODE_STRING)&(Buffer->Parameters.Parameters)
+ );
+
+ break;
+
+
+ case UserLogonHoursInformation:
+
+ NtStatus = SampReplaceUserLogonHours(
+ AccountContext,
+ (PLOGON_HOURS)&(Buffer->LogonHours.LogonHours)
+ );
+ break;
+
+
+ case UserNameInformation:
+
+ //
+ // first change the Full Name, then change the account name...
+ //
+
+ //
+ // replace the full name - no value restrictions
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ (PUNICODE_STRING)&(Buffer->Name.FullName)
+ );
+
+ //
+ // Change the account name
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampChangeUserAccountName(
+ AccountContext,
+ (PUNICODE_STRING)&(Buffer->Name.UserName),
+ &OldAccountName
+ );
+ }
+ }
+
+
+ //
+ // Don't free the OldAccountName yet; we'll need it at the
+ // very end.
+ //
+
+ break;
+
+
+ case UserAccountNameInformation:
+
+ NtStatus = SampChangeUserAccountName(
+ AccountContext,
+ (PUNICODE_STRING)&(Buffer->AccountName.UserName),
+ &OldAccountName
+ );
+
+ //
+ // Don't free the OldAccountName; we'll need it at the
+ // very end.
+ //
+
+ break;
+
+
+ case UserFullNameInformation:
+
+ //
+ // replace the full name - no value restrictions
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ (PUNICODE_STRING)&(Buffer->FullName.FullName)
+ );
+ break;
+
+
+
+
+ case UserPrimaryGroupInformation:
+
+ //
+ // Make sure the primary group is legitimate
+ // (it must be one the user is a member of)
+ //
+
+ NtStatus = SampAssignPrimaryGroup(
+ AccountContext,
+ Buffer->PrimaryGroup.PrimaryGroupId
+ );
+
+ //
+ // Update the V1_FIXED info.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ V1aFixed.PrimaryGroupId = Buffer->PrimaryGroup.PrimaryGroupId;
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ break;
+
+
+ case UserHomeInformation:
+
+ //
+ // replace the home directory
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY,
+ (PUNICODE_STRING)&(Buffer->Home.HomeDirectory)
+ );
+
+ //
+ // replace the home directory drive
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_HOME_DIRECTORY_DRIVE,
+ (PUNICODE_STRING)&(Buffer->Home.HomeDirectoryDrive)
+ );
+ }
+
+ break;
+
+ case UserScriptInformation:
+
+ //
+ // replace the script
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_SCRIPT_PATH,
+ (PUNICODE_STRING)&(Buffer->Script.ScriptPath)
+ );
+
+ break;
+
+
+ case UserProfileInformation:
+
+ //
+ // replace the Profile
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_PROFILE_PATH,
+ (PUNICODE_STRING)&(Buffer->Profile.ProfilePath)
+ );
+ }
+
+ break;
+
+
+ case UserAdminCommentInformation:
+
+ //
+ // replace the admin comment
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ (PUNICODE_STRING)&(Buffer->AdminComment.AdminComment)
+ );
+ }
+
+ break;
+
+
+ case UserWorkStationsInformation:
+
+ //
+ // Convert the workstation list, which is given to us in
+ // UI/Service format, to API list format before storing
+ // it.
+ //
+
+ NtStatus = RtlConvertUiListToApiList(
+ (PUNICODE_STRING)&(Buffer->WorkStations.WorkStations),
+ &ApiList,
+ FALSE );
+
+ //
+ // replace the admin workstations
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_WORKSTATIONS,
+ &ApiList
+ );
+
+ RtlFreeHeap( RtlProcessHeap(), 0, ApiList.Buffer );
+ }
+
+ break;
+
+
+ case UserControlInformation:
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampIsUserAccountControlValid(
+ AccountContext,
+ Buffer->Control.UserAccountControl
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( ( V1aFixed.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) !=
+ ( Buffer->Control.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) ) {
+
+ //
+ // One of the machine account bits has changed;
+ // we'll notify netlogon below.
+ //
+
+ UserAccountControlChanged = TRUE;
+
+ IgnoreStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &OldAccountName
+ );
+ }
+
+ //
+ // Untrusted clients can:
+ //
+ // 1) leave the the ACCOUNT_AUTO_LOCK flag set.
+ // 2) Clear the ACCOUNT_AUTO_LOCK flag.
+ //
+ // They can't set it. So, we must AND the user's
+ // flag value with the current value and set that
+ // in the UserAccountControl field.
+ //
+ // One more caveat, when an untrusted client clears the
+ // ACCOUNT_AUTO_LOCK flag, then we need to reset
+ // the BadPasswordCount flag to zero. We don't
+ // do this for trusted clients, because we figure
+ // they know what they are doing and will set
+ // everthing appropriately.
+ //
+
+ if (!(AccountContext->TrustedClient)) {
+
+ Buffer->Control.UserAccountControl |=
+ (USER_ACCOUNT_AUTO_LOCKED &
+ Buffer->Control.UserAccountControl &
+ V1aFixed.UserAccountControl);
+
+ //
+ // If an untrusted client is unlocking the account,
+ // then we also need to re-set the BadPasswordCount.
+ // Trusted clients are expected to explicitly set
+ // the BadPasswordCount.
+ //
+
+ CurrentlyLocked = (V1aFixed.UserAccountControl &
+ USER_ACCOUNT_AUTO_LOCKED) != 0;
+ Unlocking = (Buffer->Control.UserAccountControl &
+ USER_ACCOUNT_AUTO_LOCKED) == 0;
+
+ if (CurrentlyLocked && Unlocking) {
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: Administrator unlocking account.\n"
+ " User Account : 0x%lx\n"
+ " Clearing lockout flag.\n"
+ " Resetting BadPassowrdCount.\n",
+ V1aFixed.UserId) );
+ V1aFixed.BadPasswordCount = 0;
+ }
+ }
+
+ V1aFixed.UserAccountControl = Buffer->Control.UserAccountControl;
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ break;
+
+
+ case UserExpiresInformation:
+
+ //
+ // If the user is the special account Administrator, return an
+ // error if trying to set the expiry information, except to the
+ // value that means that the account never expires.
+ //
+
+ if ((!AccountContext->TrustedClient) &&
+ ( AccountContext->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN )) {
+
+ LARGE_INTEGER AccountNeverExpires, Temp;
+
+ AccountNeverExpires = RtlConvertUlongToLargeInteger(
+ SAMP_ACCOUNT_NEVER_EXPIRES
+ );
+
+ OLD_TO_NEW_LARGE_INTEGER(Buffer->Expires.AccountExpires, Temp);
+
+ if (!( Temp.QuadPart == AccountNeverExpires.QuadPart)) {
+
+ NtStatus = STATUS_SPECIAL_ACCOUNT;
+ break;
+ }
+ }
+
+ V1aFixed.AccountExpires = Buffer->Expires.AccountExpires;
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ break;
+
+
+ case UserSetPasswordInformation:
+
+ ASSERT(FALSE); // Should have been mapped to INTERNAL1 on client side
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ break;
+
+
+ case UserInternal1Information:
+ case UserInternal5Information:
+
+ //
+ // Get copy of the account name to pass to
+ // notification packages.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &AccountName
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+
+ if (UserInformationClass == UserInternal1Information) {
+
+ LmPresent = Buffer->Internal1.LmPasswordPresent;
+ NtPresent = Buffer->Internal1.NtPasswordPresent;
+ PasswordExpired = Buffer->Internal1.PasswordExpired;
+
+ //
+ // If our client is trusted, they are on the server side
+ // and data from them will not have been encrypted with the
+ // user session key - so don't decrypt them
+ //
+
+ if ( AccountContext->TrustedClient ) {
+
+ //
+ // Copy the (not) encrypted owfs into the owf buffers
+ //
+
+ ASSERT(ENCRYPTED_LM_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
+ ASSERT(ENCRYPTED_NT_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
+
+ RtlCopyMemory(&LmOwfPassword,
+ &Buffer->Internal1.EncryptedLmOwfPassword,
+ LM_OWF_PASSWORD_LENGTH
+ );
+
+ RtlCopyMemory(&NtOwfPassword,
+ &Buffer->Internal1.EncryptedNtOwfPassword,
+ NT_OWF_PASSWORD_LENGTH
+ );
+
+ } else {
+
+
+ //
+ // This call came from the client-side. The
+ // The OWFs will have been encrypted with the session
+ // key across the RPC link.
+ //
+ // Get the session key and decrypt both OWFs
+ //
+
+ NtStatus = RtlGetUserSessionKeyServer(
+ (RPC_BINDING_HANDLE)UserHandle,
+ &UserSessionKey
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+
+
+ //
+ // Decrypt the LM OWF Password with the session key
+ //
+
+ if ( Buffer->Internal1.LmPasswordPresent) {
+
+ NtStatus = RtlDecryptLmOwfPwdWithUserKey(
+ &Buffer->Internal1.EncryptedLmOwfPassword,
+ &UserSessionKey,
+ &LmOwfPassword
+ );
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+ }
+
+
+ //
+ // Decrypt the NT OWF Password with the session key
+ //
+
+ if ( Buffer->Internal1.NtPasswordPresent) {
+
+ NtStatus = RtlDecryptNtOwfPwdWithUserKey(
+ &Buffer->Internal1.EncryptedNtOwfPassword,
+ &UserSessionKey,
+ &NtOwfPassword
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ break; // out of switch
+ }
+ }
+ }
+ } else {
+ UNICODE_STRING FullName;
+
+ //
+ // Password was sent cleartext.
+ //
+
+ NtStatus = SampDecryptPasswordWithSessionKey(
+ UserHandle,
+ &Buffer->Internal5.UserPassword,
+ &ClearTextPassword
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+ //
+ // Compute the hashed passwords.
+ //
+
+ NtStatus = SampCalculateLmAndNtOwfPasswords(
+ &ClearTextPassword,
+ &LmPresent,
+ &LmOwfPassword,
+ &NtOwfPassword
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+
+ NtPresent = TRUE;
+ PasswordExpired = Buffer->Internal5.PasswordExpired;
+
+ //
+ // If the account is not a workstation & server trust account
+ // Get the full name to pass
+ // to the password filter.
+ //
+
+ if ((V1aFixed.UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)) == 0 ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext, // Context
+ SAMP_USER_FULL_NAME, // AttributeIndex
+ FALSE, // MakeCopy
+ &FullName // UnicodeAttribute
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampPasswordChangeFilter(
+ &AccountName,
+ &FullName,
+ &ClearTextPassword,
+ TRUE // set operation
+ );
+
+ }
+
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ break;
+ }
+
+
+ }
+ //
+ // Store away the new OWF passwords
+ //
+
+ NtStatus = SampStoreUserPasswords(
+ AccountContext,
+ &LmOwfPassword,
+ LmPresent,
+ &NtOwfPassword,
+ NtPresent,
+ FALSE
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampStorePasswordExpired(
+ AccountContext,
+ PasswordExpired
+ );
+ }
+
+ //
+ // Replicate immediately if this is a machine account
+ //
+
+ if ( V1aFixed.UserAccountControl & USER_MACHINE_ACCOUNT_MASK ) {
+ ReplicateImmediately = TRUE;
+ }
+
+ break;
+
+
+
+
+ case UserInternal2Information:
+
+ if ( AccountContext->TrustedClient ) {
+
+ TellNetlogon = FALSE;
+
+ //
+ // There are two ways to set logon/logoff statistics:
+ //
+ // 1) Directly, specifying each one being set,
+ // 2) Implicitly, specifying the action to
+ // represent
+ //
+ // These two forms are mutually exclusive. That is,
+ // you can't specify both a direct action and an
+ // implicit action. In fact, you can't specify two
+ // implicit actions either.
+ //
+
+ if (Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_INTER_SUCCESS_LOGON) {
+
+ if ( (Buffer->Internal2.StatisticsToApply
+ & ~USER_LOGON_INTER_SUCCESS_LOGON) != 0 ) {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+ } else {
+
+ //
+ // Set BadPasswordCount = 0
+ // Increment LogonCount
+ // Set LastLogon = NOW
+ //
+ //
+ //
+
+
+ V1aFixed.BadPasswordCount = 0;
+ if (V1aFixed.LogonCount != 0xFFFF) {
+ V1aFixed.LogonCount += 1;
+ }
+ NtQuerySystemTime( &V1aFixed.LastLogon );
+
+
+#if DBG
+ RtlTimeToTimeFields(
+ &V1aFixed.LastLogon,
+ &T1);
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: Successful interactive logon\n"
+ " User Account : 0x%lx\n"
+ " ReSetting BadPasswordCount: %ld\n"
+ " LastLogon Time : [0x%lx, 0x%lx] %d:%d:%d\n",
+ V1aFixed.UserId,
+ V1aFixed.BadPasswordCount,
+ V1aFixed.LastLogon.HighPart,
+ V1aFixed.LastLogon.LowPart,
+ T1.Hour, T1.Minute, T1.Second)
+ );
+#endif //DGB
+ }
+ }
+
+ if (Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_INTER_SUCCESS_LOGOFF) {
+ if ( (Buffer->Internal2.StatisticsToApply
+ & ~USER_LOGON_INTER_SUCCESS_LOGOFF) != 0 ) {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+ } else {
+
+ //
+ // Set LastLogoff time
+ // Decrement LogonCount (don't let it become negative)
+ //
+
+ if (V1aFixed.LogonCount != 0) {
+ V1aFixed.LogonCount -= 1;
+ }
+ NtQuerySystemTime( &V1aFixed.LastLogoff );
+ }
+ }
+
+ if (Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_NET_SUCCESS_LOGON) {
+
+ if ( (Buffer->Internal2.StatisticsToApply
+ & ~USER_LOGON_NET_SUCCESS_LOGON) != 0 ) {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+ } else {
+
+ //
+ // Set BadPasswordCount = 0
+ // Set LastLogon = NOW
+ //
+ //
+ //
+
+
+ V1aFixed.BadPasswordCount = 0;
+ NtQuerySystemTime( &V1aFixed.LastLogon );
+
+
+#if DBG
+ RtlTimeToTimeFields(
+ &V1aFixed.LastLogon,
+ &T1);
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: Successful network logon\n"
+ " User Account : 0x%lx\n"
+ " ReSetting BadPasswordCount: %ld\n"
+ " LastLogon Time : [0x%lx, 0x%lx] %d:%d:%d\n",
+ V1aFixed.UserId,
+ V1aFixed.BadPasswordCount,
+ V1aFixed.LastLogon.HighPart,
+ V1aFixed.LastLogon.LowPart,
+ T1.Hour, T1.Minute, T1.Second )
+ );
+#endif //DBG
+ }
+ }
+
+ if (Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_NET_SUCCESS_LOGOFF) {
+ if ( (Buffer->Internal2.StatisticsToApply
+ & ~USER_LOGON_NET_SUCCESS_LOGOFF) != 0 ) {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+ } else {
+
+ //
+ // Set LastLogoff time
+ //
+
+ NtQuerySystemTime( &V1aFixed.LastLogoff );
+ }
+ }
+
+ if (Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_BAD_PASSWORD) {
+ if ( (Buffer->Internal2.StatisticsToApply
+ & ~USER_LOGON_BAD_PASSWORD) != 0 ) {
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+ } else {
+
+ //
+ // Increment BadPasswordCount
+ // (might lockout account)
+ //
+
+
+ AccountLockedOut =
+ SampIncrementBadPasswordCount(
+ AccountContext,
+ &V1aFixed
+ );
+
+ //
+ // If the account has been locked out,
+ // ensure the BDCs in the domain are told.
+ //
+
+ if ( AccountLockedOut ) {
+ TellNetlogon = TRUE;
+ ReplicateImmediately = TRUE;
+ }
+ }
+ }
+
+
+
+
+
+
+ if ( Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_STAT_LAST_LOGON ) {
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ Buffer->Internal2.LastLogon,
+ V1aFixed.LastLogon );
+ }
+
+ if ( Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_STAT_LAST_LOGOFF ) {
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ Buffer->Internal2.LastLogoff,
+ V1aFixed.LastLogoff );
+ }
+
+ if ( Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_STAT_BAD_PWD_COUNT ) {
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: SetInformationUser: Bad Password Count\n"
+ " User Account : 0x%lx\n"
+ " Old BadPasswordCount: %ld\n"
+ " New BadPasswordCount: %ld\n",
+ V1aFixed.UserId,
+ V1aFixed.BadPasswordCount,
+ Buffer->Internal2.BadPasswordCount
+ )
+ );
+ V1aFixed.BadPasswordCount =
+ Buffer->Internal2.BadPasswordCount;
+ }
+
+ if ( Buffer->Internal2.StatisticsToApply
+ & USER_LOGON_STAT_LOGON_COUNT ) {
+
+ V1aFixed.LogonCount = Buffer->Internal2.LogonCount;
+ }
+
+ NtStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ } else {
+
+ //
+ // This information is only settable by trusted
+ // clients.
+ //
+
+ NtStatus = STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+
+
+ } // end_switch
+
+
+
+ } // end_if
+
+
+
+
+ //
+ // Go fetch any data we'll need to update the display cache
+ // Do this before we dereference the context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( MustUpdateAccountDisplay ) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &NewAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ TRUE, // Make copy
+ &NewFullName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ADMIN_COMMENT,
+ TRUE, // Make copy
+ &NewAdminComment
+ );
+ //
+ // If the account name has changed, then OldAccountName
+ // is already filled in. If the account name hasn't changed
+ // then the OldAccountName is the same as the new!
+ //
+
+ if (NT_SUCCESS(NtStatus) && (OldAccountName.Buffer == NULL)) {
+
+ NtStatus = SampDuplicateUnicodeString(
+ &OldAccountName,
+ &NewAccountName);
+ }
+ }
+ }
+ }
+ }
+
+
+ //
+ // Generate an audit if necessary. We don't account statistic
+ // updates, which we also don't notify Netlogon of.
+ //
+
+ if (NT_SUCCESS(NtStatus) &&
+ SampDoAccountAuditing(DomainIndex) &&
+ TellNetlogon) {
+
+ UNICODE_STRING
+ AccountName;
+
+ IgnoreStatus = SampGetUnicodeStringAttribute(
+ AccountContext, // Context
+ SAMP_USER_ACCOUNT_NAME, // AttributeIndex
+ FALSE, // MakeCopy
+ &AccountName // UnicodeAttribute
+ );
+ if (NT_SUCCESS(IgnoreStatus)) {
+ LsaIAuditSamEvent(
+ STATUS_SUCCESS,
+ SE_AUDITID_USER_CHANGE, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &AccountContext->TypeBody.User.Rid, // Account Rid
+ NULL // Privileges used
+ );
+ }
+
+ }
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ NtStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ } // end_if
+
+
+
+
+ //
+ // Commit the transaction and, if successful,
+ // notify netlogon of the changes. Also generate any necessary audits.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( !TellNetlogon ) {
+
+ //
+ // For logon statistics, we don't notify netlogon about changes
+ // to the database. Which means that we don't want the
+ // domain's modified count to increase. The commit routine
+ // will increase it automatically if this isn't a BDC, so we'll
+ // decrement it here.
+ //
+
+ if (SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
+ != DomainServerRoleBackup) {
+
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart -
+ 1;
+ }
+ }
+
+
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+
+ //
+ // Update the display information if the cache may be affected
+ //
+
+ if ( MustUpdateAccountDisplay ) {
+
+ SAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo;
+ SAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo;
+
+ OldAccountInfo.Name = OldAccountName;
+ OldAccountInfo.Rid = ObjectRid;
+ OldAccountInfo.AccountControl = OldUserAccountControl;
+ RtlInitUnicodeString(&OldAccountInfo.Comment, NULL);
+ RtlInitUnicodeString(&OldAccountInfo.FullName, NULL);
+
+ NewAccountInfo.Name = NewAccountName;
+ NewAccountInfo.Rid = ObjectRid;
+ NewAccountInfo.AccountControl = V1aFixed.UserAccountControl;
+ NewAccountInfo.Comment = NewAdminComment;
+ NewAccountInfo.FullName = NewFullName;
+
+ IgnoreStatus = SampUpdateDisplayInformation(&OldAccountInfo,
+ &NewAccountInfo,
+ SampUserObjectType);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Notify netlogon if machine account changes.
+ //
+
+ if ( ( UserAccountControlChanged ) &&
+ ( OldAccountName.Buffer != NULL ) ) {
+
+ //
+ // The UserAccountControl field changed (more specifically,
+ // the bits that netlogon is interested in changed) and
+ // we were able to get the name of the account. So notify
+ // netlogon of the change.
+ //
+
+ IgnoreStatus = I_NetNotifyMachineAccount(
+ ObjectRid,
+ SampDefinedDomains[
+ SampTransactionDomainIndex].Sid,
+ OldUserAccountControl,
+ V1aFixed.UserAccountControl,
+ &OldAccountName
+ );
+ }
+
+ //
+ // Notify netlogon of any user account changes
+ //
+
+ if ( ( UserInformationClass == UserNameInformation ) ||
+ ( UserInformationClass == UserAccountNameInformation ) ||
+ ( ( UserInformationClass == UserAllInformation ) &&
+ ( All->WhichFields & USER_ALL_USERNAME ) ) ) {
+
+ //
+ // The account was renamed; let Netlogon know.
+ //
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbRename,
+ SecurityDbObjectSamUser,
+ ObjectRid,
+ &OldAccountName,
+ (DWORD) ReplicateImmediately,
+ NULL // Delta data
+ );
+
+ } else {
+
+ //
+ // Something in the account was changed. Notify netlogon about
+ // everything except logon statistics changes.
+ //
+
+ if ( TellNetlogon ) {
+ SampNotifyNetlogonOfDelta(
+ DeltaType,
+ SecurityDbObjectSamUser,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) ReplicateImmediately,
+ NULL // Delta data
+ );
+ }
+ }
+ }
+ }
+
+ //
+ // Release the lock
+ //
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ //
+ // Notify any packages that a password was changed.
+ //
+
+ if (NT_SUCCESS(NtStatus) && (DeltaType == SecurityDbChangePassword)) {
+
+ //
+ // If the account name was changed, use the new account name.
+ //
+
+ if (NewAccountName.Buffer != NULL) {
+ (void) SampPasswordChangeNotify(
+ &NewAccountName,
+ UserRid,
+ &ClearTextPassword
+ );
+ } else {
+ (void) SampPasswordChangeNotify(
+ &AccountName,
+ UserRid,
+ &ClearTextPassword
+ );
+
+ }
+
+ }
+
+
+ //
+ // Clean up strings
+ //
+
+ SampFreeUnicodeString( &OldAccountName );
+ SampFreeUnicodeString( &NewAccountName );
+ SampFreeUnicodeString( &NewFullName );
+ SampFreeUnicodeString( &NewAdminComment );
+ SampFreeUnicodeString( &AccountName );
+
+ if (ClearTextPassword.Buffer != NULL) {
+
+ RtlZeroMemory(
+ ClearTextPassword.Buffer,
+ ClearTextPassword.Length
+ );
+
+ RtlFreeUnicodeString( &ClearTextPassword );
+
+ }
+
+
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SamrChangePasswordUser(
+ IN SAMPR_HANDLE UserHandle,
+ IN BOOLEAN LmPresent,
+ IN PENCRYPTED_LM_OWF_PASSWORD OldLmEncryptedWithNewLm,
+ IN PENCRYPTED_LM_OWF_PASSWORD NewLmEncryptedWithOldLm,
+ IN BOOLEAN NtPresent,
+ IN PENCRYPTED_NT_OWF_PASSWORD OldNtEncryptedWithNewNt,
+ IN PENCRYPTED_NT_OWF_PASSWORD NewNtEncryptedWithOldNt,
+ IN BOOLEAN NtCrossEncryptionPresent,
+ IN PENCRYPTED_NT_OWF_PASSWORD NewNtEncryptedWithNewLm,
+ IN BOOLEAN LmCrossEncryptionPresent,
+ IN PENCRYPTED_LM_OWF_PASSWORD NewLmEncryptedWithNewNt
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service sets the password to NewPassword only if OldPassword
+ matches the current user password for this user and the NewPassword
+ is not the same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ LMPresent - TRUE if the LM parameters (below) are valid.
+
+ LmOldEncryptedWithLmNew - the old LM OWF encrypted with the new LM OWF
+
+ LmNewEncryptedWithLmOld - the new LM OWF encrypted with the old LM OWF
+
+
+ NtPresent - TRUE if the NT parameters (below) are valid
+
+ NtOldEncryptedWithNtNew - the old NT OWF encrypted with the new NT OWF
+
+ NtNewEncryptedWithNtOld - the new NT OWF encrypted with the old NT OWF
+
+
+ NtCrossEncryptionPresent - TRUE if NtNewEncryptedWithLmNew is valid.
+
+ NtNewEncryptedWithLmNew - the new NT OWF encrypted with the new LM OWF
+
+
+ LmCrossEncryptionPresent - TRUE if LmNewEncryptedWithNtNew is valid.
+
+ LmNewEncryptedWithNtNew - the new LM OWF encrypted with the new NT OWF
+
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+ STATUS_CROSS_ENCRYPTION_REQUIRED - No NT password is stored, so the caller
+ must provide the OldNtEncryptedWithOldLm parameter.
+
+--*/
+{
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE FoundType;
+ LARGE_INTEGER TimeNow;
+ LM_OWF_PASSWORD StoredLmOwfPassword;
+ NT_OWF_PASSWORD StoredNtOwfPassword;
+ NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword;
+ LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword;
+ BOOLEAN StoredLmPasswordNonNull;
+ BOOLEAN StoredNtPasswordPresent;
+ BOOLEAN StoredNtPasswordNonNull;
+ BOOLEAN AccountLockedOut;
+ BOOLEAN V1aFixedRetrieved = FALSE;
+ BOOLEAN V1aFixedModified = FALSE;
+ ULONG ObjectRid;
+ UNICODE_STRING AccountName;
+ ULONG UserRid;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+
+
+ RtlInitUnicodeString(
+ &AccountName,
+ NULL
+ );
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the current time
+ //
+
+ NtStatus = NtQuerySystemTime( &TimeNow );
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ return(NtStatus);
+ }
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+ ObjectRid = AccountContext->TypeBody.User.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ USER_CHANGE_PASSWORD,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ return(NtStatus);
+ }
+
+ //
+ // Auditing information
+ //
+
+
+ NtStatus = SampGetUnicodeStringAttribute( AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // make a copy
+ &AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Auditing information
+ //
+
+ UserRid = AccountContext->TypeBody.User.Rid;
+
+ //
+ // Get a pointer to the domain object
+ //
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+
+ //
+ // Read the old OWF passwords from disk
+ //
+
+ NtStatus = SampRetrieveUserPasswords(
+ AccountContext,
+ &StoredLmOwfPassword,
+ &StoredLmPasswordNonNull,
+ &StoredNtOwfPassword,
+ &StoredNtPasswordPresent,
+ &StoredNtPasswordNonNull
+ );
+
+ //
+ // Check the password can be changed at this time
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Only do the check if one of the passwords is non-null.
+ // A Null password can always be changed.
+ //
+
+ if (StoredNtPasswordNonNull || StoredLmPasswordNonNull) {
+
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ //
+ // If the min password age is non zero, check it here
+ //
+ if (Domain->UnmodifiedFixed.MinPasswordAge.QuadPart != SampHasNeverTime.QuadPart) {
+
+ LARGE_INTEGER PasswordCanChange = SampAddDeltaTime(
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MinPasswordAge);
+
+ V1aFixedRetrieved = TRUE;
+
+ if (TimeNow.QuadPart < PasswordCanChange.QuadPart) {
+ NtStatus = STATUS_ACCOUNT_RESTRICTION;
+ }
+ }
+
+ }
+ }
+ }
+
+
+
+
+
+ //
+ // Macro that defines whether the old password passed in is complex
+ //
+
+#define PassedComplex() (NtPresent && !LmPresent)
+
+ //
+ // Macro that defines whether the stored passsword is complex
+ //
+
+#define StoredComplex() (StoredNtPasswordPresent && \
+ StoredNtPasswordNonNull && \
+ !StoredLmPasswordNonNull)
+
+ //
+ // Check that the complexity of the old password passed in matches
+ // the complexity of the one stored. If it doesn't then the old
+ // passwords don't match and we might as well give up now.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( (StoredComplex() != 0) != (PassedComplex() != 0) ) {
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (LmPresent) {
+
+ //
+ // Decrypt the doubly-encrypted LM passwords sent to us
+ //
+
+ NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ NewLmEncryptedWithOldLm,
+ &StoredLmOwfPassword,
+ &NewLmOwfPassword
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ OldLmEncryptedWithNewLm,
+ &NewLmOwfPassword,
+ &OldLmOwfPassword
+ );
+ }
+ }
+ }
+
+ //
+ // Decrypt the doubly-encrypted NT passwords sent to us
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (NtPresent) {
+
+ NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
+ NewNtEncryptedWithOldNt,
+ &StoredNtOwfPassword,
+ &NewNtOwfPassword
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
+ OldNtEncryptedWithNewNt,
+ &NewNtOwfPassword,
+ &OldNtOwfPassword
+ );
+ }
+ }
+ }
+
+
+
+
+ //
+ // Authenticate the password change operation based on what
+ // we have stored and what was passed.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (!NtPresent) {
+
+ //
+ // Called from a down-level machine (no NT password passed)
+ //
+
+ if (!LmPresent) {
+
+ //
+ // No NT password passed, no LM password either.
+ // They're out of luck
+ //
+
+ NtStatus = STATUS_INVALID_PARAMETER_MIX;
+
+ } else {
+
+ //
+ // LM data only passed. Use LM data for authentication
+ //
+
+ if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
+
+ //
+ // Old LM passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ } else {
+
+ //
+ // The operation was authenticated based on the LM data
+ //
+ // We have NtPresent = FALSE, LM Present = TRUE
+ //
+ // NewLmOwfPassword will be stored.
+ // No NT password will be stored.
+ //
+ }
+ }
+
+
+ } else {
+
+ //
+ // NtPresent = TRUE, we were passed an NT password
+ // The client is an NT-level machine (or higher !)
+ //
+
+ if (!LmPresent) {
+
+ //
+ // No LM version of old password - the old password is complex
+ //
+ // Use NT data for authentication
+ //
+
+ if (!RtlEqualNtOwfPassword(&OldNtOwfPassword, &StoredNtOwfPassword)) {
+
+ //
+ // Old NT passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ } else {
+
+ //
+ // Authentication was successful.
+ // We need cross encrypted version of the new LM password
+ //
+
+ if (!LmCrossEncryptionPresent) {
+
+ NtStatus = STATUS_LM_CROSS_ENCRYPTION_REQUIRED;
+
+ } else {
+
+ //
+ // Calculate the new LM Owf Password
+ //
+
+ ASSERT(NT_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ NewLmEncryptedWithNewNt,
+ (PLM_OWF_PASSWORD)&NewNtOwfPassword,
+ &NewLmOwfPassword
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ LmPresent = TRUE;
+
+ //
+ // The operation was authenticated based on NT data
+ // The new LM Password was requested and
+ // successfully obtained using cross-encryption.
+ //
+ // We have NtPresent = TRUE, LM Present = TRUE
+ //
+ // NewLmOwfPassword will be stored.
+ // NewNtOwfPassword will be stored.
+ //
+ }
+
+ }
+
+ } else {
+
+ //
+ // NtPresent == TRUE, LmPresent == TRUE
+ //
+ // The old password passed is simple (both LM and NT versions)
+ //
+ // Authenticate using both LM and NT data
+ //
+
+ if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
+
+ //
+ // Old LM passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ } else {
+
+ //
+ // Old LM passwords matched
+ //
+ // Do NT authentication if we have a stored NT password
+ // or the stored LM password is NULL.
+ //
+ // (NO stored NT and Stored LM = NULL -> stored pwd=NULL
+ // We must compare passed old NT Owf against
+ // NULL NT Owf to ensure user didn't specify complex
+ // old NT password instead of NULL password)
+ //
+ // (StoredNtOwfPassword is already initialized to
+ // the NullNtOwf if no NT password stored)
+ //
+
+ if (StoredNtPasswordPresent || !StoredLmPasswordNonNull) {
+
+ if (!RtlEqualNtOwfPassword(&OldNtOwfPassword,
+ &StoredNtOwfPassword)) {
+ //
+ // Old NT passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ } else {
+
+ //
+ // The operation was authenticated based on
+ // both LM and NT data.
+ //
+ // We have NtPresent = TRUE, LM Present = TRUE
+ //
+ // NewLmOwfPassword will be stored.
+ // NewNtOwfPassword will be stored.
+ //
+
+ }
+
+ } else {
+
+ //
+ // The LM authentication was sufficient since
+ // we have no stored NT password
+ //
+ // Go get the new NT password using cross encryption
+ //
+
+ if (!NtCrossEncryptionPresent) {
+
+ NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
+
+ } else {
+
+ //
+ // Calculate the new NT Owf Password
+ //
+
+ ASSERT(NT_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
+ NewNtEncryptedWithNewLm,
+ (PNT_OWF_PASSWORD)&NewLmOwfPassword,
+ &NewNtOwfPassword
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The operation was authenticated based on LM data
+ // The new NT Password was requested and
+ // successfully obtained using cross-encryption.
+ //
+ // We have NtPresent = TRUE, LM Present = TRUE
+ //
+ // NewLmOwfPassword will be stored.
+ // NewNtOwfPassword will be stored.
+ //
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // We now have a NewLmOwfPassword.
+ // If NtPresent = TRUE, we also have a NewNtOwfPassword
+ //
+
+ //
+ // Write the new passwords to disk
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We should always have a LM password to store.
+ //
+
+ ASSERT(LmPresent);
+
+ NtStatus = SampStoreUserPasswords(
+ AccountContext,
+ &NewLmOwfPassword,
+ TRUE,
+ &NewNtOwfPassword,
+ NtPresent,
+ TRUE
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // We know the password is not expired.
+ //
+
+ NtStatus = SampStorePasswordExpired(
+ AccountContext,
+ FALSE
+ );
+ }
+ }
+
+
+
+ //
+ // if we have a bad password, then increment the bad password
+ // count and check to see if the account should be locked.
+ //
+
+ if (NtStatus == STATUS_WRONG_PASSWORD) {
+
+ //
+ // Get the V1aFixed so we can update the bad password count
+ //
+
+
+ TmpStatus = STATUS_SUCCESS;
+ if (!V1aFixedRetrieved) {
+ TmpStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ if (!NT_SUCCESS(TmpStatus)) {
+
+ //
+ // If we can't update the V1aFixed, then return this
+ // error so that the user doesn't find out the password
+ // was not correct.
+ //
+
+ NtStatus = TmpStatus;
+
+ } else {
+
+
+ //
+ // Increment BadPasswordCount (might lockout account)
+ //
+
+
+ AccountLockedOut = SampIncrementBadPasswordCount(
+ AccountContext,
+ &V1aFixed
+ );
+
+ V1aFixedModified = TRUE;
+
+
+ }
+ }
+
+ if (V1aFixedModified) {
+ TmpStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+ }
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_WRONG_PASSWORD)) {
+
+
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ //
+ // retain previous error/success value unless we have
+ // an over-riding error from our dereference.
+ //
+
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ }
+
+ //
+ // Commit changes to disk.
+ //
+
+ if ( NT_SUCCESS(NtStatus) || NtStatus == STATUS_WRONG_PASSWORD) {
+
+ TmpStatus = SampCommitAndRetainWriteLock();
+
+ //
+ // retain previous error/success value unless we have
+ // an over-riding error from our dereference.
+ //
+
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+ if ( NT_SUCCESS(TmpStatus) ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangePassword,
+ SecurityDbObjectSamUser,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Don't Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+
+ if (SampDoAccountAuditing(AccountContext->DomainIndex)) {
+
+ LsaIAuditSamEvent( NtStatus,
+ SE_AUDITID_USER_PWD_CHANGED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &UserRid, // Account Rid
+ NULL // Privileges used
+ );
+
+ }
+
+
+ //
+ // Release the write lock
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ (void) SampPasswordChangeNotify(
+ &AccountName,
+ UserRid,
+ NULL
+ );
+
+ } else {
+
+ //
+ // Sleep for three seconds to prevent dictionary attacks.
+ //
+
+ Sleep( 3000 );
+
+ }
+
+ SampFreeUnicodeString( &AccountName );
+
+ return(NtStatus);
+}
+
+
+
+
+
+NTSTATUS
+SampDecryptPasswordWithLmOwfPassword(
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
+ IN PLM_OWF_PASSWORD StoredPassword,
+ IN BOOLEAN UnicodePasswords,
+ OUT PUNICODE_STRING ClearNtPassword
+ )
+/*++
+
+Routine Description:
+
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ return( SampDecryptPasswordWithKey(
+ EncryptedPassword,
+ (PUCHAR) StoredPassword,
+ LM_OWF_PASSWORD_LENGTH,
+ UnicodePasswords,
+ ClearNtPassword
+ ) );
+}
+
+
+NTSTATUS
+SampDecryptPasswordWithNtOwfPassword(
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
+ IN PNT_OWF_PASSWORD StoredPassword,
+ IN BOOLEAN UnicodePasswords,
+ OUT PUNICODE_STRING ClearNtPassword
+ )
+/*++
+
+Routine Description:
+
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ //
+ // The code is the same as for LM owf password.
+ //
+
+ return(SampDecryptPasswordWithKey(
+ EncryptedPassword,
+ (PUCHAR) StoredPassword,
+ NT_OWF_PASSWORD_LENGTH,
+ UnicodePasswords,
+ ClearNtPassword
+ ) );
+}
+
+NTSTATUS
+SampOpenUserInServer(
+ PUNICODE_STRING UserName,
+ BOOLEAN Unicode,
+ SAMPR_HANDLE * UserHandle
+ )
+/*++
+
+Routine Description:
+
+ Opens a user in the account domain.
+
+Arguments:
+
+ UserName - an OEM or Unicode string of the user's name
+
+ Unicode - Indicates whether UserName is OEM or Unicode
+
+ UserHandle - Receives handle to the user, opened with SamOpenUser for
+ USER_CHANGE_PASSWORD access
+
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ SAM_HANDLE ServerHandle = NULL;
+ SAM_HANDLE DomainHandle = NULL;
+ SAMPR_ULONG_ARRAY UserId;
+ SAMPR_ULONG_ARRAY SidUse;
+ UNICODE_STRING UnicodeUserName;
+ ULONG DomainIndex;
+
+
+ UserId.Element = NULL;
+ SidUse.Element = NULL;
+
+ //
+ // Get the unicode user name.
+ //
+
+ if (Unicode) {
+ UnicodeUserName = *UserName;
+ } else {
+ NtStatus = RtlOemStringToUnicodeString(
+ &UnicodeUserName,
+ (POEM_STRING) UserName,
+ TRUE // allocate destination.
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+
+
+ NtStatus = SamrConnect(
+ NULL,
+ &ServerHandle,
+ SAM_SERVER_LOOKUP_DOMAIN
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ NtStatus = SamrOpenDomain(
+ ServerHandle,
+ DOMAIN_LOOKUP |
+ DOMAIN_LIST_ACCOUNTS |
+ DOMAIN_READ_PASSWORD_PARAMETERS,
+ SampDefinedDomains[1].Sid,
+ &DomainHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+ //
+ // If cleartext password change is not allowed, we return the error code
+ // indicating that the rpc client should try using the old interfaces.
+ //
+
+ DomainIndex = ((PSAMP_OBJECT) DomainHandle)->DomainIndex;
+ if (SampDefinedDomains[DomainIndex].UnmodifiedFixed.PasswordProperties &
+ DOMAIN_PASSWORD_NO_CLEAR_CHANGE) {
+
+ NtStatus = RPC_NT_UNKNOWN_IF;
+ goto Cleanup;
+ }
+
+ NtStatus = SamrLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ (PRPC_UNICODE_STRING) &UnicodeUserName,
+ &UserId,
+ &SidUse
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ if (NtStatus == STATUS_NONE_MAPPED) {
+ NtStatus = STATUS_NO_SUCH_USER;
+ }
+ goto Cleanup;
+ }
+
+ NtStatus = SamrOpenUser(
+ DomainHandle,
+ USER_CHANGE_PASSWORD,
+ UserId.Element[0],
+ UserHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ goto Cleanup;
+ }
+
+Cleanup:
+ if (DomainHandle != NULL) {
+ SamrCloseHandle(&DomainHandle);
+ }
+ if (ServerHandle != NULL) {
+ SamrCloseHandle(&ServerHandle);
+ }
+ if (UserId.Element != NULL) {
+ MIDL_user_free(UserId.Element);
+ }
+ if (SidUse.Element != NULL) {
+ MIDL_user_free(SidUse.Element);
+ }
+ if (!Unicode && UnicodeUserName.Buffer != NULL) {
+ RtlFreeUnicodeString( &UnicodeUserName );
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampChangePasswordUser2(
+ IN PUNICODE_STRING ServerName,
+ IN PUNICODE_STRING UserName,
+ IN BOOLEAN Unicode,
+ IN BOOLEAN NtPresent,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt,
+ IN PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt,
+ IN BOOLEAN LmPresent,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
+ IN BOOLEAN NtKeyUsed,
+ IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewLmOrNt
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service sets the password to NewPassword only if OldPassword
+ matches the current user password for this user and the NewPassword
+ is not the same as the domain password parameter PasswordHistoryLength
+ passwords. This call allows users to change their own password if
+ they have access USER_CHANGE_PASSWORD. Password update restrictions
+ apply.
+
+
+Parameters:
+
+ ServerName - Name of the machine this SAM resides on. Ignored by this
+ routine, may be UNICODE or OEM string depending on Unicode parameter.
+
+ UserName - User Name of account to change password on, may be UNICODE or
+ OEM depending on Unicode parameter.
+
+ Unicode - Indicated whether the strings passed in are Unicode or OEM
+ strings.
+
+ NtPresent - Are the Nt encrypted passwords present.
+
+ NewEncryptedWithOldNt - The new cleartext password encrypted with the old
+ NT OWF password. Dependinf on the Unicode parameter, the clear text
+ password may be Unicode or OEM.
+
+ OldNtOwfEncryptedWithNewNt - Old NT OWF password encrypted with the new
+ NT OWF password.
+
+ LmPresent - are the Lm encrypted passwords present.
+
+ NewEncryptedWithOldLm - Contains new cleartext password (OEM or Unicode)
+ encrypted with the old LM OWF password
+
+ NtKeyUsed - Indicates whether the LM or NT OWF key was used to encrypt
+ the OldLmOwfEncryptedWithNewlmOrNt parameter.
+
+ OldLmOwfEncryptedWithNewlmOrNt - The old LM OWF password encrypted
+ with either the new LM OWF password or NT OWF password, depending
+ on the NtKeyUsed parameter.
+
+
+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_ILL_FORMED_PASSWORD - The new password is poorly formed,
+ e.g. contains characters that can't be entered from the
+ keyboard, etc.
+
+ STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
+ from being changed. This may be for a number of reasons,
+ including time restrictions on how often a password may be
+ changed or length restrictions on the provided password.
+
+ This error might also be returned if the new password matched
+ a password in the recent history log for the account.
+ Security administrators indicate how many of the most
+ recently used passwords may not be re-used. These are kept
+ in the password recent history log.
+
+ STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
+ current password.
+
+ 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 for this
+ operation
+
+ STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
+ incorrect role (primary or backup) to perform the requested
+ operation.
+
+ STATUS_CROSS_ENCRYPTION_REQUIRED - No NT password is stored, so the caller
+ must provide the OldNtEncryptedWithOldLm parameter.
+
+--*/
+{
+ NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE FoundType;
+ LARGE_INTEGER TimeNow;
+ LM_OWF_PASSWORD StoredLmOwfPassword;
+ NT_OWF_PASSWORD StoredNtOwfPassword;
+ NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword;
+ LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword;
+ UNICODE_STRING NewClearPassword;
+ BOOLEAN LmPasswordPresent;
+ BOOLEAN StoredLmPasswordNonNull;
+ BOOLEAN StoredNtPasswordPresent;
+ BOOLEAN StoredNtPasswordNonNull;
+ BOOLEAN AccountLockedOut;
+ BOOLEAN V1aFixedRetrieved = FALSE;
+ BOOLEAN V1aFixedModified = FALSE;
+ ULONG ObjectRid;
+ UNICODE_STRING AccountName;
+ ULONG UserRid;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ SAMPR_HANDLE UserHandle = NULL;
+
+ //
+ // Initialize variables
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ NewClearPassword.Buffer = NULL;
+ AccountName.Buffer = NULL;
+
+ //
+ // Validate some parameters. We require that one of the two passwords
+ // be present.
+ //
+
+ if (!NtPresent && !LmPresent) {
+
+ return(STATUS_INVALID_PARAMETER_MIX);
+ }
+
+ //
+ // Open the user
+ //
+
+ NtStatus = SampOpenUserInServer(
+ (PUNICODE_STRING) UserName,
+ Unicode,
+ &UserHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Grab the lock
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ SamrCloseHandle(&UserHandle);
+ return(NtStatus);
+ }
+
+
+ //
+ // Get the current time
+ //
+
+ NtStatus = NtQuerySystemTime( &TimeNow );
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ SamrCloseHandle(&UserHandle);
+ return(NtStatus);
+ }
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+ ObjectRid = AccountContext->TypeBody.User.Rid;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ USER_CHANGE_PASSWORD,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ SamrCloseHandle(&UserHandle);
+ return(NtStatus);
+ }
+
+ //
+ // Auditing information
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute( AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // make a copy
+ &AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Auditing information
+ //
+
+ UserRid = AccountContext->TypeBody.User.Rid;
+
+ //
+ // Get a pointer to the domain object
+ //
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+
+ //
+ // Read the old OWF passwords from disk
+ //
+
+ NtStatus = SampRetrieveUserPasswords(
+ AccountContext,
+ &StoredLmOwfPassword,
+ &StoredLmPasswordNonNull,
+ &StoredNtOwfPassword,
+ &StoredNtPasswordPresent,
+ &StoredNtPasswordNonNull
+ );
+
+ //
+ // Check the password can be changed at this time
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Only do the check if one of the passwords is non-null.
+ // A Null password can always be changed.
+ //
+
+ if (StoredNtPasswordNonNull || StoredLmPasswordNonNull) {
+
+
+
+ //
+ // If the min password age is non zero, check it here
+ //
+
+ if (Domain->UnmodifiedFixed.MinPasswordAge.QuadPart != SampHasNeverTime.QuadPart) {
+
+ LARGE_INTEGER PasswordCanChange = SampAddDeltaTime(
+ V1aFixed.PasswordLastSet,
+ Domain->UnmodifiedFixed.MinPasswordAge);
+
+ V1aFixedRetrieved = TRUE;
+
+ if (TimeNow.QuadPart < PasswordCanChange.QuadPart) {
+ NtStatus = STATUS_ACCOUNT_RESTRICTION;
+ }
+ }
+ }
+ }
+ }
+
+
+
+ //
+ // If we have old NtOwf passwords, use them
+ // Decrypt the doubly-encrypted NT passwords sent to us
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (StoredNtPasswordPresent && NtPresent) {
+
+ NtStatus = SampDecryptPasswordWithNtOwfPassword(
+ NewEncryptedWithOldNt,
+ &StoredNtOwfPassword,
+ Unicode,
+ &NewClearPassword
+ );
+
+ } else if (LmPresent) {
+
+ //
+ // There was no stored NT password and NT passed, so our only
+ // hope now is that the stored LM password works.
+ //
+
+ //
+ // Decrypt the new password encrypted with the old LM password
+ //
+
+ NtStatus = SampDecryptPasswordWithLmOwfPassword(
+ NewEncryptedWithOldLm,
+ &StoredLmOwfPassword,
+ Unicode,
+ &NewClearPassword
+ );
+
+
+ } else {
+
+ NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
+
+ }
+ }
+
+
+ //
+ // We now have the cleartext new password.
+ // Compute the new LmOwf and NtOwf password
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCalculateLmAndNtOwfPasswords(
+ &NewClearPassword,
+ &LmPasswordPresent,
+ &NewLmOwfPassword,
+ &NewNtOwfPassword
+ );
+
+ }
+
+ //
+ // If we have both NT passwords, compute the old NT password,
+ // otherwise compute the old LM password
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (StoredNtPasswordPresent && NtPresent) {
+ NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
+ OldNtOwfEncryptedWithNewNt,
+ &NewNtOwfPassword,
+ &OldNtOwfPassword
+ );
+
+ }
+
+ if (LmPresent) {
+
+
+ //
+ // If the NT key was used to encrypt this, use the NT key
+ // to decrypt it.
+ //
+
+
+ if (NtKeyUsed) {
+
+ ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
+
+ NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ OldLmOwfEncryptedWithNewLmOrNt,
+ (PLM_OWF_PASSWORD) &NewNtOwfPassword,
+ &OldLmOwfPassword
+ );
+
+
+ } else if (LmPasswordPresent) {
+
+ NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ OldLmOwfEncryptedWithNewLmOrNt,
+ &NewLmOwfPassword,
+ &OldLmOwfPassword
+ );
+
+
+ } else {
+ NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
+ }
+
+ }
+
+ }
+
+
+ //
+ // Authenticate the password change operation based on what
+ // we have stored and what was passed. We authenticate whatever
+ // passwords were sent .
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (NtPresent && StoredNtPasswordPresent) {
+
+ //
+ // NtPresent = TRUE, we were passed an NT password
+ //
+
+ if (!RtlEqualNtOwfPassword(&OldNtOwfPassword, &StoredNtOwfPassword)) {
+
+ //
+ // Old NT passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ }
+ } else if (LmPresent) {
+
+ //
+ // LM data passed. Use LM data for authentication
+ //
+
+ if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
+
+ //
+ // Old LM passwords didn't match
+ //
+
+ NtStatus = STATUS_WRONG_PASSWORD;
+
+ }
+
+ } else {
+ NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
+ }
+
+ }
+
+ //
+ // Now we should check password restrictions.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampCheckPasswordRestrictions(
+ UserHandle,
+ &NewClearPassword
+ );
+
+ }
+
+ //
+ // Now check our password filter if the account is not a workstation
+ // or server trust account.
+ //
+
+ if (NT_SUCCESS(NtStatus ) &&
+ ((V1aFixed.UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)) == 0 )) {
+
+
+ UNICODE_STRING FullName;
+
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_FULL_NAME,
+ FALSE, // Make copy
+ &FullName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // now see what the filter dll thinks of this password
+ //
+
+ NtStatus = SampPasswordChangeFilter(
+ &AccountName,
+ &FullName,
+ &NewClearPassword,
+ FALSE // change operation
+ );
+
+ }
+ }
+
+
+
+ //
+ // We now have a NewLmOwfPassword and a NewNtOwfPassword.
+ //
+
+ //
+ // Write the new passwords to disk
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // We should always have an LM and an NT password to store.
+ //
+
+
+ NtStatus = SampStoreUserPasswords(
+ AccountContext,
+ &NewLmOwfPassword,
+ LmPasswordPresent,
+ &NewNtOwfPassword,
+ TRUE,
+ TRUE
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // We know the password is not expired.
+ //
+
+ NtStatus = SampStorePasswordExpired(
+ AccountContext,
+ FALSE
+ );
+ }
+ }
+
+
+
+ //
+ // if we have a bad password, then increment the bad password
+ // count and check to see if the account should be locked.
+ //
+
+ if (NtStatus == STATUS_WRONG_PASSWORD) {
+
+ //
+ // Get the V1aFixed so we can update the bad password count
+ //
+
+
+ TmpStatus = STATUS_SUCCESS;
+ if (!V1aFixedRetrieved) {
+ TmpStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ }
+
+ if (!NT_SUCCESS(TmpStatus)) {
+
+ //
+ // If we can't update the V1aFixed, then return this
+ // error so that the user doesn't find out the password
+ // was not correct.
+ //
+
+ NtStatus = TmpStatus;
+
+ } else {
+
+
+ //
+ // Increment BadPasswordCount (might lockout account)
+ //
+
+
+ AccountLockedOut = SampIncrementBadPasswordCount(
+ AccountContext,
+ &V1aFixed
+ );
+
+ V1aFixedModified = TRUE;
+
+
+ }
+ }
+
+ if (V1aFixedModified) {
+ TmpStatus = SampReplaceUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+ }
+
+ //
+ // Dereference the account context
+ //
+
+ if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_WRONG_PASSWORD)) {
+
+
+
+ //
+ // De-reference the object, write out any change to current xaction.
+ //
+
+ TmpStatus = SampDeReferenceContext( AccountContext, TRUE );
+
+ //
+ // retain previous error/success value unless we have
+ // an over-riding error from our dereference.
+ //
+
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+ } else {
+
+ //
+ // De-reference the object, ignore changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ }
+
+ //
+ // Commit changes to disk.
+ //
+
+ if ( NT_SUCCESS(NtStatus) || NtStatus == STATUS_WRONG_PASSWORD) {
+
+ TmpStatus = SampCommitAndRetainWriteLock();
+
+ //
+ // retain previous error/success value unless we have
+ // an over-riding error from our dereference.
+ //
+
+ if (!NT_SUCCESS(TmpStatus)) {
+ NtStatus = TmpStatus;
+ }
+
+ if ( NT_SUCCESS(TmpStatus) ) {
+
+ SampNotifyNetlogonOfDelta(
+ SecurityDbChangePassword,
+ SecurityDbObjectSamUser,
+ ObjectRid,
+ (PUNICODE_STRING) NULL,
+ (DWORD) FALSE, // Don't Replicate immediately
+ NULL // Delta data
+ );
+ }
+ }
+
+ if (SampDoAccountAuditing(AccountContext->DomainIndex)) {
+
+ LsaIAuditSamEvent( NtStatus,
+ SE_AUDITID_USER_PWD_CHANGED, // AuditId
+ Domain->Sid, // Domain SID
+ NULL, // Member Rid (not used)
+ NULL, // Member Sid (not used)
+ &AccountName, // Account Name
+ &Domain->ExternalName, // Domain
+ &UserRid, // Account Rid
+ NULL // Privileges used
+ );
+
+ }
+
+
+
+ //
+ // Release the write lock
+ //
+
+ TmpStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ SamrCloseHandle(&UserHandle);
+
+ //
+ // Notify any notification packages that a password has changed.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ IgnoreStatus = SampPasswordChangeNotify(
+ &AccountName,
+ UserRid,
+ &NewClearPassword
+ );
+
+ } else {
+
+ //
+ // Sleep for three seconds to prevent dictionary attacks.
+ //
+
+ Sleep( 3000 );
+ }
+
+ if (NewClearPassword.Buffer != NULL) {
+
+ RtlZeroMemory(
+ NewClearPassword.Buffer,
+ NewClearPassword.Length
+ );
+
+ }
+
+ if ( Unicode ) {
+
+ SampFreeUnicodeString( &NewClearPassword );
+ } else {
+
+ RtlFreeUnicodeString( &NewClearPassword );
+ }
+
+ SampFreeUnicodeString( &AccountName );
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamrOemChangePasswordUser2(
+ IN handle_t BindingHandle,
+ IN PRPC_STRING ServerName,
+ IN PRPC_STRING UserName,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
+ IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewLm
+ )
+/*++
+
+Routine Description:
+
+ Server side stub for Unicode password change.
+ See SampChangePasswordUser2 for details
+
+Arguments:
+
+
+Return Value:
+
+--*/
+{
+ return(SampChangePasswordUser2(
+ (PUNICODE_STRING) ServerName,
+ (PUNICODE_STRING) UserName,
+ FALSE, // not unicode
+ FALSE, // NT not present
+ NULL, // new NT password
+ NULL, // old NT password
+ TRUE, // LM present
+ NewEncryptedWithOldLm,
+ FALSE, // NT key not used
+ OldLmOwfEncryptedWithNewLm
+ ) );
+
+
+
+}
+
+
+
+
+
+NTSTATUS
+SamrUnicodeChangePasswordUser2(
+ IN handle_t BindingHandle,
+ IN PRPC_UNICODE_STRING ServerName,
+ IN PRPC_UNICODE_STRING UserName,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt,
+ IN PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt,
+ IN BOOLEAN LmPresent,
+ IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
+ IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewNt
+ )
+/*++
+
+Routine Description:
+
+ Server side stub for Unicode password change.
+ See SampChangePasswordUser2 for details
+
+Arguments:
+
+
+Return Value:
+
+--*/
+
+{
+ return(SampChangePasswordUser2(
+ (PUNICODE_STRING) ServerName,
+ (PUNICODE_STRING) UserName,
+ TRUE, // unicode
+ TRUE, // NT present
+ NewEncryptedWithOldNt,
+ OldNtOwfEncryptedWithNewNt,
+ LmPresent,
+ NewEncryptedWithOldLm,
+ TRUE, // NT key used
+ OldLmOwfEncryptedWithNewNt
+ ) );
+}
+
+
+
+NTSTATUS
+SamrGetGroupsForUser(
+ IN SAMPR_HANDLE UserHandle,
+ OUT PSAMPR_GET_GROUPS_BUFFER *Groups
+ )
+
+
+/*++
+
+Routine Description:
+
+ This service returns the list of groups that a user is a member of.
+ It returns a structure for each group that includes the relative ID
+ of the group, and the attributes of the group that are assigned to
+ the user.
+
+ This service requires USER_LIST_GROUPS access to the user account
+ object.
+
+
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ Groups - Receives a pointer to a buffer containing a count of members
+ and a pointer to a second buffer containing an array of
+ GROUP_MEMBERSHIPs data structures. When this information is
+ no longer needed, these buffers must be freed 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 handle passed is invalid.
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (Groups != NULL);
+
+ if ((*Groups) != NULL) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+
+ //
+ // Allocate the first of the return buffers
+ //
+
+ (*Groups) = MIDL_user_allocate( sizeof(SAMPR_GET_GROUPS_BUFFER) );
+
+ if ( (*Groups) == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+ NtStatus = SampLookupContext(
+ AccountContext,
+ USER_LIST_GROUPS,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampRetrieveUserMembership(
+ AccountContext,
+ TRUE, // Make copy
+ &(*Groups)->MembershipCount,
+ &(*Groups)->Groups
+ );
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ (*Groups)->MembershipCount = 0;
+
+ MIDL_user_free( (*Groups) );
+ (*Groups) = NULL;
+ } else {
+ ULONG Index;
+
+ //
+ // We want to return constant group attributes, so go update them
+ // all now.
+ //
+
+ for (Index = 0; Index < (*Groups)->MembershipCount ; Index++ ) {
+ (*Groups)->Groups[Index].Attributes = SAMP_DEFAULT_GROUP_ATTRIBUTES;
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamrGetUserDomainPasswordInformation(
+ IN SAMPR_HANDLE UserHandle,
+ OUT PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
+ )
+
+
+/*++
+
+Routine Description:
+
+ Takes a user handle, finds the domain for that user, and returns
+ password information for the domain. This is so the client\wrappers.c
+ can get the information to verify the user's password before it is
+ OWF'd.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ PasswordInformation - Receives information about password restrictions
+ for the user's domain.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ Other errors may be returned from SampLookupContext() if the handle
+ is invalid or does not indicate proper access to the domain's password
+ inforamtion.
+
+--*/
+{
+ SAMP_OBJECT_TYPE FoundType;
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+
+ SampAcquireReadLock();
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+
+ NtStatus = SampLookupContext(
+ AccountContext,
+ 0,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // When the user was opened, we checked to see if the domain handle
+ // allowed access to the domain password information. Check that here.
+ //
+
+ if ( !( AccountContext->TypeBody.User.DomainPasswordInformationAccessible ) ) {
+
+ NtStatus = STATUS_ACCESS_DENIED;
+
+ } else {
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ //
+ // If the user account is a machine account,
+ // then restrictions are generally not enforced.
+ // This is so that simple initial passwords can be
+ // established. IT IS EXPECTED THAT COMPLEX PASSWORDS,
+ // WHICH MEET THE MOST STRINGENT RESTRICTIONS, WILL BE
+ // AUTOMATICALLY ESTABLISHED AND MAINTAINED ONCE THE MACHINE
+ // JOINS THE DOMAIN. It is the UI's responsibility to
+ // maintain this level of complexity.
+ //
+
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+ if ( (V1aFixed.UserAccountControl &
+ (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
+ != 0 ) {
+
+ PasswordInformation->MinPasswordLength = 0;
+ PasswordInformation->PasswordProperties = 0;
+ } else {
+
+ PasswordInformation->MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
+ PasswordInformation->PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
+ }
+ }
+ }
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ SampReleaseReadLock();
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SamrGetDomainPasswordInformation(
+ IN handle_t BindingHandle,
+ IN OPTIONAL PRPC_UNICODE_STRING ServerName,
+ OUT PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
+ )
+
+
+/*++
+
+Routine Description:
+
+ Takes a user handle, finds the domain for that user, and returns
+ password information for the domain. This is so the client\wrappers.c
+ can get the information to verify the user's password before it is
+ OWF'd.
+
+
+Parameters:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ PasswordInformation - Receives information about password restrictions
+ for the user's domain.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ Other errors may be returned from SampLookupContext() if the handle
+ is invalid or does not indicate proper access to the domain's password
+ inforamtion.
+
+--*/
+{
+ SAMP_OBJECT_TYPE FoundType;
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ SAMPR_HANDLE ServerHandle = NULL;
+ SAMPR_HANDLE DomainHandle = NULL;
+
+ //
+ // Connect to the server and open the account domain for
+ // DOMAIN_READ_PASSWORD_PARAMETERS access.
+ //
+
+ NtStatus = SamrConnect(
+ NULL,
+ &ServerHandle,
+ SAM_SERVER_LOOKUP_DOMAIN
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ NtStatus = SamrOpenDomain(
+ ServerHandle,
+ DOMAIN_READ_PASSWORD_PARAMETERS,
+ SampDefinedDomains[1].Sid,
+ &DomainHandle
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SamrCloseHandle(&ServerHandle);
+ return(NtStatus);
+ }
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // We want to look at the account domain, which is domains[1].
+ //
+
+ Domain = &SampDefinedDomains[1];
+
+ //
+ // Copy the password properites into the returned structure.
+ //
+
+ PasswordInformation->MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
+ PasswordInformation->PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
+
+
+ SampReleaseReadLock();
+
+ SamrCloseHandle(&DomainHandle);
+ SamrCloseHandle(&ServerHandle);
+
+
+ return( NtStatus );
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Private to this process //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamIAccountRestrictions(
+ IN SAM_HANDLE UserHandle,
+ IN PUNICODE_STRING LogonWorkStation,
+ IN PUNICODE_STRING WorkStations,
+ IN PLOGON_HOURS LogonHours,
+ OUT PLARGE_INTEGER LogoffTime,
+ OUT PLARGE_INTEGER KickoffTime
+ )
+
+/*++
+
+Routine Description:
+
+ Validate a user's ability to logon at this time and at the workstation
+ being logged onto.
+
+
+Arguments:
+
+ UserHandle - The handle of an opened user to operate on.
+
+ LogonWorkStation - The name of the workstation the logon is being
+ attempted at.
+
+ WorkStations - The list of workstations the user may logon to. This
+ information comes from the user's account information. It must
+ be in API list format.
+
+ LogonHours - The times the user may logon. This information comes
+ from the user's account information.
+
+ LogoffTime - Receives the time at which the user should logoff the
+ system.
+
+ KickoffTime - Receives the time at which the user should be kicked
+ off the system.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - Logon is permitted.
+
+ STATUS_INVALID_LOGON_HOURS - The user is not authorized to logon at
+ this time.
+
+ STATUS_INVALID_WORKSTATION - The user is not authorized to logon to
+ the specified workstation.
+
+
+--*/
+{
+
+#define MILLISECONDS_PER_WEEK 7 * 24 * 60 * 60 * 1000
+
+ TIME_FIELDS CurrentTimeFields;
+ LARGE_INTEGER CurrentTime, CurrentUTCTime;
+ LARGE_INTEGER MillisecondsIntoWeekXUnitsPerWeek;
+ LARGE_INTEGER LargeUnitsIntoWeek;
+ LARGE_INTEGER Delta100Ns;
+ PSAMP_OBJECT AccountContext;
+ PSAMP_DEFINED_DOMAINS Domain;
+ SAMP_OBJECT_TYPE FoundType;
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ NTSTATUS IgnoreStatus;
+ ULONG CurrentMsIntoWeek;
+ ULONG LogoffMsIntoWeek;
+ ULONG DeltaMs;
+ ULONG MillisecondsPerUnit;
+ ULONG CurrentUnitsIntoWeek;
+ ULONG LogoffUnitsIntoWeek;
+ USHORT i;
+ TIME_ZONE_INFORMATION TimeZoneInformation;
+ DWORD TimeZoneId;
+ LARGE_INTEGER BiasIn100NsUnits;
+ LONG BiasInMinutes;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+
+ SampAcquireReadLock();
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ AccountContext = (PSAMP_OBJECT)UserHandle;
+
+ NtStatus = SampLookupContext(
+ AccountContext,
+ 0L,
+ SampUserObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ AccountContext,
+ &V1aFixed
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Only check for users other than the builtin ADMIN
+ //
+
+ if (V1aFixed.UserId != DOMAIN_USER_RID_ADMIN) {
+
+ //
+ // Scan to make sure the workstation being logged into is in the
+ // list of valid workstations - or if the list of valid workstations
+ // is null, which means that all are valid.
+ //
+
+ NtStatus = SampMatchworkstation( LogonWorkStation, WorkStations );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Check to make sure that the current time is a valid time to logon
+ // in the LogonHours.
+ //
+ // We need to validate the time taking into account whether we are
+ // in daylight savings time or standard time. Thus, if the logon
+ // hours specify that we are able to logon between 9am and 5pm,
+ // this means 9am to 5pm standard time during the standard time
+ // period, and 9am to 5pm daylight savings time when in the
+ // daylight savings time. Since the logon hours stored by SAM are
+ // independent of daylight savings time, we need to add in the
+ // difference between standard time and daylight savings time to
+ // the current time before checking whether this time is a valid
+ // time to logon. Since this difference (or bias as it is called)
+ // is actually held in the form
+ //
+ // Standard time = Daylight savings time + Bias
+ //
+ // the Bias is a negative number. Thus we actually subtract the
+ // signed Bias from the Current Time.
+
+ //
+ // First, get the Time Zone Information.
+ //
+
+ TimeZoneId = GetTimeZoneInformation(
+ (LPTIME_ZONE_INFORMATION) &TimeZoneInformation
+ );
+
+ //
+ // Next, get the appropriate bias (signed integer in minutes) to subtract from
+ // the Universal Time Convention (UTC) time returned by NtQuerySystemTime
+ // to get the local time. The bias to be used depends whether we're
+ // in Daylight Savings time or Standard Time as indicated by the
+ // TimeZoneId parameter.
+ //
+ // local time = UTC time - bias in 100Ns units
+ //
+
+ switch (TimeZoneId) {
+
+ case TIME_ZONE_ID_UNKNOWN:
+
+ //
+ // There is no differentiation between standard and
+ // daylight savings time. Proceed as for Standard Time
+ //
+
+ BiasInMinutes = TimeZoneInformation.StandardBias;
+ break;
+
+ case TIME_ZONE_ID_STANDARD:
+
+ BiasInMinutes = TimeZoneInformation.StandardBias;
+ break;
+
+ case TIME_ZONE_ID_DAYLIGHT:
+
+ BiasInMinutes = TimeZoneInformation.DaylightBias;
+ break;
+
+ default:
+
+ //
+ // Something is wrong with the time zone information. Fail
+ // the logon request.
+ //
+
+ NtStatus = STATUS_INVALID_LOGON_HOURS;
+ break;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Convert the Bias from minutes to 100ns units
+ //
+
+ BiasIn100NsUnits.QuadPart = ((LONGLONG)BiasInMinutes)
+ * 60 * 10000000;
+
+ //
+ // Get the UTC time in 100Ns units used by Windows Nt. This
+ // time is GMT.
+ //
+
+ NtStatus = NtQuerySystemTime( &CurrentUTCTime );
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ CurrentTime.QuadPart = CurrentUTCTime.QuadPart -
+ BiasIn100NsUnits.QuadPart;
+
+ RtlTimeToTimeFields( &CurrentTime, &CurrentTimeFields );
+
+ CurrentMsIntoWeek = (((( CurrentTimeFields.Weekday * 24 ) +
+ CurrentTimeFields.Hour ) * 60 +
+ CurrentTimeFields.Minute ) * 60 +
+ CurrentTimeFields.Second ) * 1000 +
+ CurrentTimeFields.Milliseconds;
+
+ MillisecondsIntoWeekXUnitsPerWeek.QuadPart =
+ ((LONGLONG)CurrentMsIntoWeek) *
+ ((LONGLONG)LogonHours->UnitsPerWeek);
+
+ LargeUnitsIntoWeek = RtlExtendedLargeIntegerDivide(
+ MillisecondsIntoWeekXUnitsPerWeek,
+ MILLISECONDS_PER_WEEK,
+ (PULONG)NULL );
+
+ CurrentUnitsIntoWeek = LargeUnitsIntoWeek.LowPart;
+
+ if ( !( LogonHours->LogonHours[ CurrentUnitsIntoWeek / 8] &
+ ( 0x01 << ( CurrentUnitsIntoWeek % 8 ) ) ) ) {
+
+ NtStatus = STATUS_INVALID_LOGON_HOURS;
+
+ } else {
+
+ //
+ // Determine the next time that the user is NOT supposed to be logged
+ // in, and return that as LogoffTime.
+ //
+
+ i = 0;
+ LogoffUnitsIntoWeek = CurrentUnitsIntoWeek;
+
+ do {
+
+ i++;
+
+ LogoffUnitsIntoWeek = ( LogoffUnitsIntoWeek + 1 ) % LogonHours->UnitsPerWeek;
+
+ } while ( ( i <= LogonHours->UnitsPerWeek ) &&
+ ( LogonHours->LogonHours[ LogoffUnitsIntoWeek / 8 ] &
+ ( 0x01 << ( LogoffUnitsIntoWeek % 8 ) ) ) );
+
+ if ( i > LogonHours->UnitsPerWeek ) {
+
+ //
+ // All times are allowed, so there's no logoff
+ // time. Return forever for both logofftime and
+ // kickofftime.
+ //
+
+ LogoffTime->HighPart = 0x7FFFFFFF;
+ LogoffTime->LowPart = 0xFFFFFFFF;
+
+ KickoffTime->HighPart = 0x7FFFFFFF;
+ KickoffTime->LowPart = 0xFFFFFFFF;
+
+ } else {
+
+ //
+ // LogoffUnitsIntoWeek points at which time unit the
+ // user is to log off. Calculate actual time from
+ // the unit, and return it.
+ //
+ // CurrentTimeFields already holds the current
+ // time for some time during this week; just adjust
+ // to the logoff time during this week and convert
+ // to time format.
+ //
+
+ MillisecondsPerUnit = MILLISECONDS_PER_WEEK / LogonHours->UnitsPerWeek;
+
+ LogoffMsIntoWeek = MillisecondsPerUnit * LogoffUnitsIntoWeek;
+
+ if ( LogoffMsIntoWeek < CurrentMsIntoWeek ) {
+
+ DeltaMs = MILLISECONDS_PER_WEEK - ( CurrentMsIntoWeek - LogoffMsIntoWeek );
+
+ } else {
+
+ DeltaMs = LogoffMsIntoWeek - CurrentMsIntoWeek;
+ }
+
+ Delta100Ns = RtlExtendedIntegerMultiply(
+ RtlConvertUlongToLargeInteger( DeltaMs ),
+ 10000
+ );
+
+ LogoffTime->QuadPart = CurrentUTCTime.QuadPart +
+ Delta100Ns.QuadPart;
+
+ //
+ // Subtract Domain->ForceLogoff from LogoffTime, and return
+ // that as KickoffTime. Note that Domain->ForceLogoff is a
+ // negative delta. If its magnitude is sufficiently large
+ // (in fact, larger than the difference between LogoffTime
+ // and the largest positive large integer), we'll get overflow
+ // resulting in a KickOffTime that is negative. In this
+ // case, reset the KickOffTime to this largest positive
+ // large integer (i.e. "never") value.
+ //
+
+ Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
+
+ KickoffTime->QuadPart = LogoffTime->QuadPart -
+ Domain->UnmodifiedFixed.ForceLogoff.QuadPart;
+
+ if (KickoffTime->QuadPart < 0) {
+
+ KickoffTime->HighPart = 0x7FFFFFFF;
+ KickoffTime->LowPart = 0xFFFFFFFF;
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+
+ //
+ // Never kick administrators off
+ //
+
+ LogoffTime->HighPart = 0x7FFFFFFF;
+ LogoffTime->LowPart = 0xFFFFFFFF;
+ KickoffTime->HighPart = 0x7FFFFFFF;
+ KickoffTime->LowPart = 0xFFFFFFFF;
+ }
+ }
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ SampReleaseReadLock();
+
+ return( NtStatus );
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Private to this file //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampReplaceUserV1aFixed(
+ IN PSAMP_OBJECT Context,
+ IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ )
+
+/*++
+
+Routine Description:
+
+ This service replaces the current V1 fixed length information related to
+ a specified User.
+
+ The change is made to the in-memory object data only.
+
+
+Arguments:
+
+ Context - Points to the account context whose V1_FIXED information is
+ to be replaced.
+
+ V1aFixed - Is a buffer containing the new V1_FIXED information.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been replaced.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampSetFixedAttributes()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ NtStatus = SampSetFixedAttributes(
+ Context,
+ (PVOID)V1aFixed
+ );
+
+ return( NtStatus );
+}
+
+
+
+LARGE_INTEGER
+SampGetPasswordMustChange(
+ IN ULONG UserAccountControl,
+ IN LARGE_INTEGER PasswordLastSet,
+ IN LARGE_INTEGER MaxPasswordAge
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the correct value to set the PasswordMustChange time
+ to depending on the time the password was last set, whether the password
+ expires on the account, and the maximum password age on the domain.
+
+Arguments:
+
+ UserAccountControl - The UserAccountControl for the user. The
+ USER_DONT_EXPIRE_PASSWORD bit is set if the password doesn't expire
+ for this user.
+
+ PasswordLastSet - Time when the password was last set for this user.
+
+ MaxPasswordAge - Maximum password age for any password in the domain.
+
+
+Return Value:
+
+ Returns the time when the password for this user must change.
+
+--*/
+{
+ LARGE_INTEGER PasswordMustChange;
+
+ //
+ // If the password never expires for this user,
+ // return an infinitely large time.
+ //
+
+ if ( UserAccountControl & USER_DONT_EXPIRE_PASSWORD ) {
+
+ PasswordMustChange = SampWillNeverTime;
+
+ //
+ // If the password for this account is flagged to expire immediately,
+ // return a zero time time.
+ //
+ // Don't return the current time here. The callers clock might be a
+ // little off from ours.
+ //
+
+ } else if ( PasswordLastSet.QuadPart == SampHasNeverTime.QuadPart ) {
+
+ PasswordMustChange = SampHasNeverTime;
+
+
+ //
+ // Otherwise compute the expiration time as the time the password was
+ // last set plus the maximum age.
+ //
+
+ } else {
+
+ PasswordMustChange = SampAddDeltaTime(
+ PasswordLastSet,
+ MaxPasswordAge);
+
+ }
+
+ return PasswordMustChange;
+}
+
+
+
+NTSTATUS
+SampComputePasswordExpired(
+ IN BOOLEAN PasswordExpired,
+ OUT PLARGE_INTEGER PasswordLastSet
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the correct value to set the PasswordLastSet time
+ to depending on whether the caller has requested the password to expire.
+ It does this by setting the PasswordLastSet time to be now (if it's
+ not expired) or to SampHasNeverTime (if it is expired).
+
+Arguments:
+
+ PasswordExpired - TRUE if the password should be marked as expired.
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - the PasswordLastSet time has been set to indicate
+ whether or not the password is expired.
+
+ Errors as returned by NtQuerySystemTime.
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ //
+ // If immediate expiry is required - set this timestamp to the
+ // beginning of time. This will work if the domain enforces a
+ // maximum password age. We may have to add a separate flag to
+ // the database later if immediate expiry is required on a domain
+ // that doesn't enforce a maximum password age.
+ //
+
+ if (PasswordExpired) {
+
+ //
+ // Set password last changed at dawn of time
+ //
+
+ *PasswordLastSet = SampHasNeverTime;
+ NtStatus = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // Set password last changed 'now'
+ //
+
+ NtStatus = NtQuerySystemTime( PasswordLastSet );
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampStorePasswordExpired(
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN PasswordExpired
+ )
+
+/*++
+
+Routine Description:
+
+ This routine marks the current password as expired, or not expired.
+ It does this by setting the PasswordLastSet time to be now (if it's
+ not expired) or to SampHasNeverTime (if it is expired).
+
+Arguments:
+
+ Context - Points to the user account context.
+
+ PasswordExpired - TRUE if the password should be marked as expired.
+
+Return Value:
+
+ STATUS_SUCCESS - the PasswordLastSet time has been set to indicate
+ whether or not the password is expired.
+
+ Errors as returned by Samp{Retrieve|Replace}V1Fixed()
+
+--*/
+{
+ NTSTATUS NtStatus;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+
+ //
+ // Get the V1aFixed info for the user
+ //
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ Context,
+ &V1aFixed
+ );
+
+ //
+ // Update the password-last-changed timestamp for the account
+ //
+
+ if (NT_SUCCESS(NtStatus ) ) {
+
+ NtStatus = SampComputePasswordExpired(
+ PasswordExpired,
+ &V1aFixed.PasswordLastSet );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampReplaceUserV1aFixed(
+ Context,
+ &V1aFixed
+ );
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampStoreUserPasswords(
+ IN PSAMP_OBJECT Context,
+ IN PLM_OWF_PASSWORD LmOwfPassword,
+ IN BOOLEAN LmPasswordPresent,
+ IN PNT_OWF_PASSWORD NtOwfPassword,
+ IN BOOLEAN NtPasswordPresent,
+ IN BOOLEAN CheckHistory
+ )
+
+/*++
+
+Routine Description:
+
+ This service updates the password for the specified user.
+
+ This involves encrypting the one-way-functions of both LM and NT
+ passwords with a suitable index and writing them into the registry.
+
+ This service checks the new password for legality including history
+ and UAS compatibilty checks - returns STATUS_PASSWORD_RESTRICTION if
+ any of these checks fail.
+
+ The password-last-changed time is updated.
+
+ THE CHANGE WILL BE ADDED TO THE CURRENT RXACT TRANSACTION.
+
+
+Arguments:
+
+ Context - Points to the user account context.
+
+ LmOwfPassword - The one-way-function of the LM password.
+
+ LmPasswordPresent - TRUE if the LmOwfPassword contains valid information.
+
+ NtOwfPassword - The one-way-function of the NT password.
+
+ NtPasswordPresent - TRUE if the NtOwfPassword contains valid information.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The passwords have been updated.
+
+ STATUS_PASSWORD_RESTRICTION - The new password is not valid for
+ for this account at this time.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG ObjectRid = Context->TypeBody.User.Rid;
+ CRYPT_INDEX CryptIndex;
+ PSAMP_DEFINED_DOMAINS Domain;
+ UNICODE_STRING StringBuffer;
+ UNICODE_STRING NtOwfHistoryBuffer;
+ UNICODE_STRING LmOwfHistoryBuffer;
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ BOOLEAN NtPasswordNull, LmPasswordNull;
+
+ //
+ // Get the V1aFixed info for the user
+ //
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ Context,
+ &V1aFixed
+ );
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ return (NtStatus);
+ }
+
+ //
+ // Get a pointer to the in-memory domain info
+ //
+
+ Domain = &SampDefinedDomains[ Context->DomainIndex ];
+
+
+
+
+ //
+ // Check for a LM Owf of a NULL password.
+ //
+
+ if (LmPasswordPresent) {
+ LmPasswordNull = RtlEqualNtOwfPassword(LmOwfPassword, &SampNullLmOwfPassword);
+ }
+
+ //
+ // Check for a NT Owf of a NULL password
+ //
+
+ if (NtPasswordPresent) {
+ NtPasswordNull = RtlEqualNtOwfPassword(NtOwfPassword, &SampNullNtOwfPassword);
+ }
+
+
+
+ //
+ // Check password against restrictions if this isn't a trusted client
+ //
+
+ if (NT_SUCCESS(NtStatus) && !Context->TrustedClient) {
+
+ //
+ // If we have neither an NT or LM password, check it's allowed
+ //
+
+ if ( ((!LmPasswordPresent) || LmPasswordNull) &&
+ ((!NtPasswordPresent) || NtPasswordNull) ) {
+
+ if ( (!(V1aFixed.UserAccountControl & USER_PASSWORD_NOT_REQUIRED))
+ && (Domain->UnmodifiedFixed.MinPasswordLength > 0) ) {
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+
+ //
+ // If we have a complex NT password (no LM equivalent), check it's allowed
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ((!LmPasswordPresent || LmPasswordNull) &&
+ (NtPasswordPresent && !NtPasswordNull) ) {
+
+ if (Domain->UnmodifiedFixed.UasCompatibilityRequired) {
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+ }
+ }
+
+
+
+ //
+ // Reencrypt both OWFs with the key for this user
+ // so they can be stored on disk
+ //
+ // Note we encrypt the NULL OWF if we do not have a
+ // a particular OWF. This is so we always have something
+ // to add to the password history.
+ //
+
+ //
+ // We'll use the account rid as the encryption index
+ //
+
+ ASSERT(sizeof(ObjectRid) == sizeof(CryptIndex));
+ CryptIndex = ObjectRid;
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlEncryptLmOwfPwdWithIndex(
+ LmPasswordPresent ? LmOwfPassword :
+ &SampNullLmOwfPassword,
+ &CryptIndex,
+ &EncryptedLmOwfPassword
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlEncryptNtOwfPwdWithIndex(
+ NtPasswordPresent ? NtOwfPassword :
+ &SampNullNtOwfPassword,
+ &CryptIndex,
+ &EncryptedNtOwfPassword
+ );
+ }
+
+
+
+ //
+ // Check password against password history IF client isn't trusted.
+ // If client is trusted, it's not the user changing a password but
+ // perhaps replication resetting the password from another controller,
+ // and the password may well be in the password history but we don't
+ // want to return error.
+ //
+ // Note we don't check NULL passwords against history
+ //
+
+ NtOwfHistoryBuffer.Buffer = NULL;
+ NtOwfHistoryBuffer.MaximumLength = NtOwfHistoryBuffer.Length = 0;
+
+ LmOwfHistoryBuffer.Buffer = NULL;
+ LmOwfHistoryBuffer.MaximumLength = LmOwfHistoryBuffer.Length = 0;
+
+
+ if (NT_SUCCESS(NtStatus) && !Context->TrustedClient) {
+
+ //
+ // Always go get the existing password history.
+ // We'll use these history buffers when we save the new history
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_LM_PWD_HISTORY,
+ TRUE, // Make copy
+ &LmOwfHistoryBuffer
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_NT_PWD_HISTORY,
+ TRUE, // Make copy
+ &NtOwfHistoryBuffer
+ );
+ }
+
+
+ if (NT_SUCCESS(NtStatus) && LmPasswordPresent && !LmPasswordNull) {
+
+ NtStatus = SampCheckPasswordHistory(
+ &EncryptedLmOwfPassword,
+ ENCRYPTED_LM_OWF_PASSWORD_LENGTH,
+ Domain->UnmodifiedFixed.PasswordHistoryLength,
+ SAMP_USER_LM_PWD_HISTORY,
+ Context,
+ CheckHistory,
+ &LmOwfHistoryBuffer
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus) && NtPasswordPresent && !NtPasswordNull) {
+
+ NtStatus = SampCheckPasswordHistory(
+ &EncryptedNtOwfPassword,
+ ENCRYPTED_NT_OWF_PASSWORD_LENGTH,
+ Domain->UnmodifiedFixed.PasswordHistoryLength,
+ SAMP_USER_NT_PWD_HISTORY,
+ Context,
+ CheckHistory,
+ &NtOwfHistoryBuffer
+ );
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus ) ) {
+
+ //
+ // Write the encrypted LM OWF password into the database
+ //
+
+ if (!LmPasswordPresent || LmPasswordNull) {
+ StringBuffer.Buffer = NULL;
+ StringBuffer.Length = 0;
+ } else {
+ StringBuffer.Buffer = (PWCHAR)&EncryptedLmOwfPassword;
+ StringBuffer.Length = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
+ }
+ StringBuffer.MaximumLength = StringBuffer.Length;
+
+
+ //
+ // Write the encrypted LM OWF password into the registry
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_DBCS_PWD,
+ &StringBuffer
+ );
+ }
+
+
+
+
+ if (NT_SUCCESS(NtStatus ) ) {
+
+ //
+ // Write the encrypted NT OWF password into the database
+ //
+
+ if (!NtPasswordPresent) {
+ StringBuffer.Buffer = NULL;
+ StringBuffer.Length = 0;
+ } else {
+ StringBuffer.Buffer = (PWCHAR)&EncryptedNtOwfPassword;
+ StringBuffer.Length = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
+ }
+ StringBuffer.MaximumLength = StringBuffer.Length;
+
+
+ //
+ // Write the encrypted NT OWF password into the registry
+ //
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_UNICODE_PWD,
+ &StringBuffer
+ );
+ }
+
+ //
+ // Update the password history for this account.
+ //
+ // If both passwords are NULL then don't bother adding
+ // them to the history. Note that if either is non-NULL
+ // we add both. This is to avoid the weird case where a user
+ // changes password many times from a LM machine, then tries
+ // to change password from an NT machine and is told they
+ // cannot use the password they last set from NT (possibly
+ // many years ago.)
+ //
+ // Also, don't bother with the password history if the client is
+ // trusted. Trusted clients will set the history via SetPrivateData().
+ // Besides, we didn't get the old history buffer in the trusted
+ // client case above.
+ //
+
+ if ( (NT_SUCCESS(NtStatus)) && (!Context->TrustedClient) ) {
+
+ if ((LmPasswordPresent && !LmPasswordNull) ||
+ (NtPasswordPresent && !NtPasswordNull)) {
+
+ NtStatus = SampAddPasswordHistory(
+ Context,
+ SAMP_USER_LM_PWD_HISTORY,
+ &LmOwfHistoryBuffer,
+ &EncryptedLmOwfPassword,
+ ENCRYPTED_LM_OWF_PASSWORD_LENGTH,
+ Domain->UnmodifiedFixed.PasswordHistoryLength
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampAddPasswordHistory(
+ Context,
+ SAMP_USER_NT_PWD_HISTORY,
+ &NtOwfHistoryBuffer,
+ &EncryptedNtOwfPassword,
+ ENCRYPTED_NT_OWF_PASSWORD_LENGTH,
+ Domain->UnmodifiedFixed.PasswordHistoryLength
+ );
+ }
+ }
+ }
+
+ //
+ // Clean up our history buffers
+ //
+
+ if (NtOwfHistoryBuffer.Buffer != NULL ) {
+ MIDL_user_free(NtOwfHistoryBuffer.Buffer );
+ }
+ if (LmOwfHistoryBuffer.Buffer != NULL ) {
+ MIDL_user_free(LmOwfHistoryBuffer.Buffer );
+ }
+
+ return(NtStatus );
+}
+
+
+
+NTSTATUS
+SampRetrieveUserPasswords(
+ IN PSAMP_OBJECT Context,
+ OUT PLM_OWF_PASSWORD LmOwfPassword,
+ OUT PBOOLEAN LmPasswordNonNull,
+ OUT PNT_OWF_PASSWORD NtOwfPassword,
+ OUT PBOOLEAN NtPasswordPresent,
+ OUT PBOOLEAN NtPasswordNonNull
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves the stored OWF passwords for a user.
+
+
+Arguments:
+
+ Context - Points to the user account context.
+
+ LmOwfPassword - The one-way-function of the LM password is returned here.
+
+ LmPasswordNonNull - TRUE if the LmOwfPassword is not the well-known
+ OWF of a NULL password
+
+ NtOwfPassword - The one-way-function of the NT password is returned here.
+
+ NtPasswordPresent - TRUE if the NtOwfPassword contains valid information.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The passwords were retrieved successfully.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ ULONG ObjectRid = Context->TypeBody.User.Rid;
+ UNICODE_STRING StringBuffer;
+ CRYPT_INDEX CryptIndex;
+
+ //
+ // The OWF passwords are encrypted with the account index in the registry
+ // Setup the key we'll use for decryption.
+ //
+
+ ASSERT(sizeof(ObjectRid) == sizeof(CryptIndex));
+ CryptIndex = ObjectRid;
+
+
+
+ //
+ // Read the encrypted LM OWF password from the database
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_DBCS_PWD,
+ TRUE, // Make copy
+ &StringBuffer
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ return (NtStatus);
+ }
+
+ //
+ // Check it is in the expected form
+ //
+
+ ASSERT( (StringBuffer.Length == 0) ||
+ (StringBuffer.Length == ENCRYPTED_LM_OWF_PASSWORD_LENGTH));
+
+ //
+ // Determine if there is an LM password.
+ //
+
+ *LmPasswordNonNull = (BOOLEAN)(StringBuffer.Length != 0);
+
+ //
+ // Decrypt the encrypted LM Owf Password
+ //
+
+ if (*LmPasswordNonNull) {
+
+ NtStatus = RtlDecryptLmOwfPwdWithIndex(
+ (PENCRYPTED_LM_OWF_PASSWORD)StringBuffer.Buffer,
+ &CryptIndex,
+ LmOwfPassword
+ );
+ } else {
+
+ //
+ // Fill in the NULL password for caller convenience
+ //
+
+ *LmOwfPassword = SampNullLmOwfPassword;
+ }
+
+
+ //
+ // Free up the returned string buffer
+ //
+
+ SampFreeUnicodeString(&StringBuffer);
+
+
+ //
+ // Check if the decryption failed
+ //
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ return (NtStatus);
+ }
+
+
+
+
+ //
+ // Read the encrypted NT OWF password from the database
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_UNICODE_PWD,
+ TRUE, // Make copy
+ &StringBuffer
+ );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+ return (NtStatus);
+ }
+
+ //
+ // Check it is in the expected form
+ //
+
+ ASSERT( (StringBuffer.Length == 0) ||
+ (StringBuffer.Length == ENCRYPTED_NT_OWF_PASSWORD_LENGTH));
+
+ //
+ // Determine if there is an Nt password.
+ //
+
+ *NtPasswordPresent = (BOOLEAN)(StringBuffer.Length != 0);
+
+ //
+ // Decrypt the encrypted NT Owf Password
+ //
+
+ if (*NtPasswordPresent) {
+
+ NtStatus = RtlDecryptNtOwfPwdWithIndex(
+ (PENCRYPTED_NT_OWF_PASSWORD)StringBuffer.Buffer,
+ &CryptIndex,
+ NtOwfPassword
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ *NtPasswordNonNull = (BOOLEAN)!RtlEqualNtOwfPassword(
+ NtOwfPassword,
+ &SampNullNtOwfPassword
+ );
+ }
+
+ } else {
+
+ //
+ // Fill in the NULL password for caller convenience
+ //
+
+ *NtOwfPassword = SampNullNtOwfPassword;
+ *NtPasswordNonNull = FALSE;
+ }
+
+ //
+ // Free up the returned string buffer
+ //
+
+ SampFreeUnicodeString(&StringBuffer);
+
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampRetrieveUserMembership(
+ IN PSAMP_OBJECT UserContext,
+ IN BOOLEAN MakeCopy,
+ OUT PULONG MembershipCount,
+ OUT PGROUP_MEMBERSHIP *Membership OPTIONAL
+ )
+
+/*++
+Routine Description:
+
+ This service retrieves the number of groups a user is a member of.
+ If desired, it will also retrieve an array of RIDs and attributes
+ of the groups the user is a member of.
+
+
+Arguments:
+
+ UserContext - User context block
+
+ MakeCopy - If FALSE, the Membership pointer returned refers to the
+ in-memory data for the user. This is only valid as long
+ as the user context is valid.
+ If TRUE, memory is allocated and the membership list copied
+ into it. This buffer should be freed using MIDL_user_free.
+
+ MembershipCount - Receives the number of groups the user is a member of.
+
+ Membership - (Otional) Receives a pointer to a buffer containing an array
+ of group Relative IDs. If this value is NULL, then this information
+ is not returned. The returned buffer is allocated using
+ MIDL_user_allocate() and must be freed using MIDL_user_free() when
+ no longer needed.
+
+ If MakeCopy = TRUE, the membership buffer returned has extra space
+ allocated at the end of it for one more membership entry.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ information to be returned in.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampGetLargeIntArrayAttribute()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PGROUP_MEMBERSHIP MemberArray;
+ ULONG MemberCount;
+
+
+ NtStatus = SampGetLargeIntArrayAttribute(
+ UserContext,
+ SAMP_USER_GROUPS,
+ FALSE, //Reference data directly.
+ (PLARGE_INTEGER *)&MemberArray,
+ &MemberCount
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Fill in return info
+ //
+
+ *MembershipCount = MemberCount;
+
+ if (Membership != NULL) {
+
+ if (MakeCopy) {
+
+ //
+ // Allocate a buffer large enough to hold the existing
+ // membership data and one more and copy data into it.
+ //
+
+ ULONG BytesNow = (*MembershipCount) * sizeof(GROUP_MEMBERSHIP);
+ ULONG BytesRequired = BytesNow + sizeof(GROUP_MEMBERSHIP);
+
+ *Membership = MIDL_user_allocate(BytesRequired);
+
+ if (*Membership == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ RtlCopyMemory(*Membership, MemberArray, BytesNow);
+ }
+
+ } else {
+
+ //
+ // Reference the data directly
+ //
+
+ *Membership = (PGROUP_MEMBERSHIP)MemberArray;
+ }
+ }
+ }
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampReplaceUserMembership(
+ IN PSAMP_OBJECT UserContext,
+ IN ULONG MembershipCount,
+ IN PGROUP_MEMBERSHIP Membership
+ )
+
+/*++
+Routine Description:
+
+ This service sets the groups a user is a member of.
+
+ The information is updated in the in-memory copy of the user's data only.
+ The data is not written out by this routine.
+
+
+Arguments:
+
+ UserContext - User context block
+
+ MembershipCount - The number of groups the user is a member of.
+
+ Membership - A pointer to a buffer containing an array of group
+ membership structures. May be NULL if membership count is zero.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been set.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampSetUlongArrayAttribute()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+ NtStatus = SampSetLargeIntArrayAttribute(
+ UserContext,
+ SAMP_USER_GROUPS,
+ (PLARGE_INTEGER)Membership,
+ MembershipCount
+ );
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampRetrieveUserLogonHours(
+ IN PSAMP_OBJECT Context,
+ IN PLOGON_HOURS LogonHours
+ )
+
+/*++
+Routine Description:
+
+ This service retrieves a user's logon hours from the registry.
+
+
+Arguments:
+
+ Context - Points to the user account context whose logon hours are
+ to be retrieved.
+
+ LogonHours - Receives the logon hours information. If necessary, a buffer
+ containing the logon time restriction bitmap will be allocated using
+ MIDL_user_allocate().
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ information to be returned in.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+
+ NtStatus = SampGetLogonHoursAttribute(
+ Context,
+ SAMP_USER_LOGON_HOURS,
+ TRUE, // Make copy
+ LogonHours
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //////////////////////////////// TEMPORARY MIDL WORKAROUND ///////////
+ ///////////
+ if (LogonHours->LogonHours == NULL) { ///////////
+ ///////////
+ LogonHours->UnitsPerWeek = SAM_HOURS_PER_WEEK; ///////////
+ LogonHours->LogonHours = MIDL_user_allocate( 21 ); ///////////
+ { ///////////
+ ULONG ijk; ///////////
+ for ( ijk=0; ijk<21; ijk++ ) { ///////////
+ LogonHours->LogonHours[ijk] = 0xff; ///////////
+ } ///////////
+ } ///////////
+ } ///////////
+ ///////////
+ //////////////////////////////// TEMPORARY MIDL WORKAROUND ///////////
+ }
+
+ return( NtStatus );
+
+}
+
+
+
+
+NTSTATUS
+SampReplaceUserLogonHours(
+ IN PSAMP_OBJECT Context,
+ IN PLOGON_HOURS LogonHours
+ )
+
+/*++
+Routine Description:
+
+ This service replaces a user's logon hours in the registry.
+
+ THIS IS DONE BY ADDING AN ACTION TO THE CURRENT RXACT TRANSACTION.
+
+
+Arguments:
+
+ Context - Points to the user account context whose logon hours are
+ to be replaced.
+
+ LogonHours - Provides the new logon hours.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned are those returned
+ by:
+
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ if ( LogonHours->UnitsPerWeek > SAM_MINUTES_PER_WEEK ) {
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+
+ NtStatus = SampSetLogonHoursAttribute(
+ Context,
+ SAMP_USER_LOGON_HOURS,
+ LogonHours
+ );
+
+ return( NtStatus );
+
+
+}
+
+
+
+
+NTSTATUS
+SampAssignPrimaryGroup(
+ IN PSAMP_OBJECT Context,
+ IN ULONG GroupRid
+ )
+
+
+/*++
+Routine Description:
+
+ This service ensures a user is a member of the specified group.
+
+
+
+Arguments:
+
+ Context - Points to the user account context whose primary group is
+ being changed.
+
+ GroupRid - The RID of the group being assigned as primary group.
+ The user must be a member of this group.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to perform
+ the operation.
+
+ STATUS_MEMBER_NOT_IN_GROUP - The user is not a member of the specified
+ group.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampRetrieveUserMembership()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG MembershipCount, i;
+ PGROUP_MEMBERSHIP Membership;
+ BOOLEAN Member = FALSE;
+
+
+ NtStatus = SampRetrieveUserMembership(
+ Context,
+ FALSE, // Make copy
+ &MembershipCount,
+ &Membership
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
+ for ( i=0; i<MembershipCount; i++) {
+ if (GroupRid == Membership[i].RelativeId) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Provided for use by other SAM modules //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampRetrieveUserV1aFixed(
+ IN PSAMP_OBJECT UserContext,
+ OUT PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves the V1 fixed length information related to
+ a specified User.
+
+ It updates the ACCOUNT_AUTO_LOCKED flag in the AccountControl field
+ as appropriate while retrieving the data.
+
+
+Arguments:
+
+ UserContext - User context handle
+
+ V1aFixed - Points to a buffer into which V1_FIXED information is to be
+ retrieved.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ V1aFixed - Is a buffer into which the information is to be returned.
+
+ Other status values that may be returned are those returned
+ by:
+
+ SampGetFixedAttributes()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PVOID FixedData;
+ BOOLEAN IgnoreState;
+
+
+ NtStatus = SampGetFixedAttributes(
+ UserContext,
+ FALSE, // Don't copy
+ &FixedData
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Copy data into return buffer
+ //
+
+ RtlMoveMemory(
+ V1aFixed,
+ FixedData,
+ sizeof(SAMP_V1_0A_FIXED_LENGTH_USER)
+ );
+
+ //
+ // Update the account lockout flag (might need to be turned off)
+ //
+
+ SampUpdateAccountLockedOutFlag( UserContext,
+ V1aFixed,
+ &IgnoreState );
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+NTSTATUS
+SampRetrieveUserGroupAttribute(
+ IN ULONG UserRid,
+ IN ULONG GroupRid,
+ OUT PULONG Attribute
+ )
+
+/*++
+
+Routine Description:
+
+ This service retrieves the Attribute of the specified group as assigned
+ to the specified user account. This routine is used by group apis that
+ don't have a user context available.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ UserRid - The relative ID of the user the group is assigned to.
+
+ GroupRid - The relative ID of the assigned group.
+
+ Attribute - Receives the Attributes of the group as they are assigned
+ to the user.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+ STATUS_INTERNAL_DB_CORRUPTION - The user does not exist or the group
+ was not in the user's list of memberships.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT UserContext;
+ ULONG MembershipCount;
+ PGROUP_MEMBERSHIP Membership;
+ ULONG i;
+ BOOLEAN AttributeFound;
+
+
+ //
+ // Get a context handle for the user
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ UserRid,
+ TRUE, // We're trusted
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now we have a user context, get the user's group/alias membership
+ //
+
+ NtStatus = SampRetrieveUserMembership(
+ UserContext,
+ FALSE, // Make copy
+ &MembershipCount,
+ &Membership
+ );
+
+ //
+ // Search the list of groups for a match and return
+ // the corresponding attribute.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ AttributeFound = FALSE;
+ for ( i=0; (i<MembershipCount && !AttributeFound); i++) {
+ if (GroupRid == Membership[i].RelativeId) {
+ (*Attribute) = Membership[i].Attributes;
+ AttributeFound = TRUE;
+ }
+ }
+ }
+
+ //
+ // Clean up the user context
+ //
+
+ SampDeleteContext(UserContext);
+ }
+
+
+ if (NT_SUCCESS(NtStatus) && !AttributeFound) {
+ NtStatus = STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+
+ return( NtStatus );
+
+}
+
+
+NTSTATUS
+SampAddGroupToUserMembership(
+ IN ULONG GroupRid,
+ IN ULONG Attributes,
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA AdminGroup,
+ IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
+ OUT PBOOLEAN UserActive
+ )
+
+/*++
+
+Routine Description:
+
+ This service adds the specified group to the user's membership
+ list. It is not assumed that the caller knows anything about
+ the target user. In particular, the caller doesn't know whether
+ the user exists or not, nor whether the user is already a member
+ of the group.
+
+ If the GroupRid is DOMAIN_GROUP_RID_ADMINS, then this service
+ will also indicate whether the user account is currently active.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ GroupRid - The relative ID of the group.
+
+ Attributes - The group attributes as the group is assigned to the
+ user.
+
+ UserRid - The relative ID of the user.
+
+ AdminGroup - Indicates whether the group the user is being
+ added to is an administrator group (that is, directly
+ or indirectly a member of the Administrators alias).
+
+ OperatorGroup - Indicates whether the group the user is being
+ added to is an operator group (that is, directly
+ or indirectly a member of the Account Operators, Print
+ Operators, Backup Operators, or Server Operators aliases)
+
+ UserActive - is the address of a BOOLEAN to be set to indicate
+ whether the user account is currently active. TRUE indicates
+ the account is active. This value will only be set if the
+ GroupRid is DOMAIN_GROUP_RID_ADMINS.
+
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_NO_SUCH_USER - The user does not exist.
+
+ STATUS_MEMBER_IN_GROUP - The user is already a member of the
+ specified group.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT UserContext;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ ULONG MembershipCount;
+ PGROUP_MEMBERSHIP Membership;
+ ULONG i;
+
+ //
+ // Get a context handle for the user
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ UserRid,
+ TRUE, // We're trusted
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If this group is in the Administrators alias
+ // or we are the Domain Administrator group, then
+ // get the V1aFixed data.
+ //
+
+ if ((AdminGroup == AddToAdmin) || (OperatorGroup == AddToAdmin)) {
+ NtStatus = SampRetrieveUserV1aFixed(
+ UserContext,
+ &V1aFixed
+ );
+ }
+
+ //
+ // If necessary, return an indication as to whether this account
+ // is enabled or not.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (GroupRid == DOMAIN_GROUP_RID_ADMINS) {
+
+ ASSERT(AdminGroup == AddToAdmin); // Make sure we retrieved the V1aFixed
+
+ if ((V1aFixed.UserAccountControl & USER_ACCOUNT_DISABLED) == 0) {
+ (*UserActive) = TRUE;
+ } else {
+ (*UserActive) = FALSE;
+ }
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the user is being added to an ADMIN group, modify
+ // the user's ACLs so that account operators can once again
+ // alter the account. This will only occur if the user
+ // is no longer a member of any admin groups.
+ //
+
+ if ((AdminGroup == AddToAdmin) || (OperatorGroup == AddToAdmin)) {
+ NtStatus = SampChangeOperatorAccessToUser2(
+ UserContext,
+ &V1aFixed,
+ AdminGroup,
+ OperatorGroup
+ );
+ }
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the user membership
+ // Note the returned buffer already includes space for
+ // an extra member.
+ //
+
+ NtStatus = SampRetrieveUserMembership(
+ UserContext,
+ TRUE, // Make copy
+ &MembershipCount,
+ &Membership
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // See if the user is already a member ...
+ //
+
+ for (i = 0; i<MembershipCount ; i++ ) {
+ if ( Membership[i].RelativeId == GroupRid )
+ {
+ NtStatus = STATUS_MEMBER_IN_GROUP;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the groups's RID to the end.
+ //
+
+ Membership[MembershipCount].RelativeId = GroupRid;
+ Membership[MembershipCount].Attributes = Attributes;
+ MembershipCount += 1;
+
+ //
+ // Set the user's new membership
+ //
+
+ NtStatus = SampReplaceUserMembership(
+ UserContext,
+ MembershipCount,
+ Membership
+ );
+ }
+
+ //
+ // Free up the membership array
+ //
+
+ MIDL_user_free( Membership );
+ }
+ }
+
+ //
+ // Write out any changes to the user account
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
+ }
+
+ //
+ // Clean up the user context
+ //
+
+ SampDeleteContext(UserContext);
+ }
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampRemoveMembershipUser(
+ IN ULONG GroupRid,
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA AdminGroup,
+ IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
+ OUT PBOOLEAN UserActive
+ )
+
+/*++
+
+Routine Description:
+
+ This service removes the specified group from the user's membership
+ list. It is not assumed that the caller knows anything about
+ the target user. In particular, the caller doesn't know whether
+ the user exists or not, nor whether the user is really a member
+ of the group.
+
+ If the GroupRid is DOMAIN_GROUP_RID_ADMINS, then this service
+ will also indicate whether the user account is currently active.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ GroupRid - The relative ID of the group.
+
+ UserRid - The relative ID of the user.
+
+ AdminGroup - Indicates whether the group the user is being
+ removed from is an administrator group (that is, directly
+ or indirectly a member of the Administrators alias).
+
+ OperatorGroup - Indicates whether the group the user is being
+ added to is an operator group (that is, directly
+ or indirectly a member of the Account Operators, Print
+ Operators, Backup Operators, or Server Operators aliases)
+
+ UserActive - is the address of a BOOLEAN to be set to indicate
+ whether the user account is currently active. TRUE indicates
+ the account is active. This value will only be set if the
+ GroupRid is DOMAIN_GROUP_RID_ADMINS.
+
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_NO_SUCH_USER - The user does not exist.
+
+ STATUS_MEMBER_NOT_IN_GROUP - The user is not a member of the
+ specified group.
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG MembershipCount, i;
+ PGROUP_MEMBERSHIP MembershipArray;
+ SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
+ PSAMP_OBJECT UserContext;
+
+ //
+ // Create a context for the user
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ UserRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ //
+ // Get the v1 fixed information
+ // (contains primary group value and control flags)
+ //
+
+ NtStatus = SampRetrieveUserV1aFixed( UserContext, &V1aFixed );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the user is being removed from an ADMIN group, modify
+ // the user's ACLs so that account operators can once again
+ // alter the account. This will only occur if the user
+ // is no longer a member of any admin groups.
+ //
+
+ if ((AdminGroup == RemoveFromAdmin) ||
+ (OperatorGroup == RemoveFromAdmin)) {
+ NtStatus = SampChangeOperatorAccessToUser2(
+ UserContext,
+ &V1aFixed,
+ AdminGroup,
+ OperatorGroup
+ );
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If necessary, return an indication as to whether this account
+ // is enabled or not.
+ //
+
+ if (GroupRid == DOMAIN_GROUP_RID_ADMINS) {
+
+ if ((V1aFixed.UserAccountControl & USER_ACCOUNT_DISABLED) == 0) {
+ (*UserActive) = TRUE;
+ } else {
+ (*UserActive) = FALSE;
+ }
+ }
+
+
+ //
+ // See if this is the user's primary group...
+ //
+
+ if (GroupRid == V1aFixed.PrimaryGroupId) {
+ NtStatus = STATUS_MEMBERS_PRIMARY_GROUP;
+ }
+
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the user membership
+ //
+
+ NtStatus = SampRetrieveUserMembership(
+ UserContext,
+ TRUE, // Make copy
+ &MembershipCount,
+ &MembershipArray
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // See if the user is a member ...
+ //
+
+ NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
+ for (i = 0; i<MembershipCount ; i++ ) {
+ if ( MembershipArray[i].RelativeId == GroupRid )
+ {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Replace the removed group information
+ // with the last entry's information.
+ //
+
+ MembershipCount -= 1;
+ if (MembershipCount > 0) {
+ MembershipArray[i].RelativeId =
+ MembershipArray[MembershipCount].RelativeId;
+ MembershipArray[i].Attributes =
+ MembershipArray[MembershipCount].Attributes;
+ }
+
+ //
+ // Update the object with the new information
+ //
+
+ NtStatus = SampReplaceUserMembership(
+ UserContext,
+ MembershipCount,
+ MembershipArray
+ );
+ }
+
+ //
+ // Free up the membership array
+ //
+
+ MIDL_user_free( MembershipArray );
+ }
+ }
+ }
+ }
+
+
+ //
+ // Write out any changes to the user account
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
+ }
+
+
+ //
+ // Clean up the user context
+ //
+
+ SampDeleteContext(UserContext);
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampSetGroupAttributesOfUser(
+ IN ULONG GroupRid,
+ IN ULONG Attributes,
+ IN ULONG UserRid
+ )
+
+/*++
+
+Routine Description:
+
+ This service replaces the attributes of a group assigned to a
+ user.
+
+ The caller does not have to know whether the group is currently
+ assigned to the user.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ GroupRid - The relative ID of the group.
+
+ Attributes - The group attributes as the group is assigned to the
+ user.
+
+ UserRid - The relative ID of the user.
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been updated and added to the
+ RXACT.
+
+ STATUS_NO_SUCH_USER - The user does not exist.
+
+ STATUS_MEMBER_NOT_IN_GROUP - The user is not in the specified group.
+
+
+ Other status values that may be returned are those returned
+ by:
+
+ NtOpenKey()
+ NtQueryValueKey()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT UserContext;
+ ULONG MembershipCount;
+ PGROUP_MEMBERSHIP Membership;
+ ULONG i;
+
+
+ //
+ // Get a context handle for the user
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ UserRid,
+ TRUE, // We're trusted
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Now we have a user context, get the user's group/alias membership
+ //
+
+ NtStatus = SampRetrieveUserMembership(
+ UserContext,
+ TRUE, // Make copy
+ &MembershipCount,
+ &Membership
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // See if the user is a member ...
+ //
+
+ NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
+ for (i = 0; i<MembershipCount; i++ ) {
+ if ( Membership[i].RelativeId == GroupRid )
+ {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Change the groups's attributes.
+ //
+
+ Membership[i].Attributes = Attributes;
+
+ //
+ // Update the user's membership
+ //
+
+ NtStatus = SampReplaceUserMembership(
+ UserContext,
+ MembershipCount,
+ Membership
+ );
+ }
+
+ //
+ // Free up the membership array
+ //
+
+ MIDL_user_free(Membership);
+ }
+
+ //
+ // Write out any changes to the user account
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
+ }
+
+ //
+ // Clean up the user context
+ //
+
+ SampDeleteContext(UserContext);
+ }
+
+
+ return( NtStatus );
+}
+
+
+
+
+NTSTATUS
+SampDeleteUserKeys(
+ IN PSAMP_OBJECT Context
+ )
+
+/*++
+Routine Description:
+
+ This service deletes all registry keys related to a User object.
+
+
+Arguments:
+
+ Context - Points to the User context whose registry keys are
+ being deleted.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ ULONG Rid;
+ UNICODE_STRING AccountName, KeyName;
+
+
+ Rid = Context->TypeBody.User.Rid;
+
+
+
+
+ //
+ // Decrement the User count
+ //
+
+ NtStatus = SampAdjustAccountCount(SampUserObjectType, FALSE );
+
+
+
+
+ //
+ // Delete the registry key that has the User's name to RID mapping.
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Get the name
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ &AccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ &AccountName
+ );
+
+ SampFreeUnicodeString( &AccountName );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+ }
+
+
+
+ //
+ // Delete the attribute keys
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampDeleteAttributeKeys(
+ Context
+ );
+ }
+
+
+
+
+ //
+ // Delete the RID key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountSubKeyName(
+ SampUserObjectType,
+ &KeyName,
+ Rid,
+ NULL
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+
+
+ }
+
+
+
+ return( NtStatus );
+
+}
+
+
+
+NTSTATUS
+SampAddPasswordHistory(
+ IN PSAMP_OBJECT Context,
+ IN ULONG HistoryAttributeIndex,
+ IN PUNICODE_STRING NtOwfHistoryBuffer,
+ IN PVOID EncryptedPassword,
+ IN ULONG EncryptedPasswordLength,
+ IN USHORT PasswordHistoryLength
+ )
+
+/*++
+
+Routine Description:
+
+ This service adds a password to the given user's password history.
+ It will work for either NT or Lanman password histories.
+
+ This routine should only be called if the password is actually present.
+
+
+Arguments:
+
+ Context - a pointer to the user context to which changes will be made.
+
+ HistoryAttributeIndex - the attribue index in the user context which
+ contains the password history.
+
+ NtOwfHistoryBuffer - A pointer to the current password history, as
+ it was retrieved from the disk - it's encrypted, and pretending
+ to be in the UNICODE_STRING format.
+
+ EncryptedPasswordLength - ENCRYPTED_NT_OWF_LENGTH or
+ ENCRYPTED_LM_OWF_LENGTH, depending on which type of password
+ history is being worked on.
+
+ PasswordHistoryLength - The PasswordHistoryLength for the user's
+ domain.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The given password was added to the password history.
+
+ STATUS_INSUFFICIENT_RESOURCES - The user's password history needs to
+ be expanded, but there isn't enough memory to do so.
+
+ Other errors from building the account subkey name or writing the
+ password history out to the registry.
+
+
+--*/
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ PCHAR OldBuffer;
+
+ if ( ( NtOwfHistoryBuffer->Length / EncryptedPasswordLength ) <
+ ( (ULONG)PasswordHistoryLength ) ) {
+
+ //
+ // Password history buffer can be expanded.
+ // Allocate a larger buffer, copy the old buffer to the new one
+ // while leaving room for the new password, and free the old
+ // buffer.
+ //
+
+ OldBuffer = (PCHAR)(NtOwfHistoryBuffer->Buffer);
+
+ NtOwfHistoryBuffer->Buffer = MIDL_user_allocate(
+ NtOwfHistoryBuffer->Length + EncryptedPasswordLength );
+
+ if ( NtOwfHistoryBuffer->Buffer == NULL ) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ NtOwfHistoryBuffer->Buffer = (PWSTR)OldBuffer;
+
+ } else {
+
+ RtlCopyMemory(
+ (PVOID)( (PCHAR)(NtOwfHistoryBuffer->Buffer) + EncryptedPasswordLength ),
+ (PVOID)OldBuffer,
+ NtOwfHistoryBuffer->Length );
+
+ MIDL_user_free( OldBuffer );
+
+ NtOwfHistoryBuffer->Length = (USHORT)(NtOwfHistoryBuffer->Length +
+ EncryptedPasswordLength);
+ }
+
+ } else {
+
+ //
+ // Password history buffer is at its maximum size, or larger (for
+ // this domain). If it's larger, cut it down to the current maximum.
+ //
+
+ if ( ( NtOwfHistoryBuffer->Length / EncryptedPasswordLength ) >
+ ( (ULONG)PasswordHistoryLength ) ) {
+
+ //
+ // Password history is too large (the password history length must
+ // have been shortened recently).
+ // Set length to the proper value,
+ //
+
+ NtOwfHistoryBuffer->Length = (USHORT)(EncryptedPasswordLength *
+ PasswordHistoryLength);
+ }
+
+ //
+ // Password history buffer is full, at its maximum size.
+ // Move buffer contents right 16 bytes, which will lose the oldest
+ // password and make room for the new password at the beginning
+ // (left).
+ // Note that we CAN'T move anything if the password history size
+ // is 0. If it's 1, we could but no need since we'll overwrite
+ // it below.
+ //
+
+ if ( PasswordHistoryLength > 1 ) {
+
+ RtlMoveMemory(
+ (PVOID)( (PCHAR)(NtOwfHistoryBuffer->Buffer) + EncryptedPasswordLength ),
+ (PVOID)NtOwfHistoryBuffer->Buffer,
+ NtOwfHistoryBuffer->Length - EncryptedPasswordLength );
+ }
+ }
+
+
+ //
+ // Put the new encrypted OWF at the beginning of the password history
+ // buffer (unless, of course, the buffer size is 0), and write the password
+ // history to disk.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( PasswordHistoryLength > 0 ) {
+
+ RtlCopyMemory(
+ (PVOID)NtOwfHistoryBuffer->Buffer,
+ (PVOID)EncryptedPassword,
+ EncryptedPasswordLength );
+ }
+
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ HistoryAttributeIndex,
+ NtOwfHistoryBuffer
+ );
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampCheckPasswordHistory(
+ IN PVOID EncryptedPassword,
+ IN ULONG EncryptedPasswordLength,
+ IN USHORT PasswordHistoryLength,
+ IN ULONG HistoryAttributeIndex,
+ IN PSAMP_OBJECT Context,
+ IN BOOLEAN CheckHistory,
+ IN PUNICODE_STRING OwfHistoryBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This service takes the given password, and optionally checks it against the
+ password history on the disk. It returns a pointer to the password
+ history, which will later be passed to SampAddPasswordHistory().
+
+ This routine should only be called if the password is actually present.
+
+
+Arguments:
+
+ EncryptedPassword - A pointer to the encrypted password that we're
+ looking for.
+
+ EncryptedPasswordLength - ENCRYPTED_NT_OWF_PASSWORD or
+ ENCRYPTED_LM_OWF_PASSWORD, depending on the type of password
+ history to be searched.
+
+ PasswordHistoryLength - the length of the password history for this
+ domain.
+
+ SubKeyName - a pointer to a unicode string that describes the name
+ of the password history to be read from the disk.
+
+ Context - a pointer to the user's context.
+
+ CheckHistory - If TRUE, the password is to be checked against
+ the history to see if it is already present and an error returned
+ if it is found. If FALSE, the password will not be checked, but a
+ pointer to the appropriate history buffer will still be returned
+ because the specified password will be added to the history via
+ SampAddPasswordHistory.
+
+ NOTE: The purpose of this flag is to allow Administrator to change
+ a user's password regardless of whether it is already in the history.
+
+ OwfHistoryBuffer - a pointer to a UNICODE_STRING which will be
+ used to point to the password history.
+
+ NOTE: The caller must free OwfHistoryBuffer.Buffer with
+ MIDL_user_free().
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The given password was not found in the password
+ history.
+
+ STATUS_PASSWORD_RESTRICTION - The given password was found in the
+ password history.
+
+ Other errors from reading the password history from disk.
+
+
+--*/
+{
+ NTSTATUS NtStatus = STATUS_SUCCESS;
+ PVOID PasswordHistoryEntry;
+ ULONG i = 0;
+ BOOLEAN OldPasswordFound = FALSE;
+
+
+ if ( ( PasswordHistoryLength > 0 ) && ( OwfHistoryBuffer->Length == 0 ) ) {
+
+ //
+ // Perhaps the domain's PasswordHistoryLength was raised from 0
+ // since the last time this user's password was changed. Try to
+ // put the current password (if non-null) in the password history.
+ //
+
+ UNICODE_STRING CurrentPassword;
+ USHORT PasswordAttributeIndex;
+
+ //
+ // Initialize the CurrentPassword buffer pointer to NULL (and the
+ // rest of the structure for consistency. The called routine
+ // SampGetUnicodeStringAttribute may perform a MIDL_user_allocate
+ // on a zero buffer length and cannot safely be changed as there are
+ // many callers. The semantics of a zero-length allocate call are
+ // not clear. Currently a pointer to a heap block is returned,
+ // but this might be changed to a NULL being returned.
+ //
+
+ CurrentPassword.Length = CurrentPassword.MaximumLength = 0;
+ CurrentPassword.Buffer = NULL;
+
+
+ if ( HistoryAttributeIndex == SAMP_USER_LM_PWD_HISTORY ) {
+
+ PasswordAttributeIndex = SAMP_USER_DBCS_PWD;
+
+ } else {
+
+ ASSERT( HistoryAttributeIndex == SAMP_USER_NT_PWD_HISTORY );
+ PasswordAttributeIndex = SAMP_USER_UNICODE_PWD;
+ }
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ PasswordAttributeIndex,
+ TRUE, // Make copy
+ &CurrentPassword
+ );
+
+ if ( ( NT_SUCCESS( NtStatus ) ) && ( CurrentPassword.Length != 0 ) ) {
+
+ ASSERT( (CurrentPassword.Length == ENCRYPTED_NT_OWF_PASSWORD_LENGTH) ||
+ (CurrentPassword.Length == ENCRYPTED_LM_OWF_PASSWORD_LENGTH) );
+
+ NtStatus = SampAddPasswordHistory(
+ Context,
+ HistoryAttributeIndex,
+ OwfHistoryBuffer,
+ CurrentPassword.Buffer,
+ CurrentPassword.Length,
+ PasswordHistoryLength
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Free the old password history, and re-read the
+ // altered password history from the disk.
+ //
+
+ MIDL_user_free( OwfHistoryBuffer->Buffer );
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ HistoryAttributeIndex,
+ TRUE, // Make copy
+ OwfHistoryBuffer
+ );
+ }
+ }
+
+ //
+ // If memory was allocated, free it.
+ //
+
+ if (CurrentPassword.Buffer != NULL) {
+
+ SampFreeUnicodeString( &CurrentPassword );
+ }
+ }
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ //
+ // If requested, check the Password History to see if we can use this
+ // password. Compare the passed-in password to each of the entries in
+ // the password history.
+ //
+
+ if (CheckHistory) {
+
+ PasswordHistoryEntry = (PVOID)(OwfHistoryBuffer->Buffer);
+
+ while ( ( i < (ULONG)PasswordHistoryLength ) &&
+ ( i < ( OwfHistoryBuffer->Length / EncryptedPasswordLength ) ) &&
+ ( OldPasswordFound == FALSE ) ) {
+
+ if ( RtlCompareMemory(
+ EncryptedPassword,
+ PasswordHistoryEntry,
+ EncryptedPasswordLength ) == EncryptedPasswordLength ) {
+
+ OldPasswordFound = TRUE;
+
+ } else {
+
+ i++;
+
+ PasswordHistoryEntry = (PVOID)((PCHAR)(PasswordHistoryEntry) +
+ EncryptedPasswordLength );
+ }
+ }
+
+ if ( OldPasswordFound ) {
+
+ //
+ // We did find it in the password history, so return an appropriate
+ // error.
+ //
+
+ NtStatus = STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampMatchworkstation(
+ IN PUNICODE_STRING LogonWorkStation,
+ IN PUNICODE_STRING WorkStations
+ )
+
+/*++
+
+Routine Description:
+
+ Check if the given workstation is a member of the list of workstations
+ given.
+
+
+Arguments:
+
+ LogonWorkStations - UNICODE name of the workstation that the user is
+ trying to log into.
+
+ WorkStations - API list of workstations that the user is allowed to
+ log into.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The user is allowed to log into the workstation.
+
+
+
+--*/
+{
+ PWCHAR WorkStationName;
+ UNICODE_STRING Unicode;
+ NTSTATUS NtStatus;
+ WCHAR Buffer[256];
+ USHORT LocalBufferLength = 256;
+ UNICODE_STRING WorkStationsListCopy;
+ BOOLEAN BufferAllocated = FALSE;
+ PWCHAR TmpBuffer;
+
+ //
+ // Local workstation is always allowed
+ // If WorkStations field is 0 everybody is allowed
+ //
+
+ if ( ( LogonWorkStation == NULL ) ||
+ ( LogonWorkStation->Length == 0 ) ||
+ ( WorkStations->Length == 0 ) ) {
+
+ return( STATUS_SUCCESS );
+ }
+
+ //
+ // Assume failure; change status only if we find the string.
+ //
+
+ NtStatus = STATUS_INVALID_WORKSTATION;
+
+ //
+ // WorkStationApiList points to our current location in the list of
+ // WorkStations.
+ //
+
+ if ( WorkStations->Length > LocalBufferLength ) {
+
+ WorkStationsListCopy.Buffer = RtlAllocateHeap( RtlProcessHeap(), 0, WorkStations->Length );
+ BufferAllocated = TRUE;
+
+ if ( WorkStationsListCopy.Buffer == NULL ) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ return( NtStatus );
+ }
+
+ WorkStationsListCopy.MaximumLength = WorkStations->Length;
+
+ } else {
+
+ WorkStationsListCopy.Buffer = Buffer;
+ WorkStationsListCopy.MaximumLength = LocalBufferLength;
+ }
+
+ RtlCopyUnicodeString( &WorkStationsListCopy, WorkStations );
+ ASSERT( WorkStationsListCopy.Length == WorkStations->Length );
+
+ //
+ // wcstok requires a string the first time it's called, and NULL
+ // for all subsequent calls. Use a temporary variable so we
+ // can do this.
+ //
+
+ TmpBuffer = WorkStationsListCopy.Buffer;
+
+ while( WorkStationName = wcstok(TmpBuffer, L",") ) {
+
+ TmpBuffer = NULL;
+ RtlInitUnicodeString( &Unicode, WorkStationName );
+ if (RtlEqualComputerName( &Unicode, LogonWorkStation )) {
+ NtStatus = STATUS_SUCCESS;
+ break;
+ }
+ }
+
+ if ( BufferAllocated ) {
+ RtlFreeHeap( RtlProcessHeap(), 0, WorkStationsListCopy.Buffer );
+ }
+
+ return( NtStatus );
+}
+
+
+LARGE_INTEGER
+SampAddDeltaTime(
+ IN LARGE_INTEGER Time,
+ IN LARGE_INTEGER DeltaTime
+ )
+
+/*++
+Routine Description:
+
+ This service adds a delta time to a time and limits the result to
+ the maximum legal absolute time value
+
+Arguments:
+
+ Time - An absolute time
+
+ DeltaTime - A delta time
+
+Return Value:
+
+ The time modified by delta time.
+
+--*/
+{
+ //
+ // Check the time and delta time aren't switched
+ //
+
+ ASSERT(!(Time.QuadPart < 0));
+ ASSERT(!(DeltaTime.QuadPart > 0));
+
+ try {
+
+ Time.QuadPart = (Time.QuadPart - DeltaTime.QuadPart);
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ return( SampWillNeverTime );
+ }
+
+ //
+ // Limit the resultant time to the maximum valid absolute time
+ //
+
+ if (Time.QuadPart < 0) {
+ Time = SampWillNeverTime;
+ }
+
+ return(Time);
+}
+
+
+
+
+NTSTATUS
+SampChangeUserAccountName(
+ IN PSAMP_OBJECT Context,
+ IN PUNICODE_STRING NewAccountName,
+ OUT PUNICODE_STRING OldAccountName
+ )
+
+/*++
+Routine Description:
+
+ This routine changes the account name of a user account.
+
+ THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
+
+Arguments:
+
+ Context - Points to the User context whose name is to be changed.
+
+ NewAccountName - New name to give this account
+
+ OldAccountName - old name is returned here. The buffer should be freed
+ by calling MIDL_user_free.
+
+Return Value:
+
+
+ STATUS_SUCCESS - The information has been retrieved.
+
+
+ Other status values that may be returned by:
+
+ SampGetUnicodeStringAttribute()
+ SampSetUnicodeStringAttribute()
+ SampValidateAccountNameChange()
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+
+
+ /////////////////////////////////////////////////////////////
+ // There are two copies of the name of each account. //
+ // one is under the DOMAIN\(domainName)\USER\NAMES key, //
+ // one is the value of the //
+ // DOMAIN\(DomainName)\USER\(rid)\NAME key //
+ /////////////////////////////////////////////////////////////
+
+
+ //
+ // Get the current name so we can delete the old Name->Rid
+ // mapping key.
+ //
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ OldAccountName
+ );
+
+ //
+ // Make sure the name is valid and not already in use
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampValidateAccountNameChange(
+ NewAccountName,
+ OldAccountName
+ );
+
+ //
+ // Delete the old name key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ OldAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationDelete,
+ &KeyName,
+ 0,
+ NULL,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+ }
+
+ }
+
+ //
+ //
+ // Create the new Name->Rid mapping key
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ NewAccountName
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ ULONG ObjectRid = Context->TypeBody.User.Rid;
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ ObjectRid,
+ (PVOID)NULL,
+ 0
+ );
+
+ SampFreeUnicodeString( &KeyName );
+ }
+ }
+
+
+
+
+ //
+ // replace the account's name
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampSetUnicodeStringAttribute(
+ Context,
+ SAMP_USER_ACCOUNT_NAME,
+ NewAccountName
+ );
+ }
+
+ //
+ // Free up the old account name if we failed
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ SampFreeUnicodeString( OldAccountName );
+ OldAccountName->Buffer = NULL;
+ }
+
+ }
+
+
+ return(NtStatus);
+}
+
+
+USHORT
+SampQueryBadPasswordCount(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to retrieve the effective BadPasswordCount
+ value of a user.
+
+ When querying BadPasswordCount, some quick
+ analysis has to be done. If the last bad password
+ was set more than LockoutObservationWindow time ago,
+ then we re-set the BadPasswordCount. Otherwise, we
+ return the current value.
+
+
+ NOTE: The V1aFixed data for the user object MUST be valid.
+ This routine does not retrieve the data from disk.
+
+Arguments:
+
+ UserContext - Points to the object context block of the user whose
+ bad password count is to be returned.
+
+ V1aFixed - Points to a local copy of the user's V1aFixed data.
+
+
+Return Value:
+
+
+ The effective bad password count.
+
+
+--*/
+{
+
+ if (SampStillInLockoutObservationWindow( UserContext, V1aFixed ) ) {
+ return(V1aFixed->BadPasswordCount);
+ }
+
+ return(0);
+
+}
+
+
+BOOLEAN
+SampStillInLockoutObservationWindow(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ )
+/*++
+
+Routine Description:
+
+ This routine returns a boolean indicating whether the provided user
+ account context is within an account lockout window or not.
+
+ An account lockout window is the time window starting at the
+ last time a bad password was provided in a logon attempt
+ (since the last valid logon) and extending for the duration of
+ time specified in the LockoutObservationWindow field of the
+ corresponding domain object.
+
+ BY DEFINITION, a user account that has zero bad passwords, is
+ NOT in an observation window.
+
+ NOTE: The V1aFixed data for the both the user and corresponding
+ domain objects MUST be valid. This routine does NOT retrieve
+ data from disk.
+
+Arguments:
+
+ UserContext - Points to the user object context block.
+
+ V1aFixed - Points to a local copy of the user's V1aFixed data.
+
+
+Return Value:
+
+
+ TRUE - the user is in a lockout observation window.
+
+ FALSE - the user is not in a lockout observation window.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ LARGE_INTEGER
+ WindowLength,
+ LastBadPassword,
+ CurrentTime,
+ EndOfWindow;
+
+
+ if (V1aFixed->BadPasswordCount == 0) {
+ return(FALSE);
+ }
+
+ //
+ // At least one bad password.
+ // See if we are still in its observation window.
+ //
+
+ LastBadPassword = V1aFixed->LastBadPasswordTime;
+ ASSERT( LastBadPassword.HighPart >= 0 );
+
+ WindowLength =
+ SampDefinedDomains[UserContext->DomainIndex].CurrentFixed.LockoutObservationWindow;
+ ASSERT( WindowLength.HighPart <= 0 ); // Must be a delta time
+
+
+ NtStatus = NtQuerySystemTime( &CurrentTime );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // See if current time is outside the observation window.
+ // * you must subtract a delta time from an absolute time*
+ // * to end up with a time in the future. *
+ //
+
+ EndOfWindow = SampAddDeltaTime( LastBadPassword, WindowLength );
+
+ return(CurrentTime.QuadPart <= EndOfWindow.QuadPart);
+
+}
+
+
+BOOLEAN
+SampIncrementBadPasswordCount(
+ PSAMP_OBJECT UserContext,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
+ )
+
+/*++
+
+Routine Description:
+
+ This routine increments a user's bad password count.
+ This may result in the account becoming locked out.
+ It may also result in the BadPasswordCount being
+ reduced (because we left one LockoutObservationWindow
+ and had to start another).
+
+ If (and only if) this call results in the user account
+ transitioning from not locked out to locked out, a value
+ of TRUE will be returned. Otherwise, a value of FALSE is
+ returned.
+
+
+ NOTE: The V1aFixed data for the both the user and corresponding
+ domain objects MUST be valid. This routine does NOT retrieve
+ data from disk.
+
+Arguments:
+
+ Context - Points to the user object context block.
+
+ V1aFixed - Points to a local copy of the user's V1aFixed data.
+
+Return Value:
+
+
+ TRUE - the user became locked-out due to this call.
+
+ FALSE - the user was either already locked-out, or did
+ not become locked out due to this call.
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+ BOOLEAN
+ IsLocked,
+ WasLocked;
+
+#if DBG
+
+ TIME_FIELDS
+ T1;
+
+#endif //DBG
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: IncrementBadPasswordCount: \n"
+ " User Account: 0x%lx\n",
+ V1aFixed->UserId));
+
+ //
+ // Reset the locked out flag if necessary.
+ // We might turn right around and set it again below,
+ // but we need to know when we transition into a locked-out
+ // state. This is necessary to give us information we
+ // need to do lockout auditing at some time. Note that
+ // the lockout flag itself is updated in a very lazy fashion,
+ // and so its state may or may not be accurate at any point
+ // in time. You must call SampUpdateAccountLockoutFlag to
+ // ensure it is up to date.
+ //
+
+ SampUpdateAccountLockedOutFlag( UserContext,
+ V1aFixed,
+ &WasLocked );
+
+ //
+ // If we are not in a lockout observation window, then
+ // reset the bad password count.
+ //
+
+ if (!SampStillInLockoutObservationWindow( UserContext, V1aFixed )) {
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: IncrementBadPasswordCount: \n"
+ " Starting new observation window.\n"
+ " Resetting bad password count before increment.\n"));
+ V1aFixed->BadPasswordCount = 0; // Dirty flag will be set later
+ }
+
+ V1aFixed->BadPasswordCount++;
+
+ NtStatus = NtQuerySystemTime( &V1aFixed->LastBadPasswordTime );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+#if DBG
+ RtlTimeToTimeFields(
+ &V1aFixed->LastBadPasswordTime,
+ &T1);
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" LastBadPasswordTime: [0x%lx, 0x%lx] %d:%d:%d\n",
+ V1aFixed->LastBadPasswordTime.HighPart,
+ V1aFixed->LastBadPasswordTime.LowPart,
+ T1.Hour, T1.Minute, T1.Second )
+ );
+#endif //DBG
+
+
+ //
+ // Update the state of the flag to reflect its new situation
+ //
+
+ SampUpdateAccountLockedOutFlag( UserContext,
+ V1aFixed,
+ &IsLocked );
+
+
+ //
+ // Now to return our completion value.
+ // If the user was originally not locked, but now is locked
+ // then we need to return TRUE to indicate a transition into
+ // LOCKED occured. Otherwise, return false to indicate we
+ // did not transition into LOCKED (although we might have
+ // transitioned out of LOCKED).
+ //
+
+ if (!WasLocked) {
+ if (IsLocked) {
+ return(TRUE);
+ }
+ }
+
+ return(FALSE);
+}
+
+
+
+VOID
+SampUpdateAccountLockedOutFlag(
+ PSAMP_OBJECT Context,
+ PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
+ PBOOLEAN IsLocked
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks to see if a user's account should
+ currently be locked out. If it should, it turns on
+ the AccountLockedOut flag. If not, it turns the flag
+ off.
+
+
+Arguments:
+
+ Context - Points to the user object context block.
+
+ V1aFixed - Points to a local copy of the user's V1aFixed data.
+
+ V1aFixedDirty - If any changes are made to V1aFixed, then
+ V1aFixedDirty will be set to TRUE, otherwise V1aFixedDirty
+ WILL NOT BE MODIFIED.
+
+ IsState - Indicates whether the account is currently locked
+ or unlocked. A value of TRUE indicates the account is
+ locked. A value of false indicates the account is not
+ locked.
+
+Return Value:
+
+
+ TRUE - the user's lockout status changed.
+
+ FALSE - the user's lockout status did not change.
+
+
+--*/
+{
+ USHORT
+ Threshold;
+
+ LARGE_INTEGER
+ CurrentTime,
+ LastBadPassword,
+ LockoutDuration,
+ EndOfLockout;
+
+ BOOLEAN
+ BeyondLockoutDuration;
+
+#if DBG
+
+ LARGE_INTEGER
+ TmpTime;
+
+ TIME_FIELDS
+ AT1, AT2, AT3, DT1;
+#endif //DBG
+
+
+
+
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ ("SAM: UpdateAccountLockedOutFlag: \n"
+ " User account 0x%lx\n",
+ V1aFixed->UserId));
+
+ //
+ // One of two situations exist:
+ //
+ // 1) The account was left in a locked out state. In this
+ // case we need to see if it should still be locked
+ // out.
+ //
+ // 2) The account was left in a not locked state. In this
+ // case we need to see if we should lock it.
+ //
+
+ if ((V1aFixed->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) !=0) {
+
+ //
+ // Left locked out - do we need to unlock it?
+ //
+
+ LastBadPassword = V1aFixed->LastBadPasswordTime;
+ LockoutDuration =
+ SampDefinedDomains[Context->DomainIndex].CurrentFixed.LockoutDuration;
+
+ EndOfLockout =
+ SampAddDeltaTime( LastBadPassword, LockoutDuration );
+
+ NtQuerySystemTime( &CurrentTime );
+
+ BeyondLockoutDuration = CurrentTime.QuadPart > EndOfLockout.QuadPart;
+
+#if DBG
+
+ RtlTimeToTimeFields( &LastBadPassword, &AT1);
+ RtlTimeToTimeFields( &CurrentTime, &AT2);
+ RtlTimeToTimeFields( &EndOfLockout, &AT3 );
+
+ TmpTime.QuadPart = -LockoutDuration.QuadPart;
+ RtlTimeToElapsedTimeFields( &TmpTime, &DT1 );
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" Account previously locked.\n"
+ " Current Time : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " End of Lockout : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " Lockout Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
+ " LastBadPasswordTime: [0x%lx, 0x%lx] %d:%d:%d\n",
+ CurrentTime.HighPart, CurrentTime.LowPart, AT2.Hour, AT2.Minute, AT2.Second,
+ EndOfLockout.HighPart, EndOfLockout.LowPart, AT3.Hour, AT3.Minute, AT3.Second,
+ LockoutDuration.HighPart, LockoutDuration.LowPart, DT1.Hour, DT1.Minute, DT1.Second,
+ V1aFixed->LastBadPasswordTime.HighPart, V1aFixed->LastBadPasswordTime.LowPart,
+ AT1.Hour, AT1.Minute, AT1.Second)
+ );
+#endif //DBG
+
+ if (BeyondLockoutDuration) {
+
+ //
+ // Unlock account
+ //
+
+ V1aFixed->UserAccountControl &= ~USER_ACCOUNT_AUTO_LOCKED;
+ V1aFixed->BadPasswordCount = 0;
+
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" ** unlocking account **\n") );
+#if DBG
+ } else {
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" leaving account locked\n") );
+#endif //DBG
+
+ }
+
+ } else {
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" Account previously not locked.\n"
+ " BadPasswordCount: %ld\n",
+ V1aFixed->BadPasswordCount) );
+
+ //
+ // Left in a not locked state. Do we need to lock it?
+ //
+
+ Threshold =
+ SampDefinedDomains[Context->DomainIndex].CurrentFixed.LockoutThreshold;
+
+ if (V1aFixed->BadPasswordCount >= Threshold &&
+ Threshold != 0) { // Zero is a special case threshold
+
+ //
+ // account must be locked.
+ //
+
+ V1aFixed->UserAccountControl |= USER_ACCOUNT_AUTO_LOCKED;
+
+
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" ** locking account **\n") );
+#if DBG
+ } else {
+ SampDiagPrint( DISPLAY_LOCKOUT,
+ (" leaving account unlocked\n") );
+#endif //DBG
+
+ }
+ }
+
+
+ //
+ // Now return the state of the flag.
+ //
+
+ if ((V1aFixed->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) !=0) {
+ (*IsLocked) = TRUE;
+ } else {
+ (*IsLocked) = FALSE;
+ }
+
+ return;
+}
diff --git a/private/newsam/server/utest/makefile b/private/newsam/server/utest/makefile
new file mode 100644
index 000000000..bdd684681
--- /dev/null
+++ b/private/newsam/server/utest/makefile
@@ -0,0 +1,29 @@
+############################################################################
+#
+# Copyright (C) 1992, Microsoft Corporation.
+#
+# All rights reserved.
+#
+############################################################################
+
+!if "$(NTMAKEENV)" != ""
+
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
+
+!else
+
+
+default: all
+
+!include filelist.mk
+!include $(SECURITY)\security.mk
+!include $(COMMON)\src\win40.mk
+!include depend.mk
+
+
+!endif
diff --git a/private/newsam/server/utest/samtest.cxx b/private/newsam/server/utest/samtest.cxx
new file mode 100644
index 000000000..5711d8104
--- /dev/null
+++ b/private/newsam/server/utest/samtest.cxx
@@ -0,0 +1,2033 @@
+//+-----------------------------------------------------------------------
+//
+// Microsoft Windows
+//
+// Copyright (c) Microsoft Corporation 1992 - 1992
+//
+// File: secret.cxx
+//
+// Contents: test program to check the setup of a Cairo installation
+//
+//
+// History: 22-Dec-92 Created MikeSw
+//
+//------------------------------------------------------------------------
+
+
+extern "C"
+{
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+#include <ntsam.h>
+#include <ntlsa.h>
+#include <windows.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <caiseapi.h>
+}
+
+typedef NTSTATUS (TestFunc)( WCHAR *Parameter[]);
+
+typedef struct _Commands {
+ PSTR Name;
+ ULONG Parameter; // TRUE = yes, FALSE = no
+ TestFunc *Function;
+} CommandPair, *PCommandPair;
+
+
+typedef struct _Action {
+ ULONG CommandNumber;
+ LPWSTR Parameter[8];
+} Action, *PAction;
+
+
+
+TestFunc OpenDomain;
+TestFunc EnumDomains;
+TestFunc EnumAccounts;
+TestFunc QueryDisplay;
+TestFunc OpenGroup;
+TestFunc GroupMembers;
+TestFunc OpenAlias;
+TestFunc AliasMembers;
+TestFunc GetAliasMembership;
+TestFunc OpenUser;
+TestFunc GetGroupsForUser;
+TestFunc DumpAllUsers;
+TestFunc DumpAllGroups;
+TestFunc DumpUser;
+TestFunc DumpGroup;
+TestFunc CreateUser;
+TestFunc AddAliasMember;
+TestFunc CreateGroup;
+TestFunc CreateAlias;
+TestFunc DumpDomain;
+TestFunc Connect;
+TestFunc DelUser;
+TestFunc DelAlias;
+TestFunc DelGroup;
+
+CommandPair Commands[] = {
+ {"-od",1,OpenDomain},
+ {"-ed",0,EnumDomains},
+ {"-ea",1,EnumAccounts},
+ {"-qd",1,QueryDisplay},
+ {"-og",1,OpenGroup},
+ {"-gm",0,GroupMembers},
+ {"-oa",1,OpenAlias},
+ {"-am",0,AliasMembers},
+ {"-gam",1,GetAliasMembership},
+ {"-ou",1,OpenUser},
+ {"-ggu",0,GetGroupsForUser},
+ {"-dau",0,DumpAllUsers},
+ {"-dag",0,DumpAllGroups},
+ {"-du",0,DumpUser},
+ {"-dg",0,DumpGroup},
+ {"-cu",1,CreateUser},
+ {"-aam",1,AddAliasMember},
+ {"-cg",1,CreateGroup},
+ {"-ca",1,CreateAlias},
+ {"-dd",0,DumpDomain},
+ {"-c",1,Connect},
+ {"-delu",0,DelUser},
+ {"-dela",0,DelAlias},
+ {"-delg",0,DelGroup}
+
+
+
+};
+
+#define NUM_COMMANDS (sizeof(Commands) / sizeof(CommandPair))
+
+SAM_HANDLE SamHandle;
+SAM_HANDLE DomainHandle;
+SAM_HANDLE GroupHandle;
+SAM_HANDLE AliasHandle;
+SAM_HANDLE UserHandle;
+
+UNICODE_STRING ServerName;
+
+//+-------------------------------------------------------------------------
+//
+// Function: PrintTime
+//
+// Synopsis: Prints a text representation of a FILETIME interpreted
+// as an absolute date/time
+//
+// Arguments: [rft] -- time to print
+//
+// Notes: Used only by dump functions.
+//
+//--------------------------------------------------------------------------
+
+void
+PrintTime (
+ char * String,
+ PVOID Time
+ )
+{
+ SYSTEMTIME st;
+
+ FileTimeToSystemTime ( (PFILETIME) Time, & st );
+ printf("%s %d-%d-%d %d:%2.2d:%2.2d\n", String, st.wMonth, st.wDay, st.wYear,
+ st.wHour, st.wMinute, st.wSecond );
+}
+
+//+-------------------------------------------------------------------------
+//
+// Function: SpmDbDeltaTimeToString
+//
+// Synopsis: Converts a time delta to a string.
+//
+// Effects:
+//
+// Arguments:
+//
+// Requires:
+//
+// Returns:
+//
+// Notes:
+//
+//
+//--------------------------------------------------------------------------
+
+
+VOID
+PrintDeltaTime(
+ IN LPSTR Message,
+ IN PLARGE_INTEGER Time
+ )
+{
+ ULONG Seconds;
+ ULONG Minutes;
+ ULONG Hours;
+ ULONG Days;
+ ULONG Chars;
+ CHAR TimeBuffer[256] = "";
+ LPSTR TimeString = TimeBuffer;
+ LARGE_INTEGER DeltaTime;
+
+ DeltaTime.QuadPart = -Time->QuadPart;
+
+ Seconds = (ULONG) (DeltaTime.QuadPart / 10000000);
+
+ Minutes = Seconds / 60;
+ Hours = Minutes / 60;
+ Days = Hours / 24;
+
+ Hours = Hours % 24;
+ Minutes = Minutes % 60;
+ Seconds = Seconds % 60;
+
+ if (Days >= 1)
+ {
+ Chars = sprintf(TimeString,"%d days ",Days);
+ TimeString += Chars;
+ }
+ if (Hours >= 1 )
+ {
+ Chars = sprintf(TimeString,"%d hours ",Hours);
+ TimeString += Chars;
+ }
+
+ if (Minutes >= 1 && Days == 0)
+ {
+ Chars = sprintf(TimeString,"%d minutes ",Minutes);
+ TimeString += Chars;
+ }
+
+ if (Seconds >= 1 && (Days == 0) && (Hours == 0) )
+ {
+ Chars = sprintf(TimeString,"%d seconds ",Seconds);
+ TimeString += Chars;
+ }
+
+ printf("%s %s\n",Message,TimeBuffer);
+
+}
+
+
+NTSTATUS
+Connect( LPWSTR * Parameter)
+{
+ OBJECT_ATTRIBUTES oa;
+ NTSTATUS Status;
+
+ RtlInitUnicodeString(
+ &ServerName,
+ Parameter[0]
+ );
+
+ InitializeObjectAttributes(&oa,NULL,0,NULL,NULL);
+
+ Status = SamConnect(
+ &ServerName,
+ &SamHandle,
+ MAXIMUM_ALLOWED,
+ &oa);
+ return(Status);
+}
+
+NTSTATUS
+CloseSam()
+{
+ return(SamCloseHandle(SamHandle));
+}
+
+
+
+NTSTATUS
+EnumDomains(
+ LPWSTR * Parameter )
+{
+ NTSTATUS Status;
+ SHORT Language;
+ SAM_ENUMERATE_HANDLE Context = 0;
+ PSAM_RID_ENUMERATION Buffer = NULL;
+ ULONG Count = 0;
+ ULONG i;
+
+ Status = SamEnumerateDomainsInSamServer(
+ SamHandle,
+ &Context,
+ (PVOID *) &Buffer,
+ 2000,
+ &Count
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ return(Status);
+ }
+
+
+ for (i = 0; i < Count ; i++ )
+ {
+ printf("Domain = %wZ\n",&Buffer[i].Name);
+ }
+ SamFreeMemory(Buffer);
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+OpenDomain( LPWSTR * Parameter )
+{
+ GUID DomainGuid;
+ BOOLEAN fBuiltin;
+ NTSTATUS Status;
+ CAIROSID DomainSid;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+
+ if (!_wcsicmp(Parameter[0],L"Builtin"))
+ {
+ fBuiltin = TRUE;
+ }
+ else if (!_wcsicmp(Parameter[0],L"Account"))
+ {
+ fBuiltin = FALSE;
+ }
+ else
+ {
+ printf("Invalid domain to open: %ws\n",Parameter[0]);
+ return(STATUS_UNSUCCESSFUL);
+ }
+
+ if (fBuiltin)
+ {
+ DomainSid.Revision = SID_REVISION;
+ DomainSid.SubAuthorityCount = 1;
+ DomainSid.IdentifierAuthority = NtAuthority;
+ DomainSid.ZerothSubAuthority = SECURITY_BUILTIN_DOMAIN_RID;
+ }
+ else
+ {
+ LSA_HANDLE LsaHandle = NULL;
+ OBJECT_ATTRIBUTES Oa;
+ PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo = NULL;
+
+ RtlZeroMemory(&Oa, sizeof(OBJECT_ATTRIBUTES));
+ Status = LsaOpenPolicy(
+ &ServerName,
+ &Oa,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &LsaHandle
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to open policy: 0x%x\n",Status);
+ return(Status);
+ }
+ Status = LsaQueryInformationPolicy(
+ LsaHandle,
+ PolicyAccountDomainInformation,
+ (PVOID *) &DomainInfo
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query account domain: 0x%x\n",Status);
+ LsaClose(LsaHandle);
+ return(Status);
+ }
+ RtlCopyMemory(
+ &DomainSid,
+ DomainInfo->DomainSid,
+ RtlLengthSid(DomainInfo->DomainSid)
+ );
+ LsaFreeMemory(DomainInfo);
+ }
+
+ Status = SamOpenDomain(
+ SamHandle,
+ MAXIMUM_ALLOWED,
+ (PSID) &DomainSid,
+ &DomainHandle
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to open domain: 0x%x\n",Status);
+ }
+ return(Status);
+
+}
+
+NTSTATUS
+EnumAccounts( LPWSTR * Parameter )
+{
+ ULONG PreferedMax = 100;
+ NTSTATUS Status;
+ SAM_ENUMERATE_HANDLE EnumContext = 0;
+ ULONG CountReturned;
+
+ PSAM_RID_ENUMERATION Accounts = NULL;
+
+ swscanf(Parameter[0],L"%d",&PreferedMax);
+
+ printf("EnumAccounts: %d\n",PreferedMax);
+
+
+ EnumContext = 0;
+ ASSERT(DomainHandle != NULL);
+ do
+ {
+ Status = SamEnumerateUsersInDomain(
+ DomainHandle,
+ &EnumContext,
+ 0,
+ (PVOID *) &Accounts,
+ PreferedMax,
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES))
+ {
+ ULONG Index;
+ UNICODE_STRING SidString;
+
+ for (Index = 0; Index < CountReturned; Index++)
+ {
+ printf("Account : %wZ 0x%x\n",&Accounts[Index].Name, Accounts[Index].RelativeId);
+ }
+ SamFreeMemory(Accounts);
+ }
+ else printf("Failed to enumerate users: 0x%x\n",Status);
+ } while (NT_SUCCESS(Status) && (Status != STATUS_SUCCESS) && (CountReturned != 0) );
+
+ EnumContext = 0;
+ do
+ {
+ Status = SamEnumerateGroupsInDomain(
+ DomainHandle,
+ &EnumContext,
+ (PVOID *) &Accounts,
+ PreferedMax,
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES))
+ {
+ ULONG Index;
+ UNICODE_STRING SidString;
+
+ for (Index = 0; Index < CountReturned; Index++)
+ {
+ printf("Group : %wZ 0x%x\n",&Accounts[Index].Name, Accounts[Index].RelativeId);
+ }
+ SamFreeMemory(Accounts);
+ }
+ else printf("Failed to enumerate Groups: 0x%x\n",Status);
+ } while (NT_SUCCESS(Status) && (CountReturned != 0) ); // && (Status != STATUS_SUCCESS)
+
+
+ EnumContext = 0;
+ do
+ {
+ Status = SamEnumerateAliasesInDomain(
+ DomainHandle,
+ &EnumContext,
+ (PVOID *) &Accounts,
+ PreferedMax,
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES))
+ {
+ ULONG Index;
+ UNICODE_STRING SidString;
+
+ for (Index = 0; Index < CountReturned; Index++)
+ {
+ printf("Alias : %wZ 0x%x\n",&Accounts[Index].Name, Accounts[Index].RelativeId);
+ }
+ SamFreeMemory(Accounts);
+ }
+ else printf("Failed to enumerate aliases: 0x%x\n",Status);
+ } while (NT_SUCCESS(Status) && (CountReturned != 0) ); // && (Status != STATUS_SUCCESS)
+
+
+
+ return(Status);
+}
+
+
+NTSTATUS
+QueryDisplay( LPWSTR * Parameter )
+{
+ NTSTATUS Status;
+ DOMAIN_DISPLAY_INFORMATION Type;
+ PVOID Buffer = NULL;
+ ULONG TotalAvailable = 0;
+ ULONG TotalReturned = 0;
+ ULONG ReturnedCount = 0;
+ ULONG Index;
+ ULONG SamIndex = 0;
+
+ if (!_wcsicmp(Parameter[0],L"user"))
+ {
+ Type = DomainDisplayUser;
+ } else if (!_wcsicmp(Parameter[0],L"Machine"))
+ {
+ Type = DomainDisplayMachine;
+ } else if (!_wcsicmp(Parameter[0],L"Group"))
+ {
+ Type = DomainDisplayGroup;
+ } else if (!_wcsicmp(Parameter[0],L"OemUser"))
+ {
+ Type = DomainDisplayOemUser;
+ } else if (!_wcsicmp(Parameter[0],L"OemGroup"))
+ {
+ Type = DomainDisplayOemGroup;
+ } else {
+ printf("Invalid parameter %ws\n", Parameter[0]);
+ return(STATUS_INVALID_PARAMETER);
+ }
+
+ do
+ {
+ Status = SamQueryDisplayInformation(
+ DomainHandle,
+ Type,
+ SamIndex,
+ 5,
+ 1000,
+ &TotalAvailable,
+ &TotalReturned,
+ &ReturnedCount,
+ &Buffer
+ );
+
+ if (NT_SUCCESS(Status) && (ReturnedCount > 0))
+ {
+ printf("Total returned = %d\t total available = %d\n",
+ TotalReturned, TotalAvailable);
+ switch(Type) {
+ case DomainDisplayUser:
+ {
+ PDOMAIN_DISPLAY_USER Users = (PDOMAIN_DISPLAY_USER) Buffer;
+ for (Index = 0; Index < ReturnedCount ; Index++ )
+ {
+ printf("User %d: Index %d\n Rid 0x%x\n Control 0x%x\n name %wZ\n Comment %wZ\n Full Name %wZ\n",
+ Index,
+ Users[Index].Index,
+ Users[Index].Rid,
+ Users[Index].AccountControl,
+ &Users[Index].LogonName,
+ &Users[Index].AdminComment,
+ &Users[Index].FullName
+ );
+ }
+ break;
+ }
+ case DomainDisplayGroup:
+ {
+ PDOMAIN_DISPLAY_GROUP Groups = (PDOMAIN_DISPLAY_GROUP) Buffer;
+ for (Index = 0; Index < ReturnedCount ; Index++ )
+ {
+ printf("Group %d\n Index %d\n Rid 0x%x\n Attributes 0x%x\n name %wZ\n Comment %wZ\n",
+ Index,
+ Groups[Index].Index,
+ Groups[Index].Rid,
+ Groups[Index].Attributes,
+ &Groups[Index].Group,
+ &Groups[Index].Comment
+ );
+
+ }
+ break;
+ }
+ case DomainDisplayMachine:
+ {
+ PDOMAIN_DISPLAY_MACHINE Machines = (PDOMAIN_DISPLAY_MACHINE) Buffer;
+ for (Index = 0; Index < ReturnedCount ; Index++ )
+ {
+ printf("Machine %d\n Index %d\n Rid 0x%x\n Control 0x%x\n Name %wZ\n Comment %wZ\n",
+ Index,
+ Machines[Index].Index,
+ Machines[Index].Rid,
+ Machines[Index].AccountControl,
+ &Machines[Index].Machine,
+ &Machines[Index].Comment
+ );
+ }
+ break;
+ }
+ case DomainDisplayOemUser:
+ {
+ PDOMAIN_DISPLAY_OEM_USER OemUsers = (PDOMAIN_DISPLAY_OEM_USER) Buffer;
+ for (Index = 0; Index < ReturnedCount ; Index++ )
+ {
+ printf("OemUser %d\n Index %d\n Name %Z\n",
+ Index,
+ OemUsers[Index].Index,
+ &OemUsers[Index].User
+ );
+ }
+ break;
+ }
+ case DomainDisplayOemGroup:
+ {
+ PDOMAIN_DISPLAY_OEM_GROUP OemGroups = (PDOMAIN_DISPLAY_OEM_GROUP) Buffer;
+ for (Index = 0; Index < ReturnedCount ; Index++ )
+ {
+ printf("OemGroup %d\n Index %d\n Name %Z\n",
+ Index,
+ OemGroups[Index].Index,
+ &OemGroups[Index].Group
+ );
+ }
+ break;
+ }
+
+ }
+ SamFreeMemory(Buffer);
+ SamIndex += ReturnedCount;
+ }
+
+
+ } while (NT_SUCCESS(Status) && (ReturnedCount > 0));
+ printf("QDI returned 0x%x\n",Status);
+
+ return(Status);
+
+
+}
+
+NTSTATUS
+OpenGroup( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING GroupName;
+ ULONG RelativeId = 0;
+
+// swscanf(Parameter[0],L"%x",&RelativeId);
+ if (RelativeId == 0)
+ {
+ RtlInitUnicodeString(
+ &GroupName,
+ Parameter[0]
+ );
+
+ printf("Looking up group %wZ\n",&GroupName);
+
+ Status = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &GroupName,
+ &Rid,
+ &Use
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to lookup group: 0x%x\n",Status);
+ return(Status);
+ }
+ RelativeId = *Rid;
+ SamFreeMemory(Rid);
+ SamFreeMemory(Use);
+ }
+
+ printf("Opening Group 0x%x\n",RelativeId);
+ Status= SamOpenGroup(
+ DomainHandle,
+ MAXIMUM_ALLOWED, // GROUP_LIST_MEMBERS | GROUP_READ_INFORMATION,
+ RelativeId,
+ &GroupHandle
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to open group: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+GroupMembers(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ ULONG MembershipCount;
+ PULONG Attributes = NULL;
+ PULONG Rids = NULL;
+ ULONG Index;
+
+ Status = SamGetMembersInGroup(
+ GroupHandle,
+ &Rids,
+ &Attributes,
+ &MembershipCount
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get members in group: 0x%x\n",Status);
+ return(Status);
+ }
+
+ for (Index = 0; Index < MembershipCount ; Index++ )
+ {
+ printf("Member %d: rid 0x%x, attributes 0x%x\n",
+ Index,Rids[Index],Attributes[Index]);
+ }
+ SamFreeMemory(Rids);
+ SamFreeMemory(Attributes);
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+OpenAlias( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING AliasName;
+ ULONG RelativeId;
+
+ swscanf(Parameter[0],L"%x",&RelativeId);
+ if (RelativeId == 0)
+ {
+ RtlInitUnicodeString(
+ &AliasName,
+ Parameter[0]
+ );
+
+ printf("Looking up Alias %wZ\n",&AliasName);
+
+ Status = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &AliasName,
+ &Rid,
+ &Use
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to lookup Alias: 0x%x\n",Status);
+ return(Status);
+ }
+ RelativeId = *Rid;
+ SamFreeMemory(Rid);
+ SamFreeMemory(Use);
+ }
+
+
+ printf("Opening Alias 0x%x\n",RelativeId);
+ Status= SamOpenAlias(
+ DomainHandle,
+ ALIAS_LIST_MEMBERS | ALIAS_ADD_MEMBER,
+ RelativeId,
+ &AliasHandle
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to open alias: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+AliasMembers(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ ULONG MembershipCount;
+ PSID * Members = NULL;
+ ULONG Index;
+ UNICODE_STRING Sid;
+
+ Status = SamGetMembersInAlias(
+ AliasHandle,
+ &Members,
+ &MembershipCount
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get members in Alias: 0x%x\n",Status);
+ return(Status);
+ }
+
+ for (Index = 0; Index < MembershipCount ; Index++ )
+ {
+ RtlConvertSidToUnicodeString(
+ &Sid,
+ Members[Index],
+ TRUE
+ );
+ printf("Member %d: sid %wZ\n",
+ Index,&Sid);
+ RtlFreeUnicodeString(&Sid);
+ }
+ SamFreeMemory(Members);
+ return(STATUS_SUCCESS);
+
+}
+
+
+NTSTATUS
+GetAliasMembership(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ ULONG Index;
+ UNICODE_STRING Name;
+ OBJECT_ATTRIBUTES Oa;
+ LSA_HANDLE LsaHandle = NULL;
+ CAIROSID Sid;
+ ULONG SidLength = sizeof(CAIROSID);
+ WCHAR ReferencedDomainName[100];
+ ULONG DomainNameLength = 100;
+ SID_NAME_USE SidUse;
+ ULONG MembershipCount;
+ PULONG AliasList = NULL;
+ PSID SidAddress = (PSID) &Sid;
+
+
+
+ printf("Looking up groups for user %ws\n",Parameter[0]);
+
+ if (!LookupAccountNameW(
+ NULL,
+ Parameter[0],
+ SidAddress,
+ &SidLength,
+ ReferencedDomainName,
+ &DomainNameLength,
+ &SidUse))
+ {
+ printf("Failed to lookup account sid: %d\n",GetLastError());
+ return(STATUS_UNSUCCESSFUL);
+ }
+
+
+
+ Status = SamGetAliasMembership(
+ DomainHandle,
+ 1,
+ &SidAddress,
+ &MembershipCount,
+ &AliasList
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get alises : 0x%x\n",Status);
+ return(Status);
+ }
+
+ for (Index = 0; Index < MembershipCount ; Index++ )
+ {
+ printf("Alias Member %d: rid 0x%x\n",
+ Index,AliasList[Index]);
+
+ }
+ SamFreeMemory(AliasList);
+ return(STATUS_SUCCESS);
+
+}
+
+NTSTATUS
+GetAccountRid( LPWSTR Parameter,
+ PULONG RelativeId)
+{
+
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING UserName;
+
+ RtlInitUnicodeString(
+ &UserName,
+ Parameter
+ );
+
+ printf("Looking up User %wZ\n",&UserName);
+
+ Status = SamLookupNamesInDomain(
+ DomainHandle,
+ 1,
+ &UserName,
+ &Rid,
+ &Use
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to lookup User: 0x%x\n",Status);
+ return(Status);
+ }
+ *RelativeId = *Rid;
+ SamFreeMemory(Rid);
+ SamFreeMemory(Use);
+
+ return(STATUS_SUCCESS);
+}
+NTSTATUS
+OpenUser( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING UserName;
+ ULONG RelativeId = 0;
+
+ Status = GetAccountRid(Parameter[0],&RelativeId);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get account rid: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Opening User 0x%x\n",RelativeId);
+ Status= SamOpenUser(
+ DomainHandle,
+ MAXIMUM_ALLOWED,
+ RelativeId,
+ &UserHandle
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to open User: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+DelUser( LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+
+ Status = SamDeleteUser(UserHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to delete user: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+DelGroup( LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+
+ Status = SamDeleteGroup(GroupHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to delete user: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+DelAlias( LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+
+ Status = SamDeleteAlias(AliasHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to delete Alias: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+CreateUser( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING UserName;
+ ULONG RelativeId = 0;
+ ACCESS_MASK GrantedAccess;
+
+
+ RtlInitUnicodeString(
+ &UserName,
+ Parameter[0]
+ );
+
+ printf("Creating User %wZ\n",&UserName);
+
+ Status= SamCreateUser2InDomain(
+ DomainHandle,
+ &UserName,
+ USER_NORMAL_ACCOUNT,
+ MAXIMUM_ALLOWED,
+ &UserHandle,
+ &GrantedAccess,
+ &RelativeId
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to create User: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Created user with rid 0x%x, access 0x%x\n",
+ RelativeId, GrantedAccess);
+ return(Status);
+}
+
+NTSTATUS
+CreateGroup( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING GroupName;
+ ULONG RelativeId = 0;
+
+
+ RtlInitUnicodeString(
+ &GroupName,
+ Parameter[0]
+ );
+
+ printf("Creating Group %wZ\n",&GroupName);
+
+ Status= SamCreateGroupInDomain(
+ DomainHandle,
+ &GroupName,
+ MAXIMUM_ALLOWED,
+ &GroupHandle,
+ &RelativeId
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to create Group: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Created Group with rid 0x%x, access 0x%x\n",
+ RelativeId);
+ return(Status);
+}
+
+NTSTATUS
+CreateAlias( LPWSTR * Parameter)
+{
+ PSID_NAME_USE Use = NULL;
+ PULONG Rid = NULL;
+ NTSTATUS Status;
+ UNICODE_STRING AliasName;
+ ULONG RelativeId = 0;
+
+
+ RtlInitUnicodeString(
+ &AliasName,
+ Parameter[0]
+ );
+
+ printf("Creating Alias %wZ\n",&AliasName);
+
+ Status= SamCreateAliasInDomain(
+ DomainHandle,
+ &AliasName,
+ MAXIMUM_ALLOWED,
+ &AliasHandle,
+ &RelativeId
+ );
+
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to create Alias: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Created Alias with rid 0x%x, access 0x%x\n",
+ RelativeId);
+ return(Status);
+}
+
+
+
+NTSTATUS
+GetGroupsForUser(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ ULONG MembershipCount;
+ PULONG Attributes = NULL;
+ PULONG Rids = NULL;
+ ULONG Index;
+ PGROUP_MEMBERSHIP Groups = NULL;
+
+ Status = SamGetGroupsForUser(
+ UserHandle,
+ &Groups,
+ &MembershipCount
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get groups for user: 0x%x\n",Status);
+ return(Status);
+ }
+
+ for (Index = 0; Index < MembershipCount ; Index++ )
+ {
+ printf("Member %d: rid 0x%x, attributes 0x%x\n",
+ Index,Groups[Index].RelativeId, Groups[Index].Attributes );
+ }
+ SamFreeMemory(Groups);
+ return(STATUS_SUCCESS);
+
+}
+
+void
+PrintLogonHours(
+ char * String,
+ PLOGON_HOURS LogonHours
+ )
+{
+ int Index;
+ printf("%s",String);
+ for (Index = 0; Index < (LogonHours->UnitsPerWeek + 7) / 8 ;Index++ )
+ {
+ printf("0x%2.2x ",LogonHours->LogonHours[Index]);
+ }
+ printf("\n");
+}
+
+
+NTSTATUS
+DumpUser(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ PUSER_ALL_INFORMATION UserAll = NULL;
+ PUSER_GENERAL_INFORMATION UserGeneral = NULL;
+ PUSER_PREFERENCES_INFORMATION UserPreferences = NULL;
+ PUSER_LOGON_INFORMATION UserLogon = NULL;
+ PUSER_ACCOUNT_INFORMATION UserAccount = NULL;
+ PUSER_ACCOUNT_NAME_INFORMATION UserAccountName = NULL;
+ PUSER_FULL_NAME_INFORMATION UserFullName = NULL;
+ PUSER_NAME_INFORMATION UserName = NULL;
+ PUSER_PRIMARY_GROUP_INFORMATION UserPrimary = NULL;
+ PUSER_HOME_INFORMATION UserHome = NULL;
+ PUSER_SCRIPT_INFORMATION UserScript = NULL;
+ PUSER_PROFILE_INFORMATION UserProfile = NULL;
+ PUSER_ADMIN_COMMENT_INFORMATION UserAdminComment = NULL;
+ PUSER_WORKSTATIONS_INFORMATION UserWksta = NULL;
+ PUSER_CONTROL_INFORMATION UserControl = NULL;
+ PUSER_EXPIRES_INFORMATION UserExpires = NULL;
+ PUSER_LOGON_HOURS_INFORMATION UserLogonHours = NULL;
+
+ printf("\nDumpUser.\n");
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserAllInformation,
+ (PVOID *) &UserAll
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user all: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserAll:\n");
+ PrintTime("\tLastLogon = ",&UserAll->LastLogon);
+ PrintTime("\tLastLogoff = ",&UserAll->LastLogoff);
+ PrintTime("\tPasswordLastSet = ",&UserAll->PasswordLastSet);
+ PrintTime("\tAccountExpires = ",&UserAll->AccountExpires);
+ PrintTime("\tPasswordCanChange = ",&UserAll->PasswordCanChange);
+ PrintTime("\tPasswordMustChange = ",&UserAll->PasswordMustChange);
+ printf("\tUserName = %wZ\n",&UserAll->UserName);
+ printf("\tFullName = %wZ\n",&UserAll->FullName);
+ printf("\tHomeDirectory = %wZ\n",&UserAll->HomeDirectory);
+ printf("\tHomeDirectoryDrive = %wZ\n",&UserAll->HomeDirectoryDrive);
+ printf("\tScriptPath = %wZ\n",&UserAll->ScriptPath);
+ printf("\tProfilePath = %wZ\n",&UserAll->ProfilePath);
+ printf("\tAdminComment = %wZ\n",&UserAll->AdminComment);
+ printf("\tWorkStations = %wZ\n",&UserAll->WorkStations);
+ printf("\tUserComment = %wZ\n",&UserAll->UserComment);
+ printf("\tParameters = %wZ\n",&UserAll->Parameters);
+ printf("\tUserId = 0x%x\n",UserAll->UserId);
+ printf("\tPrimaryGroupId = 0x%x\n",UserAll->PrimaryGroupId);
+ printf("\tUserAccountControl = 0x%x\n",UserAll->UserAccountControl);
+ printf("\tWhichFields = 0x%x\n",UserAll->WhichFields);
+ PrintLogonHours("\tLogonHours = ",&UserAll->LogonHours);
+ printf("\tLogonCount = %d\n",UserAll->LogonCount);
+ printf("\tCountryCode = %d\n",UserAll->CountryCode);
+ printf("\tCodePage = %d\n",UserAll->CodePage);
+
+ SamFreeMemory(UserAll);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserGeneralInformation,
+ (PVOID *) &UserGeneral
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user general: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserGeneral:\n");
+ printf("\tUserName = %wZ\n",&UserGeneral->UserName);
+ printf("\tFullName = %wZ\n",&UserGeneral->FullName);
+ printf("\tPrimaryGroupId = 0x%x\n",UserGeneral->PrimaryGroupId);
+ printf("\tAdminComment = 0x%x\n",&UserGeneral->AdminComment);
+ printf("\tUserComment = 0x%x\n",&UserGeneral->UserComment);
+
+ SamFreeMemory(UserGeneral);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserPreferencesInformation,
+ (PVOID *) &UserPreferences
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user preferences: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserPreferences:\n");
+ printf("\tUserComment = %wZ\n",&UserPreferences->UserComment);
+ printf("\tReserved1 = %wZ\n",&UserPreferences->Reserved1);
+ printf("\tCountryCode = %d\n",&UserPreferences->CountryCode);
+ printf("\tCodePage = %d\n",&UserPreferences->CodePage);
+
+ SamFreeMemory(UserPreferences);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserLogonInformation,
+ (PVOID *) &UserLogon
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user Logon: 0x%x\n",Status);
+ return(Status);
+ }
+
+
+ printf("UserLogon:\n");
+ printf("\tUserName = %wZ\n",&UserLogon->UserName);
+ printf("\tFullName = %wZ\n",&UserLogon->FullName);
+ printf("\tUserId = 0x%x\n",UserLogon->UserId);
+ printf("\tPrimaryGroupId = 0x%x\n",UserLogon->PrimaryGroupId);
+ printf("\tHomeDirectory = %wZ\n",&UserLogon->HomeDirectory);
+ printf("\tHomeDirectoryDrive = %wZ\n",&UserLogon->HomeDirectoryDrive);
+ printf("\tScriptPath = %wZ\n",&UserLogon->ScriptPath);
+ printf("\tProfilePath = %wZ\n",&UserLogon->ProfilePath);
+ printf("\tWorkStations = %wZ\n",&UserLogon->WorkStations);
+ PrintTime("\tLastLogon = ",&UserLogon->LastLogon);
+ PrintTime("\tLastLogoff = ",&UserLogon->LastLogoff);
+ PrintTime("\tPasswordLastSet = ",&UserLogon->PasswordLastSet);
+ PrintTime("\tPasswordCanChange = ",&UserLogon->PasswordCanChange);
+ PrintTime("\tPasswordMustChange = ",&UserLogon->PasswordMustChange);
+ PrintLogonHours("\tLogonHours = ",&UserLogon->LogonHours);
+ printf("\tBadPasswordCount = %d\n",UserLogon->BadPasswordCount);
+ printf("\tLogonCount = %d\n",UserLogon->LogonCount);
+ printf("\tUserAccountControl = 0x%x\n",UserLogon->UserAccountControl);
+
+ SamFreeMemory(UserLogon);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserAccountInformation,
+ (PVOID *) &UserAccount
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user account: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserAccount:\n");
+ printf("\tUserName = %wZ\n",&UserAccount->UserName);
+ printf("\tFullName = %wZ\n",&UserAccount->FullName);
+ printf("\tUserId = 0x%x\n",UserAccount->UserId);
+ printf("\tPrimaryGroupId = 0x%x\n",UserAccount->PrimaryGroupId);
+ printf("\tHomeDirectory = %wZ\n",&UserAccount->HomeDirectory);
+ printf("\tHomeDirectoryDrive = %wZ\n",&UserAccount->HomeDirectoryDrive);
+ printf("\tScriptPath = %wZ\n",&UserAccount->ScriptPath);
+ printf("\tProfilePath = %wZ\n",&UserAccount->ProfilePath);
+ printf("\tAdminComment = %wZ\n",&UserAccount->AdminComment);
+ printf("\tWorkStations = %wZ\n",&UserAccount->WorkStations);
+ PrintTime("\tLastLogon = ",&UserAccount->LastLogon);
+ PrintTime("\tLastLogoff = ",&UserAccount->LastLogoff);
+ PrintLogonHours("\tLogonHours = ",&UserAccount->LogonHours);
+ printf("\tBadPasswordCount = %d\n",UserAccount->BadPasswordCount);
+ printf("\tLogonCount = %d\n",UserAccount->LogonCount);
+ PrintTime("\tPasswordLastSet = ",&UserAccount->PasswordLastSet);
+ PrintTime("\tAccountExpires = ",&UserAccount->AccountExpires);
+ printf("\tUserAccountControl = 0x%x\n",UserAccount->UserAccountControl);
+
+ SamFreeMemory(UserAccount);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserAccountNameInformation,
+ (PVOID *) &UserAccountName
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user account name: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserAccountName:\n");
+ printf("\tUserName = %wZ\n",&UserAccountName->UserName);
+ SamFreeMemory(UserAccountName);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserFullNameInformation,
+ (PVOID *) &UserFullName
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user full name: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserFullName:\n");
+ printf("\tFullName = %wZ\n",&UserFullName->FullName);
+ SamFreeMemory(UserFullName);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserNameInformation,
+ (PVOID *) &UserName
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user name: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserName:\n");
+ printf("\tUserName = %wZ\n",&UserName->UserName);
+ printf("\tFullName = %wZ\n",&UserName->FullName);
+ SamFreeMemory(UserName);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserPrimaryGroupInformation,
+ (PVOID *) &UserPrimary
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user all: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserPrimaryGroup:\n");
+ printf("PrimaryGroupid = 0x%x\n",UserPrimary->PrimaryGroupId);
+ SamFreeMemory(UserPrimary);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserHomeInformation,
+ (PVOID *) &UserHome
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user home: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserHome:\n");
+ printf("\tHomeDirectory = %wZ\n",&UserHome->HomeDirectory);
+ printf("\tHomeDirectoryDrive = %wZ\n",&UserHome->HomeDirectoryDrive);
+
+ SamFreeMemory(UserHome);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserScriptInformation,
+ (PVOID *) &UserScript
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user Script: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserScript:\n");
+ printf("\tScriptPath = %wZ\n",&UserScript->ScriptPath);
+
+ SamFreeMemory(UserScript);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserProfileInformation,
+ (PVOID *) &UserProfile
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user Profile: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserProfile:\n");
+ printf("\tProfilePath = %wZ\n",&UserProfile->ProfilePath);
+
+ SamFreeMemory(UserProfile);
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserAdminCommentInformation,
+ (PVOID *) &UserAdminComment
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user AdminComment: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserAdminComment:\n");
+ printf("\tAdminComment = %wZ\n",&UserAdminComment->AdminComment);
+ SamFreeMemory(UserAdminComment);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserWorkStationsInformation,
+ (PVOID *) &UserWksta
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user wksta: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserWorkStations:\n");
+ printf("\tWorkStations = %wZ\n",&UserWksta->WorkStations);
+ SamFreeMemory(UserWksta);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserControlInformation,
+ (PVOID *) &UserControl
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user Control: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserControl:\n");
+ printf("\tUserAccountControl = 0x%x\n",UserControl->UserAccountControl);
+ SamFreeMemory(UserControl);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserExpiresInformation,
+ (PVOID *) &UserExpires
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user Expires: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("UserExpires:\n");
+ PrintTime("\tAccountExpires = ",&UserExpires->AccountExpires);
+ SamFreeMemory(UserExpires);
+
+ Status = SamQueryInformationUser(
+ UserHandle,
+ UserLogonHoursInformation,
+ (PVOID *) &UserLogonHours
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query user LogonHours: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("UserLogonHours:\n");
+ PrintLogonHours("\tLogonHours = ",&UserLogonHours->LogonHours);
+
+ SamFreeMemory(UserLogonHours);
+
+
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+DumpGroup(LPWSTR * Parameter)
+{
+ NTSTATUS Status;
+ PGROUP_GENERAL_INFORMATION General = NULL;
+ PGROUP_NAME_INFORMATION Name = NULL;
+ PGROUP_ATTRIBUTE_INFORMATION Attribute = NULL;
+ PGROUP_ADM_COMMENT_INFORMATION AdmComment = NULL;
+
+ printf("\nDumpGroup.\n");
+ Status = SamQueryInformationGroup(
+ GroupHandle,
+ GroupGeneralInformation,
+ (PVOID *) &General
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get group general information: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Group General.Name = %wZ\n",&General->Name);
+ printf("Group General.Attributes = 0x%x\n",General->Attributes);
+ printf("Group general.memberCount = %d\n",General->MemberCount);
+ printf("Group general.AdminComment = %wZ\n",&General->AdminComment);
+ SamFreeMemory(General);
+
+ Status = SamQueryInformationGroup(
+ GroupHandle,
+ GroupNameInformation,
+ (PVOID *) &Name
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get group name information: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Group Name.Name = %wZ\n",&Name->Name);
+ SamFreeMemory(Name);
+
+ Status = SamQueryInformationGroup(
+ GroupHandle,
+ GroupAttributeInformation,
+ (PVOID *) &Attribute
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get group Attribute information: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Group Attribute.Attributes = 0x%x\n",Attribute->Attributes);
+ SamFreeMemory(Attribute);
+
+ Status = SamQueryInformationGroup(
+ GroupHandle,
+ GroupAdminCommentInformation,
+ (PVOID *) &AdmComment
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to get group admin comment information: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Group Admin comment.AdminComment = %wZ\n",&AdmComment->AdminComment);
+ SamFreeMemory(AdmComment);
+
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+DumpAllGroups(LPWSTR * Parameter)
+{
+ ULONG PreferedMax = 1000;
+ NTSTATUS Status,EnumStatus;
+ SAM_ENUMERATE_HANDLE EnumContext = 0;
+ ULONG CountReturned;
+ LPWSTR GroupName[1];
+
+ PSAM_RID_ENUMERATION Accounts = NULL;
+
+
+ GroupName[0] = (LPWSTR) malloc(128);
+
+ printf("DumpAllGroups:\n");
+
+
+ EnumContext = 0;
+ ASSERT(DomainHandle != NULL);
+ do
+ {
+ EnumStatus = SamEnumerateGroupsInDomain(
+ DomainHandle,
+ &EnumContext,
+ (PVOID *) &Accounts,
+ PreferedMax,
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(EnumStatus) && (EnumStatus != STATUS_NO_MORE_ENTRIES))
+ {
+ ULONG Index;
+ UNICODE_STRING SidString;
+
+ for (Index = 0; Index < CountReturned; Index++)
+ {
+ RtlCopyMemory(
+ GroupName[0],
+ Accounts[Index].Name.Buffer,
+ Accounts[Index].Name.Length
+ );
+ GroupName[0][Accounts[Index].Name.Length/sizeof(WCHAR)] = L'\0';
+
+ Status = OpenGroup(GroupName);
+ if (!NT_SUCCESS(Status))
+ {
+ break;
+ }
+ Status = DumpGroup(NULL);
+ SamCloseHandle(GroupHandle);
+ GroupHandle = NULL;
+
+ }
+ SamFreeMemory(Accounts);
+ }
+ else printf("Failed to enumerate Groups: 0x%x\n",Status);
+ } while (NT_SUCCESS(EnumStatus) && (EnumStatus != STATUS_SUCCESS) && (CountReturned != 0) );
+
+ free(GroupName[0]);
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+DumpAllUsers(LPWSTR * Parameter)
+{
+ ULONG PreferedMax = 1000;
+ NTSTATUS Status,EnumStatus;
+ SAM_ENUMERATE_HANDLE EnumContext = 0;
+ ULONG CountReturned;
+ LPWSTR UserName[1];
+
+ PSAM_RID_ENUMERATION Accounts = NULL;
+
+
+ UserName[0] = (LPWSTR) malloc(128);
+
+ printf("DumpAllUsers:\n");
+
+
+ EnumContext = 0;
+ ASSERT(DomainHandle != NULL);
+ do
+ {
+ EnumStatus = SamEnumerateUsersInDomain(
+ DomainHandle,
+ &EnumContext,
+ 0,
+ (PVOID *) &Accounts,
+ PreferedMax,
+ &CountReturned
+ );
+
+ if (NT_SUCCESS(EnumStatus) && (EnumStatus != STATUS_NO_MORE_ENTRIES))
+ {
+ ULONG Index;
+ UNICODE_STRING SidString;
+
+ for (Index = 0; Index < CountReturned; Index++)
+ {
+ RtlCopyMemory(
+ UserName[0],
+ Accounts[Index].Name.Buffer,
+ Accounts[Index].Name.Length
+ );
+ UserName[0][Accounts[Index].Name.Length/sizeof(WCHAR)] = L'\0';
+
+ Status = OpenUser(UserName);
+ if (!NT_SUCCESS(Status))
+ {
+ break;
+ }
+ Status = DumpUser(NULL);
+ Status = GetGroupsForUser(NULL);
+ SamCloseHandle(UserHandle);
+ UserHandle = NULL;
+
+ }
+ SamFreeMemory(Accounts);
+ }
+ else printf("Failed to enumerate users: 0x%x\n",Status);
+ } while (NT_SUCCESS(EnumStatus) && (EnumStatus != STATUS_SUCCESS) && (CountReturned != 0) );
+
+ free(UserName[0]);
+ return(STATUS_SUCCESS);
+}
+
+NTSTATUS
+AddAliasMember( LPWSTR * Parameter )
+{
+ BYTE Buffer[100];
+ PSID AccountSid = Buffer;
+ ULONG SidLen = 100;
+ SID_NAME_USE Use;
+ WCHAR ReferencedDomain[100];
+ ULONG DomainLen = 100;
+ NTSTATUS Status;
+
+ printf("Adding account %ws to alias\n",Parameter[0]);
+ if (!LookupAccountName(
+ NULL,
+ Parameter[0],
+ AccountSid,
+ &SidLen,
+ ReferencedDomain,
+ &DomainLen,
+ &Use))
+ {
+ printf("Failed to lookup account name: %d\n",GetLastError());
+ return(STATUS_UNSUCCESSFUL);
+ }
+
+ Status = SamAddMemberToAlias(
+ AliasHandle,
+ AccountSid
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to add member to alias: 0x%x\n",Status);
+ }
+ return(Status);
+}
+
+NTSTATUS
+DumpDomain( LPWSTR * Parameter )
+{
+ NTSTATUS Status;
+ PDOMAIN_PASSWORD_INFORMATION Password = NULL;
+ PDOMAIN_GENERAL_INFORMATION General = NULL;
+ PDOMAIN_LOGOFF_INFORMATION Logoff = NULL;
+ PDOMAIN_OEM_INFORMATION Oem = NULL;
+ PDOMAIN_NAME_INFORMATION Name = NULL;
+ PDOMAIN_REPLICATION_INFORMATION Replica = NULL;
+ PDOMAIN_SERVER_ROLE_INFORMATION ServerRole = NULL;
+ PDOMAIN_MODIFIED_INFORMATION Modified = NULL;
+ PDOMAIN_STATE_INFORMATION State = NULL;
+ PDOMAIN_GENERAL_INFORMATION2 General2 = NULL;
+ PDOMAIN_LOCKOUT_INFORMATION Lockout = NULL;
+ PDOMAIN_MODIFIED_INFORMATION2 Modified2 = NULL;
+
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainPasswordInformation,
+ (PVOID *) &Password
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query password information: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Password:\n");
+ printf("\tMinPasswordLength = %d\n",Password->MinPasswordLength);
+ printf("\tPasswordHistoryLength = %d\n",Password->PasswordHistoryLength);
+ printf("\tPasswordProperties = 0x%x\n",Password->PasswordProperties);
+ PrintDeltaTime("\tMaxPasswordAge = ",&Password->MaxPasswordAge);
+ PrintDeltaTime("\tMinPasswordAge = ",&Password->MinPasswordAge);
+
+ SamFreeMemory(Password);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainGeneralInformation,
+ (PVOID *) &General
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query general: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("General:\n");
+ PrintDeltaTime("\t ForceLogoff = ",&General->ForceLogoff);
+ printf("\t OemInformation = %wZ\n",&General->OemInformation);
+ printf("\t DomainName = %wZ\n",&General->DomainName);
+ printf("\t ReplicaSourceNodeName =%wZ\n",&General->ReplicaSourceNodeName);
+ printf("\t DomainModifiedCount = 0x%x,0x%x\n",
+ General->DomainModifiedCount.HighPart,
+ General->DomainModifiedCount.LowPart );
+ printf("\t DomainServerState = %d\n",General->DomainServerState);
+ printf("\t DomainServerRole = %d\n",General->DomainServerRole);
+ printf("\t UasCompatibilityRequired = %d\n",General->UasCompatibilityRequired);
+ printf("\t UserCount = %d\n",General->UserCount);
+ printf("\t GroupCount = %d\n",General->GroupCount);
+ printf("\t AliasCount = %d\n",General->AliasCount);
+
+ SamFreeMemory(General);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLogoffInformation,
+ (PVOID *) &Logoff
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query logoff: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Logoff:\n");
+ PrintDeltaTime("\t ForceLogoff = ",&Logoff->ForceLogoff);
+ SamFreeMemory(Logoff);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainOemInformation,
+ (PVOID *) &Oem
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Oem: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Oem:\n\t OemInformation = %wZ\n",&Oem->OemInformation);
+
+ SamFreeMemory(Oem);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainNameInformation,
+ (PVOID *) &Name
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Name: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Name:\n\t DomainName = %wZ\n",&Name->DomainName);
+
+ SamFreeMemory(Name);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainReplicationInformation,
+ (PVOID *) &Replica
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Replica: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Replica:\n\t ReplicaSourceNodeName = %wZ\n", &Replica->ReplicaSourceNodeName);
+ SamFreeMemory(Replica);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainServerRoleInformation,
+ (PVOID *) &ServerRole
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query ServerRole: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("ServerRole:\n\t DomainServerRole = %d\n",ServerRole->DomainServerRole);
+ SamFreeMemory(ServerRole);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainModifiedInformation,
+ (PVOID *) &Modified
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Modified: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("Modified:\n");
+ printf("\t DomainModifiedCount = 0x%x,0x%x\n",
+ Modified->DomainModifiedCount.HighPart,
+ Modified->DomainModifiedCount.LowPart );
+ PrintTime("\t CreationTime = ",&Modified->CreationTime);
+
+
+
+ SamFreeMemory(Modified);
+
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainStateInformation,
+ (PVOID *) &State
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query State: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("State:\n\t DomainServerState = %d\n",State->DomainServerState);
+ SamFreeMemory(State);
+
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainGeneralInformation2,
+ (PVOID *) &General2
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query General2: 0x%x\n",Status);
+ return(Status);
+ }
+
+ printf("General2:\n");
+ General = &General2->I1;
+ PrintDeltaTime("\t ForceLogoff = ",&General->ForceLogoff);
+ printf("\t OemInformation = %wZ\n",&General->OemInformation);
+ printf("\t DomainName = %wZ\n",&General->DomainName);
+ printf("\t ReplicaSourceNodeName =%wZ\n",&General->ReplicaSourceNodeName);
+ printf("\t DomainModifiedCount = 0x%x,0x%x\n",
+ General->DomainModifiedCount.HighPart,
+ General->DomainModifiedCount.LowPart );
+ printf("\t DomainServerState = %d\n",General->DomainServerState);
+ printf("\t DomainServerRole = %d\n",General->DomainServerRole);
+ printf("\t UasCompatibilityRequired = %d\n",General->UasCompatibilityRequired);
+ printf("\t UserCount = %d\n",General->UserCount);
+ printf("\t GroupCount = %d\n",General->GroupCount);
+ printf("\t AliasCount = %d\n",General->AliasCount);
+ PrintDeltaTime("\t LockoutDuration = ",&General2->LockoutDuration);
+ PrintDeltaTime("\t LockoutObservationWindow = ",&General2->LockoutObservationWindow);
+ printf("\t LockoutThreshold = %d\n",General2->LockoutThreshold);
+
+ SamFreeMemory(General2);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainLockoutInformation,
+ (PVOID *) &Lockout
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Lockout: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Lockout:\n");
+ PrintDeltaTime("\t LockoutDuration = ",&Lockout->LockoutDuration);
+ PrintDeltaTime("\t LockoutObservationWindow = ",&Lockout->LockoutObservationWindow);
+ printf("\t LockoutThreshold = %d\n",Lockout->LockoutThreshold);
+
+ SamFreeMemory(Lockout);
+
+ Status = SamQueryInformationDomain(
+ DomainHandle,
+ DomainModifiedInformation2,
+ (PVOID *) &Modified2
+ );
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to query Modified2: 0x%x\n",Status);
+ return(Status);
+ }
+ printf("Modified2:\n");
+ PrintTime("\t CreationTime = ",&Modified->CreationTime);
+ printf("\t DomainModifiedCount = 0x%x,0x%x\n",
+ Modified2->DomainModifiedCount.HighPart,
+ Modified2->DomainModifiedCount.LowPart );
+
+ printf("\t ModifiedCountAtLastPromotion = 0x%x,0x%x\n",
+ Modified2->ModifiedCountAtLastPromotion.HighPart,
+ Modified2->ModifiedCountAtLastPromotion.LowPart );
+
+ SamFreeMemory(Modified2);
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+void _cdecl
+main(int argc, char *argv[])
+{
+ ULONG Command = 0;
+ ULONG i,j,k;
+ BOOLEAN Found;
+ NTSTATUS Status;
+ Action Actions[20];
+ ULONG ActionCount = 0;
+
+ for (i = 1; i < (ULONG) argc ; i++ )
+ {
+ Found = FALSE;
+ for (j = 0; j < NUM_COMMANDS ; j++ )
+ {
+ if (!_stricmp(argv[i],Commands[j].Name))
+ {
+ Actions[ActionCount].CommandNumber = j;
+
+ if (Commands[j].Parameter != 0)
+ {
+ for (k = 0; k < Commands[j].Parameter ;k++ )
+ {
+ Actions[ActionCount].Parameter[k] = (LPWSTR) malloc(128);
+ if ((ULONG) argc > i)
+ {
+ mbstowcs(Actions[ActionCount].Parameter[k],argv[++i],128);
+ }
+ else
+ {
+ Actions[ActionCount].Parameter[k][0] = L'\0';
+ }
+ }
+ }
+ Found = TRUE;
+ ActionCount++;
+ break;
+ }
+ }
+ if (!Found)
+ {
+ printf("Switch %s not found\n", argv[i]);
+ return;
+ }
+ }
+
+// Status = OpenSam();
+// if (!NT_SUCCESS(Status))
+// {
+// printf("Failed to open sam: 0x%x\n",Status);
+// return;
+// }
+
+ for (i = 0; i < ActionCount ; i++ )
+ {
+ Status = Commands[Actions[i].CommandNumber].Function(Actions[i].Parameter);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed test %s : 0x%x\n",Commands[Actions[i].CommandNumber].Name,Status);
+ goto Cleanup;
+
+ }
+ }
+
+Cleanup:
+ if (DomainHandle != NULL)
+ {
+ Status = SamCloseHandle(DomainHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to close account: 0x%x\n",Status);
+ }
+ }
+ if (GroupHandle != NULL)
+ {
+ Status = SamCloseHandle(GroupHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to close account: 0x%x\n",Status);
+ }
+ }
+ if (AliasHandle != NULL)
+ {
+ Status = SamCloseHandle(AliasHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to close account: 0x%x\n",Status);
+ }
+ }
+ if (UserHandle != NULL)
+ {
+ Status = SamCloseHandle(UserHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to close account: 0x%x\n",Status);
+ }
+ }
+ Status = CloseSam();
+ if (!NT_SUCCESS(Status))
+ {
+ printf("Failed to close lsa: 0x%x\n",Status);
+ }
+
+ return;
+
+}
diff --git a/private/newsam/server/utest/sources b/private/newsam/server/utest/sources
new file mode 100644
index 000000000..3d54889d7
--- /dev/null
+++ b/private/newsam/server/utest/sources
@@ -0,0 +1,46 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+
+TARGETNAME=samtest
+TARGETPATH=obj
+TARGETTYPE=PROGRAM
+
+UMTYPE=console
+
+INCLUDES=..\..\..\inc
+
+SOURCES= samtest.cxx
+
+
+TARGETLIBS= \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\samlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib
+
+
+C_DEFINES=$(C_DEFINES) -DRPC_NO_WINDOWS_H -DUNICODE
+
+USE_CRTDLL=1
+
diff --git a/private/newsam/server/utility.c b/private/newsam/server/utility.c
new file mode 100644
index 000000000..ca2fddb20
--- /dev/null
+++ b/private/newsam/server/utility.c
@@ -0,0 +1,6935 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ utility.c
+
+Abstract:
+
+ This file contains utility services used by several other SAM files.
+
+Author:
+
+ Jim Kelly (JimK) 4-July-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+
+--*/
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Includes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+#include <samsrvp.h>
+#include <ntlsa.h>
+#include <nlrepl.h>
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// private service prototypes //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+#define VERBOSE_FLUSH 0
+
+#if VERBOSE_FLUSH
+#define VerbosePrint(s) KdPrint(s)
+#else
+#define VerbosePrint(s)
+#endif
+
+NTSTATUS
+SampRefreshRegistry(
+ VOID
+ );
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Database/registry access lock services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+VOID
+SampAcquireReadLock(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine obtains read access to the SAM data structures and
+ backing store.
+
+ Despite its apparent implications, read access is an exclusive access.
+ This is to support the model set up in which global variables are used
+ to track the "current" domain. In the future, if performance warrants,
+ a read lock could imply shared access to SAM data structures.
+
+ The primary implication of a read lock at this time is that no
+ changes to the SAM database will be made which require a backing
+ store update.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ None.
+
+
+--*/
+{
+ BOOLEAN Success;
+
+ //
+ // Before changing this to a non-exclusive lock, the display information
+ // module must be changed to use a separate locking mechanism. Davidc 5/12/92
+ //
+
+ Success = RtlAcquireResourceExclusive( &SampLock, TRUE );
+ ASSERT(Success);
+
+ //
+ // Allow LSA a chance to perform an integrity check
+ //
+
+ LsaIHealthCheck( LsaIHealthSamJustLocked );
+
+ return;
+}
+
+
+VOID
+SampReleaseReadLock(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases shared read access to the SAM data structures and
+ backing store.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+
+ None.
+
+
+--*/
+{
+
+ //
+ // Allow LSA a chance to perform an integrity check
+ //
+
+ LsaIHealthCheck( LsaIHealthSamAboutToFree );
+
+
+ SampTransactionWithinDomain = FALSE;
+ RtlReleaseResource( &SampLock );
+
+ return;
+}
+
+
+NTSTATUS
+SampAcquireWriteLock(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires exclusive access to the SAM data structures and
+ backing store.
+
+ This access is needed to perform a write operation.
+
+ This routine also initiates a new transaction for the write operation.
+
+
+ NOTE: It is not acceptable to acquire this lock recursively. An
+ attempt to do so will fail.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the write lock was acquired and the transaction
+ was successfully started.
+
+ Other values may be returned as a result of failure to initiate the
+ transaction. These include any values returned by RtlStartRXact().
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ (VOID)RtlAcquireResourceExclusive( &SampLock, TRUE );
+
+
+
+
+ SampTransactionWithinDomain = FALSE;
+
+ NtStatus = RtlStartRXact( SampRXactContext );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Allow LSA a chance to perform an integrity check
+ //
+
+ LsaIHealthCheck( LsaIHealthSamJustLocked );
+ return(NtStatus);
+ }
+
+ //
+ // If the transaction failed, release the lock.
+ //
+
+ (VOID)RtlReleaseResource( &SampLock );
+
+ DbgPrint("SAM: StartRxact failed, status = 0x%lx\n", NtStatus);
+
+ return(NtStatus);
+}
+
+
+VOID
+SampSetTransactionDomain(
+ IN ULONG DomainIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This routine sets a domain for a transaction. This must be done
+ if any domain-specific information is to be modified during a transaction.
+ In this case, the domain modification count will be updated upon commit.
+
+ This causes the UnmodifiedFixed information for the specified domain to
+ be copied to the CurrentFixed field for the in-memory representation of
+ that domain.
+
+
+Arguments:
+
+ DomainIndex - Index of the domain within which this transaction
+ will occur.
+
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the write lock was acquired and the transaction
+ was successfully started.
+
+ Other values may be returned as a result of failure to initiate the
+ transaction. These include any values returned by RtlStartRXact().
+
+
+
+--*/
+{
+
+ ASSERT(SampTransactionWithinDomain == FALSE);
+
+ SampTransactionWithinDomain = TRUE;
+ SampTransactionDomainIndex = DomainIndex;
+
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed =
+ SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed;
+
+
+ return;
+
+}
+
+
+
+NTSTATUS
+SampFlushThread(
+ IN PVOID ThreadParameter
+ )
+
+/*++
+
+Routine Description:
+
+ This thread is created when SAM's registry tree is changed.
+ It will sleep for a while, and if no other changes occur,
+ flush the changes to disk. If other changes keep occurring,
+ it will wait for a certain amount of time and then flush
+ anyway.
+
+ After flushing, the thread will wait a while longer. If no
+ other changes occur, it will exit.
+
+ Note that if any errors occur, this thread will simply exit
+ without flushing. The mainline code should create another thread,
+ and hopefully it will be luckier. Unfortunately, the error is lost
+ since there's nobody to give it to that will be able to do anything
+ about it.
+
+Arguments:
+
+ ThreadParameter - not used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ TIME minDelayTime, maxDelayTime, exitDelayTime;
+ LARGE_INTEGER startedWaitLoop;
+ LARGE_INTEGER currentTime;
+ NTSTATUS NtStatus;
+ BOOLEAN Finished = FALSE;
+
+ UNREFERENCED_PARAMETER( ThreadParameter );
+
+ NtQuerySystemTime( &startedWaitLoop );
+
+ //
+ // It would be more efficient to use constants here, but for now
+ // we'll recalculate the times each time we start the thread
+ // so that somebody playing with us can change the global
+ // time variables to affect performance.
+ //
+
+ minDelayTime.QuadPart = -1000 * 1000 * 10 *
+ ((LONGLONG)SampFlushThreadMinWaitSeconds);
+
+ maxDelayTime.QuadPart = -1000 * 1000 * 10 *
+ ((LONGLONG)SampFlushThreadMaxWaitSeconds);
+
+ exitDelayTime.QuadPart = -1000 * 1000 * 10 *
+ ((LONGLONG)SampFlushThreadExitDelaySeconds);
+
+ do {
+
+ VerbosePrint(("SAM: Flush thread sleeping\n"));
+
+ NtDelayExecution( FALSE, &minDelayTime );
+
+ VerbosePrint(("SAM: Flush thread woke up\n"));
+
+ NtStatus = SampAcquireWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+#ifdef SAMP_DBG_CONTEXT_TRACKING
+ SampDumpContexts();
+#endif
+
+ NtQuerySystemTime( &currentTime );
+
+ if ( LastUnflushedChange.QuadPart == SampHasNeverTime.QuadPart ) {
+
+ LARGE_INTEGER exitBecauseNoWorkRecentlyTime;
+
+ //
+ // No changes to flush. See if we should stick around.
+ //
+
+ exitBecauseNoWorkRecentlyTime = SampAddDeltaTime(
+ startedWaitLoop,
+ exitDelayTime
+ );
+
+ if ( exitBecauseNoWorkRecentlyTime.QuadPart < currentTime.QuadPart ) {
+
+ //
+ // We've waited for changes long enough; note that
+ // the thread is exiting.
+ //
+
+ FlushThreadCreated = FALSE;
+ Finished = TRUE;
+ }
+
+ } else {
+
+ LARGE_INTEGER noRecentChangesTime;
+ LARGE_INTEGER tooLongSinceFlushTime;
+
+ //
+ // There are changes to flush. See if it's time to do so.
+ //
+
+ noRecentChangesTime = SampAddDeltaTime(
+ LastUnflushedChange,
+ minDelayTime
+ );
+
+ tooLongSinceFlushTime = SampAddDeltaTime(
+ startedWaitLoop,
+ maxDelayTime
+ );
+
+ if ( (noRecentChangesTime.QuadPart < currentTime.QuadPart) ||
+ (tooLongSinceFlushTime.QuadPart < currentTime.QuadPart) ) {
+
+ //
+ // Min time has passed since last change, or Max time
+ // has passed since last flush. Let's flush.
+ //
+
+ NtStatus = NtFlushKey( SampKey );
+
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to flush RXact (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus)); // See following comment
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+ //
+ // Under normal conditions, we would have an
+ // ASSERT(NT_SUCCESS(NtStatus)) here. However,
+ // Because system shutdown can occur while we
+ // are waiting to flush, we have a race condition.
+ // When shutdown is made, another thread will be
+ // notified and perform a flush. That leaves this
+ // flush to potentially occur after the registry
+ // has been notified of system shutdown - which
+ // causes and error to be returned. Unfortunately,
+ // the error is REGISTRY_IO_FAILED - a great help.
+ //
+ // Despite this, we will only exit this loop only
+ // if we have success. This may cause us to enter
+ // into another wait and attempt another hive flush
+ // during shutdown, but the wait should never finish
+ // (unless shutdown takes more than 30 seconds). In
+ // other error situations though, we want to keep
+ // trying the flush until we succeed. Jim Kelly
+ //
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ LastUnflushedChange = SampHasNeverTime;
+ NtQuerySystemTime( &startedWaitLoop );
+
+ FlushThreadCreated = FALSE;
+ Finished = TRUE;
+ }
+ }
+ }
+
+ SampReleaseWriteLock( FALSE );
+
+ } else {
+
+ DbgPrint("SAM: Thread failed to get write lock, status = 0x%lx\n", NtStatus);
+
+ FlushThreadCreated = FALSE;
+ Finished = TRUE;
+ }
+
+ } while ( !Finished );
+
+ return( STATUS_SUCCESS );
+}
+
+
+
+NTSTATUS
+SampCommitChanges(
+ )
+
+/*++
+
+Routine Description:
+
+ Thie service commits any changes made to the backstore while exclusive
+ access was held.
+
+ If the operation was within a domain (which would have been indicated
+ via the SampSetTransactionDomain() api), then the CurrentFixed field for
+ that domain is added to the transaction before the transaction is
+ committed.
+
+ NOTE: Write operations within a domain do not have to worry about
+ updating the modified count for that domain. This routine
+ will automatically increment the ModifiedCount for a domain
+ when a commit is requested within that domain.
+
+ NOTE: When this routine returns any transaction will have either been
+ committed or aborted. i.e. there will be no transaction in progress.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the transaction was successfully commited.
+
+ Other values may be returned as a result of commital failure.
+
+--*/
+
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ BOOLEAN DomainInfoChanged = FALSE;
+ BOOLEAN AbortDone = FALSE;
+
+ NtStatus = STATUS_SUCCESS;
+
+ //
+ // If this transaction was within a domain then we have to:
+ //
+ // (1) Update the ModifiedCount of that domain,
+ //
+ // (2) Write out the CurrentFixed field for that
+ // domain (using RtlAddActionToRXact(), so that it
+ // is part of the current transaction).
+ //
+ // (3) Commit the RXACT.
+ //
+ // (4) If the commit is successful, then update the
+ // in-memory copy of the un-modified fixed-length data.
+ //
+ // Otherwise, we just do the commit.
+ //
+
+ if (SampTransactionWithinDomain == TRUE) {
+
+ if (SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
+ != DomainServerRoleBackup) {
+
+ //
+ // Don't update the modified count on backup controllers;
+ // the replicator will explicitly set the modified count.
+ //
+
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart +
+ 1;
+
+ DomainInfoChanged = TRUE;
+
+ } else {
+
+ //
+ // See if the domain information changed - if it did, we
+ // need to add the change to the RXACT.
+ //
+
+ if ( RtlCompareMemory(
+ &SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed,
+ &SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed,
+ sizeof(SAMP_V1_0A_FIXED_LENGTH_DOMAIN) ) !=
+ sizeof( SAMP_V1_0A_FIXED_LENGTH_DOMAIN) ) {
+
+ DomainInfoChanged = TRUE;
+ }
+ }
+
+ if ( DomainInfoChanged ) {
+
+ //
+ // The domain object's fixed information has changed, so set
+ // the changes in the domain object's private data.
+ //
+
+ NtStatus = SampSetFixedAttributes(
+ SampDefinedDomains[SampTransactionDomainIndex].
+ Context,
+ &SampDefinedDomains[SampTransactionDomainIndex].
+ CurrentFixed
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Normally when we dereference the context,
+ // SampStoreObjectAttributes() is called to add the
+ // latest change to the RXACT. But that won't happen
+ // for the domain object's change since this is the
+ // commit code, so we have to flush it by hand here.
+ //
+
+ NtStatus = SampStoreObjectAttributes(
+ SampDefinedDomains[SampTransactionDomainIndex].Context,
+ TRUE // Use the existing key handle
+ );
+ }
+ }
+ }
+
+
+ //
+ // If we still have no errors, try to commit the whole mess
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ if ( ( !FlushImmediately ) && ( !FlushThreadCreated ) ) {
+
+ HANDLE Thread;
+ DWORD Ignore;
+
+ //
+ // If we can't create the flush thread, ignore error and
+ // just flush by hand below.
+ //
+
+ Thread = CreateThread(
+ NULL,
+ 0L,
+ (LPTHREAD_START_ROUTINE)SampFlushThread,
+ NULL,
+ 0L,
+ &Ignore
+ );
+
+ if ( Thread != NULL ) {
+
+ FlushThreadCreated = TRUE;
+ VerbosePrint(("Flush thread created, handle = 0x%lx\n", Thread));
+ CloseHandle(Thread);
+ }
+ }
+
+ NtStatus = RtlApplyRXactNoFlush( SampRXactContext );
+
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to apply RXact without flush (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ if ( ( FlushImmediately ) || ( !FlushThreadCreated ) ) {
+
+ NtStatus = NtFlushKey( SampKey );
+
+#if SAMP_DIAGNOSTICS
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDiagPrint( DISPLAY_STORAGE_FAIL,
+ ("SAM: Failed to flush RXact (0x%lx)\n",
+ NtStatus) );
+ IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+#endif //SAMP_DIAGNOSTICS
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ FlushImmediately = FALSE;
+ LastUnflushedChange = SampHasNeverTime;
+ }
+
+ } else {
+
+ NtQuerySystemTime( &LastUnflushedChange );
+ }
+
+
+ //
+ // Commit successful, set our unmodified to now be the current...
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ if (SampTransactionWithinDomain == TRUE) {
+ SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed =
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed;
+ }
+ }
+
+ } else {
+
+ KdPrint(("SAM: Failed to commit changes to registry, status = 0x%lx\n", NtStatus));
+ KdPrint(("SAM: Restoring database to earlier consistent state\n"));
+
+ //
+ // Add an entry to the event log
+ //
+
+ SampWriteEventLog(
+ EVENTLOG_ERROR_TYPE,
+ 0, // Category
+ SAMMSG_COMMIT_FAILED,
+ NULL, // User Sid
+ 0, // Num strings
+ sizeof(NTSTATUS), // Data size
+ NULL, // String array
+ (PVOID)&NtStatus // Data
+ );
+
+ //
+ // The Rxact commital failed. We don't know how many registry
+ // writes were done for this transaction. We can't guarantee
+ // to successfully back them out anyway so all we can do is
+ // back out all changes since the last flush. When this is done
+ // we'll be back to a consistent database state although recent
+ // apis that were reported as succeeding will be 'undone'.
+ //
+
+ IgnoreStatus = SampRefreshRegistry();
+
+ if (!NT_SUCCESS(IgnoreStatus)) {
+
+ //
+ // This is very serious. We failed to revert to a previous
+ // database state and we can't proceed.
+ // Shutdown SAM operations.
+ //
+
+ SampServiceState = SampServiceTerminating;
+
+ KdPrint(("SAM: Failed to refresh registry, SAM has shutdown\n"));
+
+ //
+ // Add an entry to the event log
+ //
+
+ SampWriteEventLog(
+ EVENTLOG_ERROR_TYPE,
+ 0, // Category
+ SAMMSG_REFRESH_FAILED,
+ NULL, // User Sid
+ 0, // Num strings
+ sizeof(NTSTATUS), // Data size
+ NULL, // String array
+ (PVOID)&IgnoreStatus // Data
+ );
+
+ }
+
+
+ //
+ // Now all open contexts are invalid (contain invalid registry
+ // handles). The in memory registry handles have been
+ // re-opened so any new contexts should work ok.
+ //
+
+
+ //
+ // All unflushed changes have just been erased.
+ // There is nothing to flush
+ //
+ // If the refresh failed it is important to prevent any further
+ // registry flushes until the system is rebooted
+ //
+
+ FlushImmediately = FALSE;
+ LastUnflushedChange = SampHasNeverTime;
+
+ //
+ // The refresh effectively aborted the transaction
+ //
+
+ AbortDone = TRUE;
+ }
+ }
+
+
+
+
+ //
+ // Always abort the transaction on failure
+ //
+
+ if ( !NT_SUCCESS(NtStatus) && !AbortDone) {
+
+ NtStatus = RtlAbortRXact( SampRXactContext );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+
+
+ return( NtStatus );
+}
+
+
+
+NTSTATUS
+SampRefreshRegistry(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine backs out all unflushed changes in the registry.
+ This operation invalidates any open handles to the SAM hive.
+ Global handles that we keep around are closed and re-opened by
+ this routine. The net result of this call will be that the database
+ is taken back to a previous consistent state. All open SAM contexts
+ are invalidated since they have invalid registry handles in them.
+
+Arguments:
+
+ STATUS_SUCCESS : Operation completed successfully
+
+ Failure returns: We are in deep trouble. Normal operations can
+ not be resumed. SAM should be shutdown.
+
+Return Value:
+
+ None
+
+--*/
+{
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ HANDLE HiveKey;
+ BOOLEAN WasEnabled;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ UNICODE_STRING String;
+ ULONG i;
+
+ //
+ // Get a key handle to the root of the SAM hive
+ //
+
+ RtlInitUnicodeString( &String, L"\\Registry\\Machine\\SAM" );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &String,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &HiveKey,
+ KEY_QUERY_VALUE,
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to open SAM hive root key for refresh, status = 0x%lx\n", NtStatus));
+ return(NtStatus);
+ }
+
+
+ //
+ // Enable restore privilege in preparation for the refresh
+ //
+
+ NtStatus = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &WasEnabled);
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to enable restore privilege to refresh registry, status = 0x%lx\n", NtStatus));
+
+ IgnoreStatus = NtClose(HiveKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ return(NtStatus);
+ }
+
+
+ //
+ // Refresh the SAM hive
+ // This should not fail unless there is volatile storage in the
+ // hive or we don't have TCB privilege
+ //
+
+
+ NtStatus = NtRestoreKey(HiveKey, NULL, REG_REFRESH_HIVE);
+
+
+ IgnoreStatus = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, WasEnabled, FALSE, &WasEnabled);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ IgnoreStatus = NtClose(HiveKey);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to refresh registry, status = 0x%lx\n", NtStatus));
+ return(NtStatus);
+ }
+
+
+
+
+ //
+ // Now close the registry handles we keep in memory at all times
+ // This effectively closes all server and domain context keys
+ // since they are shared.
+ //
+
+ NtStatus = NtClose(SampKey);
+ ASSERT(NT_SUCCESS(NtStatus));
+ SampKey = INVALID_HANDLE_VALUE;
+
+ for (i = 0; i<SampDefinedDomainsCount; i++ ) {
+ NtStatus = NtClose(SampDefinedDomains[i].Context->RootKey);
+ ASSERT(NT_SUCCESS(NtStatus));
+ SampDefinedDomains[i].Context->RootKey = INVALID_HANDLE_VALUE;
+ }
+
+ //
+ // Mark all domain and server context handles as invalid since they've
+ // now been closed
+ //
+
+ SampInvalidateContextListKeys(&SampContextListHead, FALSE);
+
+ //
+ // Re-open the SAM root key
+ //
+
+ RtlInitUnicodeString( &String, L"\\Registry\\Machine\\Security\\SAM" );
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &String,
+ OBJ_CASE_INSENSITIVE,
+ 0,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &SampKey,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to re-open SAM root key after registry refresh, status = 0x%lx\n", NtStatus));
+ ASSERT(FALSE);
+ return(NtStatus);
+ }
+
+ //
+ // Re-initialize the in-memory domain contexts
+ // Each domain will re-initialize it's open user/group/alias contexts
+ //
+
+ for (i = 0; i<SampDefinedDomainsCount; i++ ) {
+
+ NtStatus = SampReInitializeSingleDomain(i);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to re-initialize domain %d context after registry refresh, status = 0x%lx\n", i, NtStatus));
+ return(NtStatus);
+ }
+ }
+
+ //
+ // Cleanup the current transcation context
+ // (It would be nice if there were a RtlDeleteRXactContext())
+ //
+ // Note we don't have to close the rootregistrykey in the
+ // xact context since it was SampKey which we've already closed.
+ //
+
+ NtStatus = RtlAbortRXact( SampRXactContext );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ NtStatus = NtClose(SampRXactContext->RXactKey);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Re-initialize the transaction context.
+ // We don't expect there to be a partially commited transaction
+ // since we're reverting to a previously consistent and committed
+ // database.
+ //
+
+ NtStatus = RtlInitializeRXact( SampKey, FALSE, &SampRXactContext );
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to re-initialize rxact context registry refresh, status = 0x%lx\n", NtStatus));
+ return(NtStatus);
+ }
+
+ ASSERT(NtStatus != STATUS_UNKNOWN_REVISION);
+ ASSERT(NtStatus != STATUS_RXACT_STATE_CREATED);
+ ASSERT(NtStatus != STATUS_RXACT_COMMIT_NECESSARY);
+ ASSERT(NtStatus != STATUS_RXACT_INVALID_STATE);
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampReleaseWriteLock(
+ IN BOOLEAN Commit
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases exclusive access to the SAM data structures and
+ backing store.
+
+ If any changes were made to the backstore while exclusive access
+ was held, then this service commits those changes. Otherwise, the
+ transaction started when exclusive access was obtained is rolled back.
+
+ If the operation was within a domain (which would have been indicated
+ via the SampSetTransactionDomain() api), then the CurrentFixed field for
+ that domain is added to the transaction before the transaction is
+ committed.
+
+ NOTE: Write operations within a domain do not have to worry about
+ updating the modified count for that domain. This routine
+ will automatically increment the ModifiedCount for a domain
+ when a commit is requested within that domain.
+
+
+
+Arguments:
+
+ Commit - A boolean value indicating whether modifications need to be
+ committed in the backing store. A value of TRUE indicates the
+ transaction should be committed. A value of FALSE indicates the
+ transaction should be aborted (rolled-back).
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the write lock was released and the transaction
+ was successfully commited or rolled back.
+
+ Other values may be returned as a result of failure to commit or
+ roll-back the transaction. These include any values returned by
+ RtlApplyRXact() or RtlAbortRXact(). In the case of a commit, it
+ may also represent errors returned by RtlAddActionToRXact().
+
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+
+ //
+ // Commit or rollback the transaction based upon the Commit parameter...
+ //
+
+ if (Commit == TRUE) {
+
+ NtStatus = SampCommitChanges();
+
+ } else {
+
+ NtStatus = RtlAbortRXact( SampRXactContext );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+
+ SampTransactionWithinDomain = FALSE;
+
+
+ //
+ // Allow LSA a chance to perform an integrity check
+ //
+
+ LsaIHealthCheck( LsaIHealthSamAboutToFree );
+
+
+ //
+ // And free the lock...
+ //
+
+ (VOID)RtlReleaseResource( &SampLock );
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampCommitAndRetainWriteLock(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to commit all changes made so far. The write-lock
+ is held for the duration of the commit and is retained by the caller upon
+ return.
+
+ The transaction domain is left intact as well.
+
+ NOTE: Write operations within a domain do not have to worry about
+ updating the modified count for that domain. This routine
+ will automatically increment the ModifiedCount for a domain
+ when a commit is requested within that domain.
+
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS - Indicates the transaction was successfully commited.
+
+
+ Other values may be returned as a result of failure to commit or
+ roll-back the transaction. These include any values returned by
+ RtlApplyRXact() or RtlAddActionToRXact().
+
+
+
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ NTSTATUS TempStatus;
+
+ NtStatus = SampCommitChanges();
+
+ //
+ // Start another transaction, since we're retaining the write lock.
+ // Note we do this even if the commit failed so that cleanup code
+ // won't get confused by the lack of a transaction.
+ //
+
+ TempStatus = RtlStartRXact( SampRXactContext );
+ ASSERT(NT_SUCCESS(TempStatus));
+
+ //
+ // Return the worst status
+ //
+
+ if (NT_SUCCESS(NtStatus)) {
+ NtStatus = TempStatus;
+ }
+
+ return(NtStatus);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Unicode registry key manipulation services //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+
+NTSTATUS
+SampRetrieveStringFromRegistry(
+ IN HANDLE ParentKey,
+ IN PUNICODE_STRING SubKeyName,
+ OUT PUNICODE_STRING Body
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves a unicode string buffer from the specified registry
+ sub-key and sets the output parameter "Body" to be that unicode string.
+
+ If the specified sub-key does not exist, then a null string will be
+ returned.
+
+ The string buffer is returned in a block of memory which the caller is
+ responsible for deallocating (using MIDL_user_free).
+
+
+
+Arguments:
+
+ ParentKey - Key to the parent registry key of the registry key
+ containing the unicode string. For example, to retrieve
+ the unicode string for a key called ALPHA\BETA\GAMMA, this
+ is the key to ALPHA\BETA.
+
+ SubKeyName - The name of the sub-key whose value contains
+ a unicode string to retrieve. This field should not begin with
+ a back-slash (\). For example, to retrieve the unicode string
+ for a key called ALPHA\BETA\GAMMA, the name specified by this
+ field would be "BETA".
+
+ Body - The address of a UNICODE_STRING whose fields are to be filled
+ in with the information retrieved from the sub-key. The Buffer
+ field of this argument will be set to point to an allocated buffer
+ containing the unicode string characters.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The string was retrieved successfully.
+
+ STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
+ string to be returned in.
+
+ Other errors as may be returned by:
+
+ NtOpenKey()
+ NtQueryInformationKey()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE SubKeyHandle;
+ ULONG IgnoreKeyType, KeyValueLength;
+ LARGE_INTEGER IgnoreLastWriteTime;
+
+
+ ASSERT(Body != NULL);
+
+ //
+ // Get a handle to the sub-key ...
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ SubKeyName,
+ OBJ_CASE_INSENSITIVE,
+ ParentKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &SubKeyHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ //
+ // Couldn't open the sub-key
+ // If it is OBJECT_NAME_NOT_FOUND, then build a null string
+ // to return. Otherwise, return nothing.
+ //
+
+ if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ Body->Buffer = MIDL_user_allocate( sizeof(UNICODE_NULL) );
+ if (Body->Buffer == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ Body->Length = 0;
+ Body->MaximumLength = sizeof(UNICODE_NULL);
+ Body->Buffer[0] = 0;
+
+ return( STATUS_SUCCESS );
+
+ } else {
+ return(NtStatus);
+ }
+
+ }
+
+
+
+ //
+ // Get the length of the unicode string
+ // We expect one of two things to come back here:
+ //
+ // 1) STATUS_BUFFER_OVERFLOW - In which case the KeyValueLength
+ // contains the length of the string.
+ //
+ // 2) STATUS_SUCCESS - In which case there is no string out there
+ // and we need to build an empty string for return.
+ //
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ SubKeyHandle,
+ &IgnoreKeyType,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ KeyValueLength = 0;
+ Body->Buffer = MIDL_user_allocate( KeyValueLength + sizeof(WCHAR) ); // Length of null string
+ if (Body->Buffer == NULL) {
+ IgnoreStatus = NtClose( SubKeyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ Body->Buffer[0] = 0;
+
+ } else {
+
+ if (NtStatus == STATUS_BUFFER_OVERFLOW) {
+ Body->Buffer = MIDL_user_allocate( KeyValueLength + sizeof(WCHAR) );
+ if (Body->Buffer == NULL) {
+ IgnoreStatus = NtClose( SubKeyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+ NtStatus = RtlpNtQueryValueKey(
+ SubKeyHandle,
+ &IgnoreKeyType,
+ Body->Buffer,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+ } else {
+ IgnoreStatus = NtClose( SubKeyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ return(NtStatus);
+ }
+ }
+
+ if (!NT_SUCCESS(NtStatus)) {
+ MIDL_user_free( Body->Buffer );
+ IgnoreStatus = NtClose( SubKeyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ return(NtStatus);
+ }
+
+ Body->Length = (USHORT)(KeyValueLength);
+ Body->MaximumLength = (USHORT)(KeyValueLength) + (USHORT)sizeof(WCHAR);
+ UnicodeTerminate(Body);
+
+
+ IgnoreStatus = NtClose( SubKeyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ return( STATUS_SUCCESS );
+
+
+}
+
+
+NTSTATUS
+SampPutStringToRegistry(
+ IN BOOLEAN RelativeToDomain,
+ IN PUNICODE_STRING SubKeyName,
+ IN PUNICODE_STRING Body
+ )
+
+/*++
+
+Routine Description:
+
+ This routine puts a unicode string into the specified registry
+ sub-key.
+
+ If the specified sub-key does not exist, then it is created.
+
+ NOTE: The string is assigned via the RXACT mechanism. Therefore,
+ it won't actually reside in the registry key until a commit
+ is performed.
+
+
+
+
+Arguments:
+
+ RelativeToDomain - This boolean indicates whether or not the name
+ of the sub-key provide via the SubKeyName parameter is relative
+ to the current domain or to the top of the SAM registry tree.
+ If the name is relative to the current domain, then this value
+ is set to TRUE. Otherwise this value is set to FALSE.
+
+ SubKeyName - The name of the sub-key to be assigned the unicode string.
+ This field should not begin with a back-slash (\). For example,
+ to put a unicode string into a key called ALPHA\BETA\GAMMA, the
+ name specified by this field would be "BETA".
+
+ Body - The address of a UNICODE_STRING to be placed in the registry.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The string was added to the RXACT transaction
+ successfully.
+
+ STATUS_INSUFFICIENT_RESOURCES - There was not enough heap memory
+ or other limited resource available to fullfil the request.
+
+ Other errors as may be returned by:
+
+ RtlAddActionToRXact()
+
+
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ UNICODE_STRING KeyName;
+
+
+ //
+ // Need to build up the name of the key from the root of the RXACT
+ // registry key. That is the root of the SAM registry database
+ // in our case. If RelativeToDomain is FALSE, then the name passed
+ // is already relative to the SAM registry database root.
+ //
+
+ if (RelativeToDomain == TRUE) {
+
+
+ NtStatus = SampBuildDomainSubKeyName(
+ &KeyName,
+ SubKeyName
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( &KeyName );
+ return(NtStatus);
+ }
+
+
+ } else {
+ KeyName = (*SubKeyName);
+ }
+
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ 0, // No KeyValueType
+ Body->Buffer,
+ Body->Length
+ );
+
+
+
+ //
+ // free the KeyName buffer if necessary
+ //
+
+ if (RelativeToDomain) {
+ SampFreeUnicodeString( &KeyName );
+ }
+
+
+ return( STATUS_SUCCESS );
+
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Unicode String related services - These use MIDL_user_allocate and //
+// MIDL_user_free so that the resultant strings can be given to the //
+// RPC runtime. //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SampInitUnicodeString(
+ IN OUT PUNICODE_STRING String,
+ IN USHORT MaximumLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes a unicode string to have zero length and
+ no initial buffer.
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+
+Arguments:
+
+ String - The address of a unicode string to initialize.
+
+ MaximumLength - The maximum length (in bytes) the string will need
+ to grow to. The buffer associated with the string is allocated
+ to be this size. Don't forget to allow 2 bytes for null termination.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - Successful completion.
+
+--*/
+
+{
+ String->Length = 0;
+ String->MaximumLength = MaximumLength;
+
+ String->Buffer = MIDL_user_allocate(MaximumLength);
+
+ if (String->Buffer != NULL) {
+ String->Buffer[0] = 0;
+
+ return(STATUS_SUCCESS);
+ } else {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+}
+
+
+
+NTSTATUS
+SampAppendUnicodeString(
+ IN OUT PUNICODE_STRING Target,
+ IN PUNICODE_STRING StringToAdd
+ )
+
+/*++
+
+Routine Description:
+
+ This routine appends the string pointed to by StringToAdd to the
+ string pointed to by Target. The contents of Target are replaced
+ by the result.
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+Arguments:
+
+ Target - The address of a unicode string to initialize to be appended to.
+
+ StringToAdd - The address of a unicode string to be added to the
+ end of Target.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - Successful completion.
+
+ STATUS_INSUFFICIENT_RESOURCES - There was not sufficient heap to fullfil
+ the requested operation.
+
+
+--*/
+{
+
+ USHORT TotalLength;
+ PWSTR NewBuffer;
+
+
+ TotalLength = Target->Length + StringToAdd->Length + (USHORT)(sizeof(UNICODE_NULL));
+
+ //
+ // If there isn't room in the target to append the new string,
+ // allocate a buffer that is large enough and move the current
+ // target into it.
+ //
+
+ if (TotalLength > Target->MaximumLength) {
+
+ NewBuffer = MIDL_user_allocate( (ULONG)TotalLength );
+ if (NewBuffer == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ RtlCopyMemory( NewBuffer, Target->Buffer, (ULONG)(Target->Length) );
+
+ MIDL_user_free( Target->Buffer );
+ Target->Buffer = NewBuffer;
+ Target->MaximumLength = TotalLength;
+
+ } else {
+ NewBuffer = Target->Buffer;
+ }
+
+
+ //
+ // There's now room in the target to append the string.
+ //
+
+ (PCHAR)NewBuffer += Target->Length;
+
+ RtlCopyMemory( NewBuffer, StringToAdd->Buffer, (ULONG)(StringToAdd->Length) );
+
+
+ Target->Length = TotalLength - (USHORT)(sizeof(UNICODE_NULL));
+
+
+ //
+ // Null terminate the resultant string
+ //
+
+ UnicodeTerminate(Target);
+
+ return(STATUS_SUCCESS);
+
+}
+
+
+
+VOID
+SampFreeUnicodeString(
+ IN PUNICODE_STRING String
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees the buffer associated with a unicode string
+ (using MIDL_user_free()).
+
+
+Arguments:
+
+ Target - The address of a unicode string to free.
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ if (String->Buffer != NULL) {
+ MIDL_user_free( String->Buffer );
+ }
+
+ return;
+}
+
+
+VOID
+SampFreeOemString(
+ IN POEM_STRING String
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees the buffer associated with an OEM string
+ (using MIDL_user_free()).
+
+
+
+Arguments:
+
+ Target - The address of an OEM string to free.
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ if (String->Buffer != NULL) {
+ MIDL_user_free( String->Buffer );
+ }
+
+ return;
+}
+
+
+NTSTATUS
+SampBuildDomainSubKeyName(
+ OUT PUNICODE_STRING KeyName,
+ IN PUNICODE_STRING SubKeyName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds a unicode string name of the string passed
+ via the SubKeyName argument. The resultant name is relative to
+ the root of the SAM root registry key.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+ The name built up is comprized of three components:
+
+ 1) The constant named domain parent key name ("DOMAINS").
+
+ 2) A backslash
+
+ 3) The name of the current transaction domain.
+
+ (optionally)
+
+ 4) A backslash
+
+ 5) The name of the domain's sub-key (specified by the SubKeyName
+ argument).
+
+
+ For example, if the current domain is called "MY_DOMAIN", then
+ the relative name of the sub-key named "FRAMITZ" is :
+
+ "DOMAINS\MY_DOMAIN\FRAMITZ"
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+
+
+Arguments:
+
+ KeyName - 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.
+
+
+ SubKeyName - (optional) The name of the domain sub-key. If this parameter
+ is not provided, then only the domain's name is produced.
+ This string is not modified.
+
+
+
+
+Return Value:
+
+
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USHORT TotalLength, SubKeyNameLength;
+
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+
+
+ //
+ // Initialize a string large enough to hold the name
+ //
+
+ if (ARGUMENT_PRESENT(SubKeyName)) {
+ SubKeyNameLength = SampBackSlash.Length + SubKeyName->Length;
+ } else {
+ SubKeyNameLength = 0;
+ }
+
+ TotalLength = SampNameDomains.Length +
+ SampBackSlash.Length +
+ SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
+ SubKeyNameLength +
+ (USHORT)(sizeof(UNICODE_NULL)); // for null terminator
+
+ NtStatus = SampInitUnicodeString( KeyName, TotalLength );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // "DOMAINS"
+ //
+
+ NtStatus = SampAppendUnicodeString( KeyName, &SampNameDomains);
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( KeyName );
+ return(NtStatus);
+ }
+
+ //
+ // "DOMAINS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( KeyName, &SampBackSlash );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( KeyName );
+ return(NtStatus);
+ }
+
+
+ //
+ // "DOMAINS\(domain name)"
+ //
+
+ NtStatus = SampAppendUnicodeString(
+ KeyName,
+ &SampDefinedDomains[SampTransactionDomainIndex].InternalName
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( KeyName );
+ return(NtStatus);
+ }
+
+
+ if (ARGUMENT_PRESENT(SubKeyName)) {
+
+ //
+ // "DOMAINS\(domain name)\"
+ //
+
+
+
+ NtStatus = SampAppendUnicodeString( KeyName, &SampBackSlash );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( KeyName );
+ return(NtStatus);
+ }
+
+
+ //
+ // "DOMAINS\(domain name)\(sub key name)"
+ //
+
+ NtStatus = SampAppendUnicodeString( KeyName, SubKeyName );
+ if (!NT_SUCCESS(NtStatus)) {
+ SampFreeUnicodeString( KeyName );
+ return(NtStatus);
+ }
+
+ }
+ return(NtStatus);
+
+}
+
+
+NTSTATUS
+SampBuildAccountKeyName(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ OUT PUNICODE_STRING AccountKeyName,
+ IN PUNICODE_STRING AccountName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds the name of either a group or user 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 account.
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+ 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 current transaction domain.
+
+ 4) A backslash
+
+ 5) The constant name of the group or user registry key
+ ("GROUPS" or "USERS").
+
+ 6) A backslash
+
+ 7) The constant name of the registry key containing the
+ account names ("NAMES").
+
+ and, if the AccountName is specified,
+
+ 8) A backslash
+
+ 9) The account name specified by the AccountName argument.
+
+
+ For example, given a AccountName of "XYZ_GROUP" and the current domain
+ is "ALPHA_DOMAIN", this would yield a resultant AccountKeyName of
+ "DOMAINS\ALPHA_DOMAIN\GROUPS\NAMES\XYZ_GROUP".
+
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+
+
+Arguments:
+
+ ObjectType - Indicates whether the account is a user or group account.
+
+ AccountKeyName - 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.
+
+
+ AccountName - The name of the account. This string is not
+ modified.
+
+
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - The name has been built.
+
+ STATUS_INVALID_ACCOUNT_NAME - The name specified is not legitimate.
+
+
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ USHORT TotalLength, AccountNameLength;
+ PUNICODE_STRING AccountTypeKeyName;
+ PUNICODE_STRING NamesSubKeyName;
+
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+ ASSERT( (ObjectType == SampGroupObjectType) ||
+ (ObjectType == SampAliasObjectType) ||
+ (ObjectType == SampUserObjectType) );
+
+
+ //
+ // If an account name was provided, then it must meet certain
+ // criteria.
+ //
+
+ if (ARGUMENT_PRESENT(AccountName)) {
+ if (
+ //
+ // Length must be legitimate
+ //
+
+ (AccountName->Length == 0) ||
+ (AccountName->Length > AccountName->MaximumLength) ||
+
+ //
+ // Buffer pointer is available
+ //
+
+ (AccountName->Buffer == NULL)
+
+
+ ) {
+ return(STATUS_INVALID_ACCOUNT_NAME);
+ }
+ }
+
+
+
+
+ switch (ObjectType) {
+ case SampGroupObjectType:
+ AccountTypeKeyName = &SampNameDomainGroups;
+ NamesSubKeyName = &SampNameDomainGroupsNames;
+ break;
+ case SampAliasObjectType:
+ AccountTypeKeyName = &SampNameDomainAliases;
+ NamesSubKeyName = &SampNameDomainAliasesNames;
+ break;
+ case SampUserObjectType:
+ AccountTypeKeyName = &SampNameDomainUsers;
+ NamesSubKeyName = &SampNameDomainUsersNames;
+ break;
+ }
+
+
+
+
+ //
+ // Allocate a buffer large enough to hold the entire name.
+ // Only count the account name if it is passed.
+ //
+
+ AccountNameLength = 0;
+ if (ARGUMENT_PRESENT(AccountName)) {
+ AccountNameLength = AccountName->Length + SampBackSlash.Length;
+ }
+
+ TotalLength = SampNameDomains.Length +
+ SampBackSlash.Length +
+ SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
+ SampBackSlash.Length +
+ AccountTypeKeyName->Length +
+ SampBackSlash.Length +
+ NamesSubKeyName->Length +
+ AccountNameLength +
+ (USHORT)(sizeof(UNICODE_NULL)); // for null terminator
+
+ NtStatus = SampInitUnicodeString( AccountKeyName, TotalLength );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampNameDomains);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)"
+ //
+
+
+ NtStatus = SampAppendUnicodeString(
+ AccountKeyName,
+ &SampDefinedDomains[SampTransactionDomainIndex].InternalName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS"
+ // or
+ // "DOMAINS\(domain name)\USERS"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, AccountTypeKeyName );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\"
+ // or
+ // "DOMAINS\(domain name)\USERS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\NAMES"
+ // or
+ // "DOMAINS\(domain name)\USERS\NAMES"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, NamesSubKeyName );
+ if (NT_SUCCESS(NtStatus) && ARGUMENT_PRESENT(AccountName)) {
+ //
+ // "DOMAINS\(domain name)\GROUPS\NAMES\"
+ // or
+ // "DOMAINS\(domain name)\USERS\NAMES\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\(account name)"
+ // or
+ // "DOMAINS\(domain name)\USERS\(account name)"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, AccountName );
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampBuildAccountSubKeyName(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ OUT PUNICODE_STRING AccountKeyName,
+ IN ULONG AccountRid,
+ IN PUNICODE_STRING SubKeyName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds the name of a key for one of the fields of either
+ a user or a group.
+
+ The name produced is relative to the SAM root.
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+ 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 current transaction domain.
+
+ 4) A backslash
+
+ 5) The constant name of the group or user registry key
+ ("Groups" or "Users").
+
+ 6) A unicode representation of the reltive ID of the account
+
+ and if the optional SubKeyName is provided:
+
+ 7) A backslash
+
+ 8) the sub key's name.
+ 4) The account name specified by the AccountName argument.
+
+
+ For example, given a AccountRid of 3187, a SubKeyName of "AdminComment"
+ and the current domain is "ALPHA_DOMAIN", this would yield a resultant
+ AccountKeyName of:
+
+ "DOMAINS\ALPHA_DOMAIN\GROUPS\00003187\AdminComment".
+
+
+
+ All allocation for this string will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+
+
+Arguments:
+
+ ObjectType - Indicates whether the account is a user or group account.
+
+ AccountKeyName - 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.
+
+
+ AccountName - The name of the account. This string is not
+ modified.
+
+Return Value:
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ USHORT TotalLength, SubKeyNameLength;
+ PUNICODE_STRING AccountTypeKeyName;
+ UNICODE_STRING RidNameU;
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+ ASSERT( (ObjectType == SampGroupObjectType) ||
+ (ObjectType == SampAliasObjectType) ||
+ (ObjectType == SampUserObjectType) );
+
+
+ switch (ObjectType) {
+ case SampGroupObjectType:
+ AccountTypeKeyName = &SampNameDomainGroups;
+ break;
+ case SampAliasObjectType:
+ AccountTypeKeyName = &SampNameDomainAliases;
+ break;
+ case SampUserObjectType:
+ AccountTypeKeyName = &SampNameDomainUsers;
+ break;
+ }
+
+ //
+ // Determine how much space will be needed in the resultant name
+ // buffer to allow for the sub-key-name.
+ //
+
+ if (ARGUMENT_PRESENT(SubKeyName)) {
+ SubKeyNameLength = SubKeyName->Length + SampBackSlash.Length;
+ } else {
+ SubKeyNameLength = 0;
+ }
+
+ //
+ // Convert the account Rid to Unicode.
+ //
+
+ NtStatus = SampRtlConvertUlongToUnicodeString(
+ AccountRid,
+ 16,
+ 8,
+ TRUE,
+ &RidNameU
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // allocate a buffer large enough to hold the entire name
+ //
+
+ TotalLength = SampNameDomains.Length +
+ SampBackSlash.Length +
+ SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
+ SampBackSlash.Length +
+ AccountTypeKeyName->Length +
+ RidNameU.Length +
+ SubKeyNameLength +
+ (USHORT)(sizeof(UNICODE_NULL)); // for null terminator
+
+ NtStatus = SampInitUnicodeString( AccountKeyName, TotalLength );
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // "DOMAINS"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampNameDomains);
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)"
+ //
+
+
+ NtStatus = SampAppendUnicodeString(
+ AccountKeyName,
+ &SampDefinedDomains[SampTransactionDomainIndex].InternalName
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS"
+ // or
+ // "DOMAINS\(domain name)\USERS"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, AccountTypeKeyName );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\"
+ // or
+ // "DOMAINS\(domain name)\USERS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\(rid)"
+ // or
+ // "DOMAINS\(domain name)\USERS\(rid)"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &RidNameU );
+
+ if (NT_SUCCESS(NtStatus) && ARGUMENT_PRESENT(SubKeyName)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\(rid)\"
+ // or
+ // "DOMAINS\(domain name)\USERS\(rid)\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // "DOMAINS\(domain name)\GROUPS\(rid)\(sub-key-name)"
+ // or
+ // "DOMAINS\(domain name)\USERS\(rid)\(sub-key-name)"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, SubKeyName );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ MIDL_user_free(RidNameU.Buffer);
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampBuildAliasMembersKeyName(
+ IN PSID AccountSid,
+ OUT PUNICODE_STRING DomainKeyName,
+ OUT PUNICODE_STRING AccountKeyName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds the name of a key for the alias membership for an
+ arbitrary account sid. Also produced is the name of the key for the
+ domain of the account. This is the account key name without the last
+ account rid component.
+
+ The names produced is relative to the SAM root.
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+ The names built up are comprised of the following components:
+
+ 1) The constant named domain parent key name ("DOMAINS").
+
+ 2) A backslash
+
+ 3) The name of the current transaction domain.
+
+ 4) A backslash
+
+ 5) The constant name of the alias registry key ("Aliases").
+
+ 6) A backslash
+
+ 7) The constant name of the alias members registry key ("Members").
+
+ 8) A backslash
+
+ 9) A unicode representation of the SID of the account domain
+
+ and for the AccountKeyName only
+
+ 10) A backslash
+
+ 11) A unicode representation of the RID of the account
+
+
+ For example, given a Account Sid of 1-2-3-3187
+ and the current domain is "ALPHA_DOMAIN",
+ this would yield a resultant AcccountKeyName of:
+
+ "DOMAINS\ALPHA_DOMAIN\ALIASES\MEMBERS\1-2-3\00003187".
+
+ and a DomainKeyName of:
+
+ "DOMAINS\ALPHA_DOMAIN\ALIASES\MEMBERS\1-2-3".
+
+
+
+ All allocation for these strings will be done using MIDL_user_allocate.
+ Any deallocations will be done using MIDL_user_free.
+
+
+
+Arguments:
+
+ AccountSid - The account whose alias membership in the current domain
+ is to be determined.
+
+ DomainKeyName - The address of a unicode string whose
+ buffer is to be filled in with the full name of the domain registry key.
+ If successfully created, this string must be released with
+ SampFreeUnicodeString() when no longer needed.
+
+ AccountKeyName - The address of a unicode string whose
+ buffer is to be filled in with the full name of the account registry key.
+ If successfully created, this string must be released with
+ SampFreeUnicodeString() when no longer needed.
+
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - the domain and account key names are valid.
+
+ STATUS_INVALID_SID - the AccountSid is not valid. AccountSids must have
+ a sub-authority count > 0
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ USHORT DomainTotalLength;
+ USHORT AccountTotalLength;
+ UNICODE_STRING DomainNameU, TempStringU;
+ UNICODE_STRING RidNameU;
+ PSID DomainSid = NULL;
+ ULONG AccountRid;
+ ULONG AccountSubAuthorities;
+
+ DomainNameU.Buffer = TempStringU.Buffer = RidNameU.Buffer = NULL;
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+
+ ASSERT(AccountSid != NULL);
+ ASSERT(DomainKeyName != NULL);
+ ASSERT(AccountKeyName != NULL);
+
+ //
+ // Split the account sid into domain sid and account rid
+ //
+
+ AccountSubAuthorities = (ULONG)*RtlSubAuthorityCountSid(AccountSid);
+
+ //
+ // Check for at least one sub-authority
+ //
+
+ if (AccountSubAuthorities < 1) {
+
+ return (STATUS_INVALID_SID);
+ }
+
+ //
+ // Allocate space for the domain sid
+ //
+
+ DomainSid = MIDL_user_allocate(RtlLengthSid(AccountSid));
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ if (DomainSid == NULL) {
+
+ return(NtStatus);
+ }
+
+ //
+ // Initialize the domain sid
+ //
+
+ NtStatus = RtlCopySid(RtlLengthSid(AccountSid), DomainSid, AccountSid);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ *RtlSubAuthorityCountSid(DomainSid) = (UCHAR)(AccountSubAuthorities - 1);
+
+ //
+ // Initialize the account rid
+ //
+
+ AccountRid = *RtlSubAuthoritySid(AccountSid, AccountSubAuthorities - 1);
+
+ //
+ // Convert the domain sid into a registry key name string
+ //
+
+ NtStatus = RtlConvertSidToUnicodeString( &DomainNameU, DomainSid, TRUE);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ DomainNameU.Buffer = NULL;
+ goto BuildAliasMembersKeyNameError;
+ }
+
+ //
+ // Convert the account rid into a registry key name string with
+ // leading zeros.
+ //
+
+ NtStatus = SampRtlConvertUlongToUnicodeString(
+ AccountRid,
+ 16,
+ 8,
+ TRUE,
+ &RidNameU
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto BuildAliasMembersKeyNameError;
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // allocate a buffer large enough to hold the entire name
+ //
+
+ DomainTotalLength =
+ SampNameDomains.Length +
+ SampBackSlash.Length +
+ SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
+ SampBackSlash.Length +
+ SampNameDomainAliases.Length +
+ SampBackSlash.Length +
+ SampNameDomainAliasesMembers.Length +
+ SampBackSlash.Length +
+ DomainNameU.Length +
+ (USHORT)(sizeof(UNICODE_NULL)); // for null terminator
+
+ AccountTotalLength = DomainTotalLength +
+ SampBackSlash.Length +
+ RidNameU.Length;
+
+ //
+ // First build the domain key name
+ //
+
+
+ NtStatus = SampInitUnicodeString( DomainKeyName, DomainTotalLength );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampInitUnicodeString( AccountKeyName, AccountTotalLength );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ SampFreeUnicodeString(DomainKeyName);
+
+ } else {
+
+ //
+ // "DOMAINS"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomains);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)"
+ //
+
+ NtStatus = SampAppendUnicodeString(
+ DomainKeyName,
+ &SampDefinedDomains[SampTransactionDomainIndex].InternalName
+ );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)\"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)\ALIASES"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomainAliases);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\MEMBERS"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomainAliasesMembers);
+ ASSERT(NT_SUCCESS(NtStatus));
+
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\MEMBERS\"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)"
+ //
+
+ NtStatus = SampAppendUnicodeString( DomainKeyName, &DomainNameU );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // Now build the account name by copying the domain name
+ // and suffixing the account Rid
+ //
+
+ RtlCopyUnicodeString(AccountKeyName, DomainKeyName);
+ ASSERT(AccountKeyName->Length = DomainKeyName->Length);
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)\"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
+ ASSERT(NT_SUCCESS(NtStatus));
+
+ //
+ // "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)\(AccountRid)"
+ //
+
+ NtStatus = SampAppendUnicodeString( AccountKeyName, &RidNameU );
+ ASSERT(NT_SUCCESS(NtStatus));
+ }
+ }
+
+ MIDL_user_free(RidNameU.Buffer);
+ }
+
+BuildAliasMembersKeyNameFinish:
+
+ //
+ // If necessary, free memory allocated for the DomainSid.
+ //
+
+ if (DomainSid != NULL) {
+
+ MIDL_user_free(DomainSid);
+ DomainSid = NULL;
+ }
+ if ( DomainNameU.Buffer != NULL ) {
+ RtlFreeUnicodeString( &DomainNameU );
+ }
+
+ return(NtStatus);
+
+BuildAliasMembersKeyNameError:
+
+ goto BuildAliasMembersKeyNameFinish;
+}
+
+
+NTSTATUS
+SampValidateNewAccountName(
+ PUNICODE_STRING NewAccountName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine validates a new user, alias or group account name.
+ This routine:
+
+ 1) Validates that the name is properly constructed.
+
+ 2) Is not already in use as a user, alias or group account name
+ in any of the local SAM domains.
+
+
+Arguments:
+
+ Name - The address of a unicode string containing the name to be
+ looked for.
+
+Return Value:
+
+ STATUS_SUCCESS - The new account name is valid, and not yet in use.
+
+ STATUS_ALIAS_EXISTS - The account name is already in use as a
+ alias account name.
+
+ STATUS_GROUP_EXISTS - The account name is already in use as a
+ group account name.
+
+ STATUS_USER_EXISTS - The account name is already in use as a user
+ account name.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ SID_NAME_USE Use;
+ ULONG Rid;
+ ULONG DomainIndex, CurrentTransactionDomainIndex;
+
+ //
+ // Save the current transaction domain indicator
+ //
+
+ CurrentTransactionDomainIndex = SampTransactionDomainIndex;
+
+ //
+ // Lookup the account in each of the local SAM domains
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ for (DomainIndex = 0;
+ ( (DomainIndex < SampDefinedDomainsCount) && NT_SUCCESS(NtStatus) );
+ DomainIndex++) {
+
+ SampTransactionWithinDomain = FALSE;
+ SampSetTransactionDomain( DomainIndex );
+
+ NtStatus = SampLookupAccountRid(
+ SampUnknownObjectType,
+ NewAccountName,
+ STATUS_NO_SUCH_USER,
+ &Rid,
+ &Use
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ //
+ // The only error allowed is that the account was not found.
+ // Convert this to success, and continue searching SAM domains.
+ // Propagate any other error.
+ //
+
+ if (NtStatus != STATUS_NO_SUCH_USER) {
+
+ break;
+ }
+
+ NtStatus = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // An account with the given Rid already exists. Return status
+ // indicating the type of the conflicting account.
+ //
+
+ switch (Use) {
+
+ case SidTypeUser:
+
+ NtStatus = STATUS_USER_EXISTS;
+ break;
+
+ case SidTypeGroup:
+
+ NtStatus = STATUS_GROUP_EXISTS;
+ break;
+
+ case SidTypeDomain:
+
+ NtStatus = STATUS_DOMAIN_EXISTS;
+ break;
+
+ case SidTypeAlias:
+
+ NtStatus = STATUS_ALIAS_EXISTS;
+ break;
+
+ case SidTypeWellKnownGroup:
+
+ NtStatus = STATUS_GROUP_EXISTS;
+ break;
+
+ case SidTypeDeletedAccount:
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+
+ case SidTypeInvalid:
+
+ NtStatus = STATUS_INVALID_PARAMETER;
+ break;
+
+ default:
+
+ NtStatus = STATUS_INTERNAL_DB_CORRUPTION;
+ break;
+ }
+ }
+ }
+
+ //
+ // Restore the Current Transaction Domain
+ //
+
+ SampTransactionWithinDomain = FALSE;
+ SampSetTransactionDomain( CurrentTransactionDomainIndex );
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampValidateAccountNameChange(
+ IN PUNICODE_STRING NewAccountName,
+ IN PUNICODE_STRING OldAccountName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine validates a user, group or alias account name that is
+ to be set on an account. This routine:
+
+ 1) Returns success if the name is the same as the existing name,
+ except with a different case
+
+ 1) Otherwise calls SampValidateNewAccountName to verify that the
+ name is properly constructed and is not already in use as a
+ user, alias or group account name.
+
+Arguments:
+
+ NewAccountName - The address of a unicode string containing the new
+ name.
+
+ OldAccountName - The address of a unicode string containing the old
+ name.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The account's name may be changed to the new
+ account name
+
+ STATUS_ALIAS_EXISTS - The account name is already in use as a
+ alias account name.
+
+ STATUS_GROUP_EXISTS - The account name is already in use as a
+ group account name.
+
+ STATUS_USER_EXISTS - The account name is already in use as a user
+ account name.
+
+
+
+--*/
+
+{
+ //
+ // Compare the old and new names without regard for case. If they
+ // are the same, return success because the name was checked when we
+ // first added it; we don't care about case changes.
+ //
+
+ if ( 0 == RtlCompareUnicodeString(
+ NewAccountName,
+ OldAccountName,
+ TRUE ) ) {
+
+ return( STATUS_SUCCESS );
+ }
+
+ //
+ // Not just a case change; this is a different name. Validate it as
+ // any new name.
+ //
+
+ return( SampValidateNewAccountName( NewAccountName ) );
+}
+
+
+
+NTSTATUS
+SampRetrieveAccountCounts(
+ OUT PULONG UserCount,
+ OUT PULONG GroupCount,
+ OUT PULONG AliasCount
+ )
+
+
+/*++
+
+Routine Description:
+
+ This routine retrieve the number of user and group accounts in a domain.
+
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseReadLock().
+
+
+
+Arguments:
+
+ UserCount - Receives the number of user accounts in the domain.
+
+ GroupCount - Receives the number of group accounts in the domain.
+
+ AliasCount - Receives the number of alias accounts in the domain.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The values have been retrieved.
+
+ STATUS_INSUFFICIENT_RESOURCES - Not enough memory could be allocated
+ to perform the requested operation.
+
+ Other values are unexpected errors. These may originate from
+ internal calls to:
+
+ NtOpenKey()
+ NtQueryInformationKey()
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING KeyName;
+ PUNICODE_STRING AccountTypeKeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE AccountHandle;
+ ULONG KeyValueLength;
+ LARGE_INTEGER IgnoreLastWriteTime;
+
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+
+
+ //
+ // Get the user count first
+ //
+
+ AccountTypeKeyName = &SampNameDomainUsers;
+ NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Open this key and get its current value
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &AccountHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The count is stored as the KeyValueType
+ //
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ AccountHandle,
+ UserCount,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+
+ IgnoreStatus = NtClose( AccountHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ SampFreeUnicodeString( &KeyName );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+ //
+ // Now get the group count
+ //
+
+ AccountTypeKeyName = &SampNameDomainGroups;
+ NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Open this key and get its current value
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &AccountHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The count is stored as the KeyValueType
+ //
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ AccountHandle,
+ GroupCount,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+
+ IgnoreStatus = NtClose( AccountHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ SampFreeUnicodeString( &KeyName );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+ }
+
+ //
+ // Now get the alias count
+ //
+
+ AccountTypeKeyName = &SampNameDomainAliases;
+ NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Open this key and get its current value
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &AccountHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The count is stored as the KeyValueType
+ //
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ AccountHandle,
+ AliasCount,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+
+ IgnoreStatus = NtClose( AccountHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ SampFreeUnicodeString( &KeyName );
+ }
+
+ return( NtStatus );
+
+}
+
+
+NTSTATUS
+SampAdjustAccountCount(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN BOOLEAN Increment
+ )
+
+/*++
+
+Routine Description:
+
+ This routine increments or decrements the count of either
+ users or groups in a domain.
+
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseWriteLock().
+
+
+
+Arguments:
+
+ ObjectType - Indicates whether the account is a user or group account.
+
+ Increment - a BOOLEAN value indicating whether the user or group
+ count is to be incremented or decremented. A value of TRUE
+ will cause the count to be incremented. A value of FALSE will
+ cause the value to be decremented.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The value has been adjusted and the new value added
+ to the current RXACT transaction.
+
+ STATUS_INSUFFICIENT_RESOURCES - Not enough memory could be allocated
+ to perform the requested operation.
+
+ Other values are unexpected errors. These may originate from
+ internal calls to:
+
+ NtOpenKey()
+ NtQueryInformationKey()
+ RtlAddActionToRXact()
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ UNICODE_STRING KeyName;
+ PUNICODE_STRING AccountTypeKeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE AccountHandle;
+ ULONG Count, KeyValueLength;
+ LARGE_INTEGER IgnoreLastWriteTime;
+
+
+ ASSERT(SampTransactionWithinDomain == TRUE);
+ ASSERT( (ObjectType == SampGroupObjectType) ||
+ (ObjectType == SampAliasObjectType) ||
+ (ObjectType == SampUserObjectType) );
+
+
+ //
+ // Build the name of the key whose count is to be incremented or
+ // decremented.
+ //
+
+ switch (ObjectType) {
+ case SampGroupObjectType:
+ AccountTypeKeyName = &SampNameDomainGroups;
+ break;
+ case SampAliasObjectType:
+ AccountTypeKeyName = &SampNameDomainAliases;
+ break;
+ case SampUserObjectType:
+ AccountTypeKeyName = &SampNameDomainUsers;
+ break;
+ }
+
+ NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Open this key and get its current value
+ //
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &AccountHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // The count is stored as the KeyValueType
+ //
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ AccountHandle,
+ &Count,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if (Increment == TRUE) {
+ Count += 1;
+ } else {
+ ASSERT( Count != 0 );
+ Count -= 1;
+ }
+
+ NtStatus = RtlAddActionToRXact(
+ SampRXactContext,
+ RtlRXactOperationSetValue,
+ &KeyName,
+ Count,
+ NULL,
+ 0
+ );
+ }
+
+
+ IgnoreStatus = NtClose( AccountHandle );
+ ASSERT( NT_SUCCESS(IgnoreStatus) );
+ }
+
+ SampFreeUnicodeString( &KeyName );
+ }
+
+
+ return( STATUS_SUCCESS );
+
+
+}
+
+
+
+NTSTATUS
+SampEnumerateAccountNamesCommon(
+ IN SAMPR_HANDLE DomainHandle,
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ IN ULONG Filter,
+ OUT PULONG CountReturned
+ )
+
+/*++
+
+Routine Description:
+
+ This routine enumerates names of either user, group or alias accounts.
+ This routine is intended to directly support
+
+ SamrEnumerateGroupsInDomain(),
+ SamrEnumerateAliasesInDomain() and
+ SamrEnumerateUsersInDomain().
+
+ This routine performs database locking, and context lookup (including
+ access validation).
+
+
+
+
+ All allocation for OUT parameters will be done using MIDL_user_allocate.
+
+
+
+Arguments:
+
+ DomainHandle - The domain handle whose users or groups are to be enumerated.
+
+ ObjectType - Indicates whether users or groups are to be enumerated.
+
+ EnumerationContext - API specific handle to allow multiple calls. The
+ caller should return this value in successive calls to retrieve
+ additional information.
+
+ Buffer - Receives a pointer to the buffer containing the
+ requested information. The information returned is
+ structured as an array of SAM_ENUMERATION_INFORMATION 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.
+
+ Filter - if ObjectType is users, the users can optionally be filtered
+ by setting this field with bits from the AccountControlField that
+ must match. Otherwise ignored.
+
+ CountReturned - Receives the number of entries returned.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no additional entries. Entries may or may not have been
+ returned from this call. The CountReturned parameter indicates
+ whether any were.
+
+ STATUS_MORE_ENTRIES - There are more entries which may be obtained
+ using successive calls to this API. This is a successful return.
+
+ STATUS_ACCESS_DENIED - Caller does not have access to request the data.
+
+ STATUS_INVALID_HANDLE - The handle passed is invalid.
+
+
+--*/
+{
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT Context;
+ SAMP_OBJECT_TYPE FoundType;
+ ACCESS_MASK DesiredAccess;
+
+
+ ASSERT( (ObjectType == SampGroupObjectType) ||
+ (ObjectType == SampAliasObjectType) ||
+ (ObjectType == SampUserObjectType) );
+
+ //
+ // Make sure we understand what RPC is doing for (to) us.
+ //
+
+ ASSERT (DomainHandle != NULL);
+ ASSERT (EnumerationContext != NULL);
+ ASSERT ( Buffer != NULL);
+ ASSERT ((*Buffer) == NULL);
+ ASSERT (CountReturned != NULL);
+
+
+ //
+ // Establish type-specific information
+ //
+
+ DesiredAccess = DOMAIN_LIST_ACCOUNTS;
+
+
+ SampAcquireReadLock();
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ Context = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ Context,
+ DesiredAccess,
+ SampDomainObjectType,
+ &FoundType
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Call our private worker routine
+ //
+
+ NtStatus = SampEnumerateAccountNames(
+ ObjectType,
+ EnumerationContext,
+ Buffer,
+ PreferedMaximumLength,
+ Filter,
+ CountReturned,
+ Context->TrustedClient
+ );
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( Context, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+ //
+ // Free the read lock
+ //
+
+ SampReleaseReadLock();
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampEnumerateAccountNames(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
+ OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
+ IN ULONG PreferedMaximumLength,
+ IN ULONG Filter,
+ OUT PULONG CountReturned,
+ IN BOOLEAN TrustedClient
+ )
+
+/*++
+
+Routine Description:
+
+ This is the worker routine used to enumerate user, group or alias accounts
+
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseReadLock().
+
+
+
+ All allocation for OUT parameters will be done using MIDL_user_allocate.
+
+
+
+Arguments:
+
+ ObjectType - Indicates whether users or groups are to be enumerated.
+
+ EnumerationContext - API specific handle to allow multiple calls. The
+ caller should return this value in successive calls to retrieve
+ additional information.
+
+ Buffer - Receives a pointer to the buffer containing the
+ requested information. The information returned is
+ structured as an array of SAM_ENUMERATION_INFORMATION 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.
+
+ Filter - if ObjectType is users, the users can optionally be filtered
+ by setting this field with bits from the AccountControlField that
+ must match. Otherwise ignored.
+
+ CountReturned - Receives the number of entries returned.
+
+ TrustedClient - says whether the caller is trusted or not. If so,
+ we'll ignore the SAMP_MAXIMUM_MEMORY_TO_USE restriction on data
+ returns.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully, and there
+ are no additional entries. Entries may or may not have been
+ returned from this call. The CountReturned parameter indicates
+ whether any were.
+
+ STATUS_MORE_ENTRIES - There are more entries which may be obtained
+ using successive calls to this API. This is a successful return.
+
+ STATUS_ACCESS_DENIED - Caller does not have access to request the data.
+
+
+--*/
+{
+ SAMP_V1_0A_FIXED_LENGTH_USER UserV1aFixed;
+ NTSTATUS NtStatus, TmpStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE TempHandle = NULL;
+ ULONG i, NamesToReturn, MaxMemoryToUse;
+ ULONG TotalLength,NewTotalLength;
+ PSAMP_OBJECT UserContext = NULL;
+ PSAMP_ENUMERATION_ELEMENT SampHead = NULL,
+ NextEntry = NULL,
+ NewEntry = NULL,
+ SampTail = NULL;
+ BOOLEAN MoreNames;
+ BOOLEAN LengthLimitReached = FALSE;
+ BOOLEAN FilteredName;
+ PSAMPR_RID_ENUMERATION ArrayBuffer = NULL;
+ ULONG ArrayBufferLength;
+ LARGE_INTEGER IgnoreLastWriteTime;
+ UNICODE_STRING AccountNamesKey;
+ SID_NAME_USE IgnoreUse;
+
+
+ //
+ // Open the registry key containing the account names
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ ObjectType,
+ &AccountNamesKey,
+ NULL
+ );
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Now try to open this registry key so we can enumerate its
+ // sub-keys
+ //
+
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &AccountNamesKey,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Read names until we have exceeded the preferred maximum
+ // length or we run out of names.
+ //
+
+ NamesToReturn = 0;
+ SampHead = NULL;
+ SampTail = NULL;
+ MoreNames = TRUE;
+
+ NewTotalLength = 0;
+ TotalLength = 0;
+
+ if ( TrustedClient ) {
+
+ //
+ // We place no restrictions on the amount of memory used
+ // by a trusted client. Rely on their
+ // PreferedMaximumLength to limit us instead.
+ //
+
+ MaxMemoryToUse = 0xffffffff;
+
+ } else {
+
+ MaxMemoryToUse = SAMP_MAXIMUM_MEMORY_TO_USE;
+ }
+
+ while (MoreNames) {
+
+ UNICODE_STRING SubKeyName;
+ USHORT LengthRequired;
+
+ //
+ // Try reading with a DEFAULT length buffer first.
+ //
+
+ LengthRequired = 32;
+
+ NewTotalLength = TotalLength +
+ sizeof(UNICODE_STRING) +
+ LengthRequired;
+
+ //
+ // Stop if SAM or user specified length limit reached
+ //
+
+ if ( ( (TotalLength != 0) &&
+ (NewTotalLength >= PreferedMaximumLength) ) ||
+ ( NewTotalLength > MaxMemoryToUse )
+ ) {
+
+ NtStatus = STATUS_SUCCESS;
+ break; // Out of while loop, MoreNames = TRUE
+ }
+
+ NtStatus = SampInitUnicodeString(&SubKeyName, LengthRequired);
+ if (!NT_SUCCESS(NtStatus)) {
+ break; // Out of while loop
+ }
+
+ NtStatus = RtlpNtEnumerateSubKey(
+ TempHandle,
+ &SubKeyName,
+ *EnumerationContext,
+ &IgnoreLastWriteTime
+ );
+
+ if (NtStatus == STATUS_BUFFER_OVERFLOW) {
+
+ //
+ // The subkey name is longer than our default size,
+ // Free the old buffer.
+ // Allocate the correct size buffer and read it again.
+ //
+
+ SampFreeUnicodeString(&SubKeyName);
+
+ LengthRequired = SubKeyName.Length;
+
+ NewTotalLength = TotalLength +
+ sizeof(UNICODE_STRING) +
+ LengthRequired;
+
+ //
+ // Stop if SAM or user specified length limit reached
+ //
+
+ if ( ( (TotalLength != 0) &&
+ (NewTotalLength >= PreferedMaximumLength) ) ||
+ ( NewTotalLength > MaxMemoryToUse )
+ ) {
+
+ NtStatus = STATUS_SUCCESS;
+ break; // Out of while loop, MoreNames = TRUE
+ }
+
+ //
+ // Try reading the name again, we should be successful.
+ //
+
+ NtStatus = SampInitUnicodeString(&SubKeyName, LengthRequired);
+ if (!NT_SUCCESS(NtStatus)) {
+ break; // Out of while loop
+ }
+
+ NtStatus = RtlpNtEnumerateSubKey(
+ TempHandle,
+ &SubKeyName,
+ *EnumerationContext,
+ &IgnoreLastWriteTime
+ );
+ }
+
+
+ //
+ // Free up our buffer if we failed to read the key data
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ SampFreeUnicodeString(&SubKeyName);
+
+ //
+ // Map a no-more-entries status to success
+ //
+
+ if (NtStatus == STATUS_NO_MORE_ENTRIES) {
+
+ MoreNames = FALSE;
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ break; // Out of while loop
+ }
+
+ //
+ // We've allocated the subkey and read the data into it
+ // Stuff it in an enumeration element.
+ //
+
+ NewEntry = MIDL_user_allocate(sizeof(SAMP_ENUMERATION_ELEMENT));
+ if (NewEntry == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ *(PUNICODE_STRING)&NewEntry->Entry.Name = SubKeyName;
+
+ //
+ // Now get the Rid value of this named
+ // account. We must be able to get the
+ // name or we have an internal database
+ // corruption.
+ //
+
+ NtStatus = SampLookupAccountRid(
+ ObjectType,
+ (PUNICODE_STRING)&NewEntry->Entry.Name,
+ STATUS_INTERNAL_DB_CORRUPTION,
+ &NewEntry->Entry.RelativeId,
+ &IgnoreUse
+ );
+
+ ASSERT(NtStatus != STATUS_INTERNAL_DB_CORRUPTION);
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ FilteredName = TRUE;
+
+ if ( ( ObjectType == SampUserObjectType ) &&
+ ( Filter != 0 ) ) {
+
+ //
+ // We only want to return users with a
+ // UserAccountControl field that matches
+ // the filter passed in. Check here.
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ NewEntry->Entry.RelativeId,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ UserContext,
+ &UserV1aFixed
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ if ( ( UserV1aFixed.UserAccountControl &
+ Filter ) == 0 ) {
+
+ FilteredName = FALSE;
+ SampFreeUnicodeString( &SubKeyName );
+ }
+ }
+
+ SampDeleteContext( UserContext );
+ }
+ }
+
+ *EnumerationContext += 1;
+
+ if ( NT_SUCCESS( NtStatus ) && ( FilteredName ) ) {
+
+ NamesToReturn += 1;
+
+ TotalLength = TotalLength + (ULONG)
+ NewEntry->Entry.Name.MaximumLength;
+
+ NewEntry->Next = NULL;
+
+ if( SampHead == NULL ) {
+
+ ASSERT( SampTail == NULL );
+
+ SampHead = SampTail = NewEntry;
+ }
+ else {
+
+ //
+ // add this new entry to the list end.
+ //
+
+ SampTail->Next = NewEntry;
+ SampTail = NewEntry;
+ }
+
+ } else {
+
+ //
+ // Entry was filtered out, or error getting
+ // filter information.
+ //
+
+ MIDL_user_free( NewEntry );
+ }
+
+ } else {
+
+ //
+ // Error looking up the RID
+ //
+
+ MIDL_user_free( NewEntry );
+ }
+ }
+
+
+ //
+ // Free up our subkey name
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ SampFreeUnicodeString(&SubKeyName);
+ break; // Out of whle loop
+ }
+
+ } // while
+
+
+
+ TmpStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(TmpStatus) );
+
+ }
+
+
+ SampFreeUnicodeString( &AccountNamesKey );
+ }
+
+
+
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+
+
+
+ //
+ // If we are returning the last of the names, then change our
+ // enumeration context so that it starts at the beginning again.
+ //
+
+ if (!( (NtStatus == STATUS_SUCCESS) && (MoreNames == FALSE))) {
+
+ NtStatus = STATUS_MORE_ENTRIES;
+ }
+
+
+
+ //
+ // Set the number of names being returned
+ //
+
+ (*CountReturned) = NamesToReturn;
+
+
+ //
+ // Build a return buffer containing an array of the
+ // SAM_ENUMERATION_INFORMATIONs pointed to by another
+ // buffer containing the number of elements in that
+ // array.
+ //
+
+ (*Buffer) = MIDL_user_allocate( sizeof(SAMPR_ENUMERATION_BUFFER) );
+
+ if ( (*Buffer) == NULL) {
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ (*Buffer)->EntriesRead = (*CountReturned);
+
+ ArrayBufferLength = sizeof( SAM_RID_ENUMERATION ) *
+ (*CountReturned);
+ ArrayBuffer = MIDL_user_allocate( ArrayBufferLength );
+ (*Buffer)->Buffer = ArrayBuffer;
+
+ if ( ArrayBuffer == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ MIDL_user_free( (*Buffer) );
+
+ } else {
+
+ //
+ // Walk the list of return entries, copying
+ // them into the return buffer
+ //
+
+ NextEntry = SampHead;
+ i = 0;
+ while (NextEntry != NULL) {
+
+ NewEntry = NextEntry;
+ NextEntry = NewEntry->Next;
+
+ ArrayBuffer[i] = NewEntry->Entry;
+ i += 1;
+
+ MIDL_user_free( NewEntry );
+ }
+
+ }
+
+ }
+
+
+
+ }
+
+
+
+
+
+ if ( !NT_SUCCESS(NtStatus) ) {
+
+ //
+ // Free the memory we've allocated
+ //
+
+ NextEntry = SampHead;
+ while (NextEntry != NULL) {
+
+ NewEntry = NextEntry;
+ NextEntry = NewEntry->Next;
+
+ if (NewEntry->Entry.Name.Buffer != NULL ) MIDL_user_free( NewEntry->Entry.Name.Buffer );
+ MIDL_user_free( NewEntry );
+ }
+
+ (*EnumerationContext) = 0;
+ (*CountReturned) = 0;
+ (*Buffer) = NULL;
+
+ }
+
+ return(NtStatus);
+
+}
+
+
+
+NTSTATUS
+SampLookupAccountRid(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN PUNICODE_STRING Name,
+ IN NTSTATUS NotFoundStatus,
+ OUT PULONG Rid,
+ OUT PSID_NAME_USE Use
+ )
+
+/*++
+
+Routine Description:
+
+
+
+
+Arguments:
+
+ ObjectType - Indicates whether the name is a user, group or unknown
+ type of object.
+
+ Name - The name of the account being looked up.
+
+ NotFoundStatus - Receives a status value to be returned if no name is
+ found.
+
+ Rid - Receives the relative ID of account with the specified name.
+
+ Use - Receives an indication of the type of account.
+
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+ (NotFoundStatus) - No name by the specified name and type could be
+ found. This value is passed to this routine.
+
+ Other values that may be returned by:
+
+ SampBuildAccountKeyName()
+ NtOpenKey()
+ NtQueryValueKey()
+
+--*/
+{
+ NTSTATUS NtStatus, TmpStatus;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ HANDLE TempHandle;
+ ULONG KeyValueLength;
+ LARGE_INTEGER IgnoreLastWriteTime;
+
+
+
+ if ( (ObjectType == SampGroupObjectType ) ||
+ (ObjectType == SampUnknownObjectType) ) {
+
+ //
+ // Search the groups for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampGroupObjectType,
+ &KeyName,
+ Name
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ (*Use) = SidTypeGroup;
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ Rid,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+ TmpStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(TmpStatus) );
+
+ return( NtStatus );
+ }
+
+
+ }
+
+ //
+ // No group (or not group type)
+ // Try an alias if appropriate
+ //
+
+ if ( (ObjectType == SampAliasObjectType ) ||
+ (ObjectType == SampUnknownObjectType) ) {
+
+ //
+ // Search the aliases for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampAliasObjectType,
+ &KeyName,
+ Name
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ (*Use) = SidTypeAlias;
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ Rid,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+ TmpStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(TmpStatus) );
+
+ return( NtStatus );
+ }
+
+
+ }
+
+
+ //
+ // No group (or not group type) nor alias (or not alias type)
+ // Try a user if appropriate
+ //
+
+
+ if ( (ObjectType == SampUserObjectType ) ||
+ (ObjectType == SampUnknownObjectType) ) {
+
+ //
+ // Search the Users for a match
+ //
+
+ NtStatus = SampBuildAccountKeyName(
+ SampUserObjectType,
+ &KeyName,
+ Name
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &TempHandle,
+ (KEY_READ),
+ &ObjectAttributes,
+ 0
+ );
+ SampFreeUnicodeString( &KeyName );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ (*Use) = SidTypeUser;
+
+ KeyValueLength = 0;
+ NtStatus = RtlpNtQueryValueKey(
+ TempHandle,
+ Rid,
+ NULL,
+ &KeyValueLength,
+ &IgnoreLastWriteTime
+ );
+
+
+ TmpStatus = NtClose( TempHandle );
+ ASSERT( NT_SUCCESS(TmpStatus) );
+
+ return( NtStatus );
+ }
+
+
+ }
+
+ if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
+ NtStatus = NotFoundStatus;
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampLookupAccountName(
+ IN ULONG Rid,
+ OUT PUNICODE_STRING Name OPTIONAL,
+ OUT PSAMP_OBJECT_TYPE ObjectType
+ )
+
+/*++
+
+Routine Description:
+
+ Looks up the specified rid in the current transaction domain.
+ Returns its name and account type.
+
+
+Arguments:
+
+ Rid - The relative ID of account
+
+ Name - Receives the name of the account if ObjectType != UnknownObjectType
+ The name buffer can be freed using MIDL_user_free
+
+ ObjectType - Receives the type of account this rid represents
+
+ SampUnknownObjectType - the account doesn't exist
+ SampUserObjectType
+ SampGroupObjectType
+ SampAliasObjectType
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully, object type contains
+ the type of object this rid represents.
+
+ Other values that may be returned by:
+
+ SampBuildAccountKeyName()
+ NtOpenKey()
+ NtQueryValueKey()
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT AccountContext;
+
+ //
+ // Search the groups for a match
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ Rid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AccountContext
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ *ObjectType = SampGroupObjectType;
+
+ if (ARGUMENT_PRESENT(Name)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_GROUP_NAME,
+ TRUE, // Make copy
+ Name
+ );
+ }
+
+ SampDeleteContext(AccountContext);
+
+ return (NtStatus);
+
+ }
+
+ //
+ // Search the aliases for a match
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampAliasObjectType,
+ Rid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AccountContext
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ *ObjectType = SampAliasObjectType;
+
+ if (ARGUMENT_PRESENT(Name)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_ALIAS_NAME,
+ TRUE, // Make copy
+ Name
+ );
+ }
+
+ SampDeleteContext(AccountContext);
+
+ return (NtStatus);
+
+ }
+
+
+ //
+ // Search the users for a match
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ Rid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &AccountContext
+ );
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ *ObjectType = SampUserObjectType;
+
+ if (ARGUMENT_PRESENT(Name)) {
+
+ NtStatus = SampGetUnicodeStringAttribute(
+ AccountContext,
+ SAMP_USER_ACCOUNT_NAME,
+ TRUE, // Make copy
+ Name
+ );
+ }
+
+ SampDeleteContext(AccountContext);
+
+ return (NtStatus);
+
+ }
+
+
+
+
+ //
+ // This account doesn't exist
+ //
+
+ *ObjectType = SampUnknownObjectType;
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampOpenAccount(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN SAMPR_HANDLE DomainHandle,
+ IN ACCESS_MASK DesiredAccess,
+ IN ULONG AccountId,
+ IN BOOLEAN WriteLockHeld,
+ OUT SAMPR_HANDLE *AccountHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This API opens an existing user, group or alias account in the account database.
+ The account is specified by a ID value that is relative to the SID of the
+ domain. The operations that will be performed on the group must be
+ declared at this time.
+
+ This call returns a handle to the newly opened account that may be
+ used for successive operations on the account. This handle may be
+ closed with the SamCloseHandle API.
+
+
+
+Parameters:
+
+ DomainHandle - A domain handle returned from a previous call to
+ SamOpenDomain.
+
+ DesiredAccess - Is an access mask indicating which access types
+ are desired to the account. These access types are reconciled
+ with the Discretionary Access Control list of the account to
+ determine whether the accesses will be granted or denied.
+
+ GroupId - Specifies the relative ID value of the user or group to be
+ opened.
+
+ GroupHandle - Receives a handle referencing the newly opened
+ user or group. This handle will be required in successive calls to
+ operate on the account.
+
+ WriteLockHeld - if TRUE, the caller holds SAM's SampLock for WRITE
+ access, so this routine does not have to obtain it.
+
+Return Values:
+
+ STATUS_SUCCESS - The account was successfully opened.
+
+ STATUS_ACCESS_DENIED - Caller does not have the appropriate
+ access to complete the operation.
+
+ STATUS_NO_SUCH_GROUP - The specified group does not exist.
+
+ STATUS_NO_SUCH_USER - The specified user does not exist.
+
+ STATUS_NO_SUCH_ALIAS - The specified alias does not exist.
+
+ STATUS_INVALID_HANDLE - The domain handle passed is invalid.
+
+--*/
+{
+
+ NTSTATUS NtStatus;
+ NTSTATUS IgnoreStatus;
+ PSAMP_OBJECT DomainContext, NewContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+ //
+ // Grab a read lock, if a lock isn't already held.
+ //
+
+ if ( !WriteLockHeld ) {
+ SampAcquireReadLock();
+ }
+
+
+ //
+ // 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)) {
+
+ //
+ // Try to create a context for the account.
+ //
+
+ NtStatus = SampCreateAccountContext(
+ ObjectType,
+ AccountId,
+ DomainContext->TrustedClient,
+ TRUE, // Account exists
+ &NewContext
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Reference the object for the validation
+ //
+
+ SampReferenceContext(NewContext);
+
+ //
+ // Validate the caller's access.
+ //
+
+ NtStatus = SampValidateObjectAccess(
+ NewContext, //Context
+ DesiredAccess, //DesiredAccess
+ FALSE //ObjectCreation
+ );
+
+ //
+ // Dereference object, discarding any changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext(NewContext, FALSE);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Clean up the new context if we didn't succeed.
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( NewContext );
+ }
+
+ }
+
+
+ //
+ // De-reference the object, discarding changes
+ //
+
+ IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+ }
+
+
+ //
+ // Return the account handle
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ (*AccountHandle) = 0;
+ } else {
+ (*AccountHandle) = NewContext;
+ }
+
+
+ //
+ // Free the lock, if we obtained it.
+ //
+
+ if ( !WriteLockHeld ) {
+ SampReleaseReadLock();
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampCreateAccountContext(
+ IN SAMP_OBJECT_TYPE ObjectType,
+ IN ULONG AccountId,
+ IN BOOLEAN TrustedClient,
+ IN BOOLEAN AccountExists,
+ OUT PSAMP_OBJECT *AccountContext
+ )
+
+/*++
+
+Routine Description:
+
+ This API creates a context for an account object. (User group or alias).
+ If the account exists flag is specified, an attempt is made to open
+ the object in the database and this api fails if it doesn't exist.
+ If AccountExists = FALSE, this routine setups up the context such that
+ data can be written into the context and the object will be created
+ when they are committed.
+
+ The account is specified by a ID value that is relative to the SID of the
+ current transaction domain.
+
+ This call returns a context handle for the newly opened account.
+ This handle may be closed with the SampDeleteContext API.
+
+ No access check is performed by this function.
+
+ Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
+ (ESTABLISHED USING SampSetTransactioDomain()). THIS
+ SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
+ AND BEFORE SampReleaseReadLock().
+
+
+
+Parameters:
+
+ ObjectType - the type of object to open
+
+ AccountId - the id of the account in the current transaction domain
+
+ TrustedClient - TRUE if client is trusted - i.e. server side process.
+
+ AccountExists - specifies whether the account already exists.
+
+ AccountContext - Receives context pointer referencing the newly opened account.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The account was successfully opened.
+
+ STATUS_NO_SUCH_GROUP - The specified group does not exist.
+
+ STATUS_NO_SUCH_USER - The specified user does not exist.
+
+ STATUS_NO_SUCH_ALIAS - The specified alias does not exist.
+
+--*/
+{
+
+ NTSTATUS NtStatus, NotFoundStatus;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PSAMP_OBJECT NewContext;
+
+
+ //
+ // Establish type-specific information
+ //
+
+ ASSERT( (ObjectType == SampGroupObjectType) ||
+ (ObjectType == SampAliasObjectType) ||
+ (ObjectType == SampUserObjectType) );
+
+ switch (ObjectType) {
+ case SampGroupObjectType:
+ NotFoundStatus = STATUS_NO_SUCH_GROUP;
+ break;
+ case SampAliasObjectType:
+ NotFoundStatus = STATUS_NO_SUCH_ALIAS;
+ break;
+ case SampUserObjectType:
+ NotFoundStatus = STATUS_NO_SUCH_USER;
+ break;
+ }
+
+ //
+ // Try to create a context for the account.
+ //
+
+ NewContext = SampCreateContext(
+ ObjectType,
+ TrustedClient
+ );
+
+ if (NewContext != NULL) {
+
+
+ //
+ // Set the account's rid
+ //
+
+ switch (ObjectType) {
+ case SampGroupObjectType:
+ NewContext->TypeBody.Group.Rid = AccountId;
+ break;
+ case SampAliasObjectType:
+ NewContext->TypeBody.Alias.Rid = AccountId;
+ break;
+ case SampUserObjectType:
+ NewContext->TypeBody.User.Rid = AccountId;
+ break;
+ }
+
+
+ //
+ // Create the specified acocunt's root key name
+ // and store it in the context.
+ // This name remains around until the context is deleted.
+ //
+
+ NtStatus = SampBuildAccountSubKeyName(
+ ObjectType,
+ &NewContext->RootName,
+ AccountId,
+ NULL // Don't give a sub-key name
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // If the account should exist, try and open the root key
+ // to the object - fail if it doesn't exist.
+ //
+
+ if (AccountExists) {
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &NewContext->RootName,
+ OBJ_CASE_INSENSITIVE,
+ SampKey,
+ NULL
+ );
+ NtStatus = RtlpNtOpenKey(
+ &NewContext->RootKey,
+ (KEY_READ | KEY_WRITE),
+ &ObjectAttributes,
+ 0
+ );
+
+ if ( !NT_SUCCESS(NtStatus) ) {
+ NewContext->RootKey = INVALID_HANDLE_VALUE;
+ NtStatus = NotFoundStatus;
+ }
+ }
+
+ } else {
+ RtlInitUnicodeString(&NewContext->RootName, NULL);
+ }
+
+ //
+ // Clean up the account context if we failed
+ //
+
+ if (!NT_SUCCESS(NtStatus)) {
+ SampDeleteContext( NewContext );
+ NewContext = NULL;
+ }
+
+ } else {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ }
+
+
+ //
+ // Return the context pointer
+ //
+
+ *AccountContext = NewContext;
+
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampIsAccountBuiltIn(
+ IN ULONG Rid
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks to see if a specified account name is a well-known
+ (built-in) account. Some restrictions apply to such accounts, such as
+ they can not be deleted or renamed.
+
+
+Parameters:
+
+ Rid - The RID of the account.
+
+
+Return Values:
+
+ STATUS_SUCCESS - The account is not a well-known (restricted) account.
+
+ STATUS_SPECIAL_ACCOUNT - Indicates the account is a restricted
+ account. This is an error status, based upon the assumption that
+ this service will primarily be utilized to determine if an
+ operation may allowed on an account.
+
+
+--*/
+{
+ if (Rid < SAMP_RESTRICTED_ACCOUNT_COUNT) {
+
+ return(STATUS_SPECIAL_ACCOUNT);
+
+ } else {
+
+ return(STATUS_SUCCESS);
+ }
+}
+
+
+
+NTSTATUS
+SampCreateFullSid(
+ IN PSID DomainSid,
+ IN ULONG Rid,
+ OUT PSID *AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates a domain account sid given a domain sid and
+ the relative id of the account within the domain.
+
+ The returned Sid may be freed with MIDL_user_free.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+{
+
+ NTSTATUS NtStatus, IgnoreStatus;
+ UCHAR AccountSubAuthorityCount;
+ ULONG AccountSidLength;
+ PULONG RidLocation;
+
+ //
+ // Calculate the size of the new sid
+ //
+
+ AccountSubAuthorityCount = *RtlSubAuthorityCountSid(DomainSid) + (UCHAR)1;
+ AccountSidLength = RtlLengthRequiredSid(AccountSubAuthorityCount);
+
+ //
+ // Allocate space for the account sid
+ //
+
+ *AccountSid = MIDL_user_allocate(AccountSidLength);
+
+ if (*AccountSid == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ } else {
+
+ //
+ // Copy the domain sid into the first part of the account sid
+ //
+
+ IgnoreStatus = RtlCopySid(AccountSidLength, *AccountSid, DomainSid);
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ //
+ // Increment the account sid sub-authority count
+ //
+
+ *RtlSubAuthorityCountSid(*AccountSid) = AccountSubAuthorityCount;
+
+ //
+ // Add the rid as the final sub-authority
+ //
+
+ RidLocation = RtlSubAuthoritySid(*AccountSid, AccountSubAuthorityCount-1);
+ *RidLocation = Rid;
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ return(NtStatus);
+}
+
+
+
+NTSTATUS
+SampCreateAccountSid(
+ IN PSAMP_OBJECT AccountContext,
+ OUT PSID *AccountSid
+ )
+
+/*++
+
+Routine Description:
+
+ This function creates the sid for an account object.
+
+ The returned Sid may be freed with MIDL_user_free.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSID DomainSid;
+ ULONG AccountRid;
+
+
+ //
+ // Get the Sid for the domain this object is in
+ //
+
+
+ DomainSid = SampDefinedDomains[AccountContext->DomainIndex].Sid;
+
+ //
+ // Get the account Rid
+ //
+
+ switch (AccountContext->ObjectType) {
+
+ case SampGroupObjectType:
+ AccountRid = AccountContext->TypeBody.Group.Rid;
+ break;
+ case SampAliasObjectType:
+ AccountRid = AccountContext->TypeBody.Alias.Rid;
+ break;
+ case SampUserObjectType:
+ AccountRid = AccountContext->TypeBody.User.Rid;
+ break;
+ default:
+ ASSERT(FALSE);
+ }
+
+
+ //
+ // Build a full sid from the domain sid and the account rid
+ //
+
+ NtStatus = SampCreateFullSid(DomainSid, AccountRid, AccountSid);
+
+ return(NtStatus);
+}
+
+
+VOID
+SampNotifyNetlogonOfDelta(
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PUNICODE_STRING ObjectName,
+ IN DWORD ReplicateImmediately,
+ IN PSAM_DELTA_DATA DeltaData OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called after any change is made to the SAM database
+ on a PDC. It will pass the parameters, along with the database type
+ and ModifiedCount to I_NetNotifyDelta() so that Netlogon will know
+ that the database has been changed.
+
+ This routine MUST be called with SAM's write lock held; however, any
+ changes must have already been committed to disk. That is, call
+ SampCommitAndRetainWriteLock() first, then this routine, then
+ SampReleaseWriteLock().
+
+Arguments:
+
+ DeltaType - Type of modification that has been made on the object.
+
+ ObjectType - Type of object that has been modified.
+
+ ObjectRid - The relative ID of the object that has been modified.
+ This parameter is valid only when the object type specified is
+ either SecurityDbObjectSamUser, SecurityDbObjectSamGroup or
+ SecurityDbObjectSamAlias otherwise this parameter is set to
+ zero.
+
+ ObjectName - The old name of the object when the object type specified
+ is either SecurityDbObjectSamUser, SecurityDbObjectSamGroup or
+ SecurityDbObjectSamAlias and the delta type is SecurityDbRename
+ otherwise this parameter is set to zero.
+
+ ReplicateImmediately - TRUE if the change should be immediately
+ replicated to all BDCs. A password change should set the flag
+ TRUE.
+
+ DeltaData - pointer to delta-type specific structure to be passed
+ - to netlogon.
+
+Return Value:
+
+ None.
+
+
+--*/
+{
+ //
+ // Only make the call if this is not a backup domain controller.
+ //
+
+ if ( SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
+ != DomainServerRoleBackup ) {
+
+ I_NetNotifyDelta(
+ SecurityDbSam,
+ SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount,
+ DeltaType,
+ ObjectType,
+ ObjectRid,
+ SampDefinedDomains[SampTransactionDomainIndex].Sid,
+ ObjectName,
+ ReplicateImmediately,
+ DeltaData
+ );
+
+ //
+ // Let any notification packages know about the delta.
+ //
+
+ SampDeltaChangeNotify(
+ SampDefinedDomains[SampTransactionDomainIndex].Sid,
+ DeltaType,
+ ObjectType,
+ ObjectRid,
+ ObjectName,
+ &SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount,
+ DeltaData
+ );
+
+ }
+}
+
+
+
+NTSTATUS
+SampSplitSid(
+ IN PSID AccountSid,
+ IN OUT PSID *DomainSid,
+ OUT ULONG *Rid
+ )
+
+/*++
+
+Routine Description:
+
+ This function splits a sid into its domain sid and rid. The caller
+ can either provide a memory buffer for the returned DomainSid, or
+ request that one be allocated. If the caller provides a buffer, the buffer
+ is assumed to be of sufficient size. If allocated on the caller's behalf,
+ the buffer must be freed when no longer required via MIDL_user_free.
+
+Arguments:
+
+ AccountSid - Specifies the Sid to be split. The Sid is assumed to be
+ syntactically valid. Sids with zero subauthorities cannot be split.
+
+ DomainSid - Pointer to location containing either NULL or a pointer to
+ a buffer in which the Domain Sid will be returned. If NULL is
+ specified, memory will be allocated on behalf of the caller.
+
+Return Value:
+
+ NTSTATUS - Standard Nt Result Code
+
+ STATUS_SUCCESS - The call completed successfully.
+
+ STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources,
+ such as memory, to complete the call successfully.
+
+ STATUS_INVALID_SID - The Sid is has a subauthority count of 0.
+--*/
+
+{
+ NTSTATUS NtStatus;
+ UCHAR AccountSubAuthorityCount;
+ ULONG AccountSidLength;
+
+ //
+ // Calculate the size of the domain sid
+ //
+
+ AccountSubAuthorityCount = *RtlSubAuthorityCountSid(AccountSid);
+
+
+ if (AccountSubAuthorityCount < 1) {
+
+ NtStatus = STATUS_INVALID_SID;
+ goto SplitSidError;
+ }
+
+ AccountSidLength = RtlLengthSid(AccountSid);
+
+ //
+ // If no buffer is required for the Domain Sid, we have to allocate one.
+ //
+
+ if (*DomainSid == NULL) {
+
+ //
+ // Allocate space for the domain sid (allocate the same size as the
+ // account sid so we can use RtlCopySid)
+ //
+
+ *DomainSid = MIDL_user_allocate(AccountSidLength);
+
+
+ if (*DomainSid == NULL) {
+
+ NtStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto SplitSidError;
+ }
+ }
+
+ //
+ // Copy the Account sid into the Domain sid
+ //
+
+ RtlMoveMemory(*DomainSid, AccountSid, AccountSidLength);
+
+ //
+ // Decrement the domain sid sub-authority count
+ //
+
+ (*RtlSubAuthorityCountSid(*DomainSid))--;
+
+ //
+ // Copy the rid out of the account sid
+ //
+
+ *Rid = *RtlSubAuthoritySid(AccountSid, AccountSubAuthorityCount-1);
+
+ NtStatus = STATUS_SUCCESS;
+
+SplitSidFinish:
+
+ return(NtStatus);
+
+SplitSidError:
+
+ goto SplitSidFinish;
+}
+
+
+
+NTSTATUS
+SampDuplicateUnicodeString(
+ IN PUNICODE_STRING OutString,
+ IN PUNICODE_STRING InString
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates memory for a new OutString and copies the
+ InString string to it.
+
+Parameters:
+
+ OutString - A pointer to a destination unicode string
+
+ InString - A pointer to an unicode string to be copied
+
+Return Values:
+
+ None.
+
+--*/
+
+{
+ ASSERT( OutString != NULL );
+ ASSERT( InString != NULL );
+
+ if ( InString->Length > 0 ) {
+
+ OutString->Buffer = MIDL_user_allocate( InString->Length );
+
+ if (OutString->Buffer == NULL) {
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ OutString->MaximumLength = InString->Length;
+
+ RtlCopyUnicodeString(OutString, InString);
+
+ } else {
+
+ RtlInitUnicodeString(OutString, NULL);
+ }
+
+ return(STATUS_SUCCESS);
+}
+
+
+NTSTATUS
+SampUnicodeToOemString(
+ IN POEM_STRING OutString,
+ IN PUNICODE_STRING InString
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates memory for a new OutString and copies the
+ InString string to it, converting to OEM string in the process.
+
+Parameters:
+
+ OutString - A pointer to a destination OEM string.
+
+ InString - A pointer to a unicode string to be copied
+
+Return Values:
+
+ None.
+
+--*/
+
+{
+ ULONG
+ OemLength,
+ Index;
+
+ NTSTATUS
+ NtStatus;
+
+ ASSERT( OutString != NULL );
+ ASSERT( InString != NULL );
+
+ if ( InString->Length > 0 ) {
+
+ OemLength = RtlUnicodeStringToOemSize(InString);
+ if ( OemLength > MAXUSHORT ) {
+ return STATUS_INVALID_PARAMETER_2;
+ }
+
+ OutString->Length = (USHORT)(OemLength - 1);
+ OutString->MaximumLength = (USHORT)OemLength;
+ OutString->Buffer = MIDL_user_allocate(OemLength);
+ if ( !OutString->Buffer ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ NtStatus = RtlUnicodeToOemN(
+ OutString->Buffer,
+ OutString->Length,
+ &Index,
+ InString->Buffer,
+ InString->Length
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ MIDL_user_free(OutString->Buffer);
+ return NtStatus;
+ }
+
+ OutString->Buffer[Index] = '\0';
+
+
+ } else {
+
+ RtlInitString(OutString, NULL);
+ }
+
+ return(STATUS_SUCCESS);
+}
+
+
+
+NTSTATUS
+SampChangeAccountOperatorAccessToMember(
+ IN PRPC_SID MemberSid,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when a member is added to or removed from an
+ ADMIN alias. If the member is from the BUILTIN or ACCOUNT domain,
+ it will change the ACL(s) of the member to allow or disallow access
+ by account operators if necessary.
+
+ This must be called BEFORE the member is actually added to the
+ alias, and AFTER the member is actually removed from the alias to
+ avoid security holes in the event that we are unable to complete the
+ entire task.
+
+ When this routine is called, the transaction domain is alredy set
+ to that of the alias. Note, however, that the member might be in a
+ different domain, so the transaction domain may be adjusted in this
+ routine.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+
+Arguments:
+
+ MemberSid - The full ID of the member being added to/ deleted from
+ an ADMIN alias.
+
+ ChangingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
+ RemoveFromAdmin if it's being removed.
+
+ ChangingToOperator - AddToAdmin if Member is being added to an OPERATOR
+ alias, RemoveFromAdmin if it's being removed.
+
+
+Return Value:
+
+ STATUS_SUCCESS - either the ACL(s) was modified, or it didn't need
+ to be.
+
+--*/
+{
+ SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed;
+ PSID MemberDomainSid = NULL;
+ PULONG UsersInGroup = NULL;
+ NTSTATUS NtStatus;
+ ULONG MemberRid;
+ ULONG OldTransactionDomainIndex = SampDefinedDomainsCount;
+ ULONG NumberOfUsersInGroup;
+ ULONG i;
+ ULONG MemberDomainIndex;
+ SAMP_OBJECT_TYPE MemberType;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ PSECURITY_DESCRIPTOR OldDescriptor;
+ ULONG SecurityDescriptorLength;
+ ULONG Revision;
+
+ ASSERT( SampTransactionWithinDomain );
+
+ //
+ // See if the SID is from one of the local domains (BUILTIN or ACCOUNT).
+ // If it's not, we don't have to worry about modifying ACLs.
+ //
+
+ NtStatus = SampSplitSid( MemberSid, &MemberDomainSid, &MemberRid );
+
+ if ( !NT_SUCCESS( NtStatus ) ) {
+
+ return( NtStatus );
+ }
+
+ for ( MemberDomainIndex = 0;
+ MemberDomainIndex < SampDefinedDomainsCount;
+ MemberDomainIndex++ ) {
+
+ if ( RtlEqualSid(
+ MemberDomainSid,
+ SampDefinedDomains[MemberDomainIndex].Sid ) ) {
+
+ break;
+ }
+ }
+
+ if ( MemberDomainIndex < SampDefinedDomainsCount ) {
+
+ //
+ // The member is from one of the local domains. MemberDomainIndex
+ // indexes that domain. First, check to see if the alias and member
+ // are in the same domain.
+ //
+
+ if ( MemberDomainIndex != SampTransactionDomainIndex ) {
+
+ //
+ // The transaction domain is set to that of the alias, but
+ // we need to set it to that of the member while we modify
+ // the member.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ OldTransactionDomainIndex = SampTransactionDomainIndex;
+
+ SampSetTransactionDomain( MemberDomainIndex );
+ }
+
+ //
+ // Now we need to change the member ACL(s), IF the member is being
+ // added to an admin alias for the first time. Find out whether
+ // the member is a user or a group, and attack accordingly.
+ //
+
+ NtStatus = SampLookupAccountName(
+ MemberRid,
+ NULL,
+ &MemberType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ switch (MemberType) {
+
+ case SampUserObjectType: {
+
+ NtStatus = SampChangeOperatorAccessToUser(
+ MemberRid,
+ ChangingToAdmin,
+ ChangingToOperator
+ );
+
+ break;
+ }
+
+ case SampGroupObjectType: {
+
+ PSAMP_OBJECT GroupContext;
+
+ //
+ // Change ACL for every user in this group.
+ // First get group member list.
+ //
+
+ //
+ // Try to create a context for the account.
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampGroupObjectType,
+ MemberRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &GroupContext
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+
+ //
+ // Now set a flag in the group itself,
+ // so that when users are added and removed
+ // in the future it is known whether this
+ // group is in an ADMIN alias or not.
+ //
+
+ NtStatus = SampRetrieveGroupV1Fixed(
+ GroupContext,
+ &GroupV1Fixed
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ ULONG OldAdminStatus = 0;
+ ULONG NewAdminStatus;
+ SAMP_MEMBERSHIP_DELTA AdminChange = NoChange;
+ SAMP_MEMBERSHIP_DELTA OperatorChange = NoChange;
+
+ if (GroupV1Fixed.AdminCount != 0 ) {
+ OldAdminStatus++;
+ }
+ if (GroupV1Fixed.OperatorCount != 0) {
+ OldAdminStatus++;
+ }
+ NewAdminStatus = OldAdminStatus;
+
+ //
+ // Update the admin count. If we added one and the
+ // count is now 1, then the group became administrative.
+ // If we subtracted one and the count is zero,
+ // then the group lost its administrive membership.
+ //
+
+ if (ChangingToAdmin == AddToAdmin) {
+ if (++GroupV1Fixed.AdminCount == 1) {
+ NewAdminStatus++;
+ AdminChange = AddToAdmin;
+ }
+ } else if (ChangingToAdmin == RemoveFromAdmin) {
+
+
+ //
+ // For removing an admin count, we need to make
+ // sure there is at least one. In the upgrade
+ // case there may not be, since prior versions
+ // of NT only had a boolean.
+ //
+ if (GroupV1Fixed.AdminCount > 0) {
+ if (--GroupV1Fixed.AdminCount == 0) {
+ NewAdminStatus --;
+ AdminChange = RemoveFromAdmin;
+ }
+ }
+
+ }
+
+ //
+ // Update the operator count
+ //
+
+ if (ChangingToOperator == AddToAdmin) {
+ if (++GroupV1Fixed.OperatorCount == 1) {
+ NewAdminStatus++;
+ OperatorChange = AddToAdmin;
+ }
+ } else if (ChangingToOperator == RemoveFromAdmin) {
+
+
+ //
+ // For removing an Operator count, we need to make
+ // sure there is at least one. In the upgrade
+ // case there may not be, since prior versions
+ // of NT only had a boolean.
+ //
+ if (GroupV1Fixed.OperatorCount > 0) {
+ if (--GroupV1Fixed.OperatorCount == 0) {
+ NewAdminStatus --;
+ OperatorChange = RemoveFromAdmin;
+ }
+ }
+
+ }
+
+
+ NtStatus = SampReplaceGroupV1Fixed(
+ GroupContext,
+ &GroupV1Fixed
+ );
+ //
+ // If the status of the group changed,
+ // modify the security descriptor to
+ // prevent account operators from adding
+ // anybody to this group
+ //
+
+ if ( NT_SUCCESS( NtStatus ) &&
+ ((NewAdminStatus != 0) != (OldAdminStatus != 0)) ) {
+
+ //
+ // Get the old security descriptor so we can
+ // modify it.
+ //
+
+ NtStatus = SampGetAccessAttribute(
+ GroupContext,
+ SAMP_GROUP_SECURITY_DESCRIPTOR,
+ FALSE, // don't make copy
+ &Revision,
+ &OldDescriptor
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampModifyAccountSecurity(
+ SampGroupObjectType,
+ (BOOLEAN) ((NewAdminStatus != 0) ? TRUE : FALSE),
+ OldDescriptor,
+ &SecurityDescriptor,
+ &SecurityDescriptorLength
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Write the new security descriptor into the object
+ //
+
+ NtStatus = SampSetAccessAttribute(
+ GroupContext,
+ SAMP_GROUP_SECURITY_DESCRIPTOR,
+ SecurityDescriptor,
+ SecurityDescriptorLength
+ );
+
+ RtlDeleteSecurityObject( &SecurityDescriptor );
+ }
+ }
+ }
+
+ //
+ // Update all the members of this group so that
+ // their security descriptors are changed.
+ //
+
+ if ( NT_SUCCESS( NtStatus ) &&
+ ( (AdminChange != NoChange) ||
+ (OperatorChange != NoChange) ) ) {
+
+ NtStatus = SampRetrieveGroupMembers(
+ GroupContext,
+ &NumberOfUsersInGroup,
+ &UsersInGroup
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ for ( i = 0; i < NumberOfUsersInGroup; i++ ) {
+
+ NtStatus = SampChangeOperatorAccessToUser(
+ UsersInGroup[i],
+ AdminChange,
+ OperatorChange
+ );
+
+ if ( !( NT_SUCCESS( NtStatus ) ) ) {
+
+ break;
+ }
+ }
+
+ MIDL_user_free( UsersInGroup );
+
+ }
+
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the modified group to the current transaction
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ NtStatus = SampStoreObjectAttributes(GroupContext, FALSE);
+ }
+
+ }
+
+
+
+ //
+ // Clean up the group context
+ //
+
+ SampDeleteContext(GroupContext);
+ }
+
+ break;
+ }
+
+ default: {
+
+ //
+ // A bad RID from a domain other than the domain
+ // current at the time of the call could slip through
+ // to this point. Return error.
+ //
+
+ //
+ // If the account is in a different domain than the alias,
+ // don't report an error if we're removing the member and
+ // the member no longer exists.
+ //
+ // Possibly caused by deleting the object before deleting
+ // the membership in the alias.
+ //
+
+ //
+ // Now that this function is called during upgrade, we
+ // can't fail if the account no longer exists. It is
+ // not really so bad to add a non-existant member to
+ // and alias so return success.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+
+
+ }
+ }
+ }
+
+ if ( OldTransactionDomainIndex != SampDefinedDomainsCount ) {
+
+ //
+ // The transaction domain should be set to that of the alias, but
+ // we switched it above to that of the member while we modified
+ // the member. Now we need to switch it back.
+ //
+
+ SampTransactionWithinDomain = FALSE;
+
+ SampSetTransactionDomain( OldTransactionDomainIndex );
+ }
+ }
+
+ MIDL_user_free( MemberDomainSid );
+
+ return( NtStatus );
+}
+
+
+NTSTATUS
+SampChangeOperatorAccessToUser(
+ IN ULONG UserRid,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adjusts the user's AdminCount field as appropriate, and
+ if the user is being removed from it's last ADMIN alias or added to
+ its first ADMIN alias, the ACL is adjusted to allow/disallow access
+ by account operators as appropriate.
+
+ This routine will also increment or decrement the domain's admin count,
+ if this operation changes that.
+
+ NOTE:
+ This routine is similar to SampChangeOperatorAccessToUser2().
+ This routine should be used in cases where a user context does NOT
+ already exist (and won't later on). You must be careful not to
+ create two contexts, since they will be independently applied back
+ to the registry, and the last one there will win.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+Arguments:
+
+ UserRid - The transaction-domain-relative ID of the user that is
+ being added to or removed from an ADMIN alias.
+
+ ChangingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
+ RemoveFromAdmin if it's being removed.
+
+ ChangingToOperator - AddToAdmin if Member is being added to an OPERATOR
+ alias, RemoveFromAdmin if it's being removed.
+
+
+Return Value:
+
+ STATUS_SUCCESS - either the ACL was modified, or it didn't need
+ to be.
+
+--*/
+{
+ SAMP_V1_0A_FIXED_LENGTH_USER UserV1aFixed;
+ NTSTATUS NtStatus;
+ PSAMP_OBJECT UserContext;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+
+ //
+ // Get the user's fixed data, and adjust the AdminCount.
+ //
+
+ NtStatus = SampCreateAccountContext(
+ SampUserObjectType,
+ UserRid,
+ TRUE, // Trusted client
+ TRUE, // Account exists
+ &UserContext
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampRetrieveUserV1aFixed(
+ UserContext,
+ &UserV1aFixed
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ NtStatus = SampChangeOperatorAccessToUser2(
+ UserContext,
+ &UserV1aFixed,
+ ChangingToAdmin,
+ ChangingToOperator
+ );
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // If we've succeeded (at changing the admin count, and
+ // the ACL if necessary) then write out the new admin
+ // count.
+ //
+
+ NtStatus = SampReplaceUserV1aFixed(
+ UserContext,
+ &UserV1aFixed
+ );
+ }
+ }
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Add the modified user context to the current transaction
+ // Don't use the open key handle since we'll be deleting the context.
+ //
+
+ NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
+ }
+
+
+ //
+ // Clean up account context
+ //
+
+ SampDeleteContext(UserContext);
+ }
+
+ if ( ( !NT_SUCCESS( NtStatus ) ) &&
+ (( ChangingToAdmin == RemoveFromAdmin ) ||
+ ( ChangingToOperator == RemoveFromAdmin )) &&
+ ( NtStatus != STATUS_SPECIAL_ACCOUNT ) ) {
+
+ //
+ // When an account is *removed* from admin groups, we can
+ // ignore errors from this routine. This routine is just
+ // making the account accessible to account operators, but
+ // it's no big deal if that doesn't work. The administrator
+ // can still get at it, so we should proceed with the calling
+ // operation.
+ //
+ // Obviously, we can't ignore errors if we're being added
+ // to an admin group, because that could be a security hole.
+ //
+ // Also, we want to make sure that the Administrator is
+ // never removed, so we DO propogate STATUS_SPECIAL_ACCOUNT.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ return( NtStatus );
+}
+
+
+NTSTATUS
+SampChangeOperatorAccessToUser2(
+ IN PSAMP_OBJECT UserContext,
+ IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
+ IN SAMP_MEMBERSHIP_DELTA AddingToAdmin,
+ IN SAMP_MEMBERSHIP_DELTA AddingToOperator
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adjusts the user's AdminCount field as appropriate, and
+ if the user is being removed from it's last ADMIN alias or added to
+ its first ADMIN alias, the ACL is adjusted to allow/disallow access
+ by account operators as appropriate.
+
+ This routine will also increment or decrement the domain's admin count,
+ if this operation changes that.
+
+ NOTE:
+ This routine is similar to SampAccountOperatorAccessToUser().
+ This routine should be used in cases where a user account context
+ already exists. You must be careful not to create two contexts,
+ since they will be independently applied back to the registry, and
+ the last one there will win.
+
+ THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
+
+Arguments:
+
+ UserContext - Context of user whose access is to be updated.
+
+ V1aFixed - Pointer to the V1aFixed length data for the user.
+ The caller of this routine must ensure that this value is
+ stored back out to disk on successful completion of this
+ routine.
+
+ AddingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
+ RemoveFromAdmin if it's being removed.
+
+ AddingToOperator - AddToAdmin if Member is being added to an OPERATOR
+ alias, RemoveFromAdmin if it's being removed.
+
+
+Return Value:
+
+ STATUS_SUCCESS - either the ACL(s) was modified, or it didn't need
+ to be.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ PSECURITY_DESCRIPTOR OldDescriptor;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+ ULONG OldAdminStatus = 0, NewAdminStatus = 0;
+ ULONG Revision;
+
+ //
+ // Compute whether we are an admin now. From that we will figure
+ // out how many times we were may not an admin to tell if we need
+ // to update the security descriptor.
+ //
+
+ if (V1aFixed->AdminCount != 0) {
+ OldAdminStatus++;
+ }
+ if (V1aFixed->OperatorCount != 0) {
+ OldAdminStatus++;
+ }
+
+ NewAdminStatus = OldAdminStatus;
+
+
+
+ if ( AddingToAdmin == AddToAdmin ) {
+
+ V1aFixed->AdminCount++;
+ NewAdminStatus++;
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Incrementing admin count for user %d\n"
+ " New admin count: %d\n",
+ V1aFixed->UserId, V1aFixed->AdminCount ) );
+ } else if (AddingToAdmin == RemoveFromAdmin) {
+
+ V1aFixed->AdminCount--;
+
+ if (V1aFixed->AdminCount == 0) {
+ NewAdminStatus--;
+ }
+
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Decrementing admin count for user %d\n"
+ " New admin count: %d\n",
+ V1aFixed->UserId, V1aFixed->AdminCount ) );
+
+ if ( V1aFixed->AdminCount == 0 ) {
+
+ //
+ // Don't allow the Administrator account to lose
+ // administrative power.
+ //
+
+ if ( V1aFixed->UserId == DOMAIN_USER_RID_ADMIN ) {
+
+ NtStatus = STATUS_SPECIAL_ACCOUNT;
+ }
+ }
+ }
+ if ( AddingToOperator == AddToAdmin ) {
+
+ V1aFixed->OperatorCount++;
+ NewAdminStatus++;
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Incrementing operator count for user %d\n"
+ " New admin count: %d\n",
+ V1aFixed->UserId, V1aFixed->OperatorCount ) );
+
+ } else if (AddingToOperator == RemoveFromAdmin) {
+
+ //
+ // Only decrement if the count is > 0, since in the upgrade case
+ // this field we start out zero.
+ //
+
+ if (V1aFixed->OperatorCount > 0) {
+ V1aFixed->OperatorCount--;
+
+ if (V1aFixed->OperatorCount == 0) {
+ NewAdminStatus--;
+ }
+ }
+
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Decrementing operator count for user %d\n"
+ " New admin count: %d\n",
+ V1aFixed->UserId, V1aFixed->OperatorCount ) );
+ }
+
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( ( NewAdminStatus != 0 ) != ( OldAdminStatus != 0 ) ) {
+
+ //
+ // User's admin status is changing. We must change the
+ // ACL.
+ //
+
+#ifdef SAMP_DIAGNOSTICS
+ if (AddingToAdmin) {
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Protecting user %d as ADMIN account\n",
+ V1aFixed->UserId ) );
+ } else {
+ SampDiagPrint( DISPLAY_ADMIN_CHANGES,
+ ("SAM DIAG: Protecting user %d as non-admin account\n",
+ V1aFixed->UserId ) );
+ }
+#endif // SAMP_DIAGNOSTICS
+
+ //
+ // Get the old security descriptor so we can
+ // modify it.
+ //
+
+ NtStatus = SampGetAccessAttribute(
+ UserContext,
+ SAMP_USER_SECURITY_DESCRIPTOR,
+ FALSE, // don't make copy
+ &Revision,
+ &OldDescriptor
+ );
+ if (NT_SUCCESS(NtStatus)) {
+
+ NtStatus = SampModifyAccountSecurity(
+ SampUserObjectType,
+ (BOOLEAN) ((NewAdminStatus != 0) ? TRUE : FALSE),
+ OldDescriptor,
+ &SecurityDescriptor,
+ &SecurityDescriptorLength
+ );
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Write the new security descriptor into the object
+ //
+
+ NtStatus = SampSetAccessAttribute(
+ UserContext,
+ SAMP_USER_SECURITY_DESCRIPTOR,
+ SecurityDescriptor,
+ SecurityDescriptorLength
+ );
+
+ RtlDeleteSecurityObject( &SecurityDescriptor );
+ }
+ }
+ }
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // Save the fixed-length attributes
+ //
+
+ NtStatus = SampReplaceUserV1aFixed(
+ UserContext,
+ V1aFixed
+ );
+ }
+
+
+ if ( ( !NT_SUCCESS( NtStatus ) ) &&
+ ( AddingToAdmin != AddToAdmin ) &&
+ ( NtStatus != STATUS_SPECIAL_ACCOUNT ) ) {
+
+ //
+ // When an account is *removed* from admin groups, we can
+ // ignore errors from this routine. This routine is just
+ // making the account accessible to account operators, but
+ // it's no big deal if that doesn't work. The administrator
+ // can still get at it, so we should proceed with the calling
+ // operation.
+ //
+ // Obviously, we can't ignore errors if we're being added
+ // to an admin group, because that could be a security hole.
+ //
+ // Also, we want to make sure that the Administrator is
+ // never removed, so we DO propogate STATUS_SPECIAL_ACCOUNT.
+ //
+
+ NtStatus = STATUS_SUCCESS;
+ }
+
+ return( NtStatus );
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// //
+// Services Private to this process //
+// //
+///////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+SamINotifyDelta (
+ IN SAMPR_HANDLE DomainHandle,
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PUNICODE_STRING ObjectName,
+ IN DWORD ReplicateImmediately,
+ IN PSAM_DELTA_DATA DeltaData OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ Performs a change to some 'virtual' data in a domain. This is used by
+ netlogon to get the domain modification count updated for cases where
+ fields stored in the database replicated to a down-level machine have
+ changed. These fields don't exist in the NT SAM database but netlogon
+ needs to keep the SAM database and the down-level database modification
+ counts in sync.
+
+Arguments:
+
+ DomainHandle - The handle of an opened domain to operate on.
+
+ All other parameters match those in I_NetNotifyDelta.
+
+
+Return Value:
+
+
+ STATUS_SUCCESS - Domain modification count updated successfully.
+
+
+--*/
+{
+ NTSTATUS NtStatus, IgnoreStatus;
+ PSAMP_OBJECT DomainContext;
+ SAMP_OBJECT_TYPE FoundType;
+
+
+ NtStatus = SampAcquireWriteLock();
+ if (!NT_SUCCESS(NtStatus)) {
+ return(NtStatus);
+ }
+
+
+ //
+ // Validate type of, and access to object.
+ //
+
+ DomainContext = (PSAMP_OBJECT)DomainHandle;
+ NtStatus = SampLookupContext(
+ DomainContext,
+ DOMAIN_ALL_ACCESS, // Trusted client should succeed
+ SampDomainObjectType, // ExpectedType
+ &FoundType
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Dump the context - don't save the non-existent changes
+ //
+
+ NtStatus = SampDeReferenceContext( DomainContext, FALSE );
+ }
+
+
+
+
+
+ //
+ // Commit changes, if successful, and notify Netlogon of changes.
+ //
+
+ if ( NT_SUCCESS(NtStatus) ) {
+
+ //
+ // This will increment domain count and write it out
+ //
+
+ NtStatus = SampCommitAndRetainWriteLock();
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ SampNotifyNetlogonOfDelta(
+ DeltaType,
+ ObjectType,
+ ObjectRid,
+ ObjectName,
+ ReplicateImmediately,
+ DeltaData
+ );
+ }
+ }
+
+ IgnoreStatus = SampReleaseWriteLock( FALSE );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SamISetAuditingInformation(
+ IN PPOLICY_AUDIT_EVENTS_INFO PolicyAuditEventsInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This function sets Policy Audit Event Info relevant to SAM Auditing
+
+Arguments:
+
+ PolicyAuditEventsInfo - Pointer to structure containing the
+ current Audit Events Information. SAM extracts values of
+ relevance.
+
+Return Value:
+
+ NTSTATUS - Standard Nt Result Code
+
+ STATUS_SUCCESSFUL - The call completed successfully.
+
+ STATUS_UNSUCCESSFUL - The call was not successful because the
+ SAM lock was not acquired.
+--*/
+
+{
+ NTSTATUS NtStatus;
+
+ //
+ // Acquire the SAM Database Write Lock.
+ //
+
+ NtStatus = SampAcquireWriteLock();
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Set boolean if Auditing is on for Account Management
+ //
+
+ SampSetAuditingInformation( PolicyAuditEventsInfo );
+
+ //
+ // Release the SAM Database Write Lock. No need to commit
+ // the database transaction as there are no entries in the
+ // transaction log.
+ //
+
+ NtStatus = SampReleaseWriteLock( FALSE );
+ }
+
+ return(NtStatus);
+}
+
+
+NTSTATUS
+SampRtlConvertUlongToUnicodeString(
+ IN ULONG Value,
+ IN ULONG Base OPTIONAL,
+ IN ULONG DigitCount,
+ IN BOOLEAN AllocateDestinationString,
+ OUT PUNICODE_STRING UnicodeString
+ )
+
+/*++
+
+Routine Description:
+
+ This function converts an unsigned long integer a Unicode String.
+ The string contains leading zeros and is Unicode-NULL terminated.
+ Memory for the output buffer can optionally be allocated by the routine.
+
+ NOTE: This routine may be eligible for inclusion in the Rtl library
+ (possibly after modification). It is modeled on
+ RtlIntegerToUnicodeString
+
+Arguments:
+
+ Value - The unsigned long value to be converted.
+
+ Base - Specifies the radix that the converted string is to be
+ converted to.
+
+ DigitCount - Specifies the number of digits, including leading zeros
+ required for the result.
+
+ AllocateDestinationString - Specifies whether memory of the string
+ buffer is to be allocated by this routine. If TRUE is specified,
+ memory will be allocated via MIDL_user_allocate(). When this memory
+ is no longer required, it must be freed via MIDL_user_free. If
+ FALSE is specified, the string will be appended to the output
+ at the point marked by the Length field onwards.
+
+ UnicodeString - Pointer to UNICODE_STRING structure which will receive
+ the output string. The Length field will be set equal to the
+ number of bytes occupied by the string (excluding NULL terminator).
+ If memory for the destination string is being allocated by
+ the routine, the MaximumLength field will be set equal to the
+ length of the string in bytes including the null terminator.
+
+Return Values:
+
+ NTSTATUS - Standard Nt Result Code.
+
+ STATUS_SUCCESS - The call completed successfully.
+
+ STATUS_NO_MEMORY - Insufficient memory for the output string buffer.
+
+ STATUS_BUFFER_OVERFLOW - Buffer supplied is too small to contain the
+ output null-terminated string.
+
+ STATUS_INVALID_PARAMETER_MIX - One or more parameters are
+ invalid in combination.
+
+ - The specified Relative Id is too large to fit when converted
+ into an integer with DigitCount digits.
+
+ STATUS_INVALID_PARAMETER - One or more parameters are invalid.
+
+ - DigitCount specifies too large a number of digits.
+--*/
+
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING TempStringU, NumericStringU, OutputUnicodeStringU;
+ USHORT OutputLengthAvailable, OutputLengthRequired, LeadingZerosLength;
+
+ OutputUnicodeStringU = *UnicodeString;
+
+ TempStringU.Buffer = NULL;
+
+ if (AllocateDestinationString) {
+
+ OutputUnicodeStringU.Buffer = NULL;
+ }
+
+ //
+ // Verify that the maximum number of digits rquested has not been
+ // exceeded.
+ //
+
+ if (DigitCount > SAMP_MAXIMUM_ACCOUNT_RID_DIGITS) {
+
+ goto ConvertUlongToUnicodeStringError;
+ }
+
+ OutputLengthRequired = (USHORT)((DigitCount + 1) * sizeof(WCHAR));
+
+ //
+ // Allocate the Destination String Buffer if requested
+ //
+
+ if (AllocateDestinationString) {
+
+ NtStatus = STATUS_NO_MEMORY;
+ OutputUnicodeStringU.MaximumLength = OutputLengthRequired;
+ OutputUnicodeStringU.Length = (USHORT) 0;
+
+ OutputUnicodeStringU.Buffer = MIDL_user_allocate(
+ OutputUnicodeStringU.MaximumLength
+ );
+
+ if (OutputUnicodeStringU.Buffer == NULL) {
+
+ goto ConvertUlongToUnicodeStringError;
+ }
+ }
+
+ //
+ // Compute the length available in the output string and compare it with
+ // the length required.
+ //
+
+ OutputLengthAvailable = OutputUnicodeStringU.MaximumLength -
+ OutputUnicodeStringU.Length;
+
+
+ NtStatus = STATUS_BUFFER_OVERFLOW;
+
+ if (OutputLengthRequired > OutputLengthAvailable) {
+
+ goto ConvertUlongToUnicodeStringError;
+ }
+
+ //
+ // Create a Unicode String with capacity equal to the required
+ // converted Rid Length
+ //
+
+ TempStringU.MaximumLength = OutputLengthRequired;
+
+ TempStringU.Buffer = MIDL_user_allocate( TempStringU.MaximumLength );
+
+ NtStatus = STATUS_NO_MEMORY;
+
+ if (TempStringU.Buffer == NULL) {
+
+ goto ConvertUlongToUnicodeStringError;
+ }
+
+ //
+ // Convert the unsigned long value to a hexadecimal Unicode String.
+ //
+
+ NtStatus = RtlIntegerToUnicodeString( Value, Base, &TempStringU );
+
+ if (!NT_SUCCESS(NtStatus)) {
+
+ goto ConvertUlongToUnicodeStringError;
+ }
+
+ //
+ // Prepend the requisite number of Unicode Zeros.
+ //
+
+ LeadingZerosLength = OutputLengthRequired - sizeof(WCHAR) - TempStringU.Length;
+
+ if (LeadingZerosLength > 0) {
+
+ RtlInitUnicodeString( &NumericStringU, L"00000000000000000000000000000000" );
+
+ RtlCopyMemory(
+ ((PUCHAR)OutputUnicodeStringU.Buffer) + OutputUnicodeStringU.Length,
+ NumericStringU.Buffer,
+ LeadingZerosLength
+ );
+
+ OutputUnicodeStringU.Length += LeadingZerosLength;
+ }
+
+ //
+ // Append the converted string
+ //
+
+ RtlAppendUnicodeStringToString( &OutputUnicodeStringU, &TempStringU);
+
+ *UnicodeString = OutputUnicodeStringU;
+ NtStatus = STATUS_SUCCESS;
+
+ConvertUlongToUnicodeStringFinish:
+
+ if (TempStringU.Buffer != NULL) {
+
+ MIDL_user_free( TempStringU.Buffer);
+ }
+
+ return(NtStatus);
+
+ConvertUlongToUnicodeStringError:
+
+ if (AllocateDestinationString) {
+
+ if (OutputUnicodeStringU.Buffer != NULL) {
+
+ MIDL_user_free( OutputUnicodeStringU.Buffer);
+ }
+ }
+
+ goto ConvertUlongToUnicodeStringFinish;
+}
+
+
+NTSTATUS
+SampRtlWellKnownPrivilegeCheck(
+ BOOLEAN ImpersonateClient,
+ IN ULONG PrivilegeId,
+ IN OPTIONAL PCLIENT_ID ClientId
+ )
+
+/*++
+
+Routine Description:
+
+ This function checks if the given well known privilege is enabled for an
+ impersonated client or for the current process.
+
+Arguments:
+
+ ImpersonateClient - If TRUE, impersonate the client. If FALSE, don't
+ impersonate the client (we may already be doing so).
+
+ PrivilegeId - Specifies the well known Privilege Id
+
+ ClientId - Specifies the client process/thread Id. If already
+ impersonating the client, or impersonation is requested, this
+ parameter should be omitted.
+
+Return Value:
+
+ NTSTATUS - Standard Nt Result Code
+
+ STATUS_SUCCESS - The call completed successfully and the client
+ is either trusted or has the necessary privilege enabled.
+
+--*/
+
+{
+ NTSTATUS Status, SecondaryStatus;
+ BOOLEAN PrivilegeHeld = FALSE;
+ HANDLE ClientThread = NULL, ClientProcess = NULL, ClientToken = NULL;
+ OBJECT_ATTRIBUTES NullAttributes;
+ PRIVILEGE_SET Privilege;
+ BOOLEAN ClientImpersonatedHere = FALSE;
+
+ InitializeObjectAttributes( &NullAttributes, NULL, 0, NULL, NULL );
+
+ //
+ // If requested, impersonate the client.
+ //
+
+ if (ImpersonateClient) {
+
+ Status = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ ClientImpersonatedHere = TRUE;
+ }
+
+ //
+ // If a client process other than ourself has been specified , open it
+ // for query information access.
+ //
+
+ if (ARGUMENT_PRESENT(ClientId)) {
+
+ if (ClientId->UniqueProcess != NtCurrentProcess()) {
+
+ Status = NtOpenProcess(
+ &ClientProcess,
+ PROCESS_QUERY_INFORMATION, // To open primary token
+ &NullAttributes,
+ ClientId
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ } else {
+
+ ClientProcess = NtCurrentProcess();
+ }
+ }
+
+ //
+ // If a client thread other than ourself has been specified , open it
+ // for query information access.
+ //
+
+ if (ARGUMENT_PRESENT(ClientId)) {
+
+ if (ClientId->UniqueThread != NtCurrentThread()) {
+
+ Status = NtOpenThread(
+ &ClientThread,
+ THREAD_QUERY_INFORMATION,
+ &NullAttributes,
+ ClientId
+ );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ } else {
+
+ ClientThread = NtCurrentThread();
+ }
+
+ } else {
+
+ ClientThread = NtCurrentThread();
+ }
+
+ //
+ // Open the specified or current thread's impersonation token (if any).
+ //
+
+ Status = NtOpenThreadToken(
+ ClientThread,
+ TOKEN_QUERY,
+ TRUE,
+ &ClientToken
+ );
+
+
+ //
+ // Make sure that we did not get any error in opening the impersonation
+ // token other than that the token doesn't exist.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ if ( Status != STATUS_NO_TOKEN ) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ //
+ // The thread isn't impersonating...open the process's token.
+ // A process Id must have been specified in the ClientId information
+ // in this case.
+ //
+
+ if (ClientProcess == NULL) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ Status = NtOpenProcessToken(
+ ClientProcess,
+ TOKEN_QUERY,
+ &ClientToken
+ );
+
+ //
+ // Make sure we succeeded in opening the token
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+ }
+
+ //
+ // OK, we have a token open. Now check for the privilege to execute this
+ // service.
+ //
+
+ Privilege.PrivilegeCount = 1;
+ Privilege.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ Privilege.Privilege[0].Luid = RtlConvertLongToLuid(PrivilegeId);
+ Privilege.Privilege[0].Attributes = 0;
+
+ Status = NtPrivilegeCheck(
+ ClientToken,
+ &Privilege,
+ &PrivilegeHeld
+ );
+
+ if (!NT_SUCCESS(Status)) {
+
+ goto WellKnownPrivilegeCheckError;
+ }
+
+ //
+ // Generate any necessary audits
+ //
+
+ SecondaryStatus = NtPrivilegedServiceAuditAlarm (
+ &SampSamSubsystem,
+ &SampSamSubsystem,
+ ClientToken,
+ &Privilege,
+ PrivilegeHeld
+ );
+ // ASSERT( NT_SUCCESS(SecondaryStatus) );
+
+
+ if ( !PrivilegeHeld ) {
+
+ Status = STATUS_PRIVILEGE_NOT_HELD;
+ goto WellKnownPrivilegeCheckError;
+ }
+
+WellKnownPrivilegeCheckFinish:
+
+ //
+ // If we impersonated the client, revert to ourself.
+ //
+
+ if (ClientImpersonatedHere) {
+
+ SecondaryStatus = I_RpcMapWin32Status(RpcRevertToSelf());
+ }
+
+ //
+ // If necessary, close the client Process.
+ //
+
+ if ((ARGUMENT_PRESENT(ClientId)) &&
+ (ClientId->UniqueProcess != NtCurrentProcess()) &&
+ (ClientProcess != NULL)) {
+
+ SecondaryStatus = NtClose( ClientProcess );
+ ASSERT(NT_SUCCESS(SecondaryStatus));
+ ClientProcess = NULL;
+ }
+
+ //
+ // If necessary, close the client token.
+ //
+
+ if (ClientToken != NULL) {
+
+ SecondaryStatus = NtClose( ClientToken );
+ ASSERT(NT_SUCCESS(SecondaryStatus));
+ ClientToken = NULL;
+ }
+
+ //
+ // If necessary, close the client thread
+ //
+
+ if ((ARGUMENT_PRESENT(ClientId)) &&
+ (ClientId->UniqueThread != NtCurrentThread()) &&
+ (ClientThread != NULL)) {
+
+ SecondaryStatus = NtClose( ClientThread );
+ ASSERT(NT_SUCCESS(SecondaryStatus));
+ ClientThread = NULL;
+ }
+
+ return(Status);
+
+WellKnownPrivilegeCheckError:
+
+ goto WellKnownPrivilegeCheckFinish;
+}
+
+
+VOID
+SampWriteEventLog (
+ IN USHORT EventType,
+ IN USHORT EventCategory OPTIONAL,
+ IN ULONG EventID,
+ IN PSID UserSid OPTIONAL,
+ IN USHORT NumStrings,
+ IN ULONG DataSize,
+ IN PUNICODE_STRING *Strings OPTIONAL,
+ IN PVOID Data OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ Routine that adds an entry to the event log
+
+Arguments:
+
+ EventType - Type of event.
+
+ EventCategory - EventCategory
+
+ EventID - event log ID.
+
+ UserSid - SID of user involved.
+
+ NumStrings - Number of strings in Strings array
+
+ DataSize - Number of bytes in Data buffer
+
+ Strings - Array of unicode strings
+
+ Data - Pointer to data buffer
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS NtStatus;
+ UNICODE_STRING Source;
+ HANDLE LogHandle;
+
+ RtlInitUnicodeString(&Source, L"SAM");
+
+ //
+ // Open the log
+ //
+
+ NtStatus = ElfRegisterEventSourceW (
+ NULL, // Server
+ &Source,
+ &LogHandle
+ );
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to registry event source with event log, status = 0x%lx\n", NtStatus));
+ return;
+ }
+
+
+
+ //
+ // Write out the event
+ //
+
+ NtStatus = ElfReportEventW (
+ LogHandle,
+ EventType,
+ EventCategory,
+ EventID,
+ UserSid,
+ NumStrings,
+ DataSize,
+ Strings,
+ Data,
+ 0, // Flags
+ NULL, // Record Number
+ NULL // Time written
+ );
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to report event to event log, status = 0x%lx\n", NtStatus));
+ }
+
+
+
+ //
+ // Close the event log
+ //
+
+ NtStatus = ElfDeregisterEventSource (LogHandle);
+
+ if (!NT_SUCCESS(NtStatus)) {
+ KdPrint(("SAM: Failed to de-register event source with event log, status = 0x%lx\n", NtStatus));
+ }
+}
+
+
+BOOL
+SampShutdownNotification(
+ DWORD dwCtrlType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by the system when system shutdown is occuring.
+
+ It causes the SAM registry to be flushed if necessary.
+
+Arguments:
+
+
+
+Return Value:
+
+ FALSE - to allow any other shutdown routines in this process to
+ also be called.
+
+
+
+--*/
+{
+ NTSTATUS
+ NtStatus;
+
+
+ if (dwCtrlType == CTRL_SHUTDOWN_EVENT) {
+
+
+ //
+ // Don't wait for the flush thread to wake up.
+ // Flush the registry now if necessary ...
+ //
+
+ NtStatus = SampAcquireWriteLock();
+ ASSERT( NT_SUCCESS(NtStatus) ); //Nothing we can do if this fails
+
+ if ( NT_SUCCESS( NtStatus ) ) {
+
+ //
+ // This flush use to be done only if FlushThreadCreated
+ // was true. However, we seem to have a race condition
+ // at setup that causes an initial replication to be
+ // lost (resulting in an additional replication).
+ // Until we resolve this problem, always flush on
+ // shutdown.
+ //
+
+ NtStatus = NtFlushKey( SampKey );
+
+ if (!NT_SUCCESS( NtStatus )) {
+ DbgPrint("NtFlushKey failed, Status = %X\n",NtStatus);
+// ASSERT( NT_SUCCESS(NtStatus) );
+ }
+
+ SampReleaseWriteLock( FALSE );
+ }
+
+
+ }
+ return(FALSE);
+}
+
+
+NTSTATUS
+SampGetAccountDomainInfo(
+ PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine retrieves ACCOUNT domain information from the LSA
+ policy database.
+
+
+Arguments:
+
+ PolicyAccountDomainInfo - Receives a pointer to a
+ POLICY_ACCOUNT_DOMAIN_INFO structure containing the account
+ domain info.
+
+
+
+Return Value:
+
+ STATUS_SUCCESS - Succeeded.
+
+ Other status values that may be returned from:
+
+ LsarQueryInformationPolicy()
+--*/
+
+{
+ NTSTATUS
+ NtStatus,
+ IgnoreStatus;
+
+ LSAPR_HANDLE
+ PolicyHandle;
+
+
+ NtStatus = LsaIOpenPolicyTrusted( &PolicyHandle );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ //
+ // Query the account domain information
+ //
+
+ NtStatus = LsarQueryInformationPolicy(
+ PolicyHandle,
+ PolicyAccountDomainInformation,
+ (PLSAPR_POLICY_INFORMATION *)PolicyAccountDomainInfo
+ );
+
+ if (NT_SUCCESS(NtStatus)) {
+
+ if ( (*PolicyAccountDomainInfo)->DomainSid == NULL ) {
+
+ NtStatus = STATUS_INVALID_SID;
+ }
+ }
+
+ IgnoreStatus = LsarClose( &PolicyHandle );
+ ASSERT(NT_SUCCESS(IgnoreStatus));
+
+ }
+
+#if DBG
+ if ( NT_SUCCESS(NtStatus) ) {
+ ASSERT( (*PolicyAccountDomainInfo) != NULL );
+ ASSERT( (*PolicyAccountDomainInfo)->DomainName.Buffer != NULL );
+ }
+#endif //DBG
+
+ return(NtStatus);
+}
+
+