summaryrefslogtreecommitdiffstats
path: root/private/net/svcdlls/logonsrv
diff options
context:
space:
mode:
Diffstat (limited to 'private/net/svcdlls/logonsrv')
-rw-r--r--private/net/svcdlls/logonsrv/client/daytona/makefile6
-rw-r--r--private/net/svcdlls/logonsrv/client/daytona/sources93
-rw-r--r--private/net/svcdlls/logonsrv/client/dirs32
-rw-r--r--private/net/svcdlls/logonsrv/client/getdclst.c437
-rw-r--r--private/net/svcdlls/logonsrv/client/getdcnam.c849
-rw-r--r--private/net/svcdlls/logonsrv/client/logonapi.c502
-rw-r--r--private/net/svcdlls/logonsrv/client/rpcbind.c625
-rw-r--r--private/net/svcdlls/logonsrv/client/ssiapi.c1446
-rw-r--r--private/net/svcdlls/logonsrv/dirs47
-rw-r--r--private/net/svcdlls/logonsrv/imports.h40
-rw-r--r--private/net/svcdlls/logonsrv/imports.idl58
-rw-r--r--private/net/svcdlls/logonsrv/logon.idl857
-rw-r--r--private/net/svcdlls/logonsrv/logoncli.acf12
-rw-r--r--private/net/svcdlls/logonsrv/logonsrv.acf10
-rw-r--r--private/net/svcdlls/logonsrv/makefil080
-rw-r--r--private/net/svcdlls/logonsrv/monitor/makefile6
-rw-r--r--private/net/svcdlls/logonsrv/monitor/makefile.inc2
-rw-r--r--private/net/svcdlls/logonsrv/monitor/monutil.c3232
-rw-r--r--private/net/svcdlls/logonsrv/monitor/nlmon.c922
-rw-r--r--private/net/svcdlls/logonsrv/monitor/nlmon.rc12
-rw-r--r--private/net/svcdlls/logonsrv/monitor/sources106
-rw-r--r--private/net/svcdlls/logonsrv/monitor/winutil.c922
-rw-r--r--private/net/svcdlls/logonsrv/nlbind.h45
-rw-r--r--private/net/svcdlls/logonsrv/server/announce.c1243
-rw-r--r--private/net/svcdlls/logonsrv/server/changelg.c1818
-rw-r--r--private/net/svcdlls/logonsrv/server/changelg.h224
-rw-r--r--private/net/svcdlls/logonsrv/server/chutil.c4337
-rw-r--r--private/net/svcdlls/logonsrv/server/chutil.h495
-rw-r--r--private/net/svcdlls/logonsrv/server/chworker.c1714
-rw-r--r--private/net/svcdlls/logonsrv/server/chworker.h215
-rw-r--r--private/net/svcdlls/logonsrv/server/error.c820
-rw-r--r--private/net/svcdlls/logonsrv/server/iniparm.h325
-rw-r--r--private/net/svcdlls/logonsrv/server/logonapi.c3426
-rw-r--r--private/net/svcdlls/logonsrv/server/logonsrv.h392
-rw-r--r--private/net/svcdlls/logonsrv/server/lsarepl.c2184
-rw-r--r--private/net/svcdlls/logonsrv/server/lsarepl.h100
-rw-r--r--private/net/svcdlls/logonsrv/server/lsrvdata.h228
-rw-r--r--private/net/svcdlls/logonsrv/server/lsrvrepl.c4700
-rw-r--r--private/net/svcdlls/logonsrv/server/lsrvutil.c3055
-rw-r--r--private/net/svcdlls/logonsrv/server/mailslot.c1238
-rw-r--r--private/net/svcdlls/logonsrv/server/makefile6
-rw-r--r--private/net/svcdlls/logonsrv/server/makefile.inc1
-rw-r--r--private/net/svcdlls/logonsrv/server/netlogon.c4427
-rw-r--r--private/net/svcdlls/logonsrv/server/netlogon.def15
-rw-r--r--private/net/svcdlls/logonsrv/server/netlogon.prf507
-rw-r--r--private/net/svcdlls/logonsrv/server/netlogon.rc12
-rw-r--r--private/net/svcdlls/logonsrv/server/nldebug.h180
-rw-r--r--private/net/svcdlls/logonsrv/server/nlp.c1246
-rw-r--r--private/net/svcdlls/logonsrv/server/nlp.h95
-rw-r--r--private/net/svcdlls/logonsrv/server/nlsecure.c104
-rw-r--r--private/net/svcdlls/logonsrv/server/nlsecure.h88
-rw-r--r--private/net/svcdlls/logonsrv/server/nltest.c3664
-rw-r--r--private/net/svcdlls/logonsrv/server/nltest.prf1
-rw-r--r--private/net/svcdlls/logonsrv/server/nltest.rc12
-rw-r--r--private/net/svcdlls/logonsrv/server/nltest1.c522
-rw-r--r--private/net/svcdlls/logonsrv/server/oldstub.c457
-rw-r--r--private/net/svcdlls/logonsrv/server/parse.c464
-rw-r--r--private/net/svcdlls/logonsrv/server/repluas.c2452
-rw-r--r--private/net/svcdlls/logonsrv/server/replutil.c3608
-rw-r--r--private/net/svcdlls/logonsrv/server/replutil.h235
-rw-r--r--private/net/svcdlls/logonsrv/server/sources115
-rw-r--r--private/net/svcdlls/logonsrv/server/srvsess.c1728
-rw-r--r--private/net/svcdlls/logonsrv/server/ssiapi.c6401
-rw-r--r--private/net/svcdlls/logonsrv/server/ssiapi.h81
-rw-r--r--private/net/svcdlls/logonsrv/server/ssiauth.c788
-rw-r--r--private/net/svcdlls/logonsrv/server/ssidelta.h164
-rw-r--r--private/net/svcdlls/logonsrv/server/ssiinit.h1375
-rw-r--r--private/net/svcdlls/logonsrv/server/trustutl.c4976
68 files changed, 70579 insertions, 0 deletions
diff --git a/private/net/svcdlls/logonsrv/client/daytona/makefile b/private/net/svcdlls/logonsrv/client/daytona/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/daytona/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/net/svcdlls/logonsrv/client/daytona/sources b/private/net/svcdlls/logonsrv/client/daytona/sources
new file mode 100644
index 000000000..5118d0a71
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/daytona/sources
@@ -0,0 +1,93 @@
+!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:
+
+ Cliff Van Dyke (CliffV) 27-Jun-1991
+
+
+Revision History:
+
+!ENDIF
+
+#
+# The TARGETNAME variable is defined by the developer. It is the name of
+# the target (component) that is being built by this makefile. It
+# should NOT include any path or file extension information.
+#
+
+MAJORCOMP = logonsrv
+MINORCOMP = client
+TARGETNAME= logonsrv
+
+
+NTPROFILEINPUT=YES
+
+#
+# The TARGETPATH and TARGETTYPE varialbes are defined by the developer.
+# The first specifies where the target is to be build. The second specifies
+# the type of target (either PROGRAM, DYNLINK or LIBRARY)
+#
+
+TARGETPATH=obj
+
+TARGETTYPE=LIBRARY
+
+TARGETLIBS=
+
+#
+# The INCLUDES variable specifies any include paths that are specific to
+# this source directory. Separate multiple directory paths with single
+# semicolons. Relative path specifications are okay.
+#
+
+INCLUDES=..;..\..;..\..\..\..\inc;..\..\..\..\api;$(BASEDIR)\public\sdk\inc;..\..\..\..\..\inc
+
+C_DEFINES=-DRPC_NO_WINDOWS_H
+MSC_WARNING_LEVEL=/W3 /WX
+
+#
+# The SOURCES variable is defined by the developer. It is a list of all the
+# source files for this component. Each source file should be on a separate
+# line using the line continuation character. This will minimize merge
+# conflicts if two developers adding source files to the same component.
+#
+
+!IFNDEF DISABLE_NET_UNICODE
+UNICODE=1
+NET_C_DEFINES=-DUNICODE
+!ENDIF
+
+SOURCES= \
+ ..\getdcnam.c \
+ ..\getdclst.c \
+ ..\logonapi.c \
+ ..\rpcbind.c \
+ ..\ssiapi.c \
+ ..\logon_c.c
+
+UMTYPE=windows
+
+
+#
+# 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
+#
diff --git a/private/net/svcdlls/logonsrv/client/dirs b/private/net/svcdlls/logonsrv/client/dirs
new file mode 100644
index 000000000..d6f158a31
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/dirs
@@ -0,0 +1,32 @@
+!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
+
+#
+# The real important directory
+
+DIRS=daytona
+
+#
+# The future cool directory
+#
+
+OPTIONAL_DIRS=
diff --git a/private/net/svcdlls/logonsrv/client/getdclst.c b/private/net/svcdlls/logonsrv/client/getdclst.c
new file mode 100644
index 000000000..aa6d1be7d
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/getdclst.c
@@ -0,0 +1,437 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ getdclst.c
+
+Abstract:
+
+ I_NetGetDCList API
+
+Author:
+
+ 04-Feb-1992 (CliffV)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+#include <rpc.h>
+#include <logon_c.h>// includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+
+#include <debuglib.h> // IF_DEBUG()
+#include <lmapibuf.h>
+#include <lmerr.h>
+#include <lmserver.h> // SV_TYPE_* defines
+#include <netdebug.h> // NetpKdPrint
+#include <netlib.h> // NetpGetDomainName
+#include <ntlsa.h> // LsaTrust list
+#include <tstring.h> // STRLEN
+#include <stdlib.h> // wcslen
+
+
+DBGSTATIC NET_API_STATUS
+InternalNetGetDCList (
+ IN LPWSTR ServerName OPTIONAL,
+ IN LPWSTR TrustedDomainName,
+ OUT PULONG DCCount,
+ OUT PUNICODE_STRING * DCNames
+ )
+
+/*++
+
+Routine Description:
+
+ Get the names of the NT Domain Controllers in a domain. The information
+ is returned in a form suitable for storing in the LSA's
+ TRUSTED_CONTROLLERS_INFO structure.
+
+ Ideally, ServerName should be the name of a Domain Controller in the
+ specified domain. However, one should first try specifying ServerName
+ as the name of the PDC in the trusting domain. If that fails,
+ the UI can prompt for the name of a DC in the domain.
+
+
+Arguments:
+
+ ServerName - name of remote server (null for local).
+
+ TrustedDomainName - name of domain.
+
+ DCCount - Returns the number of entries in the DCNames array.
+
+ DCNames - Returns a pointer to an array of names of NT Domain Controllers
+ in the specified domain. The first entry is the name of the NT PDC.
+ The first entry will be NULL if the PDC cannot be found.
+ The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ NERR_Success - Success.
+ ERROR_INVALID_NAME Badly formed domain name
+ NERR_DCNotFound - No DC's were found in the domain
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+ DWORD EntriesRead;
+ DWORD TotalEntries;
+
+ DWORD Size = 0;
+ BOOLEAN PdcFound = FALSE;
+ PUNICODE_STRING ReturnBuffer = NULL;
+ ULONG ReturnCount = 0;
+
+ PUNICODE_STRING CurrentBuffer;
+ ULONG CurrentIndex;
+ LPWSTR Where;
+
+ DWORD i;
+
+
+
+ //
+ // Enumerate ALL PDCs and BDCs in the domain.
+ // We'll filter out NT DC's ourselves.
+ //
+ *DCCount = 0;
+
+ NetStatus = NetServerEnum( ServerName,
+ 101,
+ (LPBYTE *) &ServerInfo101,
+ MAX_PREFERRED_LENGTH,
+ &EntriesRead,
+ &TotalEntries,
+ SV_TYPE_DOMAIN_CTRL | SV_TYPE_DOMAIN_BAKCTRL,
+ TrustedDomainName,
+ NULL ); // Resume Handle
+
+ if ( NetStatus != NERR_Success ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "InternalNetGetDCList: cannot NetServerEnum '%ws': %ld 0X%lx\n",
+ ServerName, NetStatus, NetStatus));
+ }
+ goto Cleanup;
+ }
+
+ //
+ // Compute the size of the information to return.
+ //
+
+ for ( i=0; i<EntriesRead; i++ ) {
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "InternalNetGetDCList: '%ws': enumerated %ws\n",
+ ServerName,
+ ServerInfo101[i].sv101_name ));
+ }
+
+ //
+ // Skip non-NT entries
+ //
+
+ if ( (ServerInfo101[i].sv101_type & SV_TYPE_NT) == 0 ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "InternalNetGetDCList: '%ws': %ws is not NT\n",
+ ServerName,
+ ServerInfo101[i].sv101_name ));
+ }
+ continue;
+ }
+
+ //
+ // Remember whether the PDC was found
+ //
+
+ if ( ServerInfo101[i].sv101_type & SV_TYPE_DOMAIN_CTRL ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "InternalNetGetDCList: '%ws': %ws is the PDC\n",
+ ServerName,
+ ServerInfo101[i].sv101_name ));
+ }
+ PdcFound = TRUE;
+ }
+
+ //
+ // Leave room for for the UNICODE_STRING structure and the string
+ // itself (including leadind \\'s.
+ //
+
+ (*DCCount) ++;
+ Size += sizeof(UNICODE_STRING) +
+ (STRLEN(ServerInfo101[i].sv101_name) + 3) * sizeof(WCHAR);
+
+ }
+
+ //
+ // We must find at least one NT server.
+ //
+
+ if ( *DCCount == 0 ) {
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+ }
+
+ if ( !PdcFound ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "InternalNetGetDCList: '%ws': PDC not found\n",
+ ServerName ));
+ }
+ (*DCCount) ++;
+ Size += sizeof(UNICODE_STRING);
+ }
+
+ //
+ // Allocate the return buffer.
+ //
+
+ NetStatus = NetApiBufferAllocate( Size, (LPVOID *) &ReturnBuffer );
+
+ if ( NetStatus != NERR_Success ) {
+ goto Cleanup;
+ }
+
+ Where = (LPWSTR) (ReturnBuffer + *DCCount);
+
+
+ //
+ // Fill in the return buffer.
+ //
+
+ CurrentIndex = 1; // The first (zeroeth) entry is for the PDC.
+ RtlInitUnicodeString( ReturnBuffer, NULL );
+
+ for ( i=0; i<EntriesRead; i++ ) {
+
+ //
+ // Skip non-NT entries
+ //
+
+ if ( (ServerInfo101[i].sv101_type & SV_TYPE_NT) == 0 ) {
+ continue;
+ }
+
+ //
+ // Determine which entry to fill in.
+ //
+ // If multiple PDC's were found, the first one is assumed
+ // to be the real PDC>
+ //
+
+ if ( (ServerInfo101[i].sv101_type & SV_TYPE_DOMAIN_CTRL) &&
+ ReturnBuffer->Buffer == NULL ) {
+ CurrentBuffer = ReturnBuffer;
+
+ } else {
+
+ NetpAssert( CurrentIndex < *DCCount );
+
+ CurrentBuffer = &ReturnBuffer[CurrentIndex];
+ CurrentIndex++;
+ }
+
+ //
+ // Copy the string itself to the return buffer
+ //
+ NetpAssert( ServerInfo101[i].sv101_name[0] != L'\\' );
+ *(Where) = '\\';
+ *(Where+1) = '\\';
+ NetpCopyTStrToWStr( Where+2, ServerInfo101[i].sv101_name );
+
+ //
+ // Set the UNICODE_STRING to point to it.
+ //
+
+ RtlInitUnicodeString( CurrentBuffer, Where );
+
+ Where += (wcslen(Where) + 1);
+
+ }
+
+ NetpAssert( CurrentIndex == *DCCount );
+
+ NetStatus = NERR_Success;
+
+
+ //
+ // Cleanup locally used resources
+ //
+Cleanup:
+
+ if ( ServerInfo101 != NULL ) {
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ if ( NetStatus != NERR_Success ) {
+ if ( ReturnBuffer != NULL ) {
+ NetApiBufferFree( ReturnBuffer );
+ ReturnBuffer = NULL;
+ }
+ *DCCount = 0;
+ }
+
+ //
+ // Return the information to the caller.
+ //
+
+ *DCNames = ReturnBuffer;
+
+ return NetStatus;
+
+}
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetGetDCList (
+ IN LPWSTR ServerName OPTIONAL,
+ IN LPWSTR TrustedDomainName,
+ OUT PULONG DCCount,
+ OUT PUNICODE_STRING * DCNames
+ )
+
+/*++
+
+Routine Description:
+
+ Get the names of the NT Domain Controllers in a domain. The information
+ is returned in a form suitable for storing in the LSA's
+ TRUSTED_CONTROLLERS_INFO structure.
+
+ Ideally, ServerName should be the name of a Domain Controller in the
+ specified domain. However, one should first try specifying ServerName
+ as NULL in which case this API will try the the following machines:
+
+ * The local machine.
+ * The PDC of the primary domain of the local machine,
+ * The PDC of the named trusted domain,
+ * Each of the DC's in the LSA's current DC list for the named trusted
+ domain.
+
+ If this "NULL" case fails, the UI should prompt for the name of a DC
+ in the trusted domain. This handles the case where the trusted domain
+ cannot be reached via the above listed servers.
+
+Arguments:
+
+ ServerName - name of remote server (null for special case).
+
+ TrustedDomainName - name of domain.
+
+ DCCount - Returns the number of entries in the DCNames array.
+
+ DCNames - Returns a pointer to an array of names of NT Domain Controllers
+ in the specified domain. The first entry is the name of the NT PDC.
+ The first entry will be NULL if the PDC cannot be found.
+ The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ NERR_Success - Success.
+ ERROR_INVALID_NAME Badly formed domain name
+ NERR_DCNotFound - No DC's were found in the domain. Perhaps,
+ a ServerName should be specified.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NET_API_STATUS SavedNetStatus;
+
+ LPWSTR DCName = NULL;
+
+ //
+ // Initialization
+ //
+ *DCCount = 0;
+
+
+
+ //
+ // Try straight forward way to get the DC list.
+ //
+
+ NetStatus = InternalNetGetDCList( ServerName,
+ TrustedDomainName,
+ DCCount,
+ DCNames );
+
+ if ( NetStatus == NERR_Success || ServerName != NULL ) {
+ SavedNetStatus = NetStatus;
+ goto Cleanup;
+ }
+
+ SavedNetStatus = NetStatus;
+
+
+
+ //
+ // Simply use the PDC name as the DC list.
+ //
+ // NetServerEnum might be several minutes out of date. NetGetDCName
+ // broadcasts to find the server, so that information will be more
+ // current.
+ //
+
+ NetStatus = NetGetDCName( NULL, TrustedDomainName, (LPBYTE*)&DCName);
+
+ if ( NetStatus == NERR_Success ) {
+
+ PUNICODE_STRING ReturnBuffer = NULL;
+ DWORD Size;
+ LPWSTR Where;
+
+ Size = sizeof(UNICODE_STRING) +
+ (wcslen(DCName) + 1) * sizeof(WCHAR);
+
+ NetStatus = NetApiBufferAllocate( Size, (LPVOID *) &ReturnBuffer );
+
+ if ( NetStatus != NERR_Success ) {
+ goto Cleanup;
+ }
+
+ Where = (LPWSTR)((LPBYTE)ReturnBuffer + sizeof(UNICODE_STRING));
+
+ wcscpy( Where, DCName );
+
+ RtlInitUnicodeString( ReturnBuffer, Where );
+
+
+ *DCNames = ReturnBuffer;
+ *DCCount = 1;
+
+ SavedNetStatus = NERR_Success;
+ }
+
+
+ //
+ // Cleanup locally used resources.
+ //
+Cleanup:
+
+ if( DCName != NULL ) {
+ (VOID) NetApiBufferFree( DCName );
+ }
+
+ //
+ // Return the status code from the original request.
+ //
+ return SavedNetStatus;
+}
diff --git a/private/net/svcdlls/logonsrv/client/getdcnam.c b/private/net/svcdlls/logonsrv/client/getdcnam.c
new file mode 100644
index 000000000..19eccda51
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/getdcnam.c
@@ -0,0 +1,849 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ getdcnam.c
+
+Abstract:
+
+ NetGetDCName API
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 09-Feb-1989 (PaulC)
+ Created file, to hold NetGetDCName.
+
+ 18-Apr-1989 (Ericpe)
+ Implemented NetGetDCName.
+
+ 30-May-1989 (DannyGl)
+ Reduced DosReadMailslot timeout.
+
+ 07-Jul-1989 (NealF)
+ Use I_NetNameCanonicalize
+
+ 27-Jul-1989 (WilliamW)
+ Use WIN3 manifest for WIN3.0 compatibility
+
+ 03-Jan-1990 (WilliamW)
+ canonicalize domain and use I_NetCompareName
+
+ 08-Jun-1991 (CliffV)
+ Ported to NT
+
+ 23-Jul-1991 JohnRo
+ Implement downlevel NetGetDCName.
+
+--*/
+
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+#ifdef _CAIRO_
+#define INC_OLE2
+#include <windows.h>
+#endif // _CAIRO_
+#include <rpc.h>
+#include <logon_c.h>// includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+
+#include <winbase.h>
+
+#include <accessp.h>
+#include <align.h>
+#include <debuglib.h> // IF_DEBUG()
+#include <icanon.h> // NAMETYPE_* defines NetpIsRemote(), NIRFLAG_ equates.
+#include <lmapibuf.h>
+#include <lmerr.h>
+#include <lmremutl.h> // SUPPORTS_* defines
+#include <lmserver.h> // SV_TYPE_* defines
+#include <lmsvc.h> // SERVICE_* defines
+#include <lmwksta.h>
+#include <logonp.h> // NetpLogon routines
+#include <names.h> // NetpIsDomainNameValid
+#include <nlbind.h> // Netlogon RPC binding cache init routines
+#include <netdebug.h> // NetpKdPrint
+#include <netlib.h> // NetpMemoryFree
+#include <netrpc.h>
+#include <rxdomain.h> // RxNetGetDCName().
+#include <string.h>
+#include <stdlib.h>
+#ifdef _CAIRO_
+#include <lmapibuf.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <dsapi.h>
+#endif // _CAIRO_
+
+
+
+//
+// Maintain a cache of the correct answer for the Primary Domain.
+//
+DBGSTATIC CRITICAL_SECTION GlobalDCNameCritSect;
+DBGSTATIC WCHAR GlobalPrimaryDCName[UNCLEN+1];
+DBGSTATIC WCHAR GlobalPrimaryDomainName[DNLEN+1];
+
+#define LOCKDOMAINSEM() EnterCriticalSection( &GlobalDCNameCritSect )
+#define FREEDOMAINSEM() LeaveCriticalSection( &GlobalDCNameCritSect )
+
+// end global dll data
+
+
+//
+// Local definitions used throughout this file.
+//
+
+#define DOMAIN_OTHER 0
+#define DOMAIN_PRIMARY 1
+
+#ifdef _CAIRO_
+WCHAR PrimaryDomainName[DNLEN+1];
+#endif // _CAIRO_
+
+
+NET_API_STATUS
+DCNameInitialize(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Perform per-process initialization.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+#ifdef _CAIRO_
+ WCHAR CairoDomainName[MAX_PATH+1];
+ ULONG DomainNameLength = MAX_PATH;
+ HRESULT Status;
+#endif // _CAIRO_
+
+
+ //
+ // Initialize the critsect that protects the DCName cache
+ //
+
+ InitializeCriticalSection( &GlobalDCNameCritSect );
+
+ //
+ // Clear the cache.
+ //
+
+ GlobalPrimaryDCName[0] = '\0';
+ GlobalPrimaryDomainName[0] = '\0';
+
+ //
+ // Initialize the RPC binding cache.
+ //
+
+ NlBindingAttachDll();
+
+#ifdef _CAIRO_
+ //
+ // Initialize local domain name cache
+ //
+
+ Status = DSGetDomainName(CairoDomainName,&DomainNameLength);
+ if (SUCCEEDED(Status)) {
+ CairoDomainToNtDomain(CairoDomainName,PrimaryDomainName);
+ } else {
+ //
+ // BUGBUG: is this the correct thing to do?
+ //
+
+ PrimaryDomainName[0] = L'\0';
+ }
+
+#endif // _CAIRO_
+ return NERR_Success;
+}
+
+
+VOID
+DCNameClose(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Perform per-process cleanup.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // Delete the critsect that protects the DCName cache
+ //
+
+ DeleteCriticalSection( &GlobalDCNameCritSect );
+
+ //
+ // Shutdown the RPC binding cache.
+ //
+
+ NlBindingDetachDll();
+
+}
+
+
+NET_API_STATUS
+DCNameValidate(
+ IN LPWSTR ServerName,
+ IN LPWSTR DomainName,
+ OUT LPBYTE *Buffer
+ )
+/*++
+
+Routine Description:
+
+ Ensure the named server is the PDC of the named domain.
+
+Arguments:
+
+ ServerName -- Suggest PDC server name (with leading \\'s).
+
+ DomainName -- Domain that ServerName is PDC of.
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of the PDC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ NERR_Success -- Server is PDC of specified domain.
+ Other sundry status codes.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ PWKSTA_INFO_100 WkstaInfo100 = NULL;
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+
+ //
+ // Ensure the specified server if it is a PDC.
+ //
+ // The call to ServerGetInfo below could be replaced with a call
+ // to UserModalsGet at level 1, which would provide a sort of
+ // referral information in case the machine is no longer a DC.
+ // In the case that the machine is no longer the primary,
+ // at this point we could potentially make use of the information
+ // that the machine we just queried sent us about who it thinks
+ // is its primary machine. Using this referral could save us the
+ // broadcast that follows, but who knows how long the referral
+ // chain could get. This could be modified later.
+ //
+
+ NetStatus = NetServerGetInfo( ServerName, 101, (LPBYTE *)&ServerInfo101 );
+ if ( NetStatus != NERR_Success ) {
+ goto Cleanup;
+ }
+
+ if ( (ServerInfo101->sv101_type & SV_TYPE_DOMAIN_CTRL) == 0 ) {
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+ }
+
+ //
+ // Ensure this PDC is still controlling the domain we think it should be.
+ //
+
+ NetStatus = NetWkstaGetInfo( ServerName, 100, (LPBYTE * )&WkstaInfo100);
+ if ( NetStatus != NERR_Success ) {
+ goto Cleanup;
+ }
+
+ if ( I_NetNameCompare( NULL,
+ DomainName,
+ WkstaInfo100->wki100_langroup,
+ NAMETYPE_DOMAIN,
+ 0L) != 0 ) {
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+ }
+
+
+ //
+ // Allocate a buffer to return to the caller and fill it in
+ //
+
+ NetStatus = NetapipBufferAllocate(
+ (wcslen(ServerName) + 1) * sizeof(WCHAR),
+ (LPVOID *) Buffer );
+
+ if ( NetStatus != NERR_Success ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(( "NetGetDCName: cannot allocate response buffer.\n"));
+ }
+ goto Cleanup;
+ }
+
+ wcscpy((LPWSTR)*Buffer, ServerName );
+
+ NetStatus = NERR_Success;
+
+Cleanup:
+ if ( ServerInfo101 != NULL ) {
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ if ( WkstaInfo100 != NULL ) {
+ NetApiBufferFree( WkstaInfo100 );
+ }
+
+ return NetStatus;
+
+}
+
+#ifdef _CAIRO_
+
+NET_API_STATUS NET_API_FUNCTION
+NetGetCairoDCName(
+ IN LPWSTR DomainName,
+ OUT LPBYTE *Buffer
+ )
+/*++
+
+Routine Description:
+
+ If the requested domain is the local domain, finds the domain controller
+
+Arguments:
+
+ DomainName - name of domain (null for primary domain)
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of a DC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Returns:
+
+ NERR_Success - Found a DC successfully
+
+ NERR_DCNotFound - the domain requested was the local domain
+ but no DCs could be found
+
+ ERROR_NO_LOGON_SERVERS - the domain was not the local domain
+ and no DCs could be found
+
+--*/
+{
+ HRESULT hrRet;
+ NET_API_STATUS NetStatus;
+ PDomainKdcInfo pDomainInfo;
+ ULONG Index;
+
+ if ((DomainName == NULL) ||
+ (!_wcsicmp(DomainName,PrimaryDomainName))) {
+
+ hrRet = FindDomainController(
+ NULL,
+ 0, // unused address type
+ &pDomainInfo
+ );
+ } else {
+ return(NERR_DCNotFound);
+ }
+
+ if (FAILED(hrRet))
+ {
+ return(hrRet);
+ }
+ for (Index = 0; Index < pDomainInfo->KdcInfo[0].AddressCount ; Index++ )
+ {
+ if (pDomainInfo->KdcInfo[0].KdcAddress[Index].AddressType == ADDRESS_TYPE_NETBIOS)
+ {
+ NetStatus = NetapipBufferAllocate(
+ pDomainInfo->KdcInfo[0].KdcAddress[Index].cbAddressString
+ + 2 * sizeof(WCHAR),
+ (PVOID *) Buffer);
+ if (NetStatus != NERR_Success)
+ {
+ hrRet = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ else
+ {
+ wcscpy((LPWSTR) *Buffer,L"\\\\");
+ wcscat((LPWSTR) *Buffer, (LPWSTR) pDomainInfo->KdcInfo[0].KdcAddress[Index].AddressString);
+ hrRet = NERR_Success;
+ }
+ }
+ }
+ FreeContextBuffer(pDomainInfo);
+ return(hrRet);
+
+}
+
+
+#endif // _CAIRO_
+
+NET_API_STATUS NET_API_FUNCTION
+NetGetDCName (
+ IN LPCWSTR ServerName OPTIONAL,
+ IN LPCWSTR DomainName OPTIONAL,
+ OUT LPBYTE *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of the primary domain controller for a domain.
+
+Arguments:
+
+ ServerName - name of remote server (null for local)
+
+ DomainName - name of domain (null for primary)
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of the PDC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ NERR_Success - Success. Buffer contains PDC name prefixed by \\.
+ NERR_DCNotFound No DC found for this domain.
+ ERROR_INVALID_NAME Badly formed domain name
+
+--*/
+{
+ NET_API_STATUS NetStatus = 0;
+
+ //
+ // Points to the actual domain to query.
+ //
+
+ LPWSTR DomainToQuery;
+ DWORD WhichDomain = DOMAIN_OTHER;
+ DWORD Version;
+
+
+ LPWSTR UnicodeComputerName = NULL;
+ LPWSTR PrimaryDomainName = NULL;
+ BOOLEAN IsWorkgroupName;
+
+
+ //
+ // API SECURITY - Anyone can call anytime. No code required.
+ //
+
+ //
+ // Check if API is to be remoted, and handle downlevel case if so.
+ //
+
+ if ( (ServerName != NULL) && ( ServerName[0] != '\0') ) {
+ TCHAR UncCanonServerName[UNCLEN+1];
+ DWORD LocalOrRemote;
+
+ NetStatus = NetpIsRemote(
+ (LPWSTR) ServerName, // uncanon server name
+ & LocalOrRemote,
+ UncCanonServerName, // output: canon
+ NIRFLAG_MAPLOCAL // flags: map null to local name
+ );
+ if (NetStatus != NERR_Success) {
+ goto Cleanup;
+ }
+ if (LocalOrRemote == ISREMOTE) {
+
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ NET_REMOTE_TRY_RPC
+
+ //
+ // Call RPC version of the API.
+ //
+ *Buffer = NULL;
+
+ NetStatus = NetrGetDCName(
+ (LPWSTR) ServerName,
+ (LPWSTR) DomainName,
+ (LPWSTR *)Buffer );
+
+ NET_REMOTE_RPC_FAILED(
+ "NetGetDCName",
+ UncCanonServerName,
+ NetStatus,
+ NET_REMOTE_FLAG_NORMAL,
+ SERVICE_NETLOGON )
+
+ //
+ // BUGBUG: Check if it's really a downlevel machine!
+ //
+
+ NetStatus = RxNetGetDCName(
+ UncCanonServerName,
+ (LPWSTR) DomainName,
+ (LPBYTE *) Buffer // may be allocated
+ );
+
+
+ NET_REMOTE_END
+
+ goto Cleanup;
+
+ }
+
+ //
+ // Must be explicit reference to local machine. Fall through and
+ // handle it.
+ //
+
+ }
+
+ //
+ // Validate the DomainName
+ //
+
+ if (( DomainName != NULL ) && ( DomainName[0] != '\0' )) {
+ if ( !NetpIsDomainNameValid((LPWSTR)DomainName) ) {
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Determine the PrimaryDomainName
+ //
+
+ NetStatus = NetpGetDomainNameEx( &PrimaryDomainName, &IsWorkgroupName );
+
+ if ( NetStatus != NERR_Success ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(( "NetGetDCName: cannot call NetpGetDomainName: %ld\n",
+ NetStatus));
+ }
+ goto Cleanup;
+ }
+
+
+ //
+ // If the given domain is NULL, the NULL string or matches
+ // the our domain, check for cache validity and make the
+ // query domain our domain.
+ //
+
+ if ( (DomainName == NULL) || (DomainName[0] == '\0') ||
+ (I_NetNameCompare( NULL,
+ (LPWSTR) DomainName,
+ PrimaryDomainName,
+ NAMETYPE_DOMAIN,
+ 0L) == 0) ) {
+
+
+ //
+ // if the current primary domain is not the same as the one we
+ // have cached, refresh the one in the cache and void the cached
+ // primary DC name.
+ //
+
+ LOCKDOMAINSEM();
+
+ if (I_NetNameCompare( NULL,
+ PrimaryDomainName,
+ GlobalPrimaryDomainName,
+ NAMETYPE_DOMAIN,
+ 0L) != 0 ) {
+
+ wcsncpy( GlobalPrimaryDomainName,
+ PrimaryDomainName,
+ DNLEN);
+ GlobalPrimaryDomainName[DNLEN] = '\0';
+ GlobalPrimaryDCName[0] = '\0';
+ }
+ FREEDOMAINSEM();
+
+ //
+ // Remember which domain to query.
+ //
+
+ DomainToQuery = PrimaryDomainName;
+ WhichDomain = DOMAIN_PRIMARY;
+
+ //
+ // This is a request on some non-NULL other domain.
+ //
+
+ } else {
+
+ DomainToQuery = (LPWSTR) DomainName;
+ }
+
+
+ //
+ // If the query domain is the primary domain AND
+ // the primary DC name is cached
+ // request the named DC to confirm that it is still the DC.
+ //
+
+ if ( WhichDomain == DOMAIN_PRIMARY ) {
+ LOCKDOMAINSEM();
+ if (GlobalPrimaryDCName[0] != '\0') {
+ WCHAR CachedPrimaryDCName[UNCLEN+1];
+ wcscpy(CachedPrimaryDCName, GlobalPrimaryDCName);
+ // Don't keep the cache locked while we validate
+ FREEDOMAINSEM();
+
+ NetStatus = DCNameValidate( CachedPrimaryDCName,
+ DomainToQuery,
+ Buffer );
+
+ if ( NetStatus == NERR_Success ) {
+ goto Cleanup;
+ }
+
+ LOCKDOMAINSEM();
+ GlobalPrimaryDCName[0] = '\0';
+ }
+ FREEDOMAINSEM();
+ }
+
+
+
+ //
+ // If we get here, it means that we need to broadcast to find the name
+ // of the DC for the given domain, if there is one. DomainToQuery
+ // points to the name of the domain to ask about. First we open a mailslot
+ // to get the response. We send the request and listen for the response.
+ //
+
+
+
+ //
+ // Pick out the computername from the Workstation information
+ //
+
+ NetStatus = NetpGetComputerName( &UnicodeComputerName );
+
+ if ( NetStatus != NERR_Success ) {
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint((
+ "NetGetDCName: cannot call NetpGetComputerName: %ld\n",
+ NetStatus));
+ }
+ goto Cleanup;
+ }
+
+
+ //
+ // Broadcast to the domain to get the primary DC name.
+ //
+ // If we're querying our primary domain,
+ // we know this isn't a lanman domain. So we can optimize.
+ // If this machine is a member of a workgroup,
+ // don't optimize since there might be a LANMAN PDC in the domain.
+ //
+
+ NetStatus = NetpLogonGetDCName(
+ UnicodeComputerName,
+ DomainToQuery,
+ (WhichDomain == DOMAIN_PRIMARY && !IsWorkgroupName) ?
+ NETLOGON_PRIMARY_DOMAIN : 0,
+ (LPWSTR *)Buffer,
+ &Version );
+
+
+ if ( NetStatus == NERR_Success ) {
+ goto CacheIt;
+ }
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("NetGetDCName: Error from NetpLogonGetDCName: %ld\n",
+ NetStatus));
+ }
+
+ switch (NetStatus) {
+ case ERROR_ACCESS_DENIED:
+ case ERROR_BAD_NETPATH:
+ case NERR_NetNotStarted:
+ case NERR_WkstaNotStarted:
+ case NERR_ServerNotStarted:
+ case NERR_BrowserNotStarted:
+ case NERR_ServiceNotInstalled:
+ case NERR_BadTransactConfig:
+ goto Cleanup;
+ }
+
+ //
+ // If none of the methods have succeeded,
+ // Just return DcNotFound
+ //
+
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+
+
+ //
+ // Cache the response.
+ //
+CacheIt:
+
+ if ( WhichDomain == DOMAIN_PRIMARY ) {
+ LOCKDOMAINSEM();
+ wcsncpy(GlobalPrimaryDCName, (LPWSTR)*Buffer, UNCLEN);
+ GlobalPrimaryDCName[UNCLEN] = '\0';
+ FREEDOMAINSEM();
+ }
+
+
+ NetStatus = NERR_Success;
+
+
+Cleanup:
+
+ //
+ // Cleanup all locally used resources
+ //
+
+ if ( PrimaryDomainName != NULL ) {
+ NetApiBufferFree( PrimaryDomainName );
+ }
+
+ if ( UnicodeComputerName != NULL ) {
+ NetApiBufferFree( UnicodeComputerName );
+ }
+
+ return NetStatus;
+}
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetGetAnyDCName (
+ IN LPCWSTR ServerName OPTIONAL,
+ IN LPCWSTR DomainName OPTIONAL,
+ OUT LPBYTE *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of the any domain controller for a domain that is directly trusted
+ by ServerName.
+
+
+ If ServerName is a standalone Windows NT Workstation or standalone Windows NT Server,
+ no DomainName is valid.
+
+ If ServerName is a Windows NT Workstation that is a member of a domain or a
+ Windows NT Server member server,
+ the DomainName must the the domain ServerName is a member of.
+
+ If ServerName is a Windows NT Server domain controller,
+ the DomainName must be one of the domains trusted by the
+ domain the server is a controller for.
+
+ The domain controller found is guaranteed to have been up at one point during
+ this API call.
+
+Arguments:
+
+ ServerName - name of remote server (null for local)
+
+ DomainName - name of domain (null for primary domain)
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of a DC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ ERROR_SUCCESS - Success. Buffer contains DC name prefixed by \\.
+
+ ERROR_NO_LOGON_SERVERS - No DC could be found
+
+ ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.
+
+ ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
+ broken.
+
+ ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
+ broken or the password is broken.
+
+ ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
+ domain controller of the specified domain.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+#ifdef _CAIRO_
+ //
+ // Try a Cairo domain first
+ //
+
+ NetStatus = NetGetCairoDCName( (LPWSTR) DomainName,Buffer);
+
+ if (NetStatus != NERR_DCNotFound)
+ {
+ return(NetStatus);
+ }
+#endif // _CAIRO_
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ *Buffer = NULL; // Force RPC to allocate
+
+ //
+ // Call RPC version of the API.
+ //
+
+ NetStatus = NetrGetAnyDCName(
+ (LPWSTR) ServerName,
+ (LPWSTR) DomainName,
+ (LPWSTR *) Buffer );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("NetGetAnyDCName rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
diff --git a/private/net/svcdlls/logonsrv/client/logonapi.c b/private/net/svcdlls/logonsrv/client/logonapi.c
new file mode 100644
index 000000000..0cb5e1143
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/logonapi.c
@@ -0,0 +1,502 @@
+/*++
+
+Copyright (c) 1991-92 Microsoft Corporation
+
+Module Name:
+
+ logonapi.c
+
+Abstract:
+
+ This module contains the Netlogon API RPC client stubs.
+
+
+Author:
+
+ Cliff Van Dyke (CliffV) 27-Jun-1991
+
+[Environment:]
+
+ User Mode - Win32
+
+Revision History:
+
+ 27-Jun-1991 CliffV
+ Created
+
+--*/
+
+//
+// INCLUDES
+//
+
+#include <nt.h>
+#include <ntrtl.h>
+
+#include <rpc.h>
+#include <logon_c.h>// includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+
+#include <crypt.h> // Encryption routines.
+#include <debuglib.h> // IF_DEBUG()
+#include <lmerr.h> // NERR_ and ERROR_ equates.
+#include <netdebug.h> // NetpKdPrint
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetLogonUasLogon (
+ IN LPWSTR UserName,
+ IN LPWSTR Workstation,
+ OUT PNETLOGON_VALIDATION_UAS_INFO *ValidationInformation
+)
+/*++
+
+Routine Description:
+
+ This function is called by the XACT server when processing a
+ I_NetWkstaUserLogon XACT SMB. This feature allows a UAS client to
+ logon to a SAM domain controller.
+
+Arguments:
+
+ UserName -- Account name of the user logging on.
+
+ Workstation -- The workstation from which the user is logging on.
+
+ ValidationInformation -- Returns the requested validation
+ information.
+
+
+Return Value:
+
+ NERR_SUCCESS if there was no error. Otherwise, the error code is
+ returned.
+
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ LPWSTR ServerName = NULL; // Not supported remotely
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ *ValidationInformation = NULL; // Force RPC to allocate
+ //
+ // Call RPC version of the API.
+ //
+
+ NetStatus = NetrLogonUasLogon(
+ (LPWSTR) ServerName,
+ UserName,
+ Workstation,
+ ValidationInformation );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("NetrLogonUasLogon rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+NET_API_STATUS
+I_NetLogonUasLogoff (
+ IN LPWSTR UserName,
+ IN LPWSTR Workstation,
+ OUT PNETLOGON_LOGOFF_UAS_INFO LogoffInformation
+)
+/*++
+
+Routine Description:
+
+ This function is called by the XACT server when processing a
+ I_NetWkstaUserLogoff XACT SMB. This feature allows a UAS client to
+ logoff from a SAM domain controller. The request is authenticated,
+ the entry is removed for this user from the logon session table
+ maintained by the Netlogon service for NetLogonEnum, and logoff
+ information is returned to the caller.
+
+ The server portion of I_NetLogonUasLogoff (in the Netlogon service)
+ compares the user name and workstation name specified in the
+ LogonInformation with the user name and workstation name from the
+ impersonation token. If they don't match, I_NetLogonUasLogoff fails
+ indicating the access is denied.
+
+ Group SECURITY_LOCAL is refused access to this function. Membership
+ in SECURITY_LOCAL implies that this call was made locally and not
+ through the XACT server.
+
+ The Netlogon service cannot be sure that this function was called by
+ the XACT server. Therefore, the Netlogon service will not simply
+ delete the entry from the logon session table. Rather, the logon
+ session table entry will be marked invisible outside of the Netlogon
+ service (i.e., it will not be returned by NetLogonEnum) until a valid
+ LOGON_WKSTINFO_RESPONSE is received for the entry. The Netlogon
+ service will immediately interrogate the client (as described above
+ for LOGON_WKSTINFO_RESPONSE) and temporarily increase the
+ interrogation frequency to at least once a minute. The logon session
+ table entry will reappear as soon as a function of interrogation if
+ this isn't a true logoff request.
+
+Arguments:
+
+ UserName -- Account name of the user logging off.
+
+ Workstation -- The workstation from which the user is logging
+ off.
+
+ LogoffInformation -- Returns the requested logoff information.
+
+Return Value:
+
+ The Net status code.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ LPWSTR ServerName = NULL; // Not supported remotely
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ NetStatus = NetrLogonUasLogoff(
+ (LPWSTR) ServerName,
+ UserName,
+ Workstation,
+ LogoffInformation );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("NetrLogonUasLogoff rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+NTSTATUS
+I_NetLogonSamLogon (
+ IN LPWSTR LogonServer OPTIONAL,
+ IN LPWSTR ComputerName OPTIONAL,
+ IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ OUT LPBYTE * ValidationInformation,
+ OUT PBOOLEAN Authoritative
+ )
+
+/*++
+
+Routine Description:
+
+ This function is called by an NT client to process an interactive or
+ network logon. This function passes a domain name, user name and
+ credentials to the Netlogon service and returns information needed to
+ build a token. It is called in three instances:
+
+ * It is called by the LSA's MSV1_0 authentication package for any
+ NT system that has LanMan installed. The MSV1_0 authentication
+ package calls SAM directly if LanMan is not installed. In this
+ case, this function is a local function and requires the caller
+ to have SE_TCB privilege. The local Netlogon service will
+ either handle this request directly (validating the request with
+ the local SAM database) or will forward this request to the
+ appropriate domain controller as documented in sections 2.4 and
+ 2.5.
+
+ * It is called by a Netlogon service on a workstation to a DC in
+ the Primary Domain of the workstation as documented in section
+ 2.4. In this case, this function uses a secure channel set up
+ between the two Netlogon services.
+
+ * It is called by a Netlogon service on a DC to a DC in a trusted
+ domain as documented in section 2.5. In this case, this
+ function uses a secure channel set up between the two Netlogon
+ services.
+
+ The Netlogon service validates the specified credentials. If they
+ are valid, adds an entry for this LogonId, UserName, and Workstation
+ into the logon session table. The entry is added to the logon
+ session table only in the domain defining the specified user's
+ account.
+
+ This service is also used to process a re-logon request.
+
+
+Arguments:
+
+ LogonServer -- Supplies the name of the logon server to process
+ this logon request. This field should be null to indicate
+ this is a call from the MSV1_0 authentication package to the
+ local Netlogon service.
+
+ ComputerName -- Name of the machine making the call. This field
+ should be null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ Authenticator -- supplied by the client. This field should be
+ null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the
+ server. This field should be null to indicate this is a call
+ from the MSV1_0 authentication package to the local Netlogon
+ service.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ ValidationLevel -- Specifies the level of information returned in
+ ValidationInformation. Must be NetlogonValidationSamInformation.
+
+ ValidationInformation -- Returns the requested validation
+ information.
+
+ Authoritative -- Returns whether the status returned is an
+ authoritative status which should be returned to the original
+ caller. If not, this logon request may be tried again on another
+ domain controller. This parameter is returned regardless of the
+ status code.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+
+ STATUS_NO_LOGON_SERVERS -- Either Pass-thru authentication or
+ Trusted Domain Authentication could not contact the requested
+ Domain Controller.
+
+ STATUS_INVALID_INFO_CLASS -- Either LogonLevel or ValidationLevel is
+ invalid.
+
+ STATUS_INVALID_PARAMETER -- Another Parameter is invalid.
+
+ STATUS_ACCESS_DENIED -- The caller does not have access to call this
+ API.
+
+ STATUS_NO_SUCH_USER -- Indicates that the user specified in
+ LogonInformation does not exist. This status should not be returned
+ to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
+
+ STATUS_WRONG_PASSWORD -- Indicates that the password information in
+ LogonInformation was incorrect. This status should not be returned
+ to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
+
+ STATUS_INVALID_LOGON_HOURES -- The user is not authorized to logon
+ at this time.
+
+ STATUS_INVALID_WORKSTATION -- The user is not authorized to logon
+ from the specified workstation.
+
+ STATUS_PASSWORD_EXPIRED -- The password for the user has expired.
+
+ STATUS_ACCOUNT_DISABLED -- The user's account has been disabled.
+
+ .
+ .
+ .
+
+
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ NETLOGON_LEVEL RpcLogonInformation;
+ NETLOGON_VALIDATION RpcValidationInformation;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ RpcLogonInformation.LogonInteractive =
+ (PNETLOGON_INTERACTIVE_INFO) LogonInformation;
+
+ RpcValidationInformation.ValidationSam = NULL;
+
+ Status = NetrLogonSamLogon(
+ LogonServer,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ LogonLevel,
+ &RpcLogonInformation,
+ ValidationLevel,
+ &RpcValidationInformation,
+ Authoritative );
+
+ *ValidationInformation = (LPBYTE)
+ RpcValidationInformation.ValidationSam;
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetLogonSamLogon rc = %lu 0x%lx\n", Status, Status));
+ }
+
+ return Status;
+}
+
+
+
+
+NTSTATUS NET_API_FUNCTION
+I_NetLogonSamLogoff (
+ IN LPWSTR LogonServer OPTIONAL,
+ IN LPWSTR ComputerName OPTIONAL,
+ IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation
+)
+/*++
+
+Routine Description:
+
+ This function is called by an NT client to process an interactive
+ logoff. It is not called for the network logoff case since the
+ Netlogon service does not maintain any context for network logons.
+
+ This function does the following. It authenticates the request. It
+ updates the logon statistics in the SAM database on whichever machine
+ or domain defines this user account. It updates the logon session
+ table in the primary domain of the machine making the request. And
+ it returns logoff information to the caller.
+
+ This function is called in same scenarios that I_NetLogonSamLogon is
+ called:
+
+ * It is called by the LSA's MSV1_0 authentication package to
+ support LsaApLogonTerminated. In this case, this function is a
+ local function and requires the caller to have SE_TCB privilege.
+ The local Netlogon service will either handle this request
+ directly (if LogonDomainName indicates this request was
+ validated locally) or will forward this request to the
+ appropriate domain controller as documented in sections 2.4 and
+ 2.5.
+
+ * It is called by a Netlogon service on a workstation to a DC in
+ the Primary Domain of the workstation as documented in section
+ 2.4. In this case, this function uses a secure channel set up
+ between the two Netlogon services.
+
+ * It is called by a Netlogon service on a DC to a DC in a trusted
+ domain as documented in section 2.5. In this case, this
+ function uses a secure channel set up between the two Netlogon
+ services.
+
+ When this function is a remote function, it is sent to the DC over a
+ NULL session.
+
+Arguments:
+
+ LogonServer -- Supplies the name of the logon server which logged
+ this user on. This field should be null to indicate this is
+ a call from the MSV1_0 authentication package to the local
+ Netlogon service.
+
+ ComputerName -- Name of the machine making the call. This field
+ should be null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ Authenticator -- supplied by the client. This field should be
+ null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the
+ server. This field should be null to indicate this is a call
+ from the MSV1_0 authentication package to the local Netlogon
+ service.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the logon domain name, logon Id,
+ user name and workstation name of the user logging off.
+
+Return Value:
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ NETLOGON_LEVEL RpcLogonInformation;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ RpcLogonInformation.LogonInteractive =
+ (PNETLOGON_INTERACTIVE_INFO) LogonInformation;
+
+ Status = NetrLogonSamLogoff(
+ LogonServer,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ LogonLevel,
+ &RpcLogonInformation );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetLogonSamLogoff rc = %lu 0x%lx\n", Status, Status));
+ }
+ return Status;
+}
diff --git a/private/net/svcdlls/logonsrv/client/rpcbind.c b/private/net/svcdlls/logonsrv/client/rpcbind.c
new file mode 100644
index 000000000..723f40852
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/rpcbind.c
@@ -0,0 +1,625 @@
+/*++
+
+Copyright (c) 1990-1992 Microsoft Corporation
+
+Module Name:
+
+ rpcbind.c
+
+Abstract:
+
+ Contains the RPC bind and un-bind routines for the Timesource
+ Service.
+
+Author:
+
+ Rajen Shah (rajens) 02-Apr-1991
+
+Environment:
+
+ User Mode -Win32
+
+Revision History:
+
+ 02-Apr-1991 RajenS
+ created
+ 22-May-1992 JohnRo
+ RAID 9829: winsvc.h and related file cleanup.
+
+--*/
+
+//
+// INCLUDES
+//
+#define NOSERVICE // Avoid <winsvc.h> vs. <lmsvc.h> conflicts.
+#include <nt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <rpc.h>
+#include <logon_c.h> // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+#include <lmerr.h> // NERR_ and ERROR_ equates.
+#include <lmsvc.h>
+#include <ntrpcp.h>
+#include <tstring.h> // IS_PATH_SEPARATOR ...
+#include <nlbind.h> // Prototypes for these routines
+#include <icanon.h> // NAMETYPE_*
+
+//
+// DataTypes
+//
+
+typedef struct _CACHE_ENTRY {
+ LIST_ENTRY Next;
+ OEM_STRING OemServerNameString;
+ RPC_BINDING_HANDLE RpcBindingHandle;
+ ULONG ReferenceCount;
+} CACHE_ENTRY, *PCACHE_ENTRY;
+
+//
+// STATIC GLOBALS
+//
+
+//
+// Maintain a cache of RPC binding handles.
+//
+
+CRITICAL_SECTION NlGlobalBindingCacheCritSect;
+LIST_ENTRY NlGlobalBindingCache;
+
+
+
+VOID
+NlBindingAttachDll (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize the RPC binding handle cache on process attach.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // Initialize the Global Cache Critical Section
+ //
+
+ InitializeCriticalSection( &NlGlobalBindingCacheCritSect );
+ InitializeListHead( &NlGlobalBindingCache );
+
+}
+
+
+VOID
+NlBindingDetachDll (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Cleanup the RPC binding handle cache on process detach.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // The binding cache must be empty,
+ // Netlogon cleans up after itself,
+ // no one else uses the cache.
+ //
+
+ ASSERT( IsListEmpty( &NlGlobalBindingCache ) );
+ DeleteCriticalSection( &NlGlobalBindingCacheCritSect );
+}
+
+
+PCACHE_ENTRY
+NlBindingFindCacheEntry (
+ IN LPWSTR UncServerName
+ )
+
+/*++
+
+Routine Description:
+
+ Find the specfied cache entry.
+
+ Entered with the NlGlobalBindingCacheCritSect locked.
+
+Arguments:
+
+ UncServerName - Name of the server to lookup
+
+Return Value:
+
+ NULL - Cache entry not found.
+
+--*/
+{
+ NTSTATUS Status;
+ PLIST_ENTRY ListEntry;
+ PCACHE_ENTRY CacheEntry;
+
+ UNICODE_STRING UnicodeServerNameString;
+ OEM_STRING OemServerNameString;
+ CHAR OemServerName[CNLEN+1];
+
+ // ?? Optimize by converting names to uppercase oem outside of loop
+
+ //
+ // Ensure the passed in parameter is really a UNC name
+ //
+
+ if ( UncServerName == NULL ||
+ !IS_PATH_SEPARATOR( UncServerName[0] ) ||
+ !IS_PATH_SEPARATOR( UncServerName[1] ) ) {
+ return NULL;
+ }
+
+ //
+ // Convert the server name to OEM once for faster comparison
+ //
+
+ RtlInitUnicodeString( &UnicodeServerNameString, UncServerName+2 );
+ OemServerNameString.Buffer = OemServerName;
+ OemServerNameString.MaximumLength = sizeof(OemServerName);
+ OemServerNameString.Length = 0;
+
+ Status = RtlUpcaseUnicodeStringToOemString(
+ &OemServerNameString,
+ &UnicodeServerNameString,
+ FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return NULL;
+ }
+
+ //
+ // Loop through the cache finding the entry.
+ //
+
+ for ( ListEntry = NlGlobalBindingCache.Flink;
+ ListEntry != &NlGlobalBindingCache;
+ ListEntry = ListEntry->Flink ) {
+
+ CacheEntry = CONTAINING_RECORD( ListEntry, CACHE_ENTRY, Next );
+
+ if ( RtlEqualString( &OemServerNameString,
+ &CacheEntry->OemServerNameString,
+ FALSE ) ) {
+ return CacheEntry;
+ }
+
+ }
+
+ return NULL;
+}
+
+
+NTSTATUS
+NlBindingAddServerToCache (
+ IN LPWSTR UncServerName
+ )
+
+/*++
+
+Routine Description:
+
+ Bind to the specified server and add it to the binding cache.
+
+Arguments:
+
+ UncServerName - UNC Name of the server to bind to.
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NTSTATUS Status;
+ RPC_STATUS RpcStatus;
+ PCACHE_ENTRY CacheEntry;
+
+ ASSERT ( UncServerName != NULL &&
+ IS_PATH_SEPARATOR( UncServerName[0] ) &&
+ IS_PATH_SEPARATOR( UncServerName[1] ) );
+
+ //
+ // If there already is an entry in the cache,
+ // just increment the reference count.
+ //
+
+ EnterCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ CacheEntry = NlBindingFindCacheEntry( UncServerName );
+
+ if ( CacheEntry != NULL ) {
+
+ CacheEntry->ReferenceCount++;
+ Status = STATUS_SUCCESS;
+
+ //
+ // Otherwise, allocate an entry and bind to the named server.
+ //
+
+ } else {
+
+ UNICODE_STRING UnicodeServerNameString;
+ OEM_STRING OemServerNameString;
+ CHAR OemServerName[CNLEN+1];
+
+ //
+ // Convert the server name to OEM for faster comparison
+ //
+
+ RtlInitUnicodeString( &UnicodeServerNameString, UncServerName+2 );
+ OemServerNameString.Buffer = OemServerName;
+ OemServerNameString.MaximumLength = sizeof(OemServerName);
+ OemServerNameString.Length = 0;
+
+ Status = RtlUpcaseUnicodeStringToOemString(
+ &OemServerNameString,
+ &UnicodeServerNameString,
+ FALSE );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ //
+ // Allocate the cache entry
+ //
+
+ CacheEntry = LocalAlloc( 0,
+ sizeof(CACHE_ENTRY) +
+ OemServerNameString.Length );
+
+ if ( CacheEntry == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+
+ } else {
+
+
+ //
+ // Initialize the cache entry.
+ //
+ // The caller has a 'reference' to the entry.
+ //
+
+ CacheEntry->OemServerNameString.Buffer = (LPSTR)(CacheEntry+1);
+ CacheEntry->OemServerNameString.Length =
+ CacheEntry->OemServerNameString.MaximumLength =
+ OemServerNameString.Length;
+ RtlCopyMemory( CacheEntry->OemServerNameString.Buffer,
+ OemServerNameString.Buffer,
+ CacheEntry->OemServerNameString.Length );
+
+ CacheEntry->ReferenceCount = 1;
+
+ //
+ // Bind to the server
+ // (Don't hold the crit sect for this potentially very long time.)
+ //
+
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+ RpcStatus = RpcpBindRpc (
+ UncServerName,
+ SERVICE_NETLOGON,
+ L"Security=Impersonation Dynamic False",
+ &CacheEntry->RpcBindingHandle );
+ EnterCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ if ( RpcStatus == 0 ) {
+
+ //
+ // Link the cache entry into the list
+ //
+ // If this were a general purpose routine, I'd have to check
+ // if someone inserted this cache entry already while we had
+ // the crit sect unlocked. However, the only caller is the
+ // netlogon service that has exclusive access to this client.
+ //
+
+ InsertHeadList( &NlGlobalBindingCache, &CacheEntry->Next );
+ Status = STATUS_SUCCESS;
+
+ } else {
+
+ Status = I_RpcMapWin32Status( RpcStatus );
+
+ (VOID) LocalFree( CacheEntry );
+ }
+
+ }
+ }
+
+ }
+
+ //
+ // Return to the caller.
+ //
+
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ return Status;
+}
+
+
+NTSTATUS
+NlBindingDecrementAndUnlock (
+ IN PCACHE_ENTRY CacheEntry
+ )
+
+/*++
+
+Routine Description:
+
+ Decrement the reference count and unlock the NlGlobalBindingCacheCritSect.
+
+ If the reference count reaches 0, unbind the interface, unlink the cache
+ entry and delete it.
+
+ Entered with the NlGlobalBindingCacheCritSect locked.
+
+Arguments:
+
+ UncServerName - UNC Name of the server to bind to.
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NTSTATUS Status;
+ RPC_STATUS RpcStatus;
+
+ //
+ // Decrement the reference count
+ //
+ // If it didn't reach zero, just unlock the crit sect and return.
+ //
+
+ if ( (--CacheEntry->ReferenceCount) != 0 ) {
+
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+ return STATUS_SUCCESS;
+
+ }
+
+ //
+ // Remove the entry from the list and unlock the crit sect.
+ //
+ // Once the entry is removed from the list, we can safely unlock the
+ // crit sect. Then we can unbind (a potentially lengthy operation) with
+ // the crit sect unlocked.
+ //
+
+ RemoveEntryList( &CacheEntry->Next );
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ //
+ // Unbind and delete the cache entry.
+ //
+
+ RpcStatus = RpcpUnbindRpc( CacheEntry->RpcBindingHandle );
+
+ if ( RpcStatus != 0 ) {
+ Status = I_RpcMapWin32Status( RpcStatus );
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ (VOID) LocalFree( CacheEntry );
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlBindingRemoveServerFromCache (
+ IN LPWSTR UncServerName
+ )
+
+/*++
+
+Routine Description:
+
+ Unbind to the specified server and remove it from the binding cache.
+
+Arguments:
+
+ UncServerName - UNC Name of the server to unbind from.
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NTSTATUS Status;
+ PCACHE_ENTRY CacheEntry;
+
+ ASSERT ( UncServerName != NULL &&
+ IS_PATH_SEPARATOR( UncServerName[0] ) &&
+ IS_PATH_SEPARATOR( UncServerName[1] ) );
+
+ //
+ // If there is no cache entry,
+ // silently ignore the situation.
+ //
+
+ EnterCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ CacheEntry = NlBindingFindCacheEntry( UncServerName );
+
+ if ( CacheEntry == NULL ) {
+
+ ASSERT( FALSE );
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Decrement the reference count and unlock the crit sect.
+ //
+
+ Status = NlBindingDecrementAndUnlock( CacheEntry );
+
+ return Status;
+}
+
+
+
+handle_t
+LOGONSRV_HANDLE_bind (
+ LOGONSRV_HANDLE UncServerName)
+
+/*++
+
+Routine Description:
+ This routine calls a common bind routine that is shared by all services.
+
+Arguments:
+
+ UncServerName - 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.
+
+--*/
+{
+ handle_t RpcBindingHandle;
+ RPC_STATUS RpcStatus;
+ PCACHE_ENTRY CacheEntry;
+
+
+ //
+ // If there is a cache entry,
+ // increment the reference count and use the cached handle
+ //
+
+ EnterCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ CacheEntry = NlBindingFindCacheEntry( UncServerName );
+
+ if ( CacheEntry != NULL ) {
+
+ CacheEntry->ReferenceCount ++;
+ RpcBindingHandle = CacheEntry->RpcBindingHandle;
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ return RpcBindingHandle;
+ }
+
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+
+ //
+ // If there is no cache entry,
+ // simply create a new binding.
+ //
+
+ RpcStatus = RpcpBindRpc (
+ UncServerName,
+ SERVICE_NETLOGON,
+ L"Security=Impersonation Dynamic False",
+ &RpcBindingHandle );
+
+ if ( RpcStatus != 0 ) {
+ RpcBindingHandle = NULL;
+ }
+
+ return RpcBindingHandle;
+
+}
+
+
+
+void
+LOGONSRV_HANDLE_unbind (
+ LOGONSRV_HANDLE UncServerName,
+ handle_t RpcBindingHandle)
+
+/*++
+
+Routine Description:
+
+ This routine calls a common unbind routine that is shared by
+ all services.
+
+
+Arguments:
+
+ UncServerName - This is the name of the server from which to unbind.
+
+ RpcBindingHandle - This is the binding handle that is to be closed.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ RPC_STATUS RpcStatus;
+ PLIST_ENTRY ListEntry;
+ PCACHE_ENTRY CacheEntry;
+
+ //
+ // Loop through the cache finding the entry.
+ //
+
+ EnterCriticalSection( &NlGlobalBindingCacheCritSect );
+ for ( ListEntry = NlGlobalBindingCache.Flink;
+ ListEntry != &NlGlobalBindingCache;
+ ListEntry = ListEntry->Flink ) {
+
+ CacheEntry = CONTAINING_RECORD( ListEntry, CACHE_ENTRY, Next );
+
+ //
+ // If the cache entry was found,
+ // decrement the reference count and unlock the crit sect.
+ //
+
+ if ( RpcBindingHandle == CacheEntry->RpcBindingHandle ) {
+ (VOID) NlBindingDecrementAndUnlock( CacheEntry );
+ return;
+ }
+
+ }
+ LeaveCriticalSection( &NlGlobalBindingCacheCritSect );
+
+
+ //
+ // Just Unbind the handle
+ //
+
+ RpcStatus = RpcpUnbindRpc( RpcBindingHandle );
+ return;
+
+ UNREFERENCED_PARAMETER(UncServerName);
+
+}
+
diff --git a/private/net/svcdlls/logonsrv/client/ssiapi.c b/private/net/svcdlls/logonsrv/client/ssiapi.c
new file mode 100644
index 000000000..e20a5eddd
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/client/ssiapi.c
@@ -0,0 +1,1446 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ ssiapi.c
+
+Abstract:
+
+ Authentication and replication API routines (client side).
+
+Author:
+
+ Cliff Van Dyke (cliffv) 30-Jul-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <ntrtl.h> // LARGE_INTEGER definition
+#include <nturtl.h> // LARGE_INTEGER definition
+
+#include <rpc.h> // Needed by logon.h
+#include <logon_c.h> // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+
+#include <debuglib.h> // IF_DEBUG()
+#include <lmerr.h> // NERR_* defines
+#include <netdebug.h> // NetpKdPrint
+#include "..\server\ssiapi.h"
+
+
+
+NTSTATUS
+I_NetServerReqChallenge(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientChallenge,
+ OUT PNETLOGON_CREDENTIAL ServerChallenge
+ )
+/*++
+
+Routine Description:
+
+ This is the client side of I_NetServerReqChallenge.
+
+ I_NetLogonRequestChallenge is the first of two functions used by a client
+ to process an authentication with a domain controller (DC). (See
+ I_NetServerAuthenticate below.) It is called for
+ a BDC (or member server) authenticating with a PDC for replication
+ purposes.
+
+ This function passes a challenge to the PDC and the PDC passes a challenge
+ back to the caller.
+
+Arguments:
+
+ PrimaryName -- Supplies the name of the PrimaryDomainController we wish to
+ authenticate with.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ ClientChallenge -- 64 bit challenge supplied by the BDC or member server.
+
+ ServerChallenge -- Receives 64 bit challenge from the PDC.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ Status = NetrServerReqChallenge(
+ PrimaryName,
+ ComputerName,
+ ClientChallenge,
+ ServerChallenge );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetServerReqChallenge rc = %lu 0x%lx\n",
+ Status, Status));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetServerAuthenticate(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential
+ )
+/*++
+
+Routine Description:
+
+ This is the client side of I_NetServerAuthenticate
+
+ I_NetServerAuthenticate is the second of two functions used by a client
+ Netlogon service to authenticate with another Netlogon service.
+ (See I_NetServerReqChallenge above.) Both a SAM or UAS server authenticates
+ using this function.
+
+ This function passes a credential to the DC and the DC passes a credential
+ back to the caller.
+
+
+Arguments:
+
+ PrimaryName -- Supplies the name of the DC we wish to authenticate with.
+
+ AccountName -- Name of the Account to authenticate with.
+
+ SecureChannelType -- The type of the account being accessed. This field
+ must be set to UasServerSecureChannel to indicate a call from
+ downlevel (LanMan 2.x and below) BDC or member server.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ ClientCredential -- 64 bit credential supplied by the BDC or member server.
+
+ ServerCredential -- Receives 64 bit credential from the PDC.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ Status = NetrServerAuthenticate(
+ PrimaryName,
+ AccountName,
+ AccountType,
+ ComputerName,
+ ClientCredential,
+ ServerCredential );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetServerAuthenticate rc = %lu 0x%lx\n",
+ Status, Status));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetServerAuthenticate2(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential,
+ IN OUT PULONG NegotiatedFlags
+ )
+/*++
+
+Routine Description:
+
+ This is the client side of I_NetServerAuthenticate
+
+ I_NetServerAuthenticate is the second of two functions used by a client
+ Netlogon service to authenticate with another Netlogon service.
+ (See I_NetServerReqChallenge above.) Both a SAM or UAS server authenticates
+ using this function.
+
+ This function passes a credential to the DC and the DC passes a credential
+ back to the caller.
+
+
+Arguments:
+
+ PrimaryName -- Supplies the name of the DC we wish to authenticate with.
+
+ AccountName -- Name of the Account to authenticate with.
+
+ SecureChannelType -- The type of the account being accessed. This field
+ must be set to UasServerSecureChannel to indicate a call from
+ downlevel (LanMan 2.x and below) BDC or member server.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ ClientCredential -- 64 bit credential supplied by the BDC or member server.
+
+ ServerCredential -- Receives 64 bit credential from the PDC.
+
+ NegotiatedFlags -- Specifies flags indicating what features the BDC supports.
+ Returns a subset of those flags indicating what features the PDC supports.
+ The PDC/BDC should ignore any bits that it doesn't understand.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ Status = NetrServerAuthenticate2(
+ PrimaryName,
+ AccountName,
+ AccountType,
+ ComputerName,
+ ClientCredential,
+ ServerCredential,
+ NegotiatedFlags );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetServerAuthenticate2 rc = %lu 0x%lx\n",
+ Status, Status));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetServerPasswordSet(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword
+ )
+/*++
+
+Routine Description:
+
+ This function is used to change the password for the account being
+ used to maintain a secure channel. This function can only be called
+ by a server which has previously authenticated with a DC by calling
+ I_NetServerAuthenticate.
+
+ The call is made differently depending on the account type:
+
+ * A domain account password is changed from the PDC in the
+ trusting domain. The I_NetServerPasswordSet call is made to any
+ DC in the trusted domain.
+
+ * A server account password is changed from the specific server.
+ The I_NetServerPasswordSet call is made to the PDC in the domain
+ the server belongs to.
+
+ * A workstation account password is changed from the specific
+ workstation. The I_NetServerPasswordSet call is made to a DC in
+ the domain the server belongs to.
+
+ For domain accounts and workstation accounts, the server being called
+ may be a BDC in the specific domain. In that case, the BDC will
+ validate the request and pass it on to the PDC of the domain using
+ the server account secure channel. If the PDC of the domain is
+ currently not available, the BDC will return STATUS_NO_LOGON_SERVERS. Since
+ the UasNewPassword is passed encrypted by the session key, such a BDC
+ will decrypt the UasNewPassword using the original session key and
+ will re-encrypt it with the session key for its session to its PDC
+ before passing the request on.
+
+ This function uses RPC to contact the DC named by PrimaryName.
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to change the servers password
+ with. NULL indicates this call is a local call being made on
+ behalf of a UAS server by the XACT server.
+
+ AccountName -- Name of the account to change the password for.
+
+ AccountType -- The type of account being accessed. This field must
+ be set to UasServerAccount to indicate a call from a downlevel
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ UasNewPassword -- The new password for the server. This
+ Password is generated by automatic means using
+ random number genertaor seeded with the current Time
+ It is assumed that the machine generated password
+ was used as key to encrypt STD text and "sesskey"
+ obtained via Challenge/Authenticate sequence was
+ used to further encrypt it before passing to this api.
+ i.e. UasNewPassword = E2(E1(STD_TXT, PW), SK)
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ Status = NetrServerPasswordSet(
+ PrimaryName,
+ AccountName,
+ AccountType,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ UasNewPassword );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetServerPasswordSet rc = %lu 0x%lx\n",
+ Status, Status));
+ }
+
+ return Status;
+}
+
+
+
+NTSTATUS
+I_NetDatabaseDeltas (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PNLPR_MODIFIED_COUNT DomainModifiedCount,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a SAM BDC or SAM member server to request
+ SAM-style account delta information from a SAM PDC. This function
+ can only be called by a server which has previously authenticated
+ with the PDC by calling I_NetServerAuthenticate. This function uses
+ RPC to contact the Netlogon service on the PDC.
+
+ This function returns a list of deltas. A delta describes an
+ individual domain, user or group and all of the field values for that
+ object. The PDC maintains a list of deltas not including all of the
+ field values for that object. Rather, the PDC retrieves the field
+ values from SAM and returns those values from this call. The PDC
+ optimizes the data returned on this call by only returning the field
+ values for a particular object once on a single invocation of this
+ function. This optimizes the typical case where multiple deltas
+ exist for a single object (e.g., an application modified many fields
+ of the same user during a short period of time using different calls
+ to the SAM service).
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the deltas from.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ DomainModifiedCount -- Specifies the DomainModifiedCount of the
+ last delta retrieved by the server. Returns the
+ DomainModifiedCount of the last delta returned from the PDC
+ on this call.
+
+ DeltaArray -- Receives a pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and
+ should call I_NetDatabaseSync to do a full synchronization with
+ the PDC.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ *DeltaArray = NULL; // Force RPC to allocate
+
+ Status = NetrDatabaseDeltas(
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ DatabaseID,
+ DomainModifiedCount,
+ DeltaArray,
+ PreferredMaximumLength );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+ NetpKdPrint(("I_NetDatabaseDeltas rc = %lu 0x%lx\n", Status, Status));
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetDatabaseSync (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PULONG SamSyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a SAM BDC or SAM member server to request
+ the entire SAM database from a SAM PDC in SAM-style format. This
+ function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+ This function uses the find-first find-next model to return portions
+ of the SAM database at a time. The SAM database is returned as a
+ list of deltas like those returned from I_NetDatabaseDeltas. The
+ following deltas are returned for each domain:
+
+ * One AddOrChangeDomain delta, followed by
+
+ * One AddOrChangeGroup delta for each group, followed by,
+
+ * One AddOrChangeUser delta for each user, followed by
+
+ * One ChangeGroupMembership delta for each group
+
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the deltas from.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ SamSyncContext -- Specifies context needed to continue the
+ operation. The caller should treat this as an opaque
+ value. The value should be zero before the first call.
+
+ DeltaArray -- Receives a pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and
+ should call I_NetDatabaseSync to do a full synchronization with
+ the PDC.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ *DeltaArray = NULL; // Force RPC to allocate
+
+ Status = NetrDatabaseSync(
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ DatabaseID,
+ SamSyncContext,
+ DeltaArray,
+ PreferredMaximumLength );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetDatabaseSync rc = %lu 0x%lx\n", Status, Status));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetDatabaseSync2 (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN SYNC_STATE RestartState,
+ IN OUT PULONG SamSyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a SAM BDC or SAM member server to request
+ the entire SAM database from a SAM PDC in SAM-style format. This
+ function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+ This function uses the find-first find-next model to return portions
+ of the SAM database at a time. The SAM database is returned as a
+ list of deltas like those returned from I_NetDatabaseDeltas. The
+ following deltas are returned for each domain:
+
+ * One AddOrChangeDomain delta, followed by
+
+ * One AddOrChangeGroup delta for each group, followed by,
+
+ * One AddOrChangeUser delta for each user, followed by
+
+ * One ChangeGroupMembership delta for each group
+
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the deltas from.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ RestartState -- Specifies whether this is a restart of the full sync and how
+ to interpret SyncContext. This value should be NormalState unless this
+ is the restart of a full sync.
+
+ However, if the caller is continuing a full sync after a reboot,
+ the following values are used:
+
+ GroupState - SyncContext is the global group rid to continue with.
+ UserState - SyncContext is the user rid to continue with
+ GroupMemberState - SyncContext is the global group rid to continue with
+ AliasState - SyncContext should be zero to restart at first alias
+ AliasMemberState - SyncContext should be zero to restart at first alias
+
+ One cannot continue the LSA database in this way.
+
+ SamSyncContext -- Specifies context needed to continue the
+ operation. The caller should treat this as an opaque
+ value. The value should be zero before the first call.
+
+ DeltaArray -- Receives a pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and
+ should call I_NetDatabaseSync to do a full synchronization with
+ the PDC.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ *DeltaArray = NULL; // Force RPC to allocate
+
+ Status = NetrDatabaseSync2(
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ DatabaseID,
+ RestartState,
+ SamSyncContext,
+ DeltaArray,
+ PreferredMaximumLength );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetDatabaseSync rc = %lu 0x%lx\n", Status, Status));
+ }
+
+ return Status;
+}
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetAccountDeltas (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN PUAS_INFO_0 RecordId,
+ IN DWORD Count,
+ IN DWORD Level,
+ OUT LPBYTE Buffer,
+ IN DWORD BufferSize,
+ OUT PULONG CountReturned,
+ OUT PULONG TotalEntries,
+ OUT PUAS_INFO_0 NextRecordId
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a UAS BDC or UAS member server to request
+ UAS-style account change information. This function can only be
+ called by a server which has previously authenticated with the PDC by
+ calling I_NetServerAuthenticate.
+
+ This function is only called by the XACT server upon receipt of a
+ I_NetAccountDeltas XACT SMB from a UAS BDC or a UAS member server.
+ As such, many of the parameters are opaque since the XACT server
+ doesn't need to interpret any of that data. This function uses RPC
+ to contact the Netlogon service.
+
+ The LanMan 3.0 SSI Functional Specification describes the operation
+ of this function.
+
+Arguments:
+
+ PrimaryName -- Must be NULL to indicate this call is a local call
+ being made on behalf of a UAS server by the XACT server.
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ RecordId -- Supplies an opaque buffer indicating the last record
+ received from a previous call to this function.
+
+ Count -- Supplies the number of Delta records requested.
+
+ Level -- Reserved. Must be zero.
+
+ Buffer -- Returns opaque data representing the information to be
+ returned.
+
+ BufferSize -- Size of buffer in bytes.
+
+ CountReturned -- Returns the number of records returned in buffer.
+
+ TotalEntries -- Returns the total number of records available.
+
+ NextRecordId -- Returns an opaque buffer identifying the last
+ record received by this function.
+
+
+Return Value:
+
+ Status code
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ NetStatus = NetrAccountDeltas (
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ RecordId,
+ Count,
+ Level,
+ Buffer,
+ BufferSize,
+ CountReturned,
+ TotalEntries,
+ NextRecordId );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetAccountDeltas rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetAccountSync (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD Reference,
+ IN DWORD Level,
+ OUT LPBYTE Buffer,
+ IN DWORD BufferSize,
+ OUT PULONG CountReturned,
+ OUT PULONG TotalEntries,
+ OUT PULONG NextReference,
+ OUT PUAS_INFO_0 LastRecordId
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a UAS BDC or UAS member server to request
+ the entire user accounts database. This function can only be called
+ by a server which has previously authenticated with the PDC by
+ calling I_NetServerAuthenticate.
+
+ This function is only called by the XACT server upon receipt of a
+ I_NetAccountSync XACT SMB from a UAS BDC or a UAS member server. As
+ such, many of the parameters are opaque since the XACT server doesn't
+ need to interpret any of that data. This function uses RPC to
+ contact the Netlogon service.
+
+ The LanMan 3.0 SSI Functional Specification describes the operation
+ of this function.
+
+ "Reference" and "NextReference" are treated as below.
+
+ 1. "Reference" should hold either 0 or value of "NextReference"
+ from previous call to this API.
+ 2. Send the modals and ALL group records in the first call. The API
+ expects the bufffer to be large enough to hold this info (worst
+ case size would be
+ MAXGROUP * (sizeof(struct group_info_1) + MAXCOMMENTSZ)
+ + sizeof(struct user_modals_info_0)
+ which, for now, will be 256 * (26 + 49) + 16 = 19216 bytes
+
+Arguments:
+
+ PrimaryName -- Must be NULL to indicate this call is a local call
+ being made on behalf of a UAS server by the XACT server.
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ Reference -- Supplies find-first find-next handle returned by the
+ previous call to this function or 0 if it is the first call.
+
+ Level -- Reserved. Must be zero.
+
+ Buffer -- Returns opaque data representing the information to be
+ returned.
+
+ BufferLen -- Length of buffer in bytes.
+
+ CountReturned -- Returns the number of records returned in buffer.
+
+ TotalEntries -- Returns the total number of records available.
+
+ NextReference -- Returns a find-first find-next handle to be
+ provided on the next call.
+
+ LastRecordId -- Returns an opaque buffer identifying the last
+ record received by this function.
+
+
+Return Value:
+
+ Status code.
+
+--*/
+
+{
+ NET_API_STATUS NetStatus;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ NetStatus = NetrAccountSync (
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ Reference,
+ Level,
+ Buffer,
+ BufferSize,
+ CountReturned,
+ TotalEntries,
+ NextReference,
+ LastRecordId );
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetAccountSync rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetLogonControl(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN DWORD FunctionCode,
+ IN DWORD QueryLevel,
+ OUT LPBYTE *QueryInformation
+ )
+
+/*++
+
+Routine Description:
+
+ This function controls various aspects of the Netlogon service. It
+ can be used to request that a BDC ensure that its copy of the SAM
+ database is brought up to date. It can, also, be used to determine
+ if a BDC currently has a secure channel open to the PDC.
+
+ Only an Admin, Account Operator or Server Operator may call this
+ function.
+
+Arguments:
+
+ ServerName - The name of the remote server.
+
+ FunctionCode - Defines the operation to be performed. The valid
+ values are:
+
+ FunctionCode Values
+
+ NETLOGON_CONTROL_QUERY - No operation. Merely returns the
+ information requested.
+
+ NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
+ to be brought in sync with the copy on the PDC. This
+ operation does NOT imply a full synchronize. The
+ Netlogon service will merely replicate any outstanding
+ differences if possible.
+
+ NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
+ completely new copy of the SAM database from the PDC.
+ This operation will perform a full synchronize.
+
+ NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
+ to replicate now.
+
+ QueryLevel - Indicates what information should be returned from
+ the Netlogon Service. Must be 1.
+
+ QueryInformation - Returns a pointer to a buffer which contains the
+ requested information. The buffer must be freed using
+ NetApiBufferFree.
+
+
+Return Value:
+
+ NERR_Success: the operation was successful
+
+ ERROR_NOT_SUPPORTED: Function code is not valid on the specified
+ server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NETLOGON_CONTROL_QUERY_INFORMATION RpcQueryInformation;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+
+ RpcQueryInformation.NetlogonInfo1 = NULL; // Force RPC to allocate
+
+ NetStatus = NetrLogonControl (
+ (LPWSTR) ServerName OPTIONAL,
+ FunctionCode,
+ QueryLevel,
+ &RpcQueryInformation );
+
+ *QueryInformation = (LPBYTE) RpcQueryInformation.NetlogonInfo1;
+
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetLogonControl rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+NET_API_STATUS NET_API_FUNCTION
+I_NetLogonControl2(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN DWORD FunctionCode,
+ IN DWORD QueryLevel,
+ IN LPBYTE InputData,
+ OUT LPBYTE *QueryInformation
+ )
+
+/*++
+
+Routine Description:
+
+ This is similar to the I_NetLogonControl function but it accepts
+ more generic input data according to the function code specified.
+
+ This function controls various aspects of the Netlogon service. It
+ can be used to request that a BDC ensure that its copy of the SAM
+ database is brought up to date. It can, also, be used to determine
+ if a BDC currently has a secure channel open to the PDC.
+
+ Only an Admin, Account Operator or Server Operator may call this
+ function.
+
+Arguments:
+
+ ServerName - The name of the remote server.
+
+ FunctionCode - Defines the operation to be performed. The valid
+ values are:
+
+ FunctionCode Values
+
+ NETLOGON_CONTROL_QUERY - No operation. Merely returns the
+ information requested.
+
+ NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
+ to be brought in sync with the copy on the PDC. This
+ operation does NOT imply a full synchronize. The
+ Netlogon service will merely replicate any outstanding
+ differences if possible.
+
+ NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
+ completely new copy of the SAM database from the PDC.
+ This operation will perform a full synchronize.
+
+ NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
+ to replicate now.
+
+ NETLOGON_CONTROL_REDISCOVER: Forces a DC to rediscover the
+ specified trusted domain DC.
+
+ NETLOGON_CONTROL_TC_QUERY: Query the status of the specified
+ trusted domain secure channel.
+
+ QueryLevel - Indicates what information should be returned from
+ the Netlogon Service. Must be 1.
+
+ InputData - According to the function code specified this parameter
+ will carry input data. NETLOGON_CONTROL_REDISCOVER and
+ NETLOGON_CONTROL_TC_QUERY function code specify the trusted
+ domain name (LPWSTR type) here.
+
+ QueryInformation - Returns a pointer to a buffer which contains the
+ requested information. The buffer must be freed using
+ NetApiBufferFree.
+
+
+Return Value:
+
+ NERR_Success: the operation was successful
+
+ ERROR_NOT_SUPPORTED: Function code is not valid on the specified
+ server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NETLOGON_CONTROL_QUERY_INFORMATION RpcQueryInformation;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ // Use new Control2Ex if either QueryLevel or FunctionCode is new
+ //
+
+ RpcQueryInformation.NetlogonInfo1 = NULL; // Force RPC to allocate
+
+ switch ( FunctionCode ) {
+ case NETLOGON_CONTROL_QUERY:
+ case NETLOGON_CONTROL_REPLICATE:
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_TRANSPORT_NOTIFY:
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ case NETLOGON_CONTROL_BREAKPOINT:
+
+ if ( QueryLevel >= 1 && QueryLevel <= 3 ) {
+ NetStatus = NetrLogonControl2 (
+ (LPWSTR) ServerName OPTIONAL,
+ FunctionCode,
+ QueryLevel,
+ (PNETLOGON_CONTROL_DATA_INFORMATION)InputData,
+ &RpcQueryInformation );
+ } else if ( QueryLevel == 4 ) {
+ NetStatus = NetrLogonControl2Ex (
+ (LPWSTR) ServerName OPTIONAL,
+ FunctionCode,
+ QueryLevel,
+ (PNETLOGON_CONTROL_DATA_INFORMATION)InputData,
+ &RpcQueryInformation );
+ } else {
+ NetStatus = ERROR_INVALID_LEVEL;
+ }
+ break;
+ case NETLOGON_CONTROL_FIND_USER:
+ case NETLOGON_CONTROL_UNLOAD_NETLOGON_DLL:
+ if ( QueryLevel >= 1 && QueryLevel <= 4 ) {
+ NetStatus = NetrLogonControl2Ex (
+ (LPWSTR) ServerName OPTIONAL,
+ FunctionCode,
+ QueryLevel,
+ (PNETLOGON_CONTROL_DATA_INFORMATION)InputData,
+ &RpcQueryInformation );
+ } else {
+ NetStatus = ERROR_INVALID_LEVEL;
+ }
+ break;
+ default:
+ NetStatus = ERROR_INVALID_LEVEL;
+ }
+
+ *QueryInformation = (LPBYTE) RpcQueryInformation.NetlogonInfo1;
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NetStatus = RpcExceptionCode();
+
+ } RpcEndExcept;
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetLogonControl rc = %lu 0x%lx\n",
+ NetStatus, NetStatus));
+ }
+
+ return NetStatus;
+}
+
+
+NTSTATUS
+I_NetDatabaseRedo(
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN LPBYTE ChangeLogEntry,
+ IN DWORD ChangeLogEntrySize,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a SAM BDC to request infomation about a single
+ account. This function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the delta from.
+
+ ComputerName -- Name of the BDC making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ ChangeLogEntry -- A description of the account to be queried.
+
+ ChangeLogEntrySize -- Size (in bytes) of the ChangeLogEntry.
+
+ DeltaArray -- Receives a pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+--*/
+{
+ NTSTATUS Status = 0;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ *DeltaArray = NULL; // Force RPC to allocate
+
+ Status = NetrDatabaseRedo(
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ ChangeLogEntry,
+ ChangeLogEntrySize,
+ DeltaArray );
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("I_NetDatabaseSync rc = %lu 0x%lx\n", Status, Status));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NetEnumerateTrustedDomains (
+ IN LPWSTR ServerName OPTIONAL,
+ OUT LPWSTR *DomainNames
+ )
+
+/*++
+
+Routine Description:
+
+ This API returns the names of the domains trusted by the domain ServerName is a member of.
+ ServerName must be an NT workstation or NT non-DC server.
+
+ The returned list does not include the domain ServerName is directly a member of.
+
+ Netlogon implements this API by calling LsaEnumerateTrustedDomains on a DC in the
+ domain ServerName is a member of. However, Netlogon returns cached information if
+ it has been less than 5 minutes since the last call was made or if no DC is available.
+ Netlogon's cache of Trusted domain names is maintained in the registry across reboots.
+ As such, the list is available upon boot even if no DC is available.
+
+
+Arguments:
+
+ ServerName - name of remote server (null for local). ServerName must be an NT workstation
+ or NT non-DC server.
+
+ DomainNames - Returns an allocated buffer containing the list of trusted domains in
+ MULTI-SZ format (i.e., each string is terminated by a zero character, the next string
+ immediately follows, the sequence is terminated by zero length domain name). The
+ buffer should be freed using NetApiBufferFree.
+
+Return Value:
+
+
+ ERROR_SUCCESS - Success.
+
+ STATUS_NOT_SUPPORTED - ServerName is not an NT workstation or NT non-DC server.
+
+ STATUS_NO_LOGON_SERVERS - No DC could be found and no cached information is available.
+
+ STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
+ broken and no cached information is available.
+
+ STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
+ broken or the password is broken and no cached information is available.
+
+--*/
+{
+ NTSTATUS Status = 0;
+ DOMAIN_NAME_BUFFER DomainNameBuffer;
+
+ //
+ // Do the RPC call with an exception handler since RPC will raise an
+ // exception if anything fails. It is up to us to figure out what
+ // to do once the exception is raised.
+ //
+
+ RpcTryExcept {
+
+ //
+ // Call RPC version of the API.
+ //
+ DomainNameBuffer.DomainNameByteCount = 0;
+ DomainNameBuffer.DomainNames = NULL; // Force RPC to allocate
+
+ Status = NetrEnumerateTrustedDomains(
+ ServerName,
+ &DomainNameBuffer );
+
+ if ( NT_SUCCESS(Status) ) {
+ *DomainNames = (LPWSTR) DomainNameBuffer.DomainNames;
+ }
+
+ } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = I_RpcMapWin32Status(RpcExceptionCode());
+
+ } RpcEndExcept;
+
+
+ IF_DEBUG( LOGON ) {
+ NetpKdPrint(("NetEnumerateDomainNames rc = %lu 0x%lx\n", Status, Status));
+ }
+
+ return Status;
+}
diff --git a/private/net/svcdlls/logonsrv/dirs b/private/net/svcdlls/logonsrv/dirs
new file mode 100644
index 000000000..0bb93420f
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/dirs
@@ -0,0 +1,47 @@
+!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
+
+#
+# This macro is defined by the developer. It is a list of all subdirectories
+# that build required components. Each subdirectory should be on a separate
+# line using the line continuation character. This will minimize merge
+# conflicts if two developers adding source files to the same component.
+# The order of the directories is the order that they will be built when
+# doing a build.
+#
+
+DIRS=server \
+ client \
+ monitor
+
+
+#
+# This macro is defined by the developer. It is a list of all subdirectories
+# that build optional components. Each subdirectory should be on a separate
+# line using the line continuation character. This will minimize merge
+# conflicts if two developers adding source files to the same component.
+# The order of the directories is the order that they will be built when
+# doing a build.
+#
+
+#OPTIONAL_DIRS=dir8 \
+# dir9
diff --git a/private/net/svcdlls/logonsrv/imports.h b/private/net/svcdlls/logonsrv/imports.h
new file mode 100644
index 000000000..4b8d0f7c7
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/imports.h
@@ -0,0 +1,40 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ imports.h
+
+Abstract:
+
+ This file allows us to include standard system header files in the
+ .idl file. The main .idl file imports a file called import.idl.
+ This allows the .idl file to use the types defined in these header
+ files. It also causes the following line to be added in the
+ MIDL generated header file:
+
+ #include "imports.h"
+
+ Thus these types are available to the RPC stub routines as well.
+
+Author:
+
+ Dan Lafferty (danl) 07-May-1991
+
+Revision History:
+
+
+--*/
+
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <lsass.h> // OLD_LARGE_INTEGER definition
+#include <windef.h>
+#include <lmcons.h>
+#include <ntsam.h>
+#include <lmaccess.h>
+#include <netlogon.h>
+#include <crypt.h>
+#include <logonmsv.h>
+#include <ssi.h>
diff --git a/private/net/svcdlls/logonsrv/imports.idl b/private/net/svcdlls/logonsrv/imports.idl
new file mode 100644
index 000000000..c3f430aa2
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/imports.idl
@@ -0,0 +1,58 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ imports.idl
+
+Abstract:
+
+ This file is useful for creating RPC interfaces that require the use
+ of types defined in other header files. The .idl file for the RPC
+ product should contain a line in the interface body that imports this
+ file. For example:
+
+ import "imports.idl";
+
+ Doing this causes the MIDL generated header file to contain the
+ #include lines that are in this file.
+
+ If this technique is not used, and instead the .idl file for the RPC
+ product simply contains #include <windef.h>, then the contents of
+ windef.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 windef.h.
+
+Author:
+
+ Dan Lafferty (danl) 20-Mar-1991
+
+Environment:
+
+ User Mode - Win32 - for use with the MIDL compiler
+
+
+Revision History:
+
+ 03-Apr-1991 danl
+ created
+
+--*/
+
+
+[
+ uuid(12345678-1234-ABCD-EF00-9948756789AB),
+#ifdef __midl
+ ms_union,
+#endif // __midl
+ version(2.0)
+]
+interface logon_imports
+
+{
+#define MIDL_PASS
+#include "imports.h"
+
+}
diff --git a/private/net/svcdlls/logonsrv/logon.idl b/private/net/svcdlls/logonsrv/logon.idl
new file mode 100644
index 000000000..9408649cd
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/logon.idl
@@ -0,0 +1,857 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ LOGON.IDL
+
+Abstract:
+
+ Contains the Netr (Net Remote) RPC interface specification for the
+ API associated with the Netlogon Service.
+
+ Also contains the RPC specific data structures for these API.
+
+Author:
+
+ Cliff Van Dyke (CliffV) 25-Jun-1991
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+ 25-Jun-1991 CliffV
+ created
+
+ 04-Apr-1992 MadanA
+ Added support for LSA replication.
+
+--*/
+
+//
+// Interface Attributes
+//
+
+[
+ uuid(12345678-1234-ABCD-EF00-01234567CFFB),
+ version(1.0),
+#ifdef __midl
+ ms_union,
+#endif // __midl
+ pointer_default(unique)
+]
+
+//
+// Interface Keyword
+//
+
+interface logon
+
+//
+// Interface Body
+//
+
+{
+
+#define _RPC_
+
+import "imports.idl"; // import all the include files
+#include <lmcons.h> // Needed for prototype below
+
+//
+// FunctionCode values for I_NetLogonControl.
+//
+
+#define NETLOGON_CONTROL_QUERY 1 // No-op: just query
+#define NETLOGON_CONTROL_REPLICATE 2 // Force replicate on BDC
+#define NETLOGON_CONTROL_SYNCHRONIZE 3 // Force synchronize on BDC
+#define NETLOGON_CONTROL_PDC_REPLICATE 4 // Force PDC to broadcast change
+#define NETLOGON_CONTROL_REDISCOVER 5 // Force to re-discover trusted domain DCs
+#define NETLOGON_CONTROL_TC_QUERY 6 // Query status of specified trusted channel status
+#define NETLOGON_CONTROL_TRANSPORT_NOTIFY 7 // Notify netlogon that a new transport has come online
+#define NETLOGON_CONTROL_FIND_USER 8 // Find named user in a trusted domain
+
+// Debug function codes
+
+#define NETLOGON_CONTROL_BACKUP_CHANGE_LOG 0xFFFC
+#define NETLOGON_CONTROL_TRUNCATE_LOG 0xFFFD
+#define NETLOGON_CONTROL_SET_DBFLAG 0xFFFE
+#define NETLOGON_CONTROL_BREAKPOINT 0xFFFF
+
+typedef [handle] wchar_t * LOGONSRV_HANDLE;
+
+//
+// Data types for rpc stubs.
+//
+
+// ?? the following data types should come from LSA or SAM idl definitions
+
+//
+// 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 _NLPR_SID_INFORMATION {
+
+ PISID SidPointer;
+
+} NLPR_SID_INFORMATION, *PNLPR_SID_INFORMATION;
+
+
+//
+// Define an array of pointers to SIDs
+//
+
+typedef struct _NLPR_SID_ARRAY {
+
+ //
+ // Indicates the number of Elements in the array.
+ //
+
+ ULONG Count;
+
+ //
+ // Points to the array of sid-pointers
+ //
+
+ [size_is(Count)] PNLPR_SID_INFORMATION Sids;
+
+} NLPR_SID_ARRAY, *PNLPR_SID_ARRAY;
+
+
+//
+// Two-way encrypted value structure in Self-relative form. This
+// is just like a String.
+//
+
+typedef struct _NLPR_CR_CIPHER_VALUE {
+
+ ULONG Length;
+ ULONG MaximumLength;
+ [size_is(MaximumLength), length_is(Length)] PUCHAR Buffer;
+
+} NLPR_CR_CIPHER_VALUE, *PNLPR_CR_CIPHER_VALUE;
+
+
+typedef struct _NLPR_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;
+
+} NLPR_LOGON_HOURS, *PNLPR_LOGON_HOURS;
+
+
+typedef struct _NLPR_USER_PRIVATE_INFO {
+
+ BOOLEAN SensitiveData;
+
+ //
+ // If SesitiveData is TRUE then the data is encrypted using
+ // sessionkey across wire.
+ //
+
+ ULONG DataLength;
+ [size_is(DataLength)] PUCHAR Data;
+
+} NLPR_USER_PRIVATE_INFO, *PNLPR_USER_PRIVATE_INFO;
+
+#pragma pack(4)
+typedef struct _NLPR_MODIFIED_COUNT {
+
+ OLD_LARGE_INTEGER ModifiedCount;
+
+} NLPR_MODIFIED_COUNT, *PNLPR_MODIFIED_COUNT;
+#pragma pack()
+
+#pragma pack(4)
+typedef struct _NLPR_QUOTA_LIMITS {
+ ULONG PagedPoolLimit;
+ ULONG NonPagedPoolLimit;
+ ULONG MinimumWorkingSetSize;
+ ULONG MaximumWorkingSetSize;
+ ULONG PagefileLimit;
+ OLD_LARGE_INTEGER TimeLimit;
+} NLPR_QUOTA_LIMITS, *PNLPR_QUOTA_LIMITS;
+#pragma pack()
+
+//
+// Enumeration structure returned from I_NetSamDeltas and I_NetSamSync
+//
+
+//
+// Structure to completely describe a user.
+//
+
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_USER {
+ UNICODE_STRING UserName;
+ UNICODE_STRING FullName;
+ ULONG UserId;
+ ULONG PrimaryGroupId;
+ UNICODE_STRING HomeDirectory;
+ UNICODE_STRING HomeDirectoryDrive;
+ UNICODE_STRING ScriptPath;
+ UNICODE_STRING AdminComment;
+ UNICODE_STRING WorkStations;
+ OLD_LARGE_INTEGER LastLogon;
+ OLD_LARGE_INTEGER LastLogoff;
+ NLPR_LOGON_HOURS LogonHours;
+ USHORT BadPasswordCount;
+ USHORT LogonCount;
+ OLD_LARGE_INTEGER PasswordLastSet;
+ OLD_LARGE_INTEGER AccountExpires;
+ ULONG UserAccountControl;
+
+ //
+ // The following fields are duplicates of information already in
+ // the Private data. Starting in NT 1.0A, these fields are zeroed.
+ //
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ BOOLEAN NtPasswordPresent;
+ BOOLEAN LmPasswordPresent;
+ BOOLEAN PasswordExpired;
+
+ UNICODE_STRING UserComment;
+ UNICODE_STRING Parameters;
+ USHORT CountryCode;
+ USHORT CodePage;
+
+ NLPR_USER_PRIVATE_INFO PrivateData; // password history
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1; // used for profile path.
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1; // used for LastBadPasswordTime.HighPart
+ ULONG DummyLong2; // used for LastBadPasswordTime.LowPart
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+
+} NETLOGON_DELTA_USER, *PNETLOGON_DELTA_USER;
+#pragma pack()
+
+//
+// Structure to completely describe a group.
+//
+typedef struct _NETLOGON_DELTA_GROUP {
+ UNICODE_STRING Name;
+ ULONG RelativeId;
+ ULONG Attributes;
+ UNICODE_STRING AdminComment;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_GROUP, *PNETLOGON_DELTA_GROUP;
+
+
+//
+// Structure to completely describe all the members of a group.
+//
+typedef struct _NETLOGON_DELTA_GROUP_MEMBER {
+ [size_is(MemberCount)] PULONG MemberIds;
+ [size_is(MemberCount)] PULONG Attributes;
+ ULONG MemberCount;
+
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_GROUP_MEMBER, *PNETLOGON_DELTA_GROUP_MEMBER;
+
+//
+// Structure to completely describe a alias.
+//
+typedef struct _NETLOGON_DELTA_ALIAS {
+ UNICODE_STRING Name;
+ ULONG RelativeId;
+// UNICODE_STRING AdminComment;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1; // used for admin comment
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_ALIAS, *PNETLOGON_DELTA_ALIAS;
+
+
+//
+// Structure to completely describe all the members of a alias.
+//
+typedef struct _NETLOGON_DELTA_ALIAS_MEMBER {
+ NLPR_SID_ARRAY Members;
+
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_ALIAS_MEMBER, *PNETLOGON_DELTA_ALIAS_MEMBER;
+
+//
+// Structure to completely describe a domain.
+//
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_DOMAIN {
+ UNICODE_STRING DomainName;
+ UNICODE_STRING OemInformation;
+ OLD_LARGE_INTEGER ForceLogoff;
+ USHORT MinPasswordLength;
+ USHORT PasswordHistoryLength;
+ OLD_LARGE_INTEGER MaxPasswordAge;
+ OLD_LARGE_INTEGER MinPasswordAge;
+
+ OLD_LARGE_INTEGER DomainModifiedCount;
+ OLD_LARGE_INTEGER DomainCreationTime;
+
+ // All this information is maintained separately on each system.
+#ifdef notdef
+ UNICODE_STRING ReplicaSourceNodeName;
+ DOMAIN_SERVER_ENABLE_STATE DomainServerState;
+ DOMAIN_SERVER_ROLE DomainServerRole;
+#endif // notdef
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1; // used to replicate DOMAIN_LOCKOUT_INFORMATION
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1; // used to replicate PasswordProperties
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_DOMAIN, *PNETLOGON_DELTA_DOMAIN;
+#pragma pack()
+
+typedef struct _NETLOGON_DELTA_RENAME {
+ UNICODE_STRING OldName;
+ UNICODE_STRING NewName;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_RENAME_GROUP, *PNETLOGON_DELTA_RENAME_GROUP,
+ NETLOGON_RENAME_USER, *PNETLOGON_DELTA_RENAME_USER,
+ NETLOGON_RENAME_ALIAS, *PNETLOGON_DELTA_RENAME_ALIAS;
+
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_POLICY {
+ ULONG MaximumLogSize;
+ OLD_LARGE_INTEGER AuditRetentionPeriod;
+
+ BOOLEAN AuditingMode;
+ ULONG MaximumAuditEventCount;
+ [size_is(MaximumAuditEventCount + 1)] PULONG EventAuditingOptions;
+
+ UNICODE_STRING PrimaryDomainName;
+ PISID PrimaryDomainSid;
+
+ NLPR_QUOTA_LIMITS QuotaLimits;
+
+ OLD_LARGE_INTEGER ModifiedId;
+ OLD_LARGE_INTEGER DatabaseCreationTime;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_POLICY, *PNETLOGON_DELTA_POLICY;
+#pragma pack()
+
+typedef struct _NETLOGON_DELTA_TRUSTED_DOMAINS {
+ UNICODE_STRING DomainName;
+ ULONG NumControllerEntries;
+ [size_is(NumControllerEntries)] PUNICODE_STRING ControllerNames;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1; // used for posix offset.
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_TRUSTED_DOMAINS, *PNETLOGON_DELTA_TRUSTED_DOMAINS;
+
+typedef struct _NETLOGON_DELTA_ACCOUNTS {
+ ULONG PrivilegeEntries;
+ ULONG PrivilegeControl;
+ [size_is(PrivilegeEntries)] PULONG PrivilegeAttributes;
+ [size_is(PrivilegeEntries)] PUNICODE_STRING PrivilegeNames;
+
+ NLPR_QUOTA_LIMITS QuotaLimits;
+ ULONG SystemAccessFlags;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_ACCOUNTS, *PNETLOGON_DELTA_ACCOUNTS;
+
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_SECRET {
+ NLPR_CR_CIPHER_VALUE CurrentValue;
+ OLD_LARGE_INTEGER CurrentValueSetTime;
+ NLPR_CR_CIPHER_VALUE OldValue;
+ OLD_LARGE_INTEGER OldValueSetTime;
+
+ SECURITY_INFORMATION SecurityInformation;
+ ULONG SecuritySize;
+ [size_is(SecuritySize)] PUCHAR SecurityDescriptor;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_SECRET, *PNETLOGON_DELTA_SECRET;
+#pragma pack()
+
+typedef struct _NETLOGON_DELTA_DELETE {
+ [string] wchar_t * AccountName;
+
+ UNICODE_STRING DummyString1;
+ UNICODE_STRING DummyString2;
+ UNICODE_STRING DummyString3;
+ UNICODE_STRING DummyString4;
+ ULONG DummyLong1;
+ ULONG DummyLong2;
+ ULONG DummyLong3;
+ ULONG DummyLong4;
+} NETLOGON_DELTA_DELETE_GROUP, *PNETLOGON_DELTA_DELETE_GROUP,
+ NETLOGON_DELTA_DELETE_USER, *PNETLOGON_DELTA_DELETE_USER;
+
+//
+// A Union of each of the above types.
+//
+#pragma pack(4)
+typedef [switch_type(NETLOGON_DELTA_TYPE)] union _NETLOGON_DELTA_UNION {
+ [case(AddOrChangeDomain)] PNETLOGON_DELTA_DOMAIN DeltaDomain;
+ [case(AddOrChangeGroup)] PNETLOGON_DELTA_GROUP DeltaGroup;
+ [case(RenameGroup)] PNETLOGON_DELTA_RENAME_GROUP DeltaRenameGroup;
+ [case(AddOrChangeUser)] PNETLOGON_DELTA_USER DeltaUser;
+ [case(RenameUser)] PNETLOGON_DELTA_RENAME_USER DeltaRenameUser;
+ [case(ChangeGroupMembership)] PNETLOGON_DELTA_GROUP_MEMBER DeltaGroupMember;
+ [case(AddOrChangeAlias)] PNETLOGON_DELTA_ALIAS DeltaAlias;
+ [case(RenameAlias)] PNETLOGON_DELTA_RENAME_ALIAS DeltaRenameAlias;
+ [case(ChangeAliasMembership)] PNETLOGON_DELTA_ALIAS_MEMBER DeltaAliasMember;
+ [case(AddOrChangeLsaPolicy)] PNETLOGON_DELTA_POLICY DeltaPolicy;
+ [case(AddOrChangeLsaTDomain)] PNETLOGON_DELTA_TRUSTED_DOMAINS DeltaTDomains;
+ [case(AddOrChangeLsaAccount)] PNETLOGON_DELTA_ACCOUNTS DeltaAccounts;
+ [case(AddOrChangeLsaSecret)] PNETLOGON_DELTA_SECRET DeltaSecret;
+ [case(DeleteGroupByName)] PNETLOGON_DELTA_DELETE_GROUP DeltaDeleteGroup;
+ [case(DeleteUserByName)] PNETLOGON_DELTA_DELETE_USER DeltaDeleteUser;
+ [case(SerialNumberSkip)] PNLPR_MODIFIED_COUNT DeltaSerialNumberSkip;
+ [default] ; // Ship nothing for Delete Cases
+} NETLOGON_DELTA_UNION, *PNETLOGON_DELTA_UNION;
+#pragma pack()
+
+typedef [switch_type(NETLOGON_DELTA_TYPE)] union _NETLOGON_DELTA_ID_UNION {
+ [case(AddOrChangeDomain,
+ AddOrChangeGroup,
+ DeleteGroup,
+ RenameGroup,
+ AddOrChangeUser,
+ DeleteUser,
+ RenameUser,
+ ChangeGroupMembership,
+ AddOrChangeAlias,
+ DeleteAlias,
+ RenameAlias,
+ ChangeAliasMembership,
+ DeleteGroupByName,
+ DeleteUserByName )] ULONG Rid;
+
+ [case(AddOrChangeLsaPolicy,
+ AddOrChangeLsaTDomain,
+ DeleteLsaTDomain,
+ AddOrChangeLsaAccount,
+ DeleteLsaAccount)] PISID Sid;
+ [case(AddOrChangeLsaSecret,
+ DeleteLsaSecret)] [string] wchar_t * Name;
+ [default] ;
+} NETLOGON_DELTA_ID_UNION, *PNETLOGON_DELTA_ID_UNION;
+
+
+//
+// A common structure to describe a single enumerated object.
+//
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_ENUM {
+ NETLOGON_DELTA_TYPE DeltaType;
+ [switch_is(DeltaType)] NETLOGON_DELTA_ID_UNION DeltaID;
+ [switch_is(DeltaType)] NETLOGON_DELTA_UNION DeltaUnion;
+} NETLOGON_DELTA_ENUM, *PNETLOGON_DELTA_ENUM;
+#pragma pack()
+
+//
+// Structure that defines the array of enumerated objects.
+//
+
+#pragma pack(4)
+typedef struct _NETLOGON_DELTA_ENUM_ARRAY {
+ DWORD CountReturned;
+ [size_is(CountReturned)] PNETLOGON_DELTA_ENUM Deltas;
+} NETLOGON_DELTA_ENUM_ARRAY, *PNETLOGON_DELTA_ENUM_ARRAY;
+#pragma pack()
+
+//
+// Function Prototypes - Logon Service
+//
+
+
+NET_API_STATUS
+NetrLogonUasLogon (
+ [in,unique,string] LOGONSRV_HANDLE ServerName,
+ [in, string] wchar_t * UserName,
+ [in, string] wchar_t * Workstation,
+ [out] PNETLOGON_VALIDATION_UAS_INFO *ValidationInformation
+ );
+
+NET_API_STATUS
+NetrLogonUasLogoff (
+ [in,unique,string] LOGONSRV_HANDLE ServerName,
+ [in, string] wchar_t * UserName,
+ [in, string] wchar_t * Workstation,
+ [out] PNETLOGON_LOGOFF_UAS_INFO LogoffInformation
+ );
+
+//
+// NetrLogonSam routines
+//
+typedef [switch_type(enum _NETLOGON_LOGON_INFO_CLASS)]
+ union _NETLOGON_LEVEL {
+ [case(NetlogonInteractiveInformation)]
+ PNETLOGON_INTERACTIVE_INFO LogonInteractive;
+ [case(NetlogonServiceInformation)]
+ PNETLOGON_SERVICE_INFO LogonService;
+ [case(NetlogonNetworkInformation)]
+ PNETLOGON_NETWORK_INFO LogonNetwork;
+ [default]
+ ;
+} NETLOGON_LEVEL, * PNETLOGON_LEVEL;
+
+typedef [switch_type(enum _NETLOGON_LOGON_INFO_CLASS)]
+ union _NETLOGON_VALIDATION {
+ [case(NetlogonValidationSamInfo)]
+ PNETLOGON_VALIDATION_SAM_INFO ValidationSam;
+ [case(NetlogonValidationSamInfo2)]
+ PNETLOGON_VALIDATION_SAM_INFO2 ValidationSam2;
+ [default]
+ ;
+} NETLOGON_VALIDATION, * PNETLOGON_VALIDATION;
+
+NTSTATUS
+NetrLogonSamLogon (
+ [in,unique,string] LOGONSRV_HANDLE LogonServer,
+ [in,string,unique] wchar_t * ComputerName,
+ [in,unique] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out,unique] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ [in,switch_is(LogonLevel)] PNETLOGON_LEVEL LogonInformation,
+ [in] NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ [out,switch_is(ValidationLevel)] PNETLOGON_VALIDATION ValidationInformation,
+ [out] PBOOLEAN Authoritative
+ );
+
+NTSTATUS
+NetrLogonSamLogoff (
+ [in,unique,string] LOGONSRV_HANDLE LogonServer,
+ [in,string,unique] wchar_t * ComputerName,
+ [in,unique] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out,unique] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ [in,switch_is(LogonLevel)] PNETLOGON_LEVEL LogonInformation
+);
+
+NTSTATUS
+NetrServerReqChallenge (
+ [in,unique,string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_CREDENTIAL ClientChallenge,
+ [out] PNETLOGON_CREDENTIAL ServerChallenge
+ );
+
+NTSTATUS
+NetrServerAuthenticate (
+ [in,unique,string] LOGONSRV_HANDLE PrimaryName,
+ [in,string] wchar_t * AccountName,
+ [in] NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_CREDENTIAL ClientCredential,
+ [out] PNETLOGON_CREDENTIAL ServerCredential
+ );
+
+NTSTATUS
+NetrServerPasswordSet (
+ [in,unique,string] LOGONSRV_HANDLE PrimaryName,
+ [in,string] wchar_t * AccountName,
+ [in] NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] PENCRYPTED_LM_OWF_PASSWORD UasNewPassword
+ );
+
+//
+// Replication Routines
+//
+
+
+NTSTATUS
+NetrDatabaseDeltas (
+ [in, string] LOGONSRV_HANDLE primaryname,
+ [in, string] wchar_t * computername,
+ [in] PNETLOGON_AUTHENTICATOR authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ret_auth,
+ [in] DWORD DatabaseID,
+ [in, out] PNLPR_MODIFIED_COUNT DomainModifiedCount,
+ [out] PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ [in] DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+NetrDatabaseSync (
+ [in, string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] DWORD DatabaseID,
+ [in, out] PULONG SyncContext,
+ [out] PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ [in] DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+NetrAccountDeltas (
+ [in, unique, string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] PUAS_INFO_0 RecordId,
+ [in] DWORD Count,
+ [in] DWORD Level,
+ [out, size_is(BufferSize)] LPBYTE Buffer,
+ [in] DWORD BufferSize,
+ [out] PULONG CountReturned,
+ [out] PULONG TotalEntries,
+ [out] PUAS_INFO_0 NextRecordId
+ );
+
+NTSTATUS
+NetrAccountSync (
+ [in, unique, string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] DWORD Reference,
+ [in] DWORD Level,
+ [out, size_is(BufferSize) ] LPBYTE Buffer,
+ [in] DWORD BufferSize,
+ [out] PULONG CountReturned,
+ [out] PULONG TotalEntries,
+ [out] PULONG NextReference,
+ [out] PUAS_INFO_0 LastRecordId
+ );
+
+
+NET_API_STATUS
+NetrGetDCName (
+ [in, string] LOGONSRV_HANDLE ServerName,
+ [in, unique, string] wchar_t *DomainName,
+ [out, string] wchar_t **Buffer
+ );
+
+//
+// I_NetLogonControl
+//
+
+typedef [switch_type(DWORD)] union _NETLOGON_CONTROL_DATA_INFORMATION {
+ [case(NETLOGON_CONTROL_REDISCOVER,
+ NETLOGON_CONTROL_TC_QUERY)] [string] wchar_t * TrustedDomainName;
+ [case(NETLOGON_CONTROL_SET_DBFLAG)] DWORD DebugFlag;
+ [case(NETLOGON_CONTROL_FIND_USER)] [string] wchar_t * UserName;
+ [default]
+ ;
+} NETLOGON_CONTROL_DATA_INFORMATION, * PNETLOGON_CONTROL_DATA_INFORMATION;
+
+typedef [switch_type(DWORD)] union _NETLOGON_CONTROL_QUERY_INFORMATION {
+ [case(1)] PNETLOGON_INFO_1 NetlogonInfo1;
+ [case(2)] PNETLOGON_INFO_2 NetlogonInfo2;
+ [case(3)] PNETLOGON_INFO_3 NetlogonInfo3;
+ [case(4)] PNETLOGON_INFO_4 NetlogonInfo4;
+ [default] ;
+} NETLOGON_CONTROL_QUERY_INFORMATION, * PNETLOGON_CONTROL_QUERY_INFORMATION;
+
+NET_API_STATUS
+NetrLogonControl(
+ [in, unique, string] LOGONSRV_HANDLE ServerName,
+ [in] DWORD FunctionCode,
+ [in] DWORD QueryLevel,
+ [out,switch_is(QueryLevel)] PNETLOGON_CONTROL_QUERY_INFORMATION Buffer
+ );
+
+NET_API_STATUS
+NetrGetAnyDCName (
+ [in, unique, string] LOGONSRV_HANDLE ServerName,
+ [in, unique, string] wchar_t *DomainName,
+ [out, string] wchar_t **Buffer
+ );
+
+NET_API_STATUS
+NetrLogonControl2(
+ [in, unique, string] LOGONSRV_HANDLE ServerName,
+ [in] DWORD FunctionCode,
+ [in] DWORD QueryLevel,
+ [in,switch_is(FunctionCode)] PNETLOGON_CONTROL_DATA_INFORMATION Data,
+ [out,switch_is(QueryLevel)] PNETLOGON_CONTROL_QUERY_INFORMATION Buffer
+ );
+
+NTSTATUS
+NetrServerAuthenticate2 (
+ [in,unique,string] LOGONSRV_HANDLE PrimaryName,
+ [in,string] wchar_t * AccountName,
+ [in] NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_CREDENTIAL ClientCredential,
+ [out] PNETLOGON_CREDENTIAL ServerCredential,
+ [in,out] PULONG NegotiateFlags
+ );
+
+//
+// The Sync state indicates tracks the progression of the sync.
+// NlSynchronize() depends on these being in order.
+//
+
+typedef enum _SYNC_STATE {
+ NormalState,
+ DomainState,
+ GroupState,
+ UasBuiltinGroupState,
+ UserState,
+ GroupMemberState,
+ AliasState,
+ AliasMemberState,
+ SamDoneState
+} SYNC_STATE, *PSYNC_STATE;
+
+NTSTATUS
+NetrDatabaseSync2 (
+ [in, string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in] DWORD DatabaseID,
+ [in] SYNC_STATE RestartState,
+ [in, out] PULONG SyncContext,
+ [out] PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ [in] DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+NetrDatabaseRedo(
+ [in, string] LOGONSRV_HANDLE PrimaryName,
+ [in, string] wchar_t * ComputerName,
+ [in] PNETLOGON_AUTHENTICATOR Authenticator,
+ [in,out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ [in, size_is(ChangeLogEntrySize)] PUCHAR ChangeLogEntry,
+ [in] DWORD ChangeLogEntrySize,
+ [out] PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray
+ );
+
+// Same as NetrLogonControl2, but support QueryLevel of 4
+// and function code of NETLOGON_CONTROL_FIND_USER
+NET_API_STATUS
+NetrLogonControl2Ex(
+ [in, unique, string] LOGONSRV_HANDLE ServerName,
+ [in] DWORD FunctionCode,
+ [in] DWORD QueryLevel,
+ [in,switch_is(FunctionCode)] PNETLOGON_CONTROL_DATA_INFORMATION Data,
+ [out,switch_is(QueryLevel)] PNETLOGON_CONTROL_QUERY_INFORMATION Buffer
+ );
+
+//
+// Routine to enumerate trusted domains.
+//
+
+typedef struct _DOMAIN_NAME_BUFFER {
+ ULONG DomainNameByteCount;
+ [unique, size_is(DomainNameByteCount)] PUCHAR DomainNames;
+} DOMAIN_NAME_BUFFER, *PDOMAIN_NAME_BUFFER;
+
+NTSTATUS
+NetrEnumerateTrustedDomains (
+ [in, unique, string] LOGONSRV_HANDLE ServerName,
+ [out] PDOMAIN_NAME_BUFFER DomainNameBuffer
+ );
+
+}
diff --git a/private/net/svcdlls/logonsrv/logoncli.acf b/private/net/svcdlls/logonsrv/logoncli.acf
new file mode 100644
index 000000000..6aed7c1b4
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/logoncli.acf
@@ -0,0 +1,12 @@
+[ implicit_handle( handle_t logon_bhandle )
+] interface logon
+{
+
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_SAM_INFO;
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_SAM_INFO2;
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_UAS_INFO;
+typedef [allocate(all_nodes)] PNETLOGON_INFO_2;
+
+typedef [allocate(all_nodes_aligned)] PNETLOGON_DELTA_ENUM_ARRAY;
+
+}
diff --git a/private/net/svcdlls/logonsrv/logonsrv.acf b/private/net/svcdlls/logonsrv/logonsrv.acf
new file mode 100644
index 000000000..5fe3164e4
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/logonsrv.acf
@@ -0,0 +1,10 @@
+[ implicit_handle( handle_t logon_bhandle )
+] interface logon
+{
+
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_SAM_INFO;
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_SAM_INFO2;
+typedef [allocate(all_nodes)] PNETLOGON_VALIDATION_UAS_INFO;
+typedef [allocate(all_nodes)] PISID;
+typedef [allocate(all_nodes)] PNLPR_SID_INFORMATION;
+}
diff --git a/private/net/svcdlls/logonsrv/makefil0 b/private/net/svcdlls/logonsrv/makefil0
new file mode 100644
index 000000000..7bb384112
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/makefil0
@@ -0,0 +1,80 @@
+#
+# This is the MIDL compile phase of the build process.
+#
+# The following is where you put the name of your .idl file without
+# the .idl extension:
+#
+
+!INCLUDE $(NTMAKEENV)\makefile.plt
+
+IDL_NAME = logon
+IMPORT = imports
+
+CLIENT_H = logon_c.h
+SERVER_H = logon_s.h
+
+CLIENT_ACF = logoncli.acf
+SERVER_ACF = logonsrv.acf
+
+!IFNDEF DISABLE_NET_UNICODE
+UNICODE=1
+NET_C_DEFINES=-DUNICODE
+!ENDIF
+
+SDKINC = $(BASEDIR)\public\sdk\inc
+NETINC = ..\..\inc
+SDKCRTINC = $(BASEDIR)\public\sdk\inc\crt
+PRIVINC = ..\..\..\inc
+
+INCS = -I$(SDKINC) -I$(SDKCRTINC) -I$(PRIVINC) -I$(NETINC)
+
+CLIENT_TARGETS = client\$(IDL_NAME)_c.c .\client\$(CLIENT_H)
+SERVER_TARGETS = server\$(IDL_NAME)_s.c .\server\$(SERVER_H)
+
+TARGETS = $(CLIENT_TARGETS) $(SERVER_TARGETS)
+
+EXTRN_DEPENDS = $(SDKINC)\lmcons.h \
+ $(SDKINC)\windef.h \
+ $(SDKINC)\nt.h \
+ $(SDKINC)\ntsam.h \
+ $(SDKINC)\lmaccess.h \
+ $(PRIVINC)\netlogon.h \
+ $(PRIVINC)\crypt.h \
+ $(PRIVINC)\logonmsv.h \
+ $(NETINC)\ssi.h
+
+CLIENT_FLAGS = -acf $(CLIENT_ACF) -header $(CLIENT_H) -oldnames
+SERVER_FLAGS = -acf $(SERVER_ACF) -header $(SERVER_H) -oldnames
+
+CPP = -cpp_cmd "$(MIDL_CPP)" $(MIDL_FLAGS) $(C_DEFINES) $(NET_C_DEFINES)
+
+#
+# Define Products and Dependencies
+#
+
+all: $(TARGETS) $(EXTRN_DEPENDS)
+!IF "$(BUILDMSG)" != ""
+ @ech ; $(BUILDMSG) ;
+!ENDIF
+
+clean: delsrc all
+
+delsrc:
+ erase $(TARGETS)
+
+#
+# MIDL COMPILE
+#
+
+$(CLIENT_TARGETS) : .\$(IDL_NAME).idl .\$(IMPORT).idl .\$(IMPORT).h $(EXTRN_DEPENDS) .\$(CLIENT_ACF)
+ midl -Oi -error allocation -error ref -ms_ext -c_ext $(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_H) copy $(CLIENT_H) .\client & del $(CLIENT_H)
+
+
+$(SERVER_TARGETS) : .\$(IDL_NAME).idl .\$(IMPORT).idl .\$(IMPORT).h $(EXTRN_DEPENDS) .\$(SERVER_ACF)
+ midl -error stub_data -error allocation -error ref -ms_ext -c_ext $(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_H) copy $(SERVER_H) .\server & del $(SERVER_H)
diff --git a/private/net/svcdlls/logonsrv/monitor/makefile b/private/net/svcdlls/logonsrv/monitor/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/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/net/svcdlls/logonsrv/monitor/makefile.inc b/private/net/svcdlls/logonsrv/monitor/makefile.inc
new file mode 100644
index 000000000..1676d5e04
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/makefile.inc
@@ -0,0 +1,2 @@
+obj\$(TARGET_DIRECTORY)\nlmon.res: nlmon.rc
+
diff --git a/private/net/svcdlls/logonsrv/monitor/monutil.c b/private/net/svcdlls/logonsrv/monitor/monutil.c
new file mode 100644
index 000000000..a42c2d8dc
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/monutil.c
@@ -0,0 +1,3232 @@
+/*--
+
+Copyright (c) 1993 Microsoft Corporation
+
+Module Name:
+
+ monutil.c
+
+Abstract:
+
+ Trusted Domain monitor program support functions.
+
+Author:
+
+ 10-May-1993 (madana)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+#define GLOBAL_DEF
+
+#include <nlmon.h>
+
+#define NlMonpInitUnicodeString( _dst_, _src_, _buf_) \
+ (_dst_)->Length = (_src_)->Length; \
+ (_dst_)->MaximumLength = (_src_)->Length + sizeof(WCHAR); \
+ (_dst_)->Buffer = (LPWSTR) (_buf_); \
+ RtlCopyMemory( (LPWSTR) (_buf_), (_src_)->Buffer, (_src_)->Length ); \
+ ((LPWSTR) (_buf_))[ (_src_)->Length / sizeof(WCHAR) ] = '\0';
+
+
+PLIST_ENTRY
+FindNamedEntry(
+ PLIST_ENTRY List,
+ PUNICODE_STRING Name
+ )
+/*++
+
+Routine Description:
+
+ This function returns the specified named entry pointer if the entry
+ doesn't exist it returns NULL.
+
+Arguments:
+
+ DCList - List to browse.
+
+ Name - name of the entry whose pointer will be returned.
+
+Return Value:
+
+ Domain entry pointer.
+
+--*/
+{
+ PLIST_ENTRY NextEntry;
+ PENTRY Entry;
+
+ for( NextEntry = List->Flink;
+ NextEntry != List; NextEntry = NextEntry->Flink ) {
+
+ Entry = (PENTRY) NextEntry;
+
+ if( RtlCompareUnicodeString( &Entry->Name, Name, TRUE ) == 0 ) {
+
+ //
+ // entry already there in the list. return the entry
+ // pointer.
+ //
+
+ return( NextEntry );
+ }
+ }
+
+ //
+ // entry not found.
+ //
+
+ return NULL;
+}
+
+
+PDOMAIN_ENTRY
+AddToDomainList(
+ PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ Create a new domain entry and add to GlobalDomains list.
+
+ Enter with list locked.
+
+Arguments:
+
+ DomainName - name of the new domain added to the list.
+
+Return Value:
+
+ Pointer to the new domain structure.
+
+--*/
+{
+ PDOMAIN_ENTRY NewEntry;
+
+ //
+ // if this domain is already in the list, just return the entry
+ // pointer.
+ //
+
+ NewEntry = (PDOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomains,
+ DomainName );
+
+ if( NewEntry != NULL ) {
+
+ //
+ // Domain entry already in there.
+ //
+
+ return(NewEntry);
+ }
+
+
+ //
+ // Allocate space for the new entry
+ //
+
+ NewEntry = NetpMemoryAllocate(
+ sizeof(DOMAIN_ENTRY) +
+ DomainName->Length + sizeof(WCHAR) );
+
+ if( NewEntry == NULL ) {
+ NlMonDbgPrint(("AddToDomainList: can't allocate memory for "
+ "new domain entry.\n"));
+ return NULL;
+ }
+
+ NlMonpInitUnicodeString(
+ &NewEntry->Name, DomainName, (LPWSTR) (NewEntry + 1) );
+
+ InitializeListHead( &NewEntry->DCList );
+ InitializeListHead( &NewEntry->TrustedDomainList );
+ NewEntry->DomainState = DomainUnknown;
+ NewEntry->ReferenceCount = 0;
+ NewEntry->IsMonitoredDomain = FALSE;
+ NewEntry->UpdateFlags = 0;
+ NewEntry->ThreadHandle = NULL;
+ NewEntry->ThreadTerminateFlag = FALSE;
+ NewEntry->LastUpdateTime = 0;
+
+ //
+ // add it to domain list.
+ //
+
+ InsertTailList(&GlobalDomains, (PLIST_ENTRY)NewEntry);
+
+ return NewEntry;
+}
+
+PMONITORED_DOMAIN_ENTRY
+AddToMonitoredDomainList(
+ PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ Create a new monitored domain entry and add it to the
+ GlobalDomainsMonitored list.
+
+ Enter with list locked.
+
+Arguments:
+
+ DomainName - name of the new domain added to the list.
+
+Return Value:
+
+ Pointer to the new domain structure.
+
+--*/
+{
+ PDOMAIN_ENTRY DomainEntry;
+ PMONITORED_DOMAIN_ENTRY NewEntry;
+
+ //
+ // if the domain is already monitored, just return entry pointer.
+ //
+
+ NewEntry = (PMONITORED_DOMAIN_ENTRY)
+ FindNamedEntry( &GlobalDomainsMonitored, DomainName );
+
+ if( NewEntry != NULL ) {
+ NewEntry->DeleteFlag = FALSE;
+ return NewEntry;
+ }
+
+ //
+ // allocate space for the new domain that will be monitored.
+ //
+
+ NewEntry = NetpMemoryAllocate(
+ sizeof(MONITORED_DOMAIN_ENTRY) +
+ DomainName->Length + sizeof(WCHAR) );
+
+ if( NewEntry == NULL ) {
+ NlMonDbgPrint(("AddToMonitoredDomainList: "
+ "can't allocate memory for "
+ "new domain entry.\n"));
+ return NULL;
+ }
+
+ NlMonpInitUnicodeString(
+ &NewEntry->Name, DomainName, (LPWSTR) (NewEntry + 1) );
+
+ DomainEntry = AddToDomainList( DomainName );
+
+ if( DomainEntry == NULL ) {
+
+ //
+ // can't create new domain entry.
+ //
+
+ NetpMemoryFree( NewEntry );
+ return(NULL);
+ }
+
+ NewEntry->DomainEntry = DomainEntry;
+ NewEntry->DeleteFlag = FALSE;
+ DomainEntry->ReferenceCount++;
+ DomainEntry->IsMonitoredDomain = TRUE;
+
+
+ //
+ // add it to GlobalDomainsMonitored list.
+ //
+
+ InsertTailList(&GlobalDomainsMonitored, (PLIST_ENTRY)NewEntry);
+
+ return(NewEntry);
+}
+
+PTRUSTED_DOMAIN_ENTRY
+AddToTrustedDomainList(
+ PLIST_ENTRY List,
+ PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ Create a new monitored domain entry and add it to the
+ GlobalDomainsTrusted list.
+
+ Enter with list locked.
+
+Arguments:
+
+ List - List to be modified.
+
+ DomainName - name of the new domain added to the list.
+
+Return Value:
+
+ Pointer to the new domain structure.
+
+--*/
+{
+ PDOMAIN_ENTRY DomainEntry;
+ PTRUSTED_DOMAIN_ENTRY NewEntry;
+
+ //
+ // if the domain is already trusted, just return entry pointer.
+ //
+
+ NewEntry = (PTRUSTED_DOMAIN_ENTRY)
+ FindNamedEntry( List, DomainName );
+
+ if( NewEntry != NULL ) {
+ NewEntry->DeleteFlag = FALSE;
+ return NewEntry;
+ }
+
+ //
+ // allocate space for the new domain that will be monitored.
+ //
+
+ NewEntry = NetpMemoryAllocate(
+ sizeof(TRUSTED_DOMAIN_ENTRY) +
+ DomainName->Length + sizeof(WCHAR) );
+
+ if( NewEntry == NULL ) {
+ NlMonDbgPrint(("AddToTrustedDomainList: "
+ "can't allocate memory for "
+ "new domain entry.\n"));
+ return NULL;
+ }
+
+ NlMonpInitUnicodeString(
+ &NewEntry->Name, DomainName, (LPWSTR) (NewEntry + 1) );
+
+ DomainEntry = AddToDomainList( DomainName );
+
+ if( DomainEntry == NULL ) {
+ //
+ // can't create new domain entry.
+ //
+
+ NetpMemoryFree( NewEntry );
+ return(NULL);
+ }
+
+ NewEntry->DomainEntry = DomainEntry;
+ NewEntry->DeleteFlag = FALSE;
+ DomainEntry->ReferenceCount++;
+
+
+ //
+ // add it to list.
+ //
+
+ InsertTailList(List, (PLIST_ENTRY)NewEntry);
+
+ return(NewEntry);
+}
+
+BOOL
+InitDomainListW(
+ LPWSTR DomainList
+ )
+/*++
+
+Routine Description:
+
+ Parse comma separated domain list.
+
+Arguments:
+
+ DomainList - comma separate domain list.
+
+Return Value:
+
+ TRUE - if successfully parsed.
+ FALSE - iff the list is bad.
+
+--*/
+{
+ WCHAR DomainName[DNLEN + 1];
+ PWCHAR d;
+ PWCHAR p;
+ DWORD Len;
+
+
+ p = DomainList;
+
+ if( *p == L'\0' ) {
+ return(FALSE);
+ }
+
+ while (*p != L'\0') {
+
+ d = DomainName; // destination to next domain name.
+
+ while( (*p != L'\0') && (*p == L' ') ) {
+ p++; // skip leading blanks.
+ }
+
+ //
+ // read next domain name.
+ //
+
+ while( (*p != L'\0') && (*p != L',') ) {
+
+ if( d < DomainName + DNLEN ) {
+ *d++ = (WCHAR) (*p++);
+ }
+ }
+
+ if( *p != L'\0' ) {
+ p++; // skip comma.
+ }
+
+ //
+ // delete tail end blanks.
+ //
+
+ while ( (d > DomainName) && (*(d-1) == L' ') ) {
+ d--;
+ }
+
+ *d = L'\0';
+
+ if( Len = wcslen(DomainName) ) {
+
+ UNICODE_STRING UnicodeDomainName;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ LOCK_LISTS();
+ if( AddToMonitoredDomainList( &UnicodeDomainName ) == NULL ) {
+ UNLOCK_LISTS();
+ return(FALSE);
+ }
+ UNLOCK_LISTS();
+ }
+ }
+
+ if( IsListEmpty( &GlobalDomainsMonitored ) ) {
+ return(FALSE);
+ }
+
+ return(TRUE);
+
+}
+
+VOID
+ConvertServerAcctNameToServerName(
+ PUNICODE_STRING ServerAccountName
+ )
+/*++
+
+Routine Description:
+
+ Convert user account type name to server type name. By current convension
+ the servers account name has a "$" sign at the end. ie.
+
+ ServerAccountName = ServerName + "$"
+
+Arguments:
+
+ ServerAccountName - server account name.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ //
+ // strip off "$" sign from account name.
+ //
+
+ ServerAccountName->Length -= sizeof(WCHAR);
+
+ return;
+}
+
+NTSTATUS
+QueryLsaInfo(
+ PUNICODE_STRING ServerName,
+ ACCESS_MASK DesiredAccess,
+ POLICY_INFORMATION_CLASS InformationClass,
+ PVOID *Info,
+ PLSA_HANDLE ReturnHandle //optional
+ )
+/*++
+
+Routine Description:
+
+ Open LSA database on the remote server, query requested info and
+ return lsa handle if asked otherwise close it.
+
+Arguments:
+
+ ServerName - Remote machine name.
+
+ DesiredAccess - Required access.
+
+ InformationClass - info class to be returned.
+
+ Info - pointer to a location where the return info buffer pointer is
+ placed. Caller should free this buffer.
+
+ ReturnHandle - if this is non-NULL pointer, LSA handle is returned
+ here. caller should close this handle after use.
+
+Return Value:
+
+ NTSTATUS.
+
+--*/
+{
+
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ LSA_HANDLE PolicyHandle;
+
+ *Info = NULL;
+
+ //
+ // Open Local policy.
+ //
+
+ INIT_OBJ_ATTR(ObjectAttributes);
+
+ Status = LsaOpenPolicy( ServerName,
+ &ObjectAttributes,
+ DesiredAccess,
+ &PolicyHandle );
+
+ if( !NT_SUCCESS(Status) ) {
+
+ NlMonDbgPrint(("QueryLsaInfo: "
+ "Cannot open LSA Policy database on server %wZ, %lx.\n",
+ ServerName, Status ));
+ return Status;
+ }
+
+ //
+ // read primary domain info.
+ //
+
+ Status = LsaQueryInformationPolicy(
+ PolicyHandle,
+ InformationClass,
+ Info );
+
+ if( !NT_SUCCESS(Status) ) {
+
+ NlMonDbgPrint(("QueryLsaInfo: "
+ "Can't read domain info from %wZ's database, %lx.\n",
+ ServerName, Status));
+
+ LsaClose(PolicyHandle);
+ return Status;
+ }
+
+ if( ReturnHandle != NULL ) {
+ *ReturnHandle = PolicyHandle;
+ }
+ else {
+ LsaClose(PolicyHandle);
+ }
+
+ return Status;
+}
+
+NET_API_STATUS
+IsValidNTDC(
+ PUNICODE_STRING ServerName,
+ PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function determines that the given server is valid NT Domain
+ Controller on the given domain.
+
+Arguments:
+
+ ServerName - name of the server which has to be validated.
+
+ DomainName - name of the domain on which this DC is member.
+
+Return Value:
+
+ NET_API_STATUS code.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ PWKSTA_INFO_100 WkstaInfo100 = NULL;
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+
+ //
+ // ServerName should be non-null string.
+ //
+
+ if( (ServerName->Length <= 0) ||
+ (ServerName->Buffer == NULL) ||
+ (*ServerName->Buffer == L'\0') ) {
+
+ NetStatus = NERR_BadServiceName;
+ goto Cleanup;
+ }
+
+ //
+ // check to see that the server is still member of specified domain.
+ //
+
+ NetStatus = NetWkstaGetInfo(
+ ServerName->Buffer,
+ 100,
+ (LPBYTE *)&WkstaInfo100
+ );
+
+ if( (GlobalTerminateFlag) || (NetStatus != NERR_Success) ) {
+ goto Cleanup;
+ }
+
+ if ( I_NetNameCompare( NULL,
+ DomainName->Buffer,
+ WkstaInfo100->wki100_langroup,
+ NAMETYPE_DOMAIN,
+ 0L) != 0 ) {
+ goto Cleanup;
+ }
+
+
+ //
+ // check the server type is appropriate.
+ //
+
+ NetStatus = NetServerGetInfo(
+ ServerName->Buffer,
+ 101,
+ (LPBYTE *)&ServerInfo101
+ );
+
+ if( (GlobalTerminateFlag) || (NetStatus != NERR_Success) ) {
+ goto Cleanup;
+ }
+
+ if (!((ServerInfo101->sv101_type &
+ (SV_TYPE_DOMAIN_CTRL | SV_TYPE_DOMAIN_BAKCTRL)) &&
+ (ServerInfo101->sv101_type & SV_TYPE_NT)) ) {
+
+ //
+ // this server isn't NT Domain Controller.
+ //
+
+ NetStatus = ERROR_INVALID_DOMAIN_ROLE;
+ }
+
+
+Cleanup:
+
+ if ( ServerInfo101 != NULL ) {
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ if ( WkstaInfo100 != NULL ) {
+ NetApiBufferFree( WkstaInfo100 );
+ }
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("IsValidNTDC: ServerName = %wZ, "
+ "DomainName = %wZ, and NetStatus = %ld .\n",
+ ServerName, DomainName, NetStatus ));
+ }
+
+ return NetStatus;
+}
+
+NET_API_STATUS
+ValidateDC(
+ PDC_ENTRY DCEntry,
+ PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function validates the given DCEntry and sets various fields of
+ this structures.
+
+Arguments:
+
+ DCEntry - pointer to DC entry structure.
+
+ DomainName - name of the domain of which this DC is member.
+
+Return Value:
+
+ NET_API_STATUS code.
+
+--*/
+{
+ NET_API_STATUS NetStatus = NERR_Success;
+ PWKSTA_INFO_100 WkstaInfo100 = NULL;
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+ PNETLOGON_INFO_1 NetlogonInfo1 = NULL;
+ LPWSTR InputDataPtr = NULL;
+
+ //
+ // lock this list while we update this entry status.
+ //
+
+ LOCK_LISTS();
+
+ //
+ // retry once in RETRY_COUNT calls.
+ //
+
+ if( ( DCEntry->State == DCOffLine ) ||
+ ( DCEntry->DCStatus != NERR_Success) ) {
+ if( DCEntry->RetryCount != 0 ) {
+ DCEntry->RetryCount = (DCEntry->RetryCount + 1) % RETRY_COUNT;
+ NetStatus = DCEntry->DCStatus;
+ goto Cleanup;
+ }
+ DCEntry->RetryCount = (DCEntry->RetryCount + 1) % RETRY_COUNT;
+ }
+
+
+ //
+ // check to see that the server is still on specified domain
+ //
+
+ UNLOCK_LISTS();
+ NetStatus = NetWkstaGetInfo(
+ DCEntry->DCName.Buffer,
+ 100,
+ (LPBYTE * )&WkstaInfo100 );
+ LOCK_LISTS();
+
+ if ( NetStatus != NERR_Success ) {
+
+ if( NetStatus == ERROR_BAD_NETPATH ) {
+ DCEntry->State = DCOffLine;
+ }
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ if ( I_NetNameCompare( NULL,
+ DomainName->Buffer,
+ WkstaInfo100->wki100_langroup,
+ NAMETYPE_DOMAIN,
+ 0L) != 0 ) {
+
+ NetStatus = ERROR_INVALID_DOMAIN_STATE;
+ goto Cleanup;
+ }
+
+
+ //
+ // check the server type is appropriate.
+ //
+
+ UNLOCK_LISTS();
+ NetStatus = NetServerGetInfo(
+ DCEntry->DCName.Buffer,
+ 101,
+ (LPBYTE *)&ServerInfo101 );
+ LOCK_LISTS();
+
+ if( (GlobalTerminateFlag) || (NetStatus != NERR_Success) ) {
+ goto Cleanup;
+ }
+
+ if( ServerInfo101->sv101_type & SV_TYPE_NT ) {
+
+ if ( ServerInfo101->sv101_type & SV_TYPE_DOMAIN_CTRL ) {
+ DCEntry->Type = NTPDC;
+ } else if ( ServerInfo101->sv101_type & SV_TYPE_DOMAIN_BAKCTRL ) {
+ DCEntry->Type = NTBDC;
+ } else {
+ NetStatus = ERROR_INVALID_DOMAIN_ROLE;
+ goto Cleanup;
+ }
+ }
+ else {
+ if ( ServerInfo101->sv101_type & SV_TYPE_DOMAIN_BAKCTRL ) {
+ DCEntry->Type = LMBDC;
+ }
+ else {
+ NetStatus = ERROR_INVALID_DOMAIN_ROLE;
+ goto Cleanup;
+ }
+ }
+
+ if( DCEntry->Type != LMBDC ) {
+
+ //
+ // Query netlogon to determine replication status and connection
+ // status to its PDC.
+ //
+
+ UNLOCK_LISTS();
+ NetStatus = I_NetLogonControl2(
+ DCEntry->DCName.Buffer,
+ NETLOGON_CONTROL_QUERY,
+ 1,
+ (LPBYTE)&InputDataPtr,
+ (LPBYTE *)&NetlogonInfo1 );
+ LOCK_LISTS();
+
+ if( (GlobalTerminateFlag) || (NetStatus != NERR_Success) ) {
+ goto Cleanup;
+ }
+
+ DCEntry->ReplicationStatus = NetlogonInfo1->netlog1_flags;
+ DCEntry->PDCLinkStatus = NetlogonInfo1->netlog1_pdc_connection_status;
+ }
+
+ DCEntry->State = DCOnLine;
+Cleanup:
+
+ if ( NetlogonInfo1 != NULL ) {
+ NetApiBufferFree( NetlogonInfo1 );
+ }
+
+ if ( ServerInfo101 != NULL ) {
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ if ( WkstaInfo100 != NULL ) {
+ NetApiBufferFree( WkstaInfo100 );
+ }
+
+ if ( NetStatus != NERR_Success ) {
+
+ //
+ // set other status to unknown.
+ //
+
+ DCEntry->ReplicationStatus = UNKNOWN_REPLICATION_STATE;
+ DCEntry->PDCLinkStatus = ERROR_BAD_NETPATH;
+ }
+
+ DCEntry->DCStatus = NetStatus;
+ UNLOCK_LISTS();
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("ValidateDC: ServerName = %wZ, "
+ "DomainName = %wZ, and NetStatus = %ld.\n",
+ &DCEntry->DCName, DomainName, NetStatus ));
+ }
+
+ return NetStatus;
+}
+
+PDC_ENTRY
+CreateDCEntry (
+ PUNICODE_STRING DCName,
+ DC_TYPE Type
+ )
+/*++
+
+Routine Description:
+
+ Create a DC Entry;
+
+Arguments:
+
+ DCName - entry name in unc form.
+
+ Type - DC type.
+
+Return Value:
+
+ Entry pointer. If it can't allocate memory, it returns NULL pointer.
+
+--*/
+{
+
+ PDC_ENTRY DCEntry;
+
+ DCEntry = NetpMemoryAllocate(
+ sizeof(DC_ENTRY) +
+ DCName->Length + sizeof(WCHAR) );
+
+ if( DCEntry != NULL ) {
+
+ NlMonpInitUnicodeString(
+ &DCEntry->DCName, DCName, (LPWSTR) (DCEntry + 1) );
+
+ DCEntry->State = DCOffLine;
+ DCEntry->Type = Type;
+ DCEntry->DCStatus = ERROR_BAD_NETPATH;
+ DCEntry->ReplicationStatus = UNKNOWN_REPLICATION_STATE; //unknown state.
+ DCEntry->PDCLinkStatus = ERROR_BAD_NETPATH;
+ DCEntry->DeleteFlag = FALSE;
+ DCEntry->RetryCount = 0;
+ InitializeListHead( &DCEntry->TrustedDCs );
+ DCEntry->TDCLinkState = FALSE;
+ }
+
+ return( DCEntry );
+}
+
+NTSTATUS
+UpdateDCListFromNTServerAccounts(
+ SAM_HANDLE SamHandle,
+ PLIST_ENTRY DCList
+ )
+/*++
+
+Routine Description:
+
+ Merge NT server accounts defined in the database to DCList.
+
+Arguments:
+
+ SamHandle : Handle SAM database.
+
+ DCList : linked list of DCs.
+
+Return Value:
+
+ NTSTATUS code.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PDOMAIN_DISPLAY_MACHINE MachineInformation = NULL;
+ DWORD SamIndex = 0;
+ DWORD EntriesRead;
+ ULONG TotalBytesAvailable;
+ ULONG BytesReturned;
+
+ DWORD i;
+
+ PDC_ENTRY DCEntry;
+
+ //
+ // enumerate NT Server accounts
+ //
+
+ do {
+ //
+ // Get the list of machine accounts from SAM
+ //
+
+ Status = SamQueryDisplayInformation(
+ SamHandle,
+ DomainDisplayMachine,
+ SamIndex,
+ MACHINES_PER_PASS,
+ 0xFFFFFFFF,
+ &TotalBytesAvailable,
+ &BytesReturned,
+ &EntriesRead,
+ &MachineInformation );
+
+ if ( (Status != STATUS_NO_MORE_ENTRIES) &&
+ (!NT_SUCCESS(Status)) ) {
+ NlMonDbgPrint(("UpdateDCListFromNTServerAccounts: "
+ "SamrQueryDisplayInformation returned, %lx.\n",
+ Status));
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // Set up for the next call to Sam.
+ //
+
+ if ( Status == STATUS_MORE_ENTRIES ) {
+ SamIndex = MachineInformation[EntriesRead-1].Index + 1;
+ }
+
+
+ //
+ // Loop though the list of machine accounts finding the Server accounts.
+ //
+
+ for ( i = 0; i < EntriesRead; i++ ) {
+
+ //
+ // Ensure the machine account is a server account.
+ //
+
+ if ( MachineInformation[i].AccountControl &
+ USER_SERVER_TRUST_ACCOUNT ) {
+
+ WCHAR UncComputerName[CNLEN + 3];
+ UNICODE_STRING UnicodeComputerName;
+
+ ConvertServerAcctNameToServerName(
+ &MachineInformation[i].Machine ); // in place conversion.
+
+ //
+ // form unicode unc computer name.
+ //
+
+ UncComputerName[0] = UncComputerName[1] = L'\\';
+
+ RtlCopyMemory(
+ UncComputerName + 2,
+ MachineInformation[i].Machine.Buffer,
+ MachineInformation[i].Machine.Length );
+
+ UncComputerName[
+ MachineInformation[i].Machine.Length /
+ sizeof(WCHAR) + 2] = L'\0';
+
+ RtlInitUnicodeString( &UnicodeComputerName, UncComputerName);
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("UpdateDCListFromNTServerAccounts: "
+ "NT Server %wZ is found on database.\n",
+ &UnicodeComputerName ));
+ }
+
+ DCEntry = (PDC_ENTRY) FindNamedEntry(
+ DCList,
+ &UnicodeComputerName );
+
+ if( DCEntry != NULL ) {
+
+ DCEntry->DeleteFlag = FALSE;
+ }
+ else {
+
+ DCEntry = CreateDCEntry( &UnicodeComputerName, NTBDC );
+
+ //
+ // silently ignore NULL entry.
+ //
+
+ if( DCEntry != NULL ) {
+
+ //
+ // add to list.
+ //
+
+ LOCK_LISTS();
+ InsertTailList(DCList, (PLIST_ENTRY)DCEntry);
+ UNLOCK_LISTS();
+ }
+ }
+ }
+ }
+
+ //
+ // free up the memory used up memory.
+ //
+
+ if( MachineInformation != NULL ) {
+ (VOID) SamFreeMemory( MachineInformation );
+ MachineInformation = NULL;
+ }
+
+ } while ( Status == STATUS_MORE_ENTRIES );
+
+Cleanup:
+
+ if( MachineInformation != NULL ) {
+ (VOID) SamFreeMemory( MachineInformation );
+ }
+
+ return Status;
+}
+
+NTSTATUS
+UpdateDCListFromLMServerAccounts(
+ SAM_HANDLE SamHandle,
+ PLIST_ENTRY DCList
+ )
+/*++
+
+Routine Description:
+
+ Read LM server accounts from SAM "Servers" global group and update
+ the DC list using this accounts.
+
+Arguments:
+
+ SamHandle : Handle SAM database.
+
+ DCList : linked list of DCs.
+
+Return Value:
+
+ NTSTATUS code.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ UNICODE_STRING NameString;
+ PSID_NAME_USE NameUse = NULL;
+ PULONG RelativeId = NULL;
+ SAM_HANDLE GroupHandle = NULL;
+
+ PULONG MemberAttributes = NULL;
+ PULONG MemberIds = NULL;
+ ULONG MemberCount;
+ PUNICODE_STRING MemberNames = NULL;
+ PSID_NAME_USE MemberNameUse = NULL;
+
+ PDC_ENTRY DCEntry;
+ DWORD i;
+
+ //
+ // Get RID of SERVERS group.
+ //
+
+ RtlInitUnicodeString( &NameString, SERVERS_GROUP );
+ Status = SamLookupNamesInDomain(
+ SamHandle,
+ 1,
+ &NameString,
+ &RelativeId,
+ &NameUse );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlMonDbgPrint(( "UpdateDCListFromLMServerAccounts: SamLookupNamesInDomain "
+ " failed to transulate %wZ %lx.\n",
+ &NameString,
+ Status ));
+ goto Cleanup;
+ }
+
+ if ( *NameUse != SidTypeGroup ) {
+ NlMonDbgPrint(( "UpdateDCListFromLMServerAccounts: %wZ is not "
+ "SidTypeGroup, %ld.\n",
+ &NameString,
+ *NameUse ));
+ goto Cleanup;
+ }
+
+ //
+ // open group object.
+ //
+
+ Status = SamOpenGroup(
+ SamHandle,
+ GROUP_LIST_MEMBERS,
+ *RelativeId,
+ &GroupHandle);
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlMonDbgPrint(( "UpdateDCListFromLMServerAccounts: SamOpenGroup of %wZ "
+ "failed, %lx.\n",
+ &NameString,
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // get group members.
+ //
+
+ Status = SamGetMembersInGroup(
+ GroupHandle,
+ &MemberIds,
+ &MemberAttributes,
+ &MemberCount );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromLMServerAccounts: SamGetMembersInGroup "
+ "returned %lx.\n", Status ));
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ if( MemberCount == 0) {
+ //
+ // nothing more to do.
+ //
+ goto Cleanup;
+ }
+
+ //
+ // trasulate members IDs.
+ //
+
+ Status = SamLookupIdsInDomain(
+ SamHandle,
+ MemberCount,
+ MemberIds,
+ &MemberNames,
+ &MemberNameUse );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint((
+ "UpdateDCListFromLMServerAccounts: SamLookupIdsInDomain "
+ "returned %lx.\n", Status ));
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // add user members to list.
+ //
+
+ for (i = 0 ; i < MemberCount; i++) {
+
+ WCHAR UncComputerName[CNLEN + 3];
+ UNICODE_STRING UnicodeComputerName;
+
+ if ( MemberNameUse[i] != SidTypeUser ) {
+ continue;
+ }
+
+ //
+ // form unicode unc computer name.
+ //
+
+ UncComputerName[0] = UncComputerName[1] = L'\\';
+
+ RtlCopyMemory(
+ UncComputerName + 2,
+ MemberNames[i].Buffer,
+ MemberNames[i].Length );
+
+ UncComputerName[MemberNames[i].Length / sizeof(WCHAR) + 2] = L'\0';
+ RtlInitUnicodeString( &UnicodeComputerName, UncComputerName);
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("UpdateDCListFromLMServerAccounts: "
+ "LM Server %wZ is found on database.\n",
+ &UnicodeComputerName ));
+ }
+
+ DCEntry = (PDC_ENTRY) FindNamedEntry(
+ DCList,
+ &UnicodeComputerName );
+
+ if( DCEntry != NULL ) {
+
+ DCEntry->DeleteFlag = FALSE;
+ }
+ else {
+
+ //
+ // create new entry and add to list
+ //
+
+ DCEntry = CreateDCEntry( &UnicodeComputerName, LMBDC );
+
+ //
+ // silently ignore NULL entries.
+ //
+
+ if( DCEntry != NULL ) {
+
+ //
+ // add to list.
+ //
+
+ LOCK_LISTS();
+ InsertTailList(DCList, (PLIST_ENTRY)DCEntry);
+ UNLOCK_LISTS();
+ }
+
+ }
+ }
+
+Cleanup:
+
+ //
+ // Free up local resources.
+ //
+
+ if( NameUse != NULL ) {
+ (VOID) SamFreeMemory( NameUse );
+ }
+
+ if( RelativeId != NULL ) {
+ (VOID) SamFreeMemory( RelativeId );
+ }
+ if( MemberAttributes != NULL ) {
+ (VOID) SamFreeMemory( MemberAttributes );
+ }
+
+ if( MemberIds != NULL ) {
+ (VOID) SamFreeMemory( MemberIds );
+ }
+ if( MemberNames != NULL ) {
+ (VOID) SamFreeMemory( MemberNames );
+ }
+
+ if( MemberNameUse != NULL ) {
+ (VOID) SamFreeMemory( MemberNameUse );
+ }
+
+ if( GroupHandle != NULL ) {
+ SamCloseHandle( GroupHandle );
+ }
+
+ return Status;
+}
+
+VOID
+UpdateDCListFromDatabase(
+ PUNICODE_STRING DomainName,
+ PLIST_ENTRY DCList
+ )
+/*++
+
+Routine Description:
+
+ Update the List of DCs on a given domain using database entries.
+
+Arguments:
+
+ DomainName : name of the domain whose DCList to be updated.
+
+ DCList : linked list of DCs. DCList must be non-empty.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PUNICODE_STRING ServerName;
+
+ PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;
+ SAM_HANDLE SamConnectHandle = NULL;
+ SAM_HANDLE SamHandle = NULL;
+
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+
+ //
+ // pick a dc from current list.
+ //
+
+ ServerName = NULL;
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ if( IsValidNTDC( &DCEntry->DCName, DomainName ) == NERR_Success ) {
+ ServerName = &DCEntry->DCName;
+ break;
+ }
+ }
+
+ if( ServerName == NULL ) {
+ NlMonDbgPrint(( "UpdateDCListFromDatabase: "
+ "No DC is valid in %wZ domain list.\n", DomainName));
+ return;
+ }
+
+ IF_DEBUG( UPDATE ) {
+ NlMonDbgPrint(( "UpdateDCListFromDatabase: "
+ "picked %wZ to get DCList from its database.\n",
+ ServerName));
+ }
+
+ //
+ // Get Domain SID info from policy database.
+ //
+
+ Status = QueryLsaInfo(
+ ServerName,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ PolicyAccountDomainInformation,
+ (PVOID *)&AccountDomainInfo,
+ NULL ) ;
+
+ if( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromDatabase: QueryLsaInfo returned %lx.\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // Connect to SAM.
+ //
+
+ Status = SamConnect(
+ ServerName,
+ &SamConnectHandle,
+ SAM_SERVER_LOOKUP_DOMAIN,
+ NULL); // object attributes.
+
+
+ if( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromDatabase: SamConnect returned %lx.\n",
+ Status ));
+ SamConnectHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Open Account SAM domain.
+ //
+
+ Status = SamOpenDomain(
+ SamConnectHandle,
+ DOMAIN_LIST_ACCOUNTS | DOMAIN_LOOKUP,
+ AccountDomainInfo->DomainSid,
+ &SamHandle );
+
+ if( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromDatabase: SamOpenDomain returned "
+ "%lx.\n", Status ));
+ SamHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // mark all entries in the current list to be deleted.
+ //
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+ DCEntry->DeleteFlag = TRUE;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // enumerate NT Server accounts
+ //
+
+ Status = UpdateDCListFromNTServerAccounts( SamHandle, DCList );
+
+ if( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromDatabase: "
+ "UpdateDCListFromNTServerAccounts returned "
+ "%lx.\n", Status ));
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // Now enumerate down level DCs.
+ //
+
+ Status = UpdateDCListFromLMServerAccounts( SamHandle, DCList );
+
+ if( !NT_SUCCESS( Status ) ) {
+ NlMonDbgPrint(("UpdateDCListFromDatabase: "
+ "UpdateDCListFromLMServerAccounts returned "
+ "%lx.\n", Status ));
+ goto Cleanup;
+ }
+
+Cleanup :
+
+ //
+ // if we have successfully updated the list then
+ // delete entries that are marked deleted otherwise
+ // mark them undelete.
+ //
+
+ LOCK_LISTS();
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+ NextEntry = NextEntry->Flink;
+
+ if( DCEntry->DeleteFlag ) {
+
+ if( Status == STATUS_SUCCESS ) {
+ DCEntry->DeleteFlag = FALSE;
+ }
+ else {
+ RemoveEntryList( (PLIST_ENTRY)DCEntry );
+ NetpMemoryFree( DCEntry );
+ }
+ }
+ }
+ UNLOCK_LISTS();
+
+ if( AccountDomainInfo != NULL ) {
+ (VOID) LsaFreeMemory( AccountDomainInfo );
+ }
+
+ if( SamHandle != NULL ) {
+ (VOID) SamCloseHandle ( SamHandle );
+ }
+
+ if( SamConnectHandle != NULL ) {
+ (VOID) SamCloseHandle ( SamConnectHandle );
+ }
+
+ return;
+}
+
+VOID
+UpdateDCListByServerEnum(
+ PUNICODE_STRING DomainName,
+ PLIST_ENTRY DCList
+ )
+/*++
+
+Routine Description:
+
+ Update the List of DCs on a given domain by calling NetServerEnum.
+ If NetServerEnum failes, it calls NetGetDCName to atleast determine
+ PDC of the domain.
+
+Arguments:
+
+ DomainName : name of the domain whose DCList to be updated.
+
+ DCList : linked list of DCs.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+ DWORD EntriesRead;
+ DWORD TotalEntries;
+
+ PDC_ENTRY DCEntry;
+ DWORD i;
+
+ NetStatus = NetServerEnum( NULL,
+ 101,
+ (LPBYTE *) &ServerInfo101,
+ (ULONG)(-1), // Prefmaxlen
+ &EntriesRead,
+ &TotalEntries,
+ SV_TYPE_DOMAIN_CTRL | SV_TYPE_DOMAIN_BAKCTRL,
+ DomainName->Buffer,
+ NULL ); // Resume Handle
+
+ if( NetStatus != NERR_Success ) {
+
+ if( NetStatus != ERROR_NO_BROWSER_SERVERS_FOUND ) {
+ NlMonDbgPrint(("UpdateDCListByServerEnum: "
+ "NetServerEnum called with domain name %wZ Failed, "
+ "%ld.\n", DomainName, NetStatus));
+ }
+
+ ServerInfo101 = NULL;
+ EntriesRead = 0;
+
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // update DC List using ServerEnumList.
+ //
+
+ for( i = 0; i < EntriesRead; i++ ) {
+
+ WCHAR UncComputerName[CNLEN + 3];
+ UNICODE_STRING UnicodeComputerName;
+ DC_TYPE ServerType;
+
+ UncComputerName[0] = UncComputerName[1] = L'\\';
+ wcscpy( UncComputerName + 2, ServerInfo101[i].sv101_name );
+ RtlInitUnicodeString(&UnicodeComputerName, UncComputerName);
+
+ if ( (ServerInfo101[i].sv101_type & SV_TYPE_NT) ) {
+ if ( ServerInfo101[i].sv101_type & SV_TYPE_DOMAIN_CTRL ) {
+ ServerType = NTPDC;
+ }
+ else {
+ ServerType = NTBDC;
+ }
+ }
+ else {
+ if ( ServerInfo101[i].sv101_type & SV_TYPE_DOMAIN_BAKCTRL ) {
+ ServerType = LMBDC;
+ }
+ else {
+ NlMonDbgPrint(("UpdateDCListByServerEnum: "
+ "NetServerEnum called with domain name %wZ "
+ "returned LM PDC %wZ.\n",
+ DomainName, &UnicodeComputerName ));
+ ServerType = LMBDC;
+ }
+ }
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("UpdateDCListByServerEnum: "
+ "Server %wZ found in NetServerEnumList.\n",
+ &UnicodeComputerName ));
+ }
+
+ DCEntry = (PDC_ENTRY) FindNamedEntry( DCList, &UnicodeComputerName );
+
+ if( DCEntry != NULL ) {
+ DCEntry->Type = ServerType;
+ DCEntry->State = DCOnLine;
+ }
+ else {
+
+ //
+ // create new entry and add to list
+ //
+
+ DCEntry = CreateDCEntry( &UnicodeComputerName, ServerType );
+
+ //
+ // silently ignore NULL entries.
+ //
+
+ if( DCEntry != NULL ) {
+
+ //
+ // add to list.
+ //
+
+ DCEntry->State = DCOnLine;
+ LOCK_LISTS();
+ InsertTailList(DCList, (PLIST_ENTRY)DCEntry);
+ UNLOCK_LISTS();
+ }
+ }
+ }
+
+Cleanup:
+
+ if( ServerInfo101 != NULL ) {
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ return;
+}
+
+VOID
+UpdateTrustList(
+ PDOMAIN_ENTRY Domain
+ )
+/*++
+
+Routine Description:
+
+ This function creates/updates the trusted domains list.
+
+Arguments:
+
+ Domain : pointer domain structure.
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ DWORD i;
+ PUNICODE_STRING ServerName = NULL;
+ PLIST_ENTRY ListHead;
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ LSA_HANDLE PolicyHandle = NULL;
+ LSA_ENUMERATION_HANDLE EnumContext;
+ PLSA_TRUST_INFORMATION TrustedDomainList = NULL;
+ DWORD Entries;
+
+ PTRUSTED_DOMAIN_ENTRY TDEntry;
+
+ IF_DEBUG( TRUST ) {
+ NlMonDbgPrint(("UpdateTrustList: called for domain %wZ.\n",
+ &Domain->Name ));
+ }
+
+ //
+ // pick up a DC to query the trusted domain list.
+ //
+
+ ListHead = &Domain->DCList;
+
+ //
+ // first try possibly good DCs.
+ //
+
+ for( NextEntry = ListHead->Flink;
+ NextEntry != ListHead; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ if( (DCEntry->State == DCOnLine) &&
+ ( DCEntry->DCStatus == NERR_Success ) ) {
+
+ if( IsValidNTDC( &DCEntry->DCName, &Domain->Name ) == NERR_Success ) {
+
+ ServerName = &DCEntry->DCName;
+ break;
+ }
+ }
+ }
+
+ if( ServerName == NULL ) {
+
+ //
+ // now try all.
+ //
+
+ for( NextEntry = ListHead->Flink;
+ NextEntry != ListHead; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ if( IsValidNTDC( &DCEntry->DCName, &Domain->Name ) == NERR_Success ) {
+
+ ServerName = &DCEntry->DCName;
+ break;
+ }
+ }
+ }
+
+
+ if( ServerName == NULL ) {
+
+ IF_DEBUG( TRUST ) {
+ NlMonDbgPrint(( "UpdateTrustList: "
+ "No DC is valid in %wZ domain list.\n",
+ &Domain->Name));
+ }
+
+ goto Cleanup;
+ }
+
+ IF_DEBUG( TRUST ) {
+ NlMonDbgPrint(("UpdateTrustList: for domain %wZ picked %wZ to query "
+ "trusted domains.\n",
+ &Domain->Name, ServerName ));
+ }
+
+ //
+ // Open policy database.
+ //
+
+ INIT_OBJ_ATTR(ObjectAttributes);
+
+ Status = LsaOpenPolicy( ServerName,
+ &ObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &PolicyHandle );
+
+ if( !NT_SUCCESS(Status) ) {
+
+ NlMonDbgPrint(("UpdateTrustList: "
+ "Cannot open LSA Policy on server %wZ, %lx.\n",
+ ServerName, Status ));
+ goto Cleanup;
+ }
+
+ //
+ // enum trusted domains.
+ //
+
+ EnumContext = 0;
+
+ Status = LsaEnumerateTrustedDomains(
+ PolicyHandle,
+ &EnumContext,
+ &TrustedDomainList,
+ (ULONG)-1,
+ &Entries );
+
+ if( !NT_SUCCESS(Status) &&
+ (Status != STATUS_NO_MORE_ENTRIES) ) {
+
+ NlMonDbgPrint(("UpdateTrustList: "
+ "Cannot Enumerate trust list on server %wZ, %lx.\n",
+ ServerName, Status ));
+ goto Cleanup;
+ }
+
+ if( GlobalTerminateFlag ) {
+ goto Cleanup;
+ }
+
+ //
+ // update trust list.
+ //
+
+ ListHead = &Domain->TrustedDomainList;
+
+ for ( i = 0; i < Entries; i++) {
+
+ TDEntry = (PTRUSTED_DOMAIN_ENTRY)
+ FindNamedEntry(
+ ListHead,
+ &TrustedDomainList[i].Name );
+
+ if( TDEntry == NULL ) {
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("UpdateTrustList: "
+ "%wZ is added to %wZ domain trust list.\n",
+ &TrustedDomainList[i].Name,
+ &Domain->Name ));
+ }
+
+ //
+ // add this entry to list.
+ //
+
+ LOCK_LISTS();
+ if( AddToTrustedDomainList(
+ ListHead,
+ &TrustedDomainList[i].Name ) == NULL ) {
+
+ UNLOCK_LISTS();
+ NlMonDbgPrint(("UpdateTrustList: can't allocate memory for "
+ "new trusted domain entry.\n" ));
+ goto Cleanup;
+ }
+ UNLOCK_LISTS();
+ }
+ }
+
+Cleanup :
+
+ if( TrustedDomainList != NULL ) {
+ LsaFreeMemory( TrustedDomainList );
+ }
+
+ if( PolicyHandle != NULL ) {
+ LsaClose( PolicyHandle );
+ }
+
+ return;
+}
+
+VOID
+UpdateTrustConnectionList(
+ PDOMAIN_ENTRY Domain
+ )
+/*++
+
+Routine Description:
+
+ This function creates/updates trust connection entries of all DCs.
+
+Arguments:
+
+ Domain : pointer domain structure.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextDCEntry;
+ PDC_ENTRY DCEntry;
+
+ PLIST_ENTRY TrustConnectList;
+ PTD_LINK TrustConnectEntry;
+
+ PLIST_ENTRY TrustList;
+ PLIST_ENTRY NextTrustEntry;
+ PTRUSTED_DOMAIN_ENTRY TrustEntry;
+
+ //
+ // for each DC on this domain.
+ //
+
+ DCList = &Domain->DCList;
+ TrustList = &Domain->TrustedDomainList;
+
+ for( NextDCEntry = DCList->Flink;
+ NextDCEntry != DCList; NextDCEntry = NextDCEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextDCEntry;
+
+ //
+ // do this update only for running DC
+ // and it should be NT DC.
+ //
+
+ if( (DCEntry->State != DCOnLine) ||
+ (DCEntry->Type == LMBDC) ){
+ continue;
+ }
+
+
+ TrustConnectList = &DCEntry->TrustedDCs;
+
+ //
+ // for each trusted domain, get connection status.
+ //
+
+ for( NextTrustEntry = TrustList->Flink;
+ NextTrustEntry != TrustList;
+ NextTrustEntry = NextTrustEntry->Flink ) {
+
+ TrustEntry = (PTRUSTED_DOMAIN_ENTRY) NextTrustEntry;
+
+ //
+ // search in the current list.
+ //
+
+ TrustConnectEntry = (PTD_LINK) FindNamedEntry(
+ TrustConnectList,
+ &TrustEntry->Name );
+
+ if( TrustConnectEntry == NULL ) {
+
+ PTD_LINK NewTrustConnectEntry;
+
+ //
+ // create new entry.
+ //
+
+ NewTrustConnectEntry =
+ NetpMemoryAllocate(
+ sizeof(TD_LINK) +
+ TrustEntry->Name.Length + sizeof(WCHAR) +
+ (CNLEN + 3) * sizeof(WCHAR)
+ // max computer name space + '\\' + '\0'
+ );
+
+ if( NewTrustConnectEntry == NULL ) {
+ NlMonDbgPrint(("UpdateTrustConnectionList: can't allocate "
+ "memory for new connect entry.\n"));
+ return;
+ }
+
+ InitializeListHead(&NewTrustConnectEntry->NextEntry);
+
+ NlMonpInitUnicodeString(
+ &NewTrustConnectEntry->TDName,
+ &TrustEntry->Name,
+ (LPWSTR)(NewTrustConnectEntry + 1) );
+
+ NewTrustConnectEntry->DCName.Length = 0;
+ NewTrustConnectEntry->DCName.MaximumLength =
+ (CNLEN + 3) * sizeof(WCHAR);
+ NewTrustConnectEntry->DCName.Buffer = (WCHAR *)
+ ((PCHAR)(NewTrustConnectEntry->TDName.Buffer) +
+ NewTrustConnectEntry->TDName.MaximumLength);
+
+ NewTrustConnectEntry->SecureChannelStatus = ERROR_BAD_NETPATH;
+ NewTrustConnectEntry->DeleteFlag = FALSE;
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("UpdateTrustConnectionList: "
+ "Trust Connection entry for DC %wZ of "
+ "domain %wZ added.\n",
+ &DCEntry->DCName,
+ &TrustEntry->Name ));
+
+ }
+
+ //
+ // add to trust connect list.
+ //
+
+ LOCK_LISTS();
+ InsertTailList(
+ TrustConnectList,
+ (PLIST_ENTRY)NewTrustConnectEntry);
+ UNLOCK_LISTS();
+
+ }
+ }
+ }
+
+ return;
+}
+
+VOID
+ValidateTrustConnectionList(
+ PDC_ENTRY DCEntry,
+ BOOL ValidateTrustedDCs
+ )
+/*++
+
+Routine Description:
+
+ This function determines trust DC for each trusted domain to which
+ the given DC having connection.
+
+Arguments:
+
+ DCEntry : pointer DC entry structure.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY TrustConnectList;
+ PLIST_ENTRY NextTrustConnectEntry;
+ PTD_LINK TrustConnectEntry;
+ BOOL TDCLinkState = TRUE;
+
+ //
+ // do this update only for running DC
+ // and it should be NT DC.
+ //
+
+ if( (DCEntry->State != DCOnLine) ||
+ (DCEntry->Type == LMBDC) ) {
+ return;
+ }
+
+ IF_DEBUG( TRUST ) {
+ NlMonDbgPrint(("ValidateTrustConnectionList: "
+ "Trust Connections for the %wZ DC are validated.\n",
+ &DCEntry->DCName ));
+ }
+
+ TrustConnectList = &DCEntry->TrustedDCs;
+
+ for( NextTrustConnectEntry = TrustConnectList->Flink;
+ NextTrustConnectEntry != TrustConnectList;
+ NextTrustConnectEntry = NextTrustConnectEntry->Flink ) {
+
+ NET_API_STATUS NetStatus;
+ PNETLOGON_INFO_2 NetlogonInfo2 = NULL;
+
+ TrustConnectEntry = (PTD_LINK) NextTrustConnectEntry;
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ //
+ // find trusted DC for this domain and its secure channel
+ // status.
+ //
+
+ NetStatus = I_NetLogonControl2(
+ DCEntry->DCName.Buffer,
+ NETLOGON_CONTROL_TC_QUERY,
+ 2,
+ (LPBYTE)&TrustConnectEntry->TDName.Buffer,
+ (LPBYTE *)&NetlogonInfo2 );
+
+
+ if( NetStatus != NERR_Success ) {
+
+ IF_DEBUG( TRUST ) {
+ NlMonDbgPrint(("ValidateTrustConnectionList: "
+ "I_NetLogonControl2 (%wZ) call to query trust "
+ "channel status of %wZ domain failed, (%ld).\n",
+ &DCEntry->DCName,
+ &TrustConnectEntry->TDName,
+ NetStatus ));
+ }
+
+ //
+ // Cleanup the previous DC Name.
+ //
+
+ LOCK_LISTS();
+ TrustConnectEntry->DCName.Length = 0;
+ *TrustConnectEntry->DCName.Buffer = L'\0';
+
+ TrustConnectEntry->SecureChannelStatus = NetStatus;
+ UNLOCK_LISTS();
+
+ TDCLinkState = FALSE;
+ continue;
+ }
+
+ IF_DEBUG( VERBOSE ) {
+ NlMonDbgPrint(("ValidateTrustConnectionList: "
+ "I_NetLogonControl2 (%wZ) call to query trust "
+ "channel status of %wZ domain returned : "
+ "TDCName = %ws, TCStatus = %ld.\n",
+ &DCEntry->DCName,
+ &TrustConnectEntry->TDName,
+ NetlogonInfo2->netlog2_trusted_dc_name,
+ NetlogonInfo2->netlog2_tc_connection_status ));
+ }
+
+ //
+ // copy this DC name.
+ //
+
+ LOCK_LISTS();
+ TrustConnectEntry->DCName.Length =
+ wcslen( NetlogonInfo2->netlog2_trusted_dc_name ) * sizeof(WCHAR);
+
+ RtlCopyMemory(
+ TrustConnectEntry->DCName.Buffer,
+ NetlogonInfo2->netlog2_trusted_dc_name,
+ TrustConnectEntry->DCName.Length + sizeof(WCHAR) );
+ // copy terminator also
+
+ TrustConnectEntry->SecureChannelStatus =
+ NetlogonInfo2->netlog2_tc_connection_status;
+
+ if( TrustConnectEntry->SecureChannelStatus != NERR_Success ) {
+ TDCLinkState = FALSE;
+ }
+
+ //
+ // update DC status info.
+ //
+
+ DCEntry->ReplicationStatus = NetlogonInfo2->netlog2_flags;
+ DCEntry->PDCLinkStatus = NetlogonInfo2->netlog2_pdc_connection_status;
+ UNLOCK_LISTS();
+
+ //
+ // free up API memory.
+ //
+
+ NetApiBufferFree( NetlogonInfo2 );
+ NetlogonInfo2 = NULL;
+
+ //
+ // validate the this DC. Only non-null server and successful
+ // connection.
+ //
+
+ if( (TrustConnectEntry->SecureChannelStatus == NERR_Success) &&
+ (*TrustConnectEntry->DCName.Buffer != L'\0') ) {
+
+ if( ValidateTrustedDCs ) {
+
+
+ NetStatus = IsValidNTDC(
+ &TrustConnectEntry->DCName,
+ &TrustConnectEntry->TDName );
+
+ if( NetStatus != NERR_Success ) {
+
+ IF_DEBUG( TRUST ) {
+
+ NlMonDbgPrint(("ValidateTrustConnectionList: "
+ "%wZ's trust DC %wZ is invalid for "
+ "domain %wz.\n",
+ &DCEntry->DCName,
+ &TrustConnectEntry->DCName,
+ &TrustConnectEntry->TDName ));
+ }
+
+ //
+ // hack, hack, hack ...
+ //
+ // For foreign trusted domains, the above check will
+ // return ERROR_LOGON_FAILURE. Just ignore this
+ // error for now. When the domain wide credential is
+ // implemeted this problem will be cured.
+ //
+
+
+ if( NetStatus != ERROR_LOGON_FAILURE ) {
+ TrustConnectEntry->SecureChannelStatus = NetStatus;
+ TDCLinkState = FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // finally set the state of the trusted dc link for this DC.
+ //
+
+ DCEntry->TDCLinkState = TDCLinkState;
+}
+
+VOID
+UpdateDomainState(
+ PDOMAIN_ENTRY DomainEntry
+ )
+/*++
+
+Routine Description:
+
+ Update the status of the given domain. The status is determined as
+ below :
+
+ 1. if all DCs status and the Trusted channel status are success then
+ DomainState is set to DomainSuccess.
+
+ 2. if all online DCs status are success and if any of the secure
+ channel status is non-success or any of the BDC is offline then the
+ Domainstate is set to DomainProblem.
+
+ 3. if any of the domain status is non-success or the PDC is
+ offline then the DomainState is set to DomainSick.
+
+ 4. if none of the DC is online, then DomainState is set to
+ DomainDown.
+
+Arguments:
+
+ DomainEntry : pointer to domain structure.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+ BOOL DomainDownFlag = TRUE;
+ BOOL PDCOnLine = FALSE;
+
+ DCList = &(DomainEntry->DCList);
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ if( DCEntry->State == DCOnLine ) {
+
+ DomainDownFlag = FALSE;
+
+ if( DCEntry->DCStatus != ERROR_SUCCESS ) {
+ DomainEntry->DomainState = DomainSick;
+ return;
+ }
+
+ //
+ // if this is PDC, mark PDC is heathy.
+ //
+
+ if( DCEntry->Type == NTPDC ) {
+ PDCOnLine = TRUE;
+ }
+ }
+ }
+
+ if( DomainDownFlag ) {
+ DomainEntry->DomainState = DomainDown;
+ return;
+ }
+
+ //
+ // if PDC is not online ..
+ //
+
+ if( !PDCOnLine ) {
+ DomainEntry->DomainState = DomainSick;
+ return;
+ }
+
+ //
+ // now determine the secure channel status
+ //
+
+ DCList = &(DomainEntry->DCList);
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ //
+ // if a DC is offline ..
+ //
+
+ if( DCEntry->State == DCOffLine ) {
+ DomainEntry->DomainState = DomainProblem;
+ return;
+ }
+
+ if( DCEntry->Type == LMBDC ) {
+
+ //
+ // LMBDC does not have trusted secure channel.
+ //
+
+ continue;
+ }
+
+ //
+ // examine the PDC secure channel status.
+ //
+
+ if( (DCEntry->PDCLinkStatus != ERROR_SUCCESS) ||
+ (DCEntry->TDCLinkState == FALSE) ) {
+ DomainEntry->DomainState = DomainProblem;
+ return;
+ }
+ }
+
+ DomainEntry->DomainState = DomainSuccess;
+ return;
+}
+
+BOOL
+IsDomainUpdateThreadRunning(
+ HANDLE *ThreadHandle
+ )
+/*++
+
+Routine Description:
+
+ Test if the Domain update thread is running
+
+ Enter with GlobalDomainUpdateThreadCritSect locked.
+
+Arguments:
+
+ ThreadHandle - pointer to thread handle.
+
+Return Value:
+
+ TRUE - The domain update thread is running
+
+ FALSE - The domain update thread is not running.
+
+--*/
+{
+ DWORD WaitStatus;
+
+ //
+ // Determine if the Update thread is already running.
+ //
+
+ if ( *ThreadHandle != NULL ) {
+
+ //
+ // Time out immediately if the thread is still running.
+ //
+
+ WaitStatus = WaitForSingleObject(
+ *ThreadHandle,
+ 0 );
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ return TRUE;
+
+ } else if ( WaitStatus == 0 ) {
+ CloseHandle( *ThreadHandle );
+ *ThreadHandle = NULL;
+ return FALSE;
+
+ } else {
+ NlMonDbgPrint((
+ "IsDomainUpdateThreadRunning: "
+ "Cannot WaitFor Domain Update thread: %ld\n",
+ WaitStatus ));
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+VOID
+StopDomainUpdateThread(
+ HANDLE *ThreadHandle,
+ BOOL *ThreadTerminateFlag
+ )
+/*++
+
+Routine Description:
+
+ Stops the domain update thread if it is running and waits for it to
+ stop.
+
+ Enter with GlobalDomainUpdateThreadCritSect locked.
+
+Arguments:
+
+ ThreadHandle - pointer to thread handle.
+
+ ThreadTerminateFlag - pointer to thread terminate flag.
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ //
+ // Ask the domain update thread to stop running.
+ //
+
+ *ThreadTerminateFlag = TRUE;
+
+ //
+ // Determine if the domain update thread is already running.
+ //
+
+ if ( *ThreadHandle != NULL ) {
+
+ //
+ // We've asked the thread to stop. It should do so soon.
+ // Wait for it to stop.
+ //
+
+ DWORD WaitStatus;
+
+ WaitStatus = WaitForSingleObject( *ThreadHandle,
+ 5*60*1000 ); // 5 minutes
+
+ if ( WaitStatus != 0 ) {
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ NlMonDbgPrint(("NlStopDomainUpdateThread"
+ "WaitForSingleObject 5-minute timeout.\n" ));
+
+ //
+ // kill the thread.
+ //
+
+ TerminateThread( *ThreadHandle, (DWORD)-1 );
+
+ } else {
+ NlMonDbgPrint(("NlStopDomainUpdateThread"
+ "WaitForSingleObject error: %ld\n",
+ WaitStatus));
+ }
+ }
+
+ CloseHandle( *ThreadHandle );
+ *ThreadHandle = NULL;
+ }
+
+ *ThreadTerminateFlag = FALSE;
+
+ return;
+}
+
+BOOL
+StartDomainUpdateThread(
+ PDOMAIN_ENTRY DomainEntry,
+ DWORD UpdateFlags
+ )
+/*++
+
+Routine Description:
+
+ Start the Domain Update thread if it is not already running.
+
+ NOTE: LOCK_LISTS() should be locked when this function is called.
+ since we do update the domain structure here.
+
+Arguments:
+
+ DomainEntry - Pointer to domain structure.
+
+Return Value:
+ None
+
+--*/
+{
+ DWORD LocalThreadHandle;
+
+ //
+ // If the domain update thread is already running, do nothing.
+ //
+
+ EnterCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ if ( IsDomainUpdateThreadRunning( &DomainEntry->ThreadHandle ) ) {
+ LeaveCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ return FALSE;
+ }
+
+ //
+ // if the last update was done within the GlobalUpdateTimeMSec
+ // msecs then ignore
+
+ //
+ // Initialize the domain update parameters
+ //
+
+ DomainEntry->ThreadTerminateFlag = FALSE;
+ DomainEntry->UpdateFlags = UpdateFlags;
+ DomainEntry->DomainState = DomainUnknown;
+
+ DomainEntry->ThreadHandle = CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE)
+ DomainUpdateThread,
+ (LPVOID)DomainEntry,
+ 0, // No special creation flags
+ &LocalThreadHandle );
+
+ if ( DomainEntry->ThreadHandle == NULL ) {
+
+ NlMonDbgPrint(("StartDomainUpdateThread"
+ "Can't create domain update Thread %lu\n",
+ GetLastError() ));
+
+ LeaveCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ return FALSE;
+ }
+
+ LeaveCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ return TRUE;
+
+}
+
+VOID
+DomainUpdateThread(
+ PDOMAIN_ENTRY DomainEntry
+ )
+/*++
+
+Routine Description:
+
+ Update and validate status of DCs and Trusted DCs of the specifed
+ Monitored domain.
+
+Arguments:
+
+ DomainEntry : Pointer to the domain entry to be updated.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+ DWORD UpdateFlags = DomainEntry->UpdateFlags;
+ BOOL ThreadTerminate = DomainEntry->ThreadTerminateFlag;
+
+ //
+ // monitored domain first.
+ //
+
+ if (DomainEntry->IsMonitoredDomain ) {
+
+ //
+ // update lists.
+ //
+
+ if( UpdateFlags & UPDATE_DCS_FROM_SERVER_ENUM ) {
+ UpdateDCListByServerEnum(
+ &DomainEntry->Name,
+ &DomainEntry->DCList );
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ if( UpdateFlags & UPDATE_DCS_FROM_DATABASE ) {
+ UpdateDCListFromDatabase(
+ &DomainEntry->Name,
+ &DomainEntry->DCList );
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate) {
+ return;
+ }
+
+ if( UpdateFlags & UPDATE_TRUST_DOMAINS_FROM_DATABASE ) {
+ UpdateTrustList( DomainEntry );
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ UpdateTrustConnectionList( DomainEntry );
+
+ //
+ // start update threads for new trusted domain introduced
+ // here.
+ //
+
+ if( GlobalMonitorTrust ) {
+ UpdateAndValidateLists( UpdateFlags, FALSE );
+ }
+ }
+
+ //
+ // validate list content.
+ //
+
+ if( UpdateFlags & VALIDATE_DCS ) {
+
+ DCList = &(DomainEntry->DCList);
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ (VOID) ValidateDC( DCEntry, &DomainEntry->Name );
+
+ //
+ // update trust connection status.
+ //
+
+ ValidateTrustConnectionList(
+ DCEntry,
+ (BOOL)(UpdateFlags & VALIDATE_TRUST_CONNECTIONS) );
+ }
+ }
+ }
+
+ //
+ // trusted domain.
+ //
+
+ else {
+
+ if( UpdateFlags & UPDATE_TRUST_DCS_FROM_SERVER_ENUM ) {
+ UpdateDCListByServerEnum(
+ &DomainEntry->Name,
+ &DomainEntry->DCList );
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ if( UpdateFlags & UPDATE_TRUST_DCS_FROM_DATABASE ) {
+ UpdateDCListFromDatabase(
+ &DomainEntry->Name,
+ &DomainEntry->DCList );
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ //
+ // validate list content.
+ //
+
+ if( UpdateFlags & VALIDATE_TRUST_DCS ) {
+
+ DCList = &(DomainEntry->DCList);
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag || ThreadTerminate ) {
+ return;
+ }
+
+ (VOID) ValidateDC( DCEntry, &DomainEntry->Name );
+ }
+ }
+ }
+
+ //
+ // update domain status.
+ //
+
+ UpdateDomainState( DomainEntry );
+
+ //
+ // Set GloballUpdateEvent to enable the display thread to
+ // display the update.
+ //
+
+ if ( !SetEvent( GlobalUpdateEvent ) ) {
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("UpdateAndValidateDomain: Cannot set "
+ "GlobalUpdateEvent: %lu\n",
+ WinError ));
+ }
+
+ //
+ // finally set the last update time.
+ //
+
+
+ DomainEntry->LastUpdateTime = GetCurrentTime();
+}
+
+VOID
+UpdateAndValidateLists(
+ DWORD UpdateFlags,
+ BOOL ForceFlag
+ )
+/*++
+
+Routine Description:
+
+ Update and validate all lists.
+
+Arguments:
+
+ UpdateFlags : This bit mapped flags indicate what need to be update
+ during this pass.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ PLIST_ENTRY DomainList;
+ PLIST_ENTRY NextDomainEntry;
+ PMONITORED_DOMAIN_ENTRY DomainMonEntry;
+ PDOMAIN_ENTRY DomainEntry;
+ DWORD CurrentTime;
+
+ //
+ // delete if any GlobalDomainsMonitored entry marked to be deleted.
+ //
+
+ LOCK_LISTS();
+ DomainList = &GlobalDomainsMonitored;
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList; ) {
+
+ PLIST_ENTRY TDomainList;
+ PLIST_ENTRY NextTDomainEntry;
+ PTRUSTED_DOMAIN_ENTRY TDomainEntry;
+
+ DomainMonEntry = (PMONITORED_DOMAIN_ENTRY)NextDomainEntry;
+ NextDomainEntry = NextDomainEntry->Flink;
+
+ if( DomainMonEntry->DeleteFlag ) {
+
+ //
+ // unreference from GlobalDomains.
+ //
+
+ DomainMonEntry->DomainEntry->ReferenceCount--;
+ NetpAssert(DomainMonEntry->DomainEntry->ReferenceCount >= 0);
+
+ //
+ // unreference the trusted domains from GlobalDomains list.
+ //
+
+ TDomainList = &DomainMonEntry->DomainEntry->TrustedDomainList;
+
+ for( NextTDomainEntry = TDomainList->Flink;
+ NextTDomainEntry != TDomainList;
+ NextTDomainEntry = NextTDomainEntry->Flink ) {
+
+ TDomainEntry = (PTRUSTED_DOMAIN_ENTRY)NextTDomainEntry;
+
+ TDomainEntry->DomainEntry->ReferenceCount--;
+ NetpAssert(TDomainEntry->DomainEntry->ReferenceCount >= 0);
+ }
+
+ RemoveEntryList( (PLIST_ENTRY)DomainMonEntry );
+ NetpMemoryFree( DomainMonEntry );
+ }
+ }
+
+ //
+ // remove GlobalDomains entries that are not referenced anymore.
+ //
+
+ DomainList = &GlobalDomains;
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList; ) {
+
+ DomainEntry = (PDOMAIN_ENTRY)NextDomainEntry;
+ NextDomainEntry = NextDomainEntry->Flink;
+
+ if( DomainEntry->ReferenceCount == 0 ) {
+
+ //
+ // if the DomainUpdateThread is running, postpone this
+ // delete.
+ //
+
+ EnterCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ if ( !IsDomainUpdateThreadRunning( &DomainEntry->ThreadHandle ) ) {
+ RemoveEntryList( (PLIST_ENTRY)DomainEntry );
+ CleanupDomainEntry( DomainEntry );
+ }
+ LeaveCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ }
+ }
+
+ CurrentTime = GetCurrentTime();
+
+ //
+ // if we are monitoring trusted domains also then update
+ // GlobalDomains otherwise update just GlobalDomainsMonitored.
+ //
+
+ if( GlobalMonitorTrust ) {
+ DomainList = &GlobalDomains;
+ }
+ else {
+ DomainList = &GlobalDomainsMonitored;
+ }
+
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList;
+ NextDomainEntry = NextDomainEntry->Flink ) {
+
+ if( GlobalMonitorTrust ) {
+ DomainEntry = (PDOMAIN_ENTRY)NextDomainEntry;
+ }
+ else {
+ DomainEntry =
+ ((PMONITORED_DOMAIN_ENTRY)NextDomainEntry)->DomainEntry;
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ //
+ // if the last update was done within the last
+ // GlobalUpdateTimeMSec msecs then don't start UpdateThread.
+ // However start the UpdateThread during startup and when
+ // forced.
+ //
+ // Also takecare of wrap around
+ //
+
+ if( (ForceFlag == TRUE) ||
+ (DomainEntry->LastUpdateTime == 0) ||
+ (CurrentTime - DomainEntry->LastUpdateTime > GlobalUpdateTimeMSec ) ) {
+
+ StartDomainUpdateThread( DomainEntry, UpdateFlags );
+ }
+ }
+
+ UNLOCK_LISTS();
+}
+
+DWORD
+InitGlobals(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Initialize all global variables.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ GlobalTrace = CONST_GLOBALTRACE;
+ GlobalMonitorTrust = CONST_GLOBALMONITORTRUST;
+ GlobalUpdateTimeMSec = CONST_GLOBALUPDATETIME * 60000;
+
+ InitializeListHead( &GlobalDomains );
+ InitializeListHead( &GlobalDomainsMonitored );
+ InitializeCriticalSection( &GlobalListCritSect );
+ InitializeCriticalSection( &GlobalDomainUpdateThreadCritSect );
+
+ GlobalTerminateFlag = FALSE;
+
+ GlobalTerminateEvent = CreateEvent( NULL, // No security attributes
+ TRUE, // Must be manual reset
+ FALSE, // Initially not signaled
+ NULL );// No name
+
+ if( GlobalTerminateEvent == NULL ) {
+
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("Can't create GlobalTerminateEvent %lu.\n", WinError));
+
+ return( WinError );
+ }
+
+ GlobalRefreshEvent = CreateEvent( NULL, // No security attributes
+ FALSE, // Must be auto reset
+ FALSE, // Initially not signaled
+ NULL );// No name
+
+ if( GlobalRefreshEvent == NULL ) {
+
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("Can't create GlobalRefreshEvent %lu.\n", WinError));
+
+ CloseHandle( GlobalTerminateEvent );
+ return( WinError );
+ }
+
+ GlobalRefreshDoneEvent = CreateEvent( NULL, // No security attributes
+ FALSE, // Must be auto reset
+ FALSE, // Initially not signaled
+ NULL );// No name
+
+ if( GlobalRefreshDoneEvent == NULL ) {
+
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("Can't create GlobalRefresheDoneEvent %lu.\n", WinError));
+
+ CloseHandle( GlobalTerminateEvent );
+ CloseHandle( GlobalRefreshEvent );
+ return( WinError );
+ }
+
+ GlobalUpdateEvent = CreateEvent( NULL, // No security attributes
+ FALSE, // Must be auto reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if( GlobalUpdateEvent == NULL ) {
+
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("Can't create GlobalUpdateEvent %lu.\n", WinError));
+
+ CloseHandle( GlobalTerminateEvent );
+ CloseHandle( GlobalRefreshEvent );
+ CloseHandle( GlobalRefreshDoneEvent );
+ return( WinError );
+ }
+
+ GlobalInitialized = TRUE;
+ return(ERROR_SUCCESS);
+}
+
+VOID
+FreeList(
+ PLIST_ENTRY List
+ )
+/*++
+
+Routine Description:
+
+ Freeup entries in a given lists.
+
+Arguments:
+
+ List : pointer to a list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY NextEntry;
+
+ while ( !IsListEmpty(List) ) {
+ NextEntry = RemoveHeadList(List);
+ NetpMemoryFree( NextEntry );
+ }
+}
+
+VOID
+CleanupDomainEntry(
+ PDOMAIN_ENTRY DomainEntry
+ )
+/*++
+
+Routine Description:
+
+ Frees a domain entry and its lists.
+
+Arguments:
+
+ DomainEntry : pointer to domain structure.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+
+
+ //
+ // first free trusted dc lists
+ //
+
+ DCList = &(DomainEntry->DCList);
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+ FreeList( &DCEntry->TrustedDCs );
+ }
+
+ //
+ // now free the DC list.
+ //
+
+ FreeList( &DomainEntry->DCList );
+
+ //
+ // now free trusted domain list.
+ //
+
+ FreeList( &DomainEntry->TrustedDomainList );
+}
+
+VOID
+CleanupLists(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Freeup all lists.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PDOMAIN_ENTRY DomainEntry;
+ PLIST_ENTRY NextDomainEntry;
+
+
+ LOCK_LISTS();
+
+ while ( !IsListEmpty(&GlobalDomains) ) {
+
+ NextDomainEntry = RemoveHeadList(&GlobalDomains);
+ DomainEntry = (PDOMAIN_ENTRY)NextDomainEntry;
+ CleanupDomainEntry( DomainEntry );
+ }
+
+ FreeList(&GlobalDomainsMonitored);
+ UNLOCK_LISTS();
+}
+
+
+VOID
+WorkerThread(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This thread updates the lists and validate the list content.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ None.
+
+--*/
+{
+#define COUNT_UPDATE_FROM_DATABASE 50
+#define COUNT_VALIDATE_TRUST_CONNECTIONS 5
+
+#define WAIT_COUNT 2
+#define REFRESH_EVENT 0
+#define TERMINATE_EVENT 1
+
+ DWORD LoopCount;
+ DWORD WaitStatus;
+ HANDLE WaitHandles[ WAIT_COUNT ];
+ DWORD UpdateFlags;
+
+ //
+ // perpare wait event array.
+ //
+
+ WaitHandles[REFRESH_EVENT] = GlobalRefreshEvent;
+ WaitHandles[TERMINATE_EVENT] = GlobalTerminateEvent;
+
+ LoopCount = 1;
+ for( ;; ) {
+
+ //
+ // wait for one of the following event to happen :
+ //
+ // 1. GlobalRefreshEvent
+ // 2. GlobalTerminateEvent.
+ // 3. Timeout in GlobalUpdateTimeMSec
+ //
+
+ WaitStatus = WaitForMultipleObjects(
+ WAIT_COUNT,
+ WaitHandles,
+ FALSE, // Wait for ANY handle
+ GlobalUpdateTimeMSec );
+
+ switch ( WaitStatus ) {
+ case WAIT_TIMEOUT: // timeout
+ //
+ // determine what to update.
+ //
+
+ if( LoopCount % COUNT_UPDATE_FROM_DATABASE ) {
+ UpdateFlags = UPDATE_FROM_DATABASE;
+ }
+ else if( LoopCount % COUNT_VALIDATE_TRUST_CONNECTIONS) {
+ UpdateFlags = UPDATE_TRUST_CONNECTIONS_STATUS;
+ }
+ else {
+ UpdateFlags = STANDARD_UPDATE;
+ }
+
+ UpdateAndValidateLists( UpdateFlags, FALSE );
+
+ //
+ // also indicate to the UI that the domain state has been
+ // changed.
+ //
+
+ if ( !SetEvent( GlobalUpdateEvent ) ) {
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("WokerThread: Cannot set "
+ "GlobalUpdateEvent: %lu\n",
+ WinError ));
+ }
+
+ LoopCount++;
+ break;
+
+ case REFRESH_EVENT:
+
+ UpdateAndValidateLists( UPDATE_ALL, TRUE );
+
+ if ( !SetEvent( GlobalRefreshDoneEvent ) ) {
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("WokerThread: Cannot set "
+ "GlobalRefreshDoneEvent: %lu\n",
+ WinError ));
+ }
+
+ //
+ // also indicate to the UI that the domain state has been
+ // changed.
+ //
+
+ if ( !SetEvent( GlobalUpdateEvent ) ) {
+ DWORD WinError;
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("WokerThread: Cannot set "
+ "GlobalUpdateEvent: %lu\n",
+ WinError ));
+ }
+
+ break;
+
+ case TERMINATE_EVENT:
+ //
+ // done.
+ //
+
+ goto Cleanup;
+ break;
+
+ default:
+ NlMonDbgPrint((
+ "WorkerThread: WaitForMultipleObjects error: %ld\n",
+ WaitStatus));
+ break;
+ }
+
+ }
+
+Cleanup:
+ return;
+}
+
diff --git a/private/net/svcdlls/logonsrv/monitor/nlmon.c b/private/net/svcdlls/logonsrv/monitor/nlmon.c
new file mode 100644
index 000000000..71226d7cb
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/nlmon.c
@@ -0,0 +1,922 @@
+/*--
+
+Copyright (c) 1993 Microsoft Corporation
+
+Module Name:
+
+ nlmon.c
+
+Abstract:
+
+ Trusted Domain monitor program.
+
+Author:
+
+ 10-May-1993 (madana)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+
+--*/
+
+#include <nlmon.h>
+
+VOID
+PrintUsage()
+/*++
+
+Routine Description:
+
+ Print usage of this apps.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ printf( "Usage: nlmon "
+ DOMAIN_PARAM "<DomainList> "
+ MONTRUST_PARAM "<Yes/No> "
+ UPDATE_PARAM "<Mins> "
+ DEBUG_PARAM "<HexValue> "
+ "\n" );
+
+ printf(
+ "\n"
+ " " DOMAIN_PARAM "<DomainList> - Specify comma separated domain list to monitor, default is Primary/Account Domain \n"
+ " " MONTRUST_PARAM "<Yes/No> - Specify to monitor trusted domains also, default is NO \n"
+ " " UPDATE_PARAM "<Mins> - Specify refresh time \n"
+ " " DEBUG_PARAM "<HexValue> - debug out level \n"
+ "\n" );
+}
+
+VOID
+DisplayDCEntryStatus(
+ PDC_ENTRY DCEntry
+)
+/*++
+
+Routine Description:
+
+ Display the content of DC Entry. List must be locked when this
+ function is called.
+
+Arguments:
+
+ DCEntry - pointer dc structure.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LPWSTR DCStateStr, DCTypeStr, DCReplStatusStr;
+
+ if( (DCEntry->State == DCOffLine) && (DCEntry->RetryCount == 1) ) {
+ return; // print status only when status is updated.
+ }
+
+ switch( DCEntry->State ) {
+ case DCOnLine:
+ DCStateStr = DCSTATE_ONLINE;
+ break;
+ case DCOffLine:
+ DCStateStr = DCSTATE_OFFLINE;
+ break;
+ default:
+ DCStateStr = UNKNOWN;
+ }
+
+ switch( DCEntry->Type ) {
+ case NTPDC:
+ DCTypeStr = TYPE_NTPDC;
+ break;
+ case NTBDC:
+ DCTypeStr = TYPE_NTBDC;
+ break;
+ case LMBDC:
+ DCTypeStr = TYPE_LMBDC;
+ break;
+ default:
+ DCTypeStr = UNKNOWN;
+ break;
+ }
+
+ if ( DCEntry->ReplicationStatus == 0 ) {
+ DCReplStatusStr = REPL_STATE_SYNC;
+ }
+ else if ( DCEntry->ReplicationStatus & NETLOGON_REPLICATION_IN_PROGRESS) {
+ DCReplStatusStr = REPL_STATE_PROGRESS;
+ }
+ else if ( DCEntry->ReplicationStatus & NETLOGON_REPLICATION_NEEDED ) {
+ DCReplStatusStr = REPL_STATE_REQ;
+ } else {
+ DCReplStatusStr = UNKNOWN;
+ }
+
+ printf("%-15wZ %-10ws %-10ws %-10ld %-10ws %-10ld\n",
+ &DCEntry->DCName, DCStateStr, DCTypeStr,
+ DCEntry->DCStatus, DCReplStatusStr,
+ DCEntry->PDCLinkStatus );
+
+ return;
+}
+
+VOID
+PrintTime(
+ VOID
+ )
+{
+ SYSTEMTIME SystemTime;
+
+ //
+ // print time.
+ //
+
+ GetLocalTime( &SystemTime );
+ printf( "TIME : [%02u/%02u %02u:%02u:%02u]\n",
+ SystemTime.wMonth,
+ SystemTime.wDay,
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond );
+}
+
+VOID
+DisplayLists(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Display the content of the global domain lists.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY DomainList;
+ PLIST_ENTRY NextDomainEntry;
+ PMONITORED_DOMAIN_ENTRY DomainMonEntry;
+
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextEntry;
+ PDC_ENTRY DCEntry;
+
+ PLIST_ENTRY TrustConnectList;
+ PLIST_ENTRY NextTrustConnectEntry;
+ PTD_LINK TrustConnectEntry;
+
+ //
+ // lock lists so that the update is paused while we display the
+ // current content.
+ //
+
+ LOCK_LISTS();
+
+ PrintTime();
+
+ DomainList = &GlobalDomainsMonitored;
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList;
+ NextDomainEntry = NextDomainEntry->Flink ) {
+
+ DomainMonEntry = (PMONITORED_DOMAIN_ENTRY)NextDomainEntry;
+
+ DCList = &(DomainMonEntry->DomainEntry->DCList);
+
+ if( IsListEmpty( DCList ) ) {
+ continue;
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ printf("DomainName: %wZ \n", &DomainMonEntry->Name);
+ printf("%-15s %-10s %-10s %-10s %-10s %-10s\n",
+ "ServerName", "DCState", "DCType", "DCStatus",
+ "ReplStatus", "PDCLinkStatus" );
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+ DisplayDCEntryStatus( DCEntry );
+
+ TrustConnectList = &DCEntry->TrustedDCs;
+
+ if( IsListEmpty( TrustConnectList ) ) {
+ continue;
+ }
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ //
+ // print connection status for each trusted DC.
+ //
+
+ printf("\n");
+ printf(" " "Trusted DC List:\n" );
+
+ printf(" "
+ "%-15s %-15s %-10s\n",
+ "TDomainName", "TDCName", "TSCStatus" );
+
+
+ for( NextTrustConnectEntry = TrustConnectList->Flink;
+ NextTrustConnectEntry != TrustConnectList;
+ NextTrustConnectEntry = NextTrustConnectEntry->Flink ) {
+
+ TrustConnectEntry = (PTD_LINK) NextTrustConnectEntry;
+
+
+ printf(" "
+ "%-15wZ %-15wZ %-10ld\n",
+ &TrustConnectEntry->TDName,
+ &TrustConnectEntry->DCName,
+ TrustConnectEntry->SecureChannelStatus );
+ }
+ printf("\n");
+
+ }
+
+ //
+ // print status trusted domain DCs.
+ //
+
+ if( GlobalMonitorTrust ) {
+
+ PLIST_ENTRY TrustedDomainEntry;
+ PLIST_ENTRY NextTrustedDomainEntry;
+ PTRUSTED_DOMAIN_ENTRY TrustedDomain;
+
+ TrustedDomainEntry = &DomainMonEntry->DomainEntry->TrustedDomainList;
+
+ if( !IsListEmpty( TrustedDomainEntry ) ) {
+
+ //
+ // if we are asked to terminate, do so.
+ //
+
+ if( GlobalTerminateFlag ) {
+ break;
+ }
+
+ printf(" " "Trusted Domain DCs:\n" );
+
+ for( NextTrustedDomainEntry = TrustedDomainEntry->Flink;
+ NextTrustedDomainEntry != TrustedDomainEntry;
+ NextTrustedDomainEntry = NextTrustedDomainEntry->Flink ) {
+
+ TrustedDomain = (PTRUSTED_DOMAIN_ENTRY) NextTrustedDomainEntry;
+
+ DCList = &TrustedDomain->DomainEntry->DCList;
+
+ printf(" "
+ "DomainName: %wZ \n",
+ &TrustedDomain->Name);
+
+ if( IsListEmpty( DCList ) ) {
+ printf(" " " " "EMPTY. \n");
+ continue;
+ }
+
+ printf(" "
+ "%-15s %-10s %-10s %-10s %-10s %-10s\n",
+ "ServerName", "DCState", "DCType", "DCStatus",
+ "ReplStatus", "PDCLinkStatus" );
+
+ for( NextEntry = DCList->Flink;
+ NextEntry != DCList; NextEntry = NextEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY) NextEntry;
+ printf(" ");
+ DisplayDCEntryStatus( DCEntry );
+ }
+ }
+ }
+
+ }
+
+ printf("%s\n", DOMAINLINE);
+ }
+
+ printf("%s\n", SESSLINE);
+
+ UNLOCK_LISTS();
+}
+
+BOOL
+InitDomainList(
+ PCHAR DomainList
+ )
+/*++
+
+Routine Description:
+
+ Parse comma separated domain list.
+
+Arguments:
+
+ DomainList - comma separate domain list.
+
+Return Value:
+
+ TRUE - if successfully parsed.
+ FALSE - iff the list is bad.
+
+--*/
+{
+ WCHAR DomainName[DNLEN + 1];
+ PWCHAR d;
+ PCHAR p;
+ DWORD Len;
+
+
+ p = DomainList;
+
+ if( *p == '\0' ) {
+ return(FALSE);
+ }
+
+ while (*p != '\0') {
+
+ d = DomainName; // destination to next domain name.
+
+ while( (*p != '\0') && (*p == ' ') ) {
+ p++; // skip leading blanks.
+ }
+
+ //
+ // read next domain name.
+ //
+
+ while( (*p != '\0') && (*p != ',') ) {
+
+ if( d < DomainName + DNLEN ) {
+ *d++ = (WCHAR) (*p++);
+ }
+ }
+
+ if( *p != '\0' ) {
+ p++; // skip comma.
+ }
+
+ //
+ // delete tail end blanks.
+ //
+
+ while ( (d > DomainName) && (*(d-1) == ' ') ) {
+ d--;
+ }
+
+ *d = L'\0';
+
+ if( Len = wcslen(DomainName) ) {
+
+ UNICODE_STRING UnicodeDomainName;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ LOCK_LISTS();
+ if( AddToMonitoredDomainList( &UnicodeDomainName ) == NULL ) {
+ UNLOCK_LISTS();
+ return(FALSE);
+ }
+ UNLOCK_LISTS();
+ }
+ }
+
+ if( IsListEmpty( &GlobalDomainsMonitored ) ) {
+ return(FALSE);
+ }
+
+ return(TRUE);
+
+}
+
+BOOL
+ParseInputParams(
+ IN int argc,
+ IN char ** argv
+ )
+/*++
+
+Routine Description:
+
+ Parses input parameters and sets appropriate global variables.
+
+Arguments:
+
+ argc - the number of command-line arguments.
+
+ argv - an array of pointers to the arguments.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PCHAR NextArg;
+ PCHAR EndArg;
+ DWORD i;
+ NT_PRODUCT_TYPE NtProductType;
+
+
+ //
+ // Loop through the arguments handle each in turn
+ //
+
+ for ( i = 1; i < (DWORD)argc; i++ ) {
+
+ //
+ // Handle /DOMAINLIST:
+ //
+
+ NextArg = argv[i];
+
+ if ( _strnicmp( NextArg, DOMAIN_PARAM, sizeof(DOMAIN_PARAM) - 1 ) == 0 ) {
+
+ NextArg = NextArg + sizeof(DOMAIN_PARAM) - 1;
+
+ if( !InitDomainList( NextArg ) ) {
+ PrintUsage();
+ return(FALSE);
+ }
+ }
+ else if ( _strnicmp( NextArg, MONTRUST_PARAM, sizeof(MONTRUST_PARAM) - 1 ) == 0 ) {
+
+ NextArg = NextArg + sizeof(MONTRUST_PARAM) - 1;
+
+ if( _strnicmp( NextArg, YES_PARAM, sizeof(YES_PARAM) -1 ) == 0 ) {
+ GlobalMonitorTrust = TRUE;
+ }
+ else if( _strnicmp( NextArg, NO_PARAM, sizeof(NO_PARAM) -1 ) == 0 ) {
+ GlobalMonitorTrust = FALSE;
+ }
+ else {
+ PrintUsage();
+ return(FALSE);
+ }
+ }
+ else if ( _strnicmp( NextArg, UPDATE_PARAM, sizeof(UPDATE_PARAM) - 1 ) == 0 ) {
+
+ NextArg = NextArg + sizeof(UPDATE_PARAM) - 1;
+
+ GlobalUpdateTimeMSec = strtoul( NextArg, &EndArg, 10 ) * 60000;
+
+ if( (INT)GlobalUpdateTimeMSec <= 0 ) {
+ PrintUsage();
+ return(FALSE);
+ }
+ }
+ else if ( _strnicmp( NextArg, DEBUG_PARAM, sizeof(DEBUG_PARAM) - 1 ) == 0 ) {
+
+ NextArg = NextArg + sizeof(DEBUG_PARAM) - 1;
+ GlobalTrace = strtoul( NextArg, &EndArg, 16 );
+ }
+ else {
+ PrintUsage();
+ return(FALSE);
+ }
+ }
+
+ if( IsListEmpty( &GlobalDomainsMonitored ) ) {
+
+ NTSTATUS Status;
+ PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo;
+
+ //
+ // Read product type
+ //
+
+ if ( RtlGetNtProductType( &NtProductType ) ) {
+ if ( (NtProductType != NtProductWinNt) &&
+ (NtProductType != NtProductLanManNt) &&
+ (NtProductType != NtProductServer) ) {
+
+ NlMonDbgPrint(("ParseInputParams: Invalid Product Type.\n"));
+
+ return(FALSE);
+ }
+ }
+ else {
+ NlMonDbgPrint(("ParseInputParams: Can't read product type.\n"));
+ }
+
+ Status = QueryLsaInfo(
+ NULL,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ (NtProductType == NtProductLanManNt) ?
+ PolicyAccountDomainInformation :
+ PolicyPrimaryDomainInformation,
+ (PVOID *) &DomainInfo,
+ NULL );
+
+ LOCK_LISTS();
+ if( ( DomainInfo->DomainName.Length == 0 ) ||
+ ( AddToMonitoredDomainList( &DomainInfo->DomainName ) == NULL ) ) {
+
+ LsaFreeMemory( DomainInfo );
+ UNLOCK_LISTS();
+ return(FALSE);
+ }
+ UNLOCK_LISTS();
+
+ LsaFreeMemory( DomainInfo );
+ }
+
+
+ IF_DEBUG( INIT ) {
+ PLIST_ENTRY DomainList;
+ PLIST_ENTRY NextDomainEntry;
+ PMONITORED_DOMAIN_ENTRY DomainMonEntry;
+
+
+ NlMonDbgPrint(("Domains Monitored:\n"));
+
+ DomainList = &GlobalDomainsMonitored;
+ i = 1;
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList;
+ NextDomainEntry = NextDomainEntry->Flink ) {
+
+ DomainMonEntry = (PMONITORED_DOMAIN_ENTRY)NextDomainEntry;
+
+ NlMonDbgPrint((" "
+ "%ld: %wZ\n", i++, &DomainMonEntry->Name ));
+ }
+
+ NlMonDbgPrint(("MonitorTrust: %s \n", (GlobalMonitorTrust) ? "YES" : "NO" ));
+ NlMonDbgPrint(("UpdateTime: %ld \n\n", GlobalUpdateTimeMSec ));
+ }
+
+ return(TRUE);
+}
+
+VOID
+CleanupGlobals(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Free all resources consumed.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ DWORD WaitStatus;
+
+ //
+ // wait for other threads to go away.
+ //
+
+ WaitStatus = WaitForSingleObject(
+ GlobalCmdProcessThreadHandle,
+ THREAD_WAIT_TIME );
+
+ if ( WaitStatus != 0 ) {
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ NlMonDbgPrint(("CleanupGlobals: "
+ "CmdProcess thread doesn't stop: %ld\n",
+ WaitStatus ));
+ } else {
+ NlMonDbgPrint(("CleanupGlobals: "
+ "Cannot WaitFor CmdProcess thread: %ld\n",
+ WaitStatus ));
+ }
+ }
+
+ CloseHandle( GlobalCmdProcessThreadHandle );
+ GlobalCmdProcessThreadHandle = NULL;
+
+ WaitStatus = WaitForSingleObject(
+ GlobalWorkerThreadHandle,
+ THREAD_WAIT_TIME );
+
+ if ( WaitStatus != 0 ) {
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ NlMonDbgPrint(("CleanupGlobals: "
+ "Worker thread doesn't stop: %ld\n",
+ WaitStatus ));
+ } else {
+ NlMonDbgPrint(("CleanupGlobals: "
+ "Cannot WaitFor Worker thread: %ld\n",
+ WaitStatus ));
+ }
+ }
+
+ CloseHandle( GlobalWorkerThreadHandle );
+ GlobalWorkerThreadHandle = NULL;
+
+ //
+ // now cleanup all lists.
+ //
+
+ CleanupLists();
+
+ //
+ // delete list critsect.
+ //
+
+ DeleteCriticalSection( &GlobalListCritSect );
+ DeleteCriticalSection( &GlobalDomainUpdateThreadCritSect );
+
+ //
+ // close event handles.
+ //
+
+ if( !CloseHandle( GlobalRefreshDoneEvent ) ) {
+ NlMonDbgPrint((
+ "Cleanup: CloseHandle GlobalRefreshDoneEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalRefreshEvent ) ) {
+ NlMonDbgPrint((
+ "Cleanup: CloseHandle GlobalRefreshEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalUpdateEvent ) ) {
+ NlMonDbgPrint((
+ "Cleanup: CloseHandle GlobalUpdateEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalTerminateEvent ) ) {
+ NlMonDbgPrint((
+ "Cleanup: CloseHandle GlobalTerminateEvent error: %lu\n",
+ GetLastError() ));
+ }
+}
+
+
+VOID
+CmdProcessThread(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This thread process input commands.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+{
+ CHAR InputCmd;
+
+ for( ;; ) {
+
+ //
+ // read next input command.
+ //
+
+ InputCmd = _getch();
+
+ switch( InputCmd ) {
+ case 'd':
+ case 'D':
+ DisplayLists();
+ break;
+
+ case 'r':
+ case 'R':
+
+ if ( !SetEvent( GlobalRefreshEvent ) ) {
+ NlMonDbgPrint(("CmdProcessThread: Cannot set "
+ "GlobalRefreshEvent: %lu\n",
+ GetLastError() ));
+ }
+ return;
+ break;
+
+ case EOF:
+ case '\003':
+ case 'q':
+ case 'Q':
+
+ if ( !SetEvent( GlobalTerminateEvent ) ) {
+ NlMonDbgPrint(("CmdProcessThread: Cannot set "
+ "termination event: %lu\n",
+ GetLastError() ));
+ }
+ else {
+ GlobalTerminateFlag = TRUE;
+ }
+
+ return;
+ break;
+
+ case 'h':
+ case 'H':
+ printf( "CmdUsage:\n"
+ " " "D/d: Display the last known status of servers.\n"
+ " " "R/r: Refresh list content.\n"
+ " " "H/h: Display this message.\n"
+ " " "Q/q: Quit this apps.\n"
+ "\n" );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+int _CRTAPI1
+main(
+ IN int argc,
+ IN char ** argv
+ )
+/*++
+
+Routine Description:
+
+ Monitors Trusted Domain DCs by calling various network control and
+ GetInfo APIs.
+
+Arguments:
+
+ argc - the number of command-line arguments.
+
+ argv - an array of pointers to the arguments.
+
+Return Value:
+
+ Exit status
+
+--*/
+{
+ DWORD ThreadHandle;
+
+#define WAIT_COUNT 2
+
+#define UPDATE_EVENT 0
+#define TERMINATE_EVENT 1
+
+ DWORD WaitStatus;
+ HANDLE WaitHandles[ WAIT_COUNT ];
+
+ DWORD WinError;
+
+ PrintTime();
+
+ //
+ // Initialize Globals.
+ //
+
+ WinError = InitGlobals();
+ if( WinError != ERROR_SUCCESS) {
+ return( WinError );
+ }
+
+ //
+ // parse input parameters.
+ //
+
+ if( !ParseInputParams( argc, argv ) ) {
+ goto Cleanup;
+ }
+
+ //
+ // Make initial DCList and TrustDomainList of domains we monitor.
+ //
+
+ UpdateAndValidateLists( UPDATE_ALL, TRUE );
+
+ //
+ // create worker thread.
+ //
+
+ GlobalWorkerThreadHandle =
+ CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE) WorkerThread,
+ NULL,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( GlobalWorkerThreadHandle == NULL ) {
+
+ NlMonDbgPrint(("Can't create Worker Thread %lu.\n", GetLastError()));
+
+ goto Cleanup;
+ }
+
+ //
+ // create command processing thread.
+ //
+
+ GlobalCmdProcessThreadHandle =
+ CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE) CmdProcessThread,
+ NULL,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( GlobalCmdProcessThreadHandle == NULL ) {
+
+ NlMonDbgPrint(("Can't create Command processing Thread %lu.\n",
+ GetLastError()));
+ goto Cleanup;
+ }
+
+ //
+ // perpare wait event array.
+ //
+
+ WaitHandles[UPDATE_EVENT] = GlobalUpdateEvent;
+ WaitHandles[TERMINATE_EVENT] = GlobalTerminateEvent;
+
+ for( ;; ) {
+
+ //
+ // wait for one of the following event to happen :
+ //
+ // 1. GlobalUpdateEvent
+ // 2. GlobalTerminateEvent.
+ //
+
+ WaitStatus = WaitForMultipleObjects(
+ WAIT_COUNT,
+ WaitHandles,
+ FALSE, // Wait for ANY handle
+ INFINITE );
+
+ switch ( WaitStatus ) {
+ case UPDATE_EVENT:
+ DisplayLists();
+ break;
+
+ case TERMINATE_EVENT:
+ //
+ // done.
+ //
+ goto Cleanup;
+
+ default:
+ NlMonDbgPrint((
+ "main: WaitForMultipleObjects error: %ld\n",
+ WaitStatus));
+ break;
+ }
+
+ }
+
+Cleanup:
+
+ CleanupGlobals();
+
+ return(0);
+}
+
diff --git a/private/net/svcdlls/logonsrv/monitor/nlmon.rc b/private/net/svcdlls/logonsrv/monitor/nlmon.rc
new file mode 100644
index 000000000..f304eef03
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/nlmon.rc
@@ -0,0 +1,12 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_APP
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "Microsoft\256 NetWork Licence Monitor Utility"
+
+#define VER_INTERNALNAME_STR "nlmon.exe"
+#define VER_ORIGINALFILENAME_STR "nlmon.exe"
+
+#include <common.ver>
+
diff --git a/private/net/svcdlls/logonsrv/monitor/sources b/private/net/svcdlls/logonsrv/monitor/sources
new file mode 100644
index 000000000..845590ad2
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/sources
@@ -0,0 +1,106 @@
+!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:
+
+
+Revision History:
+
+!ENDIF
+
+#
+# The MAJORCOMP and MINORCOMP variables are defined
+# so that $(MAJORCOMP)$(MINORCOMP)filename can be used in
+# cross compiling to provide unique filenames in a flat namespace.
+#
+
+MAJORCOMP=net
+MINORCOMP=logonsrv
+
+#
+# The TARGETNAME variable is defined by the developer. It is the name of
+# the target (component) that is being built by this makefile. It
+# should NOT include any path or file extension information.
+#
+
+TARGETNAME=nlmonlib
+
+#
+# The TARGETPATH and TARGETTYPE variables are defined by the developer.
+# The first specifies where the target is to be build. The second specifies
+# the type of target (either PROGRAM, DYNLINK, LIBRARY, UMAPPL_NOLIB or
+# BOOTPGM). UMAPPL_NOLIB is used when you're only building user-mode
+# apps and don't need to build a library.
+#
+
+TARGETPATH=$(BASEDIR)\public\sdk\lib
+
+TARGETTYPE=LIBRARY
+
+#
+# The TARGETLIBS specifies additional libraries to link with you target
+# image. Each library path specification should contain an asterisk (*)
+# where the machine specific subdirectory name should go.
+#
+
+TARGETLIBS=
+
+
+#
+# The INCLUDES variable specifies any include paths that are specific to
+# this source directory. Separate multiple directory paths with single
+# semicolons. Relative path specifications are okay.
+#
+
+INCLUDES=.;..;..\..\..\inc;..\..\..\..\inc
+
+#
+# The SOURCES variable is defined by the developer. It is a list of all the
+# source files for this component. Each source file should be on a separate
+# line using the line continuation character. This will minimize merge
+# conflicts if two developers adding source files to the same component.
+#
+
+!IFNDEF DISABLE_NET_UNICODE
+UNICODE=1
+NET_C_DEFINES=-DUNICODE
+!ENDIF
+
+SOURCES= monutil.c \
+ winutil.c
+
+#
+# This line makes the apps. to use crtdll.dll instead of libc.lib
+#
+
+USE_CRTDLL=1
+
+#
+# Next specify one or more user mode test programs and their type
+# UMTEST is used for optional test programs. UMAPPL is used for
+# programs that always get built when the directory is built.
+#
+
+UMTYPE=console
+UMAPPL=nlmon
+UMRES=$(@R).res
+UMLIBS= $(BASEDIR)\Public\Sdk\Lib\*\nlmonlib.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\netlib.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\samlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\netapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib
+
+NTTARGETFILE1=obj\*\nlmon.res
diff --git a/private/net/svcdlls/logonsrv/monitor/winutil.c b/private/net/svcdlls/logonsrv/monitor/winutil.c
new file mode 100644
index 000000000..634c6eea1
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/monitor/winutil.c
@@ -0,0 +1,922 @@
+/*--
+
+Copyright (c) 1993 Microsoft Corporation
+
+Module Name:
+
+ monutil.c
+
+Abstract:
+
+ Contains support functions required for GUI version of the monitor
+ program.
+
+Author:
+
+ 14-Jun-1993 (madana)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+#include <nlmon.h>
+
+VOID
+CleanupWin(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Free all resources consumed.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // now cleanup all lists.
+ //
+
+ CleanupLists();
+
+ //
+ // delete list critsect.
+ //
+
+ DeleteCriticalSection( &GlobalListCritSect );
+ DeleteCriticalSection( &GlobalDomainUpdateThreadCritSect );
+
+ //
+ // close event handles.
+ //
+
+ if( !CloseHandle( GlobalRefreshDoneEvent ) ) {
+ NlMonDbgPrint((
+ "CleanupWin: CloseHandle GlobalRefreshDoneEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalRefreshEvent ) ) {
+ NlMonDbgPrint((
+ "CleanupWin: CloseHandle GlobalRefreshEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalUpdateEvent ) ) {
+ NlMonDbgPrint((
+ "CleanupWin: CloseHandle GlobalUpdateEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+ if( !CloseHandle( GlobalTerminateEvent ) ) {
+ NlMonDbgPrint((
+ "CleanupWin: CloseHandle GlobalTerminateEvent error: %lu\n",
+ GetLastError() ));
+ }
+}
+
+DWORD
+StartMonitor(
+ LPWSTR DomainList,
+ DWORD Interval,
+ BOOL MonitorTD
+ )
+/*++
+
+Routine Description:
+
+ This function sets up necessary data structures and starts a worker
+ thread to update the domain status at the given interval.
+
+Arguments:
+
+ DomainList : list of domains (separated by comma) to be monitored.
+
+ Interval : Status update interval in millisecond.
+
+ MonitorTD : Whether to update the trusted domain DC list or not.
+
+Return Value:
+
+ NT Status code.
+
+--*/
+{
+ DWORD ThreadHandle;
+ DWORD WinError;
+
+ //
+ // Initialize Globals.
+ //
+
+ WinError = InitGlobals();
+
+ if( WinError != ERROR_SUCCESS ) {
+ return(WinError);
+ }
+
+ //
+ // parse input parameters.
+ //
+
+ GlobalMonitorTrust = MonitorTD;
+ GlobalUpdateTimeMSec = Interval;
+ (VOID)InitDomainListW( DomainList );
+
+ //
+ // initial complete update.
+ //
+
+ UpdateAndValidateLists( UPDATE_ALL, TRUE );
+
+ //
+ // create worker thread.
+ //
+
+ GlobalWorkerThreadHandle =
+ CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE) WorkerThread,
+ NULL,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( GlobalWorkerThreadHandle == NULL ) {
+
+ DWORD WinError;
+
+ WinError = GetLastError();
+
+ NlMonDbgPrint(("Can't create Worker Thread %lu.\n", WinError));
+
+ CleanupWin();
+ return( WinError );
+ }
+
+ return( ERROR_SUCCESS );
+}
+
+VOID
+StopMonitor(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This will stop the worker thread, cleanup the lists and free up all
+ resources consumed.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ DWORD WinError;
+ DWORD WaitStatus;
+
+ PLIST_ENTRY DomainList;
+ PLIST_ENTRY NextDomainEntry;
+ PDOMAIN_ENTRY DomainEntry;
+
+ //
+ // Set Terminate Event to stop the worker.
+ //
+
+ if ( !SetEvent( GlobalTerminateEvent ) ) {
+
+ WinError = GetLastError();
+ NlMonDbgPrint(("StopMonitor: Cannot set "
+ "termination event: %lu\n",
+ WinError ));
+ return;
+ }
+
+ GlobalTerminateFlag = TRUE;
+
+ WaitStatus = WaitForSingleObject(
+ GlobalWorkerThreadHandle,
+ THREAD_WAIT_TIME );
+
+ if ( WaitStatus != 0 ) {
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ NlMonDbgPrint(("StopMonitor: "
+ "Worker thread doesn't stop: %ld\n",
+ WaitStatus ));
+ } else {
+ NlMonDbgPrint(("StopMonitor: "
+ "Cannot WaitFor Worker thread: %ld\n",
+ WaitStatus ));
+ }
+ }
+
+ CloseHandle( GlobalWorkerThreadHandle );
+ GlobalWorkerThreadHandle = NULL;
+
+ //
+ // Stop all DomainUpdateThreads.
+ //
+
+ LOCK_LISTS();
+ EnterCriticalSection( &GlobalDomainUpdateThreadCritSect );
+
+ DomainList = &GlobalDomains;
+ for( NextDomainEntry = DomainList->Flink;
+ NextDomainEntry != DomainList;
+ NextDomainEntry = NextDomainEntry->Flink ) {
+
+ DomainEntry = (PDOMAIN_ENTRY)NextDomainEntry;
+
+ if ( IsDomainUpdateThreadRunning( &DomainEntry->ThreadHandle ) ) {
+
+ //
+ // Wait and stop this thread. Unlock the lists so that the
+ // DomainUpdateThread can complete.
+ //
+
+ UNLOCK_LISTS();
+ StopDomainUpdateThread(
+ &DomainEntry->ThreadHandle,
+ &DomainEntry->ThreadTerminateFlag );
+
+ //
+ // since we dropped the list above, restart from begining.
+ //
+ LOCK_LISTS();
+ NextDomainEntry = &GlobalDomains;
+ }
+ }
+
+ LeaveCriticalSection( &GlobalDomainUpdateThreadCritSect );
+ UNLOCK_LISTS();
+
+ CleanupWin();
+ return;
+}
+
+DOMAIN_STATE
+QueryHealth(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function returns health of given domain.
+
+Arguments:
+
+ DomainName : Domain name of whose health will be returned.
+
+Return Value:
+
+ Domain State.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+ DOMAIN_STATE State = DomainUnknown;
+
+ if (GlobalInitialized)
+ {
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ LOCK_LISTS();
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ State = DomainUnknown;
+ }
+ else {
+ State = DomainEntry->DomainEntry->DomainState;
+ }
+
+ UNLOCK_LISTS();
+ }
+
+ return(State);
+}
+
+
+LPWSTR
+QueryPDC(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function returns PDC name of the given domain.
+
+Arguments:
+
+ DomainName : Domain name of whose PDC name will be returned.
+
+Return Value:
+
+ PDC Name.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+
+ PLIST_ENTRY DCList;
+ PLIST_ENTRY NextDCEntry;
+ PDC_ENTRY DCEntry;
+
+ if (GlobalInitialized)
+ {
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ LOCK_LISTS();
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ UNLOCK_LISTS();
+ return(NULL);
+ }
+
+ //
+ // Search for the PDC from the DC List.
+ //
+
+ DCList = &DomainEntry->DomainEntry->DCList;
+ for( NextDCEntry = DCList->Flink;
+ NextDCEntry != DCList;
+ NextDCEntry = NextDCEntry->Flink ) {
+
+ DCEntry = (PDC_ENTRY)NextDCEntry;
+
+ if( DCEntry->Type == NTPDC ) {
+
+ LPWSTR PDCName;
+
+ //
+ // Capture the DC Name.
+ //
+
+ PDCName = NetpMemoryAllocate(
+ DCEntry->DCName.MaximumLength );
+
+ if( PDCName != NULL ) {
+
+ RtlCopyMemory(
+ PDCName,
+ DCEntry->DCName.Buffer,
+ DCEntry->DCName.MaximumLength );
+
+ }
+
+ UNLOCK_LISTS();
+ return( PDCName );
+ }
+
+ }
+
+ UNLOCK_LISTS();
+ }
+
+ return( NULL );
+}
+
+PLIST_ENTRY
+QueryTrustedDomain(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function returns a trusted domain list of the specified
+ domain.
+
+ This function must be called after LOCKING the list and the
+ caller must UNLOCK the list after usage.
+
+Arguments:
+
+ DomainName : Name of the domain whose trusted domain list is
+ returned.
+
+Return Value:
+
+ Trusted Domain list.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ return(NULL);
+ }
+
+ return( &DomainEntry->DomainEntry->TrustedDomainList );
+}
+
+PLIST_ENTRY
+QueryDCList(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function returns the pointer to a doublely link list
+ data structures of all DCs in the given domain.
+
+ This function must be called after LOCKING the list and the
+ caller must UNLOCK the list after usage.
+
+Arguments:
+
+ DomainName : Name of the domain whose DC list data structure will be
+ returned.
+
+Return Value:
+
+ List pointer.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ return(NULL);
+ }
+
+ return( &DomainEntry->DomainEntry->DCList );
+}
+
+PLIST_ENTRY
+QueryTDLink(
+ const LPWSTR DomainName,
+ const LPWSTR DCName
+ )
+/*++
+
+Routine Description:
+
+ This function returns the list of trusted DCs.
+
+ This function must be called after LOCKING the list and the
+ caller must UNLOCK the list after usage.
+
+Arguments:
+
+ DCName : Name of the DC whose trusted DCs list is returned.
+
+Return Value:
+
+ List pointer.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ UNICODE_STRING UnicodeDCName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+ PDC_ENTRY DCEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+ RtlInitUnicodeString( &UnicodeDCName, DCName );
+
+ //
+ // search the specified domain.
+ //
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ return(NULL);
+ }
+
+ //
+ // search the specified DC.
+ //
+
+ DCEntry = (PDC_ENTRY) FindNamedEntry(
+ &DomainEntry->DomainEntry->DCList,
+ &UnicodeDCName );
+
+ if( DCEntry == NULL ) {
+ return(NULL);
+ }
+
+ return( &DCEntry->TrustedDCs );
+}
+
+PLIST_ENTRY
+QueryTDCList(
+ const LPWSTR DomainName,
+ const LPWSTR TrustedDomainName
+ )
+/*++
+
+Routine Description:
+
+ This function returns a trusted domain's dc list of the specified
+ domain.
+
+ This function must be called after LOCKING the list and the
+ caller must UNLOCK the list after usage.
+
+Arguments:
+
+ DomainName : Name of the domain whose trusted domain list is
+ returned.
+
+ TrustedDomainName: The name of the trusted domain
+Return Value:
+
+ Trusted Domain's dc list.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ UNICODE_STRING UnicodeTrustedDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+ PTRUSTED_DOMAIN_ENTRY TDEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+ RtlInitUnicodeString( &UnicodeTrustedDomainName, TrustedDomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ return(NULL);
+ }
+
+ TDEntry = (PTRUSTED_DOMAIN_ENTRY) FindNamedEntry(
+ &DomainEntry->DomainEntry->TrustedDomainList,
+ &UnicodeTrustedDomainName);
+
+ if (TDEntry == NULL)
+ {
+ return(NULL);
+ }
+
+ return( &TDEntry->DomainEntry->DCList );
+}
+
+DWORD
+DisConnect(
+ const LPWSTR DomainName,
+ const LPWSTR DCName,
+ const LPWSTR TrustedDomainName
+ )
+/*++
+
+Routine Description:
+
+ This function disconnects from the current trusted DC of the
+ specified trusted domain and makes a discovery of the another
+ trusted DC.
+
+Arguments:
+
+ DCName : Name of the DC whose specified trusted DC is disconnected.
+
+ TrustedDomainName : Name of the trusted domain DC whose trusted DC
+ is disconnected.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ PNETLOGON_INFO_2 NetlogonInfo2 = NULL;
+
+ UNICODE_STRING UnicodeDomainName;
+ UNICODE_STRING UnicodeTrustedDomainName;
+ UNICODE_STRING UnicodeDCName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+ PTD_LINK TDLinkEntry;
+ PDC_ENTRY DCEntry;
+
+ PLIST_ENTRY TDLinkList;
+ PLIST_ENTRY NextTDLinkEntry;
+ BOOL TDCLinkState;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+ RtlInitUnicodeString( &UnicodeTrustedDomainName, TrustedDomainName );
+ RtlInitUnicodeString( &UnicodeDCName, DCName );
+
+ NetStatus = I_NetLogonControl2(
+ DCName,
+ NETLOGON_CONTROL_REDISCOVER,
+ 2,
+ (LPBYTE)&TrustedDomainName,
+ (LPBYTE *)&NetlogonInfo2 );
+
+ //
+ // search the specified domain.
+ //
+
+ LOCK_LISTS();
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ NetStatus = ERROR_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+
+ DCEntry = (PDC_ENTRY) FindNamedEntry(
+ &DomainEntry->DomainEntry->DCList,
+ &UnicodeDCName);
+
+ if (DCEntry == NULL) {
+ NetStatus = NERR_DCNotFound;
+ goto Cleanup;
+ }
+
+ TDLinkEntry = (PTD_LINK) FindNamedEntry(
+ &DCEntry->TrustedDCs,
+ &UnicodeTrustedDomainName);
+
+ if (TDLinkEntry == NULL) {
+ NetStatus = ERROR_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+
+
+ if ( NetStatus != NERR_Success ) {
+ //
+ // Cleanup the previous DC Name.
+ //
+
+ TDLinkEntry->DCName.Length = 0;
+ *TDLinkEntry->DCName.Buffer = L'\0';
+
+ TDLinkEntry->SecureChannelStatus = NetStatus;
+ goto Cleanup;
+ }
+
+
+ //
+ // update TDLinkEntry.
+ //
+
+ //
+ // copy this DC name.
+ //
+
+ TDLinkEntry->DCName.Length =
+ wcslen( NetlogonInfo2->netlog2_trusted_dc_name ) * sizeof(WCHAR);
+
+ RtlCopyMemory(
+ TDLinkEntry->DCName.Buffer,
+ NetlogonInfo2->netlog2_trusted_dc_name,
+ TDLinkEntry->DCName.Length + sizeof(WCHAR) );
+ // copy terminator also
+
+ TDLinkEntry->SecureChannelStatus =
+ NetlogonInfo2->netlog2_tc_connection_status;
+
+
+ //
+ // update DC status info.
+ //
+
+ DCEntry->ReplicationStatus = NetlogonInfo2->netlog2_flags;
+ DCEntry->PDCLinkStatus = NetlogonInfo2->netlog2_pdc_connection_status;
+
+ //
+ // validate trusted DC.
+ //
+
+ if( (TDLinkEntry->SecureChannelStatus == NERR_Success) &&
+ (*TDLinkEntry->DCName.Buffer != L'\0') ) {
+
+ NET_API_STATUS LocalNetStatus;
+
+ LocalNetStatus = IsValidNTDC(
+ &TDLinkEntry->DCName,
+ &TDLinkEntry->TDName );
+
+ if( LocalNetStatus != NERR_Success ) {
+
+ //
+ // hack, hack, hack ...
+ //
+ // For foreign trusted domains, the above check will
+ // return ERROR_LOGON_FAILURE. Just ignore this
+ // error for now. When the domain wide credential is
+ // implemeted this problem will be cured.
+ //
+
+
+ if( LocalNetStatus != ERROR_LOGON_FAILURE ) {
+ TDLinkEntry->SecureChannelStatus = LocalNetStatus;
+ TDCLinkState = FALSE;
+ }
+ }
+
+ }
+
+ //
+ // recompute trust connection status and domain status.
+ //
+
+ TDLinkList = &DCEntry->TrustedDCs;
+ TDCLinkState = TRUE;
+
+ for( NextTDLinkEntry = TDLinkList->Flink;
+ NextTDLinkEntry != TDLinkList;
+ NextTDLinkEntry = NextTDLinkEntry->Flink ) {
+
+ TDLinkEntry = (PTD_LINK) NextTDLinkEntry;
+
+ if( TDLinkEntry->SecureChannelStatus != NERR_Success ) {
+ TDCLinkState = FALSE;
+ }
+ }
+ DCEntry->TDCLinkState = TDCLinkState;
+
+ //
+ // recompute domain status if the update is not in progress.
+ //
+
+ if( DomainEntry->DomainEntry->DomainState != DomainUnknown ) {
+ UpdateDomainState( DomainEntry->DomainEntry );
+
+ //
+ // also notify UI.
+ //
+
+ SetEvent( GlobalUpdateEvent );
+ }
+
+Cleanup:
+
+ if( NetlogonInfo2 != NULL ) {
+ NetApiBufferFree( NetlogonInfo2 );
+ }
+
+ UNLOCK_LISTS();
+ return(NetStatus);
+}
+
+VOID
+AddDomainToList(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function adds the new specified domain to domain list.
+
+Arguments:
+
+ DomainName : Name of the domain to be added to list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ LOCK_LISTS();
+ (VOID)AddToMonitoredDomainList( &UnicodeDomainName );
+
+ //
+ // update this domain DC list.
+ //
+
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+ if( DomainEntry == NULL) {
+ UNLOCK_LISTS();
+ return;
+ }
+
+ StartDomainUpdateThread( DomainEntry->DomainEntry, UPDATE_ALL );
+ UNLOCK_LISTS();
+
+
+ return;
+}
+
+VOID
+RemoveDomainFromList(
+ const LPWSTR DomainName
+ )
+/*++
+
+Routine Description:
+
+ This function removes the new specified domain from domain list.
+
+Arguments:
+
+ DomainName : Name of the domain to be removed to list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ UNICODE_STRING UnicodeDomainName;
+ PMONITORED_DOMAIN_ENTRY DomainEntry;
+
+ RtlInitUnicodeString( &UnicodeDomainName, DomainName );
+
+ //
+ // search the specified domain.
+ //
+
+ LOCK_LISTS();
+ DomainEntry = (PMONITORED_DOMAIN_ENTRY) FindNamedEntry(
+ &GlobalDomainsMonitored,
+ &UnicodeDomainName );
+
+ if( (DomainEntry == NULL) || (DomainEntry->DeleteFlag) ) {
+ UNLOCK_LISTS();
+ return;
+ }
+
+ //
+ // mark this entry is dead.
+ //
+
+ DomainEntry->DeleteFlag = TRUE;
+
+ UNLOCK_LISTS();
+ return;
+}
+
diff --git a/private/net/svcdlls/logonsrv/nlbind.h b/private/net/svcdlls/logonsrv/nlbind.h
new file mode 100644
index 000000000..a7502830a
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/nlbind.h
@@ -0,0 +1,45 @@
+/*++
+
+Copyright (c) 1993 Microsoft Corporation
+
+Module Name:
+
+ nlbind.h
+
+Abstract:
+
+ Interface to the Netlogon service RPC handle cacheing routines
+
+Author:
+
+ Cliff Van Dyke (01-Oct-1993)
+
+Revision History:
+
+--*/
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Procedure forwards
+//
+////////////////////////////////////////////////////////////////////////////
+
+VOID
+NlBindingAttachDll (
+ VOID
+ );
+
+VOID
+NlBindingDetachDll (
+ VOID
+ );
+
+NTSTATUS
+NlBindingAddServerToCache (
+ IN LPWSTR UncServerName
+ );
+
+NTSTATUS
+NlBindingRemoveServerFromCache (
+ IN LPWSTR UncServerName
+ );
diff --git a/private/net/svcdlls/logonsrv/server/announce.c b/private/net/svcdlls/logonsrv/server/announce.c
new file mode 100644
index 000000000..499cdde79
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/announce.c
@@ -0,0 +1,1243 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ announce.c
+
+Abstract:
+
+ Routines to handle ssi announcements.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+--*/
+
+//
+// Common include files.
+//
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <alertmsg.h> // ALERT_* defines
+#include <stddef.h> // offsetof
+#include <stdlib.h> // max()
+
+//
+// Maximum number of pulses that we allow a BDC to ignore before ignoring it.
+//
+#define MAX_PULSE_TIMEOUT 3
+
+VOID
+NlRemovePendingBdc(
+ IN PSERVER_SESSION ServerSession
+ )
+/*++
+
+Routine Description:
+
+ Remove the specified Server Session from the list of pending BDCs.
+
+ Enter with the ServerSessionTable Sem locked
+
+Arguments:
+
+ ServerSession -- Pointer to the server session structure to remove from the
+ list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // Ensure the server session is really on the list.
+ //
+
+ if ( (ServerSession->SsFlags & SS_PENDING_BDC) == 0 ) {
+ return;
+ }
+
+ //
+ // Decrement the count of pending BDCs
+ //
+
+ NlAssert( NlGlobalPendingBdcCount > 0 );
+ NlGlobalPendingBdcCount --;
+
+ //
+ // If this is the last BDC in the pending list,
+ // turn off the timer.
+ //
+
+ if ( NlGlobalPendingBdcCount == 0 ) {
+ NlGlobalPendingBdcTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+ }
+
+ //
+ // Remove the pending BDC from the list of pending BDCs.
+ //
+
+ RemoveEntryList( &ServerSession->SsPendingBdcList );
+
+ //
+ // Turn off the flag indicating we're in the list.
+ //
+
+ ServerSession->SsFlags &= ~SS_PENDING_BDC;
+
+ NlPrint((NL_PULSE_MORE,
+ "NlRemovePendingBdc: %s: Removed from pending list. Count: %ld\n",
+ ServerSession->SsComputerName,
+ NlGlobalPendingBdcCount ));
+
+}
+
+
+VOID
+NlAddPendingBdc(
+ IN PSERVER_SESSION ServerSession
+ )
+/*++
+
+Routine Description:
+
+ Add the specified Server Session to the list of pending BDCs.
+
+ Enter with the ServerSessionTable Sem locked
+
+Arguments:
+
+ ServerSession -- Pointer to the server session structure to add to the
+ list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // Ensure the server session is really off the list.
+ //
+
+ if ( ServerSession->SsFlags & SS_PENDING_BDC ) {
+ return;
+ }
+
+ //
+ // If this is the first pending BDC,
+ // start the timer.
+ //
+
+ if ( NlGlobalPendingBdcCount == 0 ) {
+ // Run the timer at twice the frequency of the timeout to ensure that
+ // entries don't have to wait nearly twice the timeout period before
+ // they expire.
+ NlGlobalPendingBdcTimer.Period = NlGlobalPulseTimeout1Parameter * 500;
+ (VOID) NtQuerySystemTime( &NlGlobalPendingBdcTimer.StartTime );
+
+ //
+ // Tell the main thread that I've changed a timer.
+ //
+
+ if ( !SetEvent( NlGlobalTimerEvent ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlAddPendingBdc: %s: SetEvent2 failed %ld\n",
+ ServerSession->SsComputerName,
+ GetLastError() ));
+ }
+
+ }
+
+ //
+ // Increment the count of pending BDCs
+ //
+
+ NlGlobalPendingBdcCount ++;
+
+ //
+ // Add the pending BDC to the list of pending BDCs.
+ //
+
+ InsertTailList( &NlGlobalPendingBdcList, &ServerSession->SsPendingBdcList );
+
+ //
+ // Turn on the flag indicating we're in the list.
+ //
+
+ ServerSession->SsFlags |= SS_PENDING_BDC;
+
+ NlPrint((NL_PULSE_MORE,
+ "NlAddPendingBdc: %s: Added to pending list. Count: %ld\n",
+ ServerSession->SsComputerName,
+ NlGlobalPendingBdcCount ));
+
+}
+
+
+VOID
+NetpLogonPutDBInfo(
+ IN PDB_CHANGE_INFO DBInfo,
+ IN OUT PCHAR * Where
+)
+/*++
+
+Routine Description:
+
+ Put Database info structure in mailslot buffer.
+
+Arguments:
+
+ DBInfo : database info structure pointer.
+
+ Where : indirect pointer to mailslot buffer. Database info
+ is copied over here. When returned this pointer is
+ updated to point the end of mailslot buffer.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ NetpLogonPutBytes( &DBInfo->DBIndex, sizeof(DBInfo->DBIndex), Where);
+
+ NetpLogonPutBytes( &(DBInfo->LargeSerialNumber),
+ sizeof(DBInfo->LargeSerialNumber),
+ Where);
+
+ NetpLogonPutBytes( &(DBInfo->NtDateAndTime),
+ sizeof(DBInfo->NtDateAndTime),
+ Where);
+}
+
+
+VOID
+NetpLogonUpdateDBInfo(
+ IN PLARGE_INTEGER SerialNumber,
+ IN OUT PCHAR * Where
+)
+/*++
+
+Routine Description:
+
+ Update the Serial Number within the already packed mailslot buffer.
+
+Arguments:
+
+ SerialNumber: New SerialNumber.
+
+ Where : indirect pointer to mailslot buffer. Database info
+ is copied over here. When returned this pointer is
+ updated to point the end of mailslot buffer.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ *Where += sizeof(DWORD);
+
+ NetpLogonPutBytes( SerialNumber, sizeof(LARGE_INTEGER), Where);
+
+ *Where += sizeof(LARGE_INTEGER);
+}
+
+
+
+BOOLEAN
+NlAllocatePrimaryAnnouncement(
+ OUT PNETLOGON_DB_CHANGE *UasChangeBuffer,
+ OUT LPDWORD UasChangeSize,
+ OUT PCHAR *DbChangeInfoPointer
+ )
+/*++
+
+Routine Description:
+
+ Build and allocate an UAS_CHANGE message which describes the latest
+ account database changes.
+
+Arguments:
+
+ UasChangeBuffer - Returns a pointer to the buffer containing the message.
+ The caller is responsible for freeing the buffer using NetpMemoryFree.
+
+ UasChangeSize - Returns the size (in bytes) of the allocated buffer.
+
+ DbChangeInfoPointer - Returns the address of the DB Change info
+ within the allocated buffer. The field is not properly aligned.
+
+Return Value:
+
+ TRUE - iff the buffer could be successfully allocated.
+
+
+--*/
+{
+ PNETLOGON_DB_CHANGE UasChange;
+ DB_CHANGE_INFO DBChangeInfo;
+ ULONG DateAndTime1970;
+
+ DWORD NumDBs;
+ PCHAR Where;
+
+ DWORD i;
+ DWORD DomainSidSize;
+
+ //
+ // allocate space for this message.
+ //
+
+ DomainSidSize = RtlLengthSid( NlGlobalPrimaryDomainId );
+
+ UasChange = NetpMemoryAllocate(
+ sizeof(NETLOGON_DB_CHANGE)+
+ (NUM_DBS - 1) * sizeof(DB_CHANGE_INFO) +
+ (DomainSidSize - 1) +
+ sizeof(DWORD) // for DWORD alignment of SID
+ );
+
+ if( UasChange == NULL ) {
+
+ NlPrint((NL_CRITICAL, "NlAllocatePrimaryAnnouncement can't allocate memory\n" ));
+ return FALSE;
+ }
+
+
+ //
+ // Build the UasChange message using the latest domain modified
+ // information from SAM.
+ //
+
+ UasChange->Opcode = LOGON_UAS_CHANGE;
+
+ LOCK_CHANGELOG();
+ SmbPutUlong( &UasChange->LowSerialNumber,
+ NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart);
+
+ if (!RtlTimeToSecondsSince1970( &NlGlobalDBInfoArray[SAM_DB].CreationTime,
+ &DateAndTime1970 )) {
+ NlPrint((NL_CRITICAL, "DomainCreationTime can't be converted\n" ));
+ DateAndTime1970 = 0;
+ }
+ SmbPutUlong( &UasChange->DateAndTime, DateAndTime1970 );
+
+ // Tell the BDC we only intend to send pulses infrequently
+ SmbPutUlong( &UasChange->Pulse, NlGlobalPulseMaximumParameter);
+
+ // Set the randomize parameter to the value it should be for lanman
+ // BDCs. The caller will change it for NT BDCs
+ SmbPutUlong( &UasChange->Random,
+ max(NlGlobalRandomizeParameter,
+ NlGlobalPulseParameter/10) );
+
+ Where = UasChange->PrimaryDCName;
+
+ NetpLogonPutOemString( NlGlobalAnsiPrimaryName,
+ sizeof(UasChange->PrimaryDCName),
+ &Where );
+
+ NetpLogonPutOemString( NlGlobalAnsiDomainName,
+ sizeof(UasChange->DomainName),
+ &Where );
+
+ //
+ // builtin domain support
+ //
+
+ NetpLogonPutUnicodeString( NlGlobalUnicodePrimaryName,
+ sizeof(UasChange->UnicodePrimaryDCName),
+ &Where );
+
+ NetpLogonPutUnicodeString( NlGlobalUnicodeDomainName,
+ sizeof(UasChange->UnicodeDomainName),
+ &Where );
+
+ //
+ // number of database info that follow
+ //
+
+ NumDBs = NUM_DBS;
+
+ NetpLogonPutBytes( &NumDBs, sizeof(NumDBs), &Where );
+
+ *DbChangeInfoPointer = Where;
+ for( i = 0; i < NUM_DBS; i++) {
+
+ DBChangeInfo.DBIndex =
+ NlGlobalDBInfoArray[i].DBIndex;
+ DBChangeInfo.LargeSerialNumber =
+ NlGlobalChangeLogDesc.SerialNumber[i];
+ DBChangeInfo.NtDateAndTime =
+ NlGlobalDBInfoArray[i].CreationTime;
+
+ NetpLogonPutDBInfo( &DBChangeInfo, &Where );
+ }
+
+ //
+ // place domain SID in the message.
+ //
+
+ NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
+ NetpLogonPutDomainSID( NlGlobalPrimaryDomainId, DomainSidSize, &Where );
+
+ NetpLogonPutNtToken( &Where );
+ UNLOCK_CHANGELOG();
+
+
+ *UasChangeSize = (DWORD)(Where - (PCHAR)UasChange);
+ *UasChangeBuffer = UasChange;
+ return TRUE;
+}
+
+
+
+VOID
+NlPrimaryAnnouncementFinish(
+ IN PSERVER_SESSION ServerSession,
+ IN DWORD DatabaseId,
+ IN PLARGE_INTEGER SerialNumber
+ )
+/*++
+
+Routine Description:
+
+ Indicate that the specified BDC has completed replication of the specified
+ database.
+
+ Note: this BDC might not be on the pending list at at if it was doing the
+ replication on its own accord. This routine is designed to handle that
+ eventuality.
+
+Arguments:
+
+ ServerSession -- Pointer to the server session structure to remove from the
+ list.
+
+ DatabaseID -- Database ID of the database
+
+ SerialNumber -- SerialNumber of the latest delta returned to the BDC.
+ NULL indicates a full sync just completed
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ BOOLEAN SendPulse = FALSE;
+ //
+ // Mark the session that the replication of this particular database
+ // has finished.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession->SsFlags &= ~NlGlobalDBInfoArray[DatabaseId].DBSessionFlag;
+
+ //
+ // If all of the databases are now replicated, OR
+ // the BDC just finished a full sync on one or more of its database,
+ // remove this BDC from the pending list.
+ //
+
+ if ( (ServerSession->SsFlags & SS_REPL_MASK) == 0 || SerialNumber == NULL ) {
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncementFinish: %s: all databases are now in sync on BDC\n",
+ ServerSession->SsComputerName ));
+ NlRemovePendingBdc( ServerSession );
+ SendPulse = TRUE;
+ }
+
+ //
+ // If a full sync just completed,
+ // force a partial sync so we can update our serial numbers.
+ //
+
+ if ( SerialNumber == NULL ) {
+
+ ServerSession->SsBdcDbSerialNumber[DatabaseId].QuadPart = 0;
+ ServerSession->SsFlags |= NlGlobalDBInfoArray[DatabaseId].DBSessionFlag;
+
+ //
+ // Save the current serial number for this BDC.
+ //
+
+ } else {
+ ServerSession->SsBdcDbSerialNumber[DatabaseId] = *SerialNumber;
+ }
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncementFinish: %s: " FORMAT_LPWSTR " SerialNumber: %lx %lx\n",
+ ServerSession->SsComputerName,
+ NlGlobalDBInfoArray[DatabaseId].DBName,
+ ServerSession->SsBdcDbSerialNumber[DatabaseId].HighPart,
+ ServerSession->SsBdcDbSerialNumber[DatabaseId].LowPart ));
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // If this BDC is finished,
+ // try to send a pulse to more BDCs.
+ //
+
+ if ( SendPulse ) {
+ NlPrimaryAnnouncement( ANNOUNCE_CONTINUE );
+ }
+}
+
+
+VOID
+NlPrimaryAnnouncementTimeout(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ The primary announcement timer has expired.
+
+ Handle timing out any BDC's that haven't responded yet.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LARGE_INTEGER TimeNow;
+ BOOLEAN SendPulse = FALSE;
+ PLIST_ENTRY ListEntry;
+ PSERVER_SESSION ServerSession;
+
+ //
+ // Get the current time of day
+ //
+
+ (VOID) NtQuerySystemTime( &TimeNow );
+
+ //
+ // Handle each BDC that has a pulse pending.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ for ( ListEntry = NlGlobalPendingBdcList.Flink ;
+ ListEntry != &NlGlobalPendingBdcList ;
+ ListEntry = ListEntry->Flink) {
+
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsPendingBdcList );
+
+ //
+ // Ignore entries that haven't timed out yet.
+ //
+
+ if ( ServerSession->SsLastPulseTime.QuadPart +
+ NlGlobalPulseTimeout1.QuadPart >
+ TimeNow.QuadPart ) {
+
+ continue;
+ }
+
+ //
+ // If the pulse has been sent and there has been no response at all,
+ // OR there hasn't been another response in a VERY long time
+ // time this entry out.
+ //
+ if ( (ServerSession->SsFlags & SS_PULSE_SENT) ||
+ (ServerSession->SsLastPulseTime.QuadPart +
+ NlGlobalPulseTimeout2.QuadPart <=
+ TimeNow.QuadPart) ) {
+
+ //
+ // Increment the count of times this BDC has timed out.
+ //
+ if ( ServerSession->SsPulseTimeoutCount < MAX_PULSE_TIMEOUT ) {
+ ServerSession->SsPulseTimeoutCount++;
+ }
+
+ //
+ // Remove this entry from the queue.
+ //
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncementTimeout: %s: BDC didn't respond to pulse.\n",
+ ServerSession->SsComputerName ));
+ NlRemovePendingBdc( ServerSession );
+
+ //
+ // Indicate we should send more pulses
+ //
+
+ SendPulse = TRUE;
+
+ }
+
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // If any entry has timed out,
+ // try to send a pulse to more BDCs.
+ //
+
+ if ( SendPulse ) {
+ NlPrimaryAnnouncement( ANNOUNCE_CONTINUE );
+ }
+}
+
+
+
+VOID
+NlPrimaryAnnouncement(
+ IN DWORD AnnounceFlags
+ )
+/*++
+
+Routine Description:
+
+ Periodic broadcast of messages to domain containing latest
+ account database changes.
+
+Arguments:
+
+ AnnounceFlags - Flags requesting special handling of the announcement.
+
+ ANNOUNCE_FORCE -- set to indicate that the pulse should be forced to
+ all BDCs in the domain.
+
+ ANNOUNCE_CONTINUE -- set to indicate that this call is a
+ continuation of a previous call to process all entries.
+
+ ANNOUNCE_IMMEDIATE -- set to indicate that this call is a result
+ of a request for immediate replication
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+ PNETLOGON_DB_CHANGE UasChange;
+ DWORD UasChangeSize;
+ PCHAR DbChangeInfoPointer;
+ ULONG LanmanRandomize;
+ LARGE_INTEGER TimeNow;
+ DWORD SessionFlags;
+
+ PSERVER_SESSION ServerSession;
+ PLIST_ENTRY ListEntry;
+ static ULONG EntriesHandled = 0;
+ static BOOLEAN ImmediateAnnouncement;
+
+
+ NlPrint((NL_PULSE_MORE, "NlPrimaryAnnouncement: Entered %ld\n", AnnounceFlags ));
+
+ //
+ // Allocate the UAS_CHANGE message to send.
+ //
+
+ if ( !NlAllocatePrimaryAnnouncement( &UasChange,
+ &UasChangeSize,
+ &DbChangeInfoPointer ) ) {
+ return;
+ }
+ LanmanRandomize = SmbGetUlong( &UasChange->Random );
+
+ //
+ // Get the current time of day.
+ //
+
+ (VOID) NtQuerySystemTime( &TimeNow );
+
+ //
+ // If we need to force the pulse to all the BDCs,
+ // mark that we've not done any entries yet, and
+ // mark each entry that a pulse is needed.
+ //
+
+
+ LOCK_SERVER_SESSION_TABLE();
+ if ( AnnounceFlags & ANNOUNCE_FORCE ) {
+ EntriesHandled = 0;
+
+ for ( ListEntry = NlGlobalBdcServerSessionList.Flink ;
+ ListEntry != &NlGlobalBdcServerSessionList ;
+ ListEntry = ListEntry->Flink) {
+
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsBdcList );
+
+ ServerSession->SsFlags |= SS_FORCE_PULSE;
+
+ }
+
+ }
+
+ //
+ // If this isn't a continuation of a previous request to send out pulses,
+ // Reset the count of BDCs that have been handled.
+ //
+
+ if ( (AnnounceFlags & ANNOUNCE_CONTINUE) == 0 ) {
+ EntriesHandled = 0;
+
+ //
+ // Remember whether this was an immediate announcement for the
+ // initial call and all of the continuations.
+ //
+ ImmediateAnnouncement = (AnnounceFlags & ANNOUNCE_IMMEDIATE) != 0;
+ }
+
+
+ //
+ // Loop sending announcements until
+ // we have the maximum number of announcements outstanding, OR
+ // we've processed all the entries in the list.
+ //
+
+ while ( NlGlobalPendingBdcCount < NlGlobalPulseConcurrencyParameter &&
+ EntriesHandled < NlGlobalBdcServerSessionCount ) {
+
+ BOOLEAN SendPulse;
+ LPWSTR TransportName;
+
+ //
+ // If netlogon is exitting,
+ // stop sending announcements
+ //
+
+ if ( NlGlobalTerminate ) {
+ break;
+ }
+
+ //
+ // Get the server session entry for the next BDC in the list.
+ //
+ // The BDC Server Session list is maintained in the order pulses should
+ // be sent. As a pulse is sent (or is skipped), the entry is placed
+ // at the tail of the list. This gives each BDC a chance at a pulse
+ // before any BDC is repeated.
+
+ ListEntry = NlGlobalBdcServerSessionList.Flink ;
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsBdcList );
+ SendPulse = FALSE;
+ TransportName = ServerSession->SsTransportName;
+
+ //
+ // Determine if we should send an announcement to this BDC.
+ //
+ // LM BDCs are anti-social. They need a pulse every time, but
+ // they refuse to call us back when we send them one. Therefore,
+ // we'll just send them the pulse and not wait for a response.
+ //
+ //
+
+ if ( ServerSession->SsFlags & SS_LM_BDC ) {
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: always send a pulse to a LanMan BDC\n",
+ ServerSession->SsComputerName ));
+ SendPulse = TRUE;
+ SessionFlags = 0;
+
+ //
+ // Send a pluse unconditionally if a pulse is being forced.
+ //
+
+ } else if ( ServerSession->SsFlags & SS_FORCE_PULSE ) {
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: pulse forced to be sent\n",
+ ServerSession->SsComputerName ));
+ SendPulse = TRUE;
+ ServerSession->SsFlags &= ~SS_FORCE_PULSE;
+ SessionFlags = SS_REPL_MASK;
+ TransportName = NULL; // Send on all transports
+
+ //
+ // Only send to any other BDC if there isn't a pulse outstanding and
+ // previous announcements haven't been ignored.
+ //
+
+ } else if ( (ServerSession->SsFlags & SS_PENDING_BDC) == 0 &&
+ ServerSession->SsPulseTimeoutCount < MAX_PULSE_TIMEOUT ) {
+
+ ULONG i;
+ SessionFlags = 0;
+
+ //
+ // Only send an announcement if at least one database is out
+ // of sync.
+ //
+
+ for( i = 0; i < NUM_DBS; i++) {
+
+ //
+ // If we need to know the serial number of the BDC,
+ // force the replication.
+ //
+
+ if ( ServerSession->SsFlags &
+ NlGlobalDBInfoArray[i].DBSessionFlag ) {
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: " FORMAT_LPWSTR " database serial number needed. Pulse sent.\n",
+ ServerSession->SsComputerName,
+ NlGlobalDBInfoArray[i].DBName ));
+ SendPulse = TRUE;
+ SessionFlags |= NlGlobalDBInfoArray[i].DBSessionFlag;
+
+ //
+ // If the BDC is out of sync with us,
+ // tell it.
+ //
+
+ } else if ( NlGlobalChangeLogDesc.SerialNumber[i].QuadPart >
+ ServerSession->SsBdcDbSerialNumber[i].QuadPart ) {
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: " FORMAT_LPWSTR " database is out of sync. Pulse sent.\n",
+ ServerSession->SsComputerName,
+ NlGlobalDBInfoArray[i].DBName ));
+ SendPulse = TRUE;
+ SessionFlags |= NlGlobalDBInfoArray[i].DBSessionFlag;
+
+ }
+ }
+
+ //
+ // Fix a timing window on NT 1.0 BDCs.
+ //
+ // During promotion of a BDC to PDC, the following events occur:
+ // Two server accounts are changed on the old PDC and
+ // are marked for immediate replication.
+ // The Server manager asks the new PDC to partial sync.
+ //
+ // If the first immediate replication starts immediately, and the
+ // second immediate replication pulse is ignored because replication
+ // is in progress, and the first replication has finished the SAM
+ // database and is working on the LSA database when the server
+ // manager partial sync request comes in, then that request will be
+ // ignored (rightfully) since replication is still in progress.
+ // However, an NT 1.0 BDC will replicator thread will not go back to
+ // do the SAM database once it finishes with the LSA database. So
+ // the replicator thread terminates with the SAM database still
+ // needing replication. The server manager (rightfully) interprets
+ // this as an error.
+ //
+ // Our solution is to set the backoff period on such "immediate"
+ // replication attempts to the same value an NT 1.0 PDC would use.
+ // This typically prevents the initial replication from starting in
+ // the first place.
+ //
+ // Only do it for NT 1.0 BDCs since we're risking being overloaded.
+ //
+
+ if ( ImmediateAnnouncement &&
+ SendPulse &&
+ (ServerSession->SsFlags & SS_AUTHENTICATED) &&
+ (ServerSession->SsNegotiatedFlags & NETLOGON_SUPPORTS_PERSISTENT_BDC) == 0 ) {
+ SessionFlags = 0;
+ }
+ }
+
+ //
+ // Send a pulse unconditionally if it has been PulseMaximum since the
+ // latest pulse.
+ //
+
+ if ( !SendPulse &&
+ (ServerSession->SsLastPulseTime.QuadPart +
+ NlGlobalPulseMaximum.QuadPart <= TimeNow.QuadPart) ) {
+
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: Maximum pulse since previous pulse. Pulse sent.\n",
+ ServerSession->SsComputerName ));
+ SendPulse = TRUE;
+ SessionFlags = 0;
+ TransportName = NULL; // Send on all transports
+ }
+
+ //
+ // Put this entry at the tail of the list regardless of whether
+ // we'll actually send an announcement to it.
+ //
+
+ RemoveEntryList( ListEntry );
+ InsertTailList( &NlGlobalBdcServerSessionList, ListEntry );
+ EntriesHandled ++;
+
+ //
+ // Send the announcement.
+ //
+
+ if ( SendPulse ) {
+ CHAR ComputerName[CNLEN+1];
+ PCHAR Where;
+ ULONG i;
+
+ //
+ // Add this BDC to the list of BDCs that have a pulse pending.
+ //
+ // Don't add this BDC to the list if we don't expect a response.
+ // We don't expect a response from an LM BDC. We don't expect
+ // a response from a BDC that is merely getting its PulseMaximum
+ // pulse.
+ //
+ // If we don't expect a response, set the backoff period to a
+ // large value to prevent a large load on the PDC in the case
+ // that the BDC does actuall respond.
+ //
+ // If we expect a response, set the backoff period to almost
+ // immediately since we're waiting for them.
+ //
+
+ if ( SessionFlags == 0 ) {
+ SmbPutUlong( &UasChange->Random, LanmanRandomize );
+ } else {
+ NlAddPendingBdc( ServerSession );
+ SmbPutUlong( &UasChange->Random, NlGlobalRandomizeParameter );
+ }
+
+ //
+ // Indicate that the pulse has been sent.
+ // This flag is set from the time we send the pulse until the
+ // first time the BDC responds. We use this to detect failed
+ // BDCs that have a secure channel up.
+ //
+
+ ServerSession->SsFlags &= ~SS_REPL_MASK;
+ ServerSession->SsFlags |= SS_PULSE_SENT | SessionFlags;
+ (VOID) NtQuerySystemTime( &TimeNow );
+ ServerSession->SsLastPulseTime = TimeNow;
+
+ //
+ // Don't keep the server session locked since sending the mailslot
+ // message takes a long time.
+ //
+
+ (VOID) lstrcpyA(ComputerName, ServerSession->SsComputerName );
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Update the message to be specific to this BDC.
+ //
+ // If we need the BDC to respond,
+ // set the serial number to make the BDC think it has a lot
+ // of deltas to pick up.
+ //
+
+ LOCK_CHANGELOG();
+ Where = DbChangeInfoPointer;
+ for( i = 0; i < NUM_DBS; i++) {
+ LARGE_INTEGER SerialNumber;
+
+ SerialNumber = NlGlobalChangeLogDesc.SerialNumber[i];
+
+ if ( NlGlobalDBInfoArray[i].DBSessionFlag & SessionFlags ) {
+ //
+ // Don't change the high part since
+ // a) NT 1.0 BDCs will do a full sync if there are too
+ // many changes.
+ // b) The high part contains the PDC promotion count.
+ //
+ SerialNumber.LowPart = 0xFFFFFFFF;
+ }
+
+ NetpLogonUpdateDBInfo( &SerialNumber, &Where );
+ }
+ UNLOCK_CHANGELOG();
+
+
+
+ //
+ // Send the datagram to this BDC.
+ // Failure isn't fatal
+ //
+
+ Status = NlBrowserSendDatagram(
+ ComputerName,
+ TransportName,
+ NETLOGON_LM_MAILSLOT_A,
+ UasChange,
+ UasChangeSize );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot send datagram to '%s' 0x%lx\n",
+ ComputerName,
+ Status ));
+ }
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ } else {
+ NlPrint((NL_PULSE_MORE,
+ "NlPrimaryAnnouncement: %s: pulse not needed at this time.\n",
+ ServerSession->SsComputerName ));
+ }
+
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+ //
+ // Free up message memory.
+ //
+
+ NetpMemoryFree( UasChange );
+
+ NlPrint((NL_PULSE_MORE, "NlPrimaryAnnouncement: Return\n" ));
+ return;
+}
+
+
+
+VOID
+NlLanmanPrimaryAnnouncement(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Periodic broadcast of messages to domain containing latest
+ account database changes.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+ PNETLOGON_DB_CHANGE UasChange;
+ DWORD UasChangeSize;
+ PCHAR DbChangeInfoPointer;
+
+ PSERVER_SESSION ServerSession;
+ PLIST_ENTRY ListEntry;
+
+ NlPrint((NL_PULSE_MORE, "NlLanmanPrimaryAnnouncement: Entered\n" ));
+
+ //
+ // Allocate the UAS_CHANGE message to send.
+ //
+
+ if ( !NlAllocatePrimaryAnnouncement( &UasChange,
+ &UasChangeSize,
+ &DbChangeInfoPointer ) ) {
+ return;
+ }
+
+ //
+ // Send the message to each Lanman BDC.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ for ( ListEntry = NlGlobalBdcServerSessionList.Flink ;
+ ListEntry != &NlGlobalBdcServerSessionList ;
+ ListEntry = ListEntry->Flink) {
+
+
+ //
+ // Only send this message to Lanman BDCs
+ //
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsBdcList );
+
+ if ( (ServerSession->SsFlags & SS_LM_BDC) == 0) {
+ continue;
+ }
+
+ //
+ // If netlogon is exitting,
+ // stop sending announcements
+ //
+
+ if ( NlGlobalTerminate ) {
+ break;
+ }
+
+
+ //
+ // Send the datagram to this BDC.
+ // Failure isn't fatal
+ //
+
+ NlPrint((NL_PULSE_MORE,
+ "NlLanmanPrimaryAnnouncement: %s: pulse message sent to LANMAN BDC.\n",
+ ServerSession->SsComputerName ));
+
+ Status = NlBrowserSendDatagram(
+ ServerSession->SsComputerName,
+ ServerSession->SsTransportName,
+ NETLOGON_LM_MAILSLOT_A,
+ UasChange,
+ UasChangeSize );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot send datagram to '%s' 0x%lx\n",
+ ServerSession->SsComputerName,
+ Status ));
+ }
+
+
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+ //
+ // Free up message memory.
+ //
+
+ NetpMemoryFree( UasChange );
+
+ NlPrint((NL_PULSE_MORE, "NlLanmanPrimaryAnnouncement: Return\n" ));
+ return;
+}
+
+
+VOID
+NlAnnouncePrimaryStart(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Announce to all machines in the domain that a
+ Primary Domain Controller is successfully up
+ and running.
+
+ announcement made with LOGON_START_PRIMARY opcode
+
+ Assumptions:
+ NlGlobalAnsiPrimaryName and NlGlobalAnsiDomainName globals initialized
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_PRIMARY NetlogonPrimary;
+ PCHAR Where;
+ PLIST_ENTRY ListEntry;
+ PSERVER_SESSION ServerSession;
+
+
+ //
+ // Build an announcement containing the name of the primary and
+ // a token indicating LM 2.0 compatibility.
+ //
+
+ NetlogonPrimary.Opcode = LOGON_START_PRIMARY;
+
+ Where = NetlogonPrimary.PrimaryDCName;
+
+ NetpLogonPutOemString( NlGlobalAnsiPrimaryName,
+ sizeof(NetlogonPrimary.PrimaryDCName),
+ &Where);
+
+ NetpLogonPutLM20Token( &Where );
+
+ //
+ // Send the message to each Lanman BDC.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ for ( ListEntry = NlGlobalBdcServerSessionList.Flink ;
+ ListEntry != &NlGlobalBdcServerSessionList ;
+ ListEntry = ListEntry->Flink) {
+
+
+ //
+ // Only send this message to Lanman BDCs
+ //
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsBdcList );
+
+ if ( (ServerSession->SsFlags & SS_LM_BDC) == 0) {
+ continue;
+ }
+
+ //
+ // Send the datagram.
+ //
+
+ NlPrint((NL_PULSE_MORE,
+ "NlAnnouncementPrimaryStart: %s: primary start message sent to LANMAN BDC.\n",
+ ServerSession->SsComputerName ));
+
+ Status = NlBrowserSendDatagram(
+ ServerSession->SsComputerName,
+ ServerSession->SsTransportName,
+ NETLOGON_LM_MAILSLOT_A,
+ &NetlogonPrimary,
+ (DWORD)(Where - (PCHAR)&NetlogonPrimary) );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot send datagram to '%s' 0x%lx\n",
+ ServerSession->SsComputerName,
+ Status ));
+ }
+
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ return;
+}
diff --git a/private/net/svcdlls/logonsrv/server/changelg.c b/private/net/svcdlls/logonsrv/server/changelg.c
new file mode 100644
index 000000000..e017d0720
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/changelg.c
@@ -0,0 +1,1818 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ changelg.c
+
+Abstract:
+
+ Change Log implementation.
+
+ This file implements the change log. It is isolated in this file
+ because it has several restrictions.
+
+ * The globals maintained by this module are initialized during
+ netlogon.dll process attach. They are cleaned up netlogon.dll
+ process detach.
+
+ * These procedures are used by SAM, LSA, and the netlogon service.
+ The LSA should be the first to load netlogon.dll. It should
+ then immediately call I_NetNotifyRole before allowing SAM or the
+ netlogon service to start.
+
+ * These procedures cannot use any globals initialized by the netlogon
+ service.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 22-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+ 04-Apr-1992 (madana)
+ Added support for LSA replication.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <ntrtl.h> // LARGE_INTEGER definition
+#include <nturtl.h> // LARGE_INTEGER definition
+#include <ntlsa.h> // needed by changelg.h
+
+#define NOMINMAX // Avoid redefinition of min and max in stdlib.h
+#include <rpc.h> // Needed by logon.h
+#include <logon_s.h>// includes lmcons.h, lmaccess.h, netlogon.h,
+ // ssi.h, windef.h
+#include <winbase.h>
+#include <stdio.h> // sprintf ...
+
+//
+// Include files specific to this .c file
+//
+#include <config.h> // net config helpers.
+#include <configp.h> // USE_WIN32_CONFIG (if defined), etc.
+#include <confname.h> // SECTION_ equates, NETLOGON_KEYWORD_ equates.
+#include "iniparm.h" // defaults
+
+//
+// BEWARE: Be careful about adding netlogon.dll specific include files here.
+// This module is call by SAM and LSA. The netlogon service may not yet
+// be running. Therefore, guard against referencing netlogon.dll globals
+// other than those defined in changelg.h.
+//
+
+#include <samrpc.h> // Needed by samisrv.h
+#include <samisrv.h> // Needed by changelg.h
+#include <lsarpc.h> // Needed by lsrvdata.h and logonsrv.h
+#define CHANGELOG_ALLOCATE
+#include <changelg.h> // Local procedure definitions
+#undef CHANGELOG_ALLOCATE
+
+#include <lmerrlog.h> // NELOG_* defined here ..
+#include <netlib.h> // NetpMemoryAllocate
+#include <netlibnt.h> // NetpNtStatusToApiStatus
+
+#define DEBUG_ALLOCATE
+#include <nldebug.h> // Netlogon debugging
+#undef DEBUG_ALLOCATE
+#include <align.h>
+#include <nlp.h> // NlpWriteEventlog defined here.
+
+#include <nlrepl.h> // I_Net* definitions
+#include <chworker.h> // worker functions
+#include "chutil.h" // utility functions
+
+
+enum {
+ ChangeLogPrimary,
+ ChangeLogBackup,
+ ChangeLogMemberWorkstation,
+ ChangeLogUnknown
+ } NlGlobalChangeLogRole;
+
+//
+// from parse.c
+//
+
+NET_API_STATUS
+NlParseOne(
+ IN LPNET_CONFIG_HANDLE SectionHandle,
+ IN LPWSTR Keyword,
+ IN ULONG DefaultValue,
+ IN ULONG MinimumValue,
+ IN ULONG MaximumValue,
+ OUT PULONG Value
+ );
+
+
+
+NTSTATUS
+NlSendChangeLogNotification(
+ IN enum CHANGELOG_NOTIFICATION_TYPE EntryType,
+ IN PUNICODE_STRING ObjectName,
+ IN PSID ObjectSid,
+ IN ULONG ObjectRid
+ )
+/*++
+
+Routine Description:
+
+ Put a ChangeLog Notification entry for netlogon to pick up.
+
+Arguments:
+
+ EntryType - The type of the entry being inserted
+
+ ObjectName - The name of the account being changed.
+
+ ObjectSid - Sid of the account be changed.
+
+ ObjectRid - Rid of the object being changed.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ PCHANGELOG_NOTIFICATION Notification;
+ LPBYTE Where;
+ ULONG SidSize = 0;
+ ULONG NameSize = 0;
+ ULONG Size;
+
+ //
+ // If the netlogon service isn't running (or at least starting),
+ // don't queue messages to it.
+ //
+
+ if( NlGlobalChangeLogNetlogonState == NetlogonStopped ) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Allocate a buffer for the object name.
+ //
+
+ if ( ObjectSid != NULL ) {
+ SidSize = RtlLengthSid( ObjectSid );
+ }
+
+ if ( ObjectName != NULL ) {
+ NameSize = ObjectName->Length + sizeof(WCHAR);
+ }
+
+ Size = sizeof(*Notification) + SidSize + NameSize;
+ Size = ROUND_UP_COUNT( Size, ALIGN_WORST );
+
+ Notification = NetpMemoryAllocate( Size );
+
+ if ( Notification == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ Notification->EntryType = EntryType;
+ Notification->ObjectRid = ObjectRid;
+
+ Where = (LPBYTE) (Notification + 1);
+
+ //
+ // Copy the object sid into the buffer.
+ //
+
+ if ( ObjectSid != NULL ) {
+ RtlCopyMemory( Where, ObjectSid, SidSize );
+ Notification->ObjectSid = (PSID) Where;
+ Where += SidSize;
+ } else {
+ Notification->ObjectSid = NULL;
+ }
+
+
+ //
+ // Copy the new server name into the buffer.
+ //
+
+ if ( ObjectName != NULL ) {
+ Where = ROUND_UP_POINTER( Where, ALIGN_WCHAR );
+ RtlCopyMemory( Where, ObjectName->Buffer, ObjectName->Length );
+ ((LPWSTR)Where)[ObjectName->Length/sizeof(WCHAR)] = L'\0';
+
+ RtlInitUnicodeString( &Notification->ObjectName, (LPWSTR)Where);
+ Where += NameSize;
+ } else {
+ RtlInitUnicodeString( &Notification->ObjectName, NULL);
+ }
+
+ //
+ // Indicate we're about to send the event.
+ //
+
+ NlPrint((NL_CHANGELOG,
+ "NlSendChangeLogNotification: sent %ld for %wZ Rid: 0x%lx Sid: ",
+ Notification->EntryType,
+ &Notification->ObjectName,
+ Notification->ObjectRid ));
+ NlpDumpSid( NL_CHANGELOG, Notification->ObjectSid );
+
+
+
+ //
+ // Insert the entry into the list
+ //
+
+ LOCK_CHANGELOG();
+ InsertTailList( &NlGlobalChangeLogNotifications, &Notification->Next );
+ UNLOCK_CHANGELOG();
+
+ if ( !SetEvent( NlGlobalChangeLogEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot set ChangeLog event: %lu\n",
+ GetLastError() ));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+VOID
+NlLmBdcListSet(
+ IN ULONG LmBdcCount,
+ IN PULONG LmBdcRidArray
+ )
+/*++
+
+Routine Description:
+
+ Set the list of LM BDCs to the specified list.
+
+Arguments:
+
+ LmBdcCount - Number of BDCs in the list
+
+ LmBdcRidArray - Array of Rids of Lanman BDC accounts.
+
+Return Value:
+
+ None
+
+--*/
+{
+ //
+ // If a previous array exists,
+ // delete it.
+ //
+ LOCK_CHANGELOG();
+ if ( NlGlobalLmBdcRidArray != NULL ) {
+ NetpMemoryFree( NlGlobalLmBdcRidArray );
+ NlGlobalLmBdcRidArray = NULL;
+ NlGlobalLmBdcCount = 0;
+ }
+
+ //
+ // Allocate the new array.
+ //
+
+ NlGlobalLmBdcRidArray = NetpMemoryAllocate( LmBdcCount * sizeof(ULONG) );
+
+ if ( NlGlobalLmBdcRidArray != NULL ) {
+ RtlCopyMemory( NlGlobalLmBdcRidArray,
+ LmBdcRidArray,
+ LmBdcCount * sizeof(ULONG) );
+ NlGlobalLmBdcCount = LmBdcCount;
+ }
+ UNLOCK_CHANGELOG();
+}
+
+
+PULONG
+NlLmBdcListFind(
+ IN ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Returns a pointer to the specified RID in the LM BDC list.
+
+ Enter with the change log crit sect locked.
+
+Arguments:
+
+ Rid - Rid of the Lanman BDC being found
+
+Return Value:
+
+ NULL, if the entry can't be found
+
+--*/
+{
+ ULONG i;
+
+ //
+ // Simply loop through the array entries.
+ //
+
+ for ( i=0; i<NlGlobalLmBdcCount; i++ ) {
+ if ( NlGlobalLmBdcRidArray[i] == Rid ) {
+ return &NlGlobalLmBdcRidArray[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+
+VOID
+NlLmBdcListAdd(
+ IN ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Add the specified RID to the LM BDC list.
+
+ Notify the changelog worker thread and the netlogon service of the new BDC.
+
+Arguments:
+
+ Rid - Rid of the Lanman BDC being added
+
+Return Value:
+
+ None
+
+--*/
+{
+ //
+ // Ensure the RID doesn't already exist in the array.
+ //
+
+ LOCK_CHANGELOG();
+
+ if ( NlLmBdcListFind( Rid ) == NULL ) {
+
+ //
+ // Allocate a larger array.
+ // (NetpMemoryReallocate properly handles the case where the
+ // array didn't previously exist.)
+ //
+
+ NlGlobalLmBdcRidArray = NetpMemoryReallocate(
+ NlGlobalLmBdcRidArray,
+ (NlGlobalLmBdcCount+1) * sizeof(ULONG) );
+
+ if ( NlGlobalLmBdcRidArray == NULL ) {
+ NlGlobalLmBdcCount = 0;
+ UNLOCK_CHANGELOG();
+ return;
+ }
+
+ //
+ // Set the RID into the array entry.
+ //
+
+ NlGlobalLmBdcRidArray[NlGlobalLmBdcCount] = Rid;
+ NlGlobalLmBdcCount ++;
+ NlPrint((NL_CHANGELOG,
+ "NlLmBdcListAdd: Lm Bdc 0x%lx added (%ld)\n",
+ Rid,
+ NlGlobalLmBdcCount ));
+
+ //
+ // Start the changelog worker thread if it's not running.
+ //
+
+ (VOID) NlStartChangeLogWorkerThread();
+
+ //
+ // Tell netlogon that a downlevel BDC has been added.
+ //
+
+ (VOID) NlSendChangeLogNotification(
+ ChangeLogLmServerAdded,
+ NULL,
+ NULL,
+ Rid );
+
+ }
+
+ UNLOCK_CHANGELOG();
+}
+
+
+
+VOID
+NlLmBdcListDel(
+ IN ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Delete the specified RID from the LM BDC list.
+
+ This routine is specifically designed to be called on ALL user account
+ deletions. Since the user account might have been a member of the servers
+ group, all user account deletions are checked to see if they represent
+ an LM BDC.
+
+ Notify the changelog worker thread and the netlogon service of the deleted BDC.
+
+Arguments:
+
+ Rid - Rid of the Lanman BDC being deleted.
+
+Return Value:
+
+ None
+
+--*/
+{
+ PULONG RidEntry;
+
+ //
+ // If the entry exists,
+ // delete it by copying the last entry of the array on top of this one
+ // and making the array one entry smaller.
+ //
+
+ LOCK_CHANGELOG();
+
+ RidEntry = NlLmBdcListFind( Rid );
+
+ if ( RidEntry != NULL ) {
+ *RidEntry = NlGlobalLmBdcRidArray[ NlGlobalLmBdcCount-1 ];
+ NlGlobalLmBdcCount --;
+ NlPrint((NL_CHANGELOG,
+ "NlLmBdcListDel: Lm Bdc 0x%lx deleted (%ld)\n",
+ Rid,
+ NlGlobalLmBdcCount ));
+
+ if ( NlGlobalLmBdcCount == 0 ) {
+ NetpMemoryFree( NlGlobalLmBdcRidArray );
+ NlGlobalLmBdcRidArray = NULL;
+ }
+
+ //
+ // worker thread must be running now
+ //
+
+ if( IsChangeLogWorkerRunning() ) {
+
+ (VOID) NlAddWorkerQueueEntry( ServersGroupDel, 0 );
+
+ } else {
+ NlAssert( FALSE );
+ }
+
+ //
+ // Tell netlogon that a downlevel BDC has been removed.
+ //
+
+ (VOID) NlSendChangeLogNotification(
+ ChangeLogLmServerDeleted,
+ NULL,
+ NULL,
+ Rid );
+ }
+
+ UNLOCK_CHANGELOG();
+}
+
+
+
+NTSTATUS
+I_NetNotifyDelta (
+ IN SECURITY_DB_TYPE DbType,
+ IN LARGE_INTEGER SerialNumber,
+ IN SECURITY_DB_DELTA_TYPE DeltaType,
+ IN SECURITY_DB_OBJECT_TYPE ObjectType,
+ IN ULONG ObjectRid,
+ IN PSID ObjectSid,
+ IN PUNICODE_STRING ObjectName,
+ IN DWORD ReplicateImmediately,
+ IN PSAM_DELTA_DATA MemberId
+ )
+/*++
+
+Routine Description:
+
+ This function is called by the SAM and LSA services after each
+ change is made to the SAM and LSA databases. The services describe
+ the type of object that is modified, the type of modification made
+ on the object, the serial number of this modification etc. This
+ information is stored for later retrieval when a BDC or member
+ server wants a copy of this change. See the description of
+ I_NetSamDeltas for a description of how the change log is used.
+
+ Add a change log entry to circular change log maintained in cache as
+ well as on the disk and update the head and tail pointers
+
+ It is assumed that Tail points to a block where this new change log
+ entry may be stored.
+
+Arguments:
+
+ DbType - Type of the database that has been modified.
+
+ SerialNumber - The value of the DomainModifiedCount field for the
+ domain following the modification.
+
+ DeltaType - The type of modification that has been made on the object.
+
+ ObjectType - The 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.
+
+ ObjectSid - The SID of the object that has been modified. If the object
+ modified is in a SAM database, ObjectSid is the DomainId of the Domain
+ containing the object.
+
+ ObjectName - The name of the secret object when the object type
+ specified is SecurityDbObjectLsaSecret or 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 NULL.
+
+ ReplicateImmediately - TRUE if the change should be immediately
+ replicated to all BDCs. A password change should set the flag
+ TRUE.
+
+ MemberId - This parameter is specified when group/alias membership
+ is modified. This structure will then point to the member's ID that
+ has been updated.
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+{
+ NTSTATUS Status;
+ CHANGELOG_ENTRY ChangeLogEntry;
+ NETLOGON_DELTA_TYPE NetlogonDeltaType;
+ USHORT Flags = 0;
+ BOOL LanmanReplicateImmediately = FALSE;
+
+ //
+ // Ensure the role is right. Otherwise, all the globals used below
+ // aren't initialized.
+ //
+
+ if ( NlGlobalChangeLogRole != ChangeLogPrimary ) {
+ return STATUS_INVALID_DOMAIN_ROLE;
+ }
+
+ //
+ // Also make sure that the change log cache is available.
+ //
+
+ if ( NlGlobalChangeLogDesc.Buffer == NULL ) {
+ return STATUS_INVALID_DOMAIN_ROLE;
+ }
+
+
+ //
+ // Determine the database index.
+ //
+
+ if( DbType == SecurityDbLsa ) {
+
+ ChangeLogEntry.DBIndex = LSA_DB;
+
+ } else if( DbType == SecurityDbSam ) {
+
+ if ( RtlEqualSid( ObjectSid, NlGlobalChWorkerBuiltinDomainSid )) {
+
+ ChangeLogEntry.DBIndex = BUILTIN_DB;
+
+ } else {
+
+ ChangeLogEntry.DBIndex = SAM_DB;
+
+ }
+
+ //
+ // For the SAM database, we no longer need the ObjectSid.
+ // Null out the pointer to prevent us from storing it in the
+ // changelog.
+ //
+
+ ObjectSid = NULL;
+
+ } else {
+
+ //
+ // unknown database, do nothing.
+ //
+
+ return STATUS_SUCCESS;
+ }
+
+
+
+ //
+ // Map object type and delta type to NetlogonDeltaType
+ //
+
+ switch( ObjectType ) {
+ case SecurityDbObjectLsaPolicy:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeLsaPolicy;
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+
+ case SecurityDbObjectLsaTDomain:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeLsaTDomain;
+
+ //
+ // Tell the netlogon service to update its in-memory list now.
+ //
+ (VOID) NlSendChangeLogNotification( ChangeLogTrustAdded,
+ NULL,
+ ObjectSid,
+ 0 );
+ break;
+
+ case SecurityDbDelete:
+ NetlogonDeltaType = DeleteLsaTDomain;
+
+ //
+ // Tell the netlogon service to update its in-memory list now.
+ //
+ (VOID) NlSendChangeLogNotification( ChangeLogTrustDeleted,
+ NULL,
+ ObjectSid,
+ 0 );
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+
+ case SecurityDbObjectLsaAccount:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeLsaAccount;
+ break;
+
+ case SecurityDbDelete:
+ NetlogonDeltaType = DeleteLsaAccount;
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+
+ case SecurityDbObjectLsaSecret:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeLsaSecret;
+ break;
+
+ case SecurityDbDelete:
+ NetlogonDeltaType = DeleteLsaSecret;
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+
+ case SecurityDbObjectSamDomain:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeDomain;
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+ case SecurityDbObjectSamUser:
+
+ switch (DeltaType) {
+ case SecurityDbChangePassword:
+ Flags |= CHANGELOG_PASSWORD_CHANGE;
+ LanmanReplicateImmediately = TRUE;
+ NetlogonDeltaType = AddOrChangeUser;
+ break;
+
+ case SecurityDbNew:
+
+ //
+ // For down-level system, a newly added user needs to
+ // have it's membership in "Domain Users" updated, too.
+ // The following worker entry will add the additional
+ // delta entry and increment the serial number
+ // accordingly.
+ //
+
+ LOCK_CHANGELOG();
+ if( IsChangeLogWorkerRunning() ) {
+ (VOID) NlAddWorkerQueueEntry( ChangeLogAddUser, ObjectRid );
+ }
+ UNLOCK_CHANGELOG();
+
+ NetlogonDeltaType = AddOrChangeUser;
+ break;
+
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeUser;
+ break;
+
+ //
+ // This is a dummy delta sent by chworker to indicate that "Domain Users"
+ // was added as a member of this user.
+ //
+ case SecurityDbChangeMemberAdd:
+ Flags |= CHANGELOG_DOMAINUSERS_CHANGED;
+ NetlogonDeltaType = AddOrChangeUser;
+ break;
+
+ case SecurityDbDelete:
+
+ //
+ // This might be a Lanman BDC so check to be sure.
+ //
+
+ NlLmBdcListDel( ObjectRid );
+
+ NetlogonDeltaType = DeleteUser;
+ break;
+
+
+ case SecurityDbRename:
+ NetlogonDeltaType = RenameUser;
+
+ //
+ // For down-level system, Rename user is handled as two
+ // deltas, viz. 1) Delete old user and 2) Add new user.
+ // The following worker entry will add the additional
+ // delta entry and increment the serial number
+ // accordingly.
+ //
+
+ LOCK_CHANGELOG();
+
+ if( IsChangeLogWorkerRunning() ) {
+ (VOID) NlAddWorkerQueueEntry( ChangeLogRenameUser, ObjectRid );
+ }
+
+ UNLOCK_CHANGELOG();
+
+ break;
+
+ //
+ // unknown delta type
+ //
+
+ default:
+ return STATUS_SUCCESS;
+ }
+
+ break;
+
+ case SecurityDbObjectSamGroup:
+
+ switch ( DeltaType ) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeGroup;
+ break;
+
+ case SecurityDbDelete:
+ NetlogonDeltaType = DeleteGroup;
+
+ //
+ // when a global group is deleted, we also delete it
+ // from the special group list, if it is included
+ // in the list.
+ //
+
+ LOCK_CHANGELOG();
+
+ if( IsChangeLogWorkerRunning() ) {
+
+ PGLOBAL_GROUP_ENTRY GroupEntry;
+
+ GroupEntry = NlGetGroupEntry (
+ &NlGlobalSpecialServerGroupList,
+ ObjectRid );
+
+ if( GroupEntry != NULL ) {
+
+ RemoveEntryList( &GroupEntry->Next );
+ NetpMemoryFree( GroupEntry );
+ }
+
+ }
+
+ UNLOCK_CHANGELOG();
+ break;
+
+ case SecurityDbRename:
+ NetlogonDeltaType = RenameGroup;
+
+ //
+ // For down-level system, Rename group is handled as
+ // three deltas, viz. 1) Delete old group, 2) Add new
+ // group and 3. Changemembership of new group. The
+ // following worker entry will add the additional
+ // two delta entries and increment the serial number
+ // accordingly.
+ //
+
+ LOCK_CHANGELOG();
+
+ if( IsChangeLogWorkerRunning() ) {
+ (VOID) NlAddWorkerQueueEntry( ChangeLogRenameGroup, ObjectRid );
+
+ }
+
+ UNLOCK_CHANGELOG();
+
+ break;
+
+ case SecurityDbChangeMemberAdd:
+ case SecurityDbChangeMemberSet:
+ case SecurityDbChangeMemberDel: {
+
+ UNICODE_STRING ServersGroup;
+
+ NetlogonDeltaType = ChangeGroupMembership;
+
+ //
+ // without object name we can't do much here.
+ //
+ if( ObjectName == NULL ) {
+ break;
+ }
+
+ //
+ // do something for down level
+ //
+
+ RtlInitUnicodeString( &ServersGroup, SSI_SERVER_GROUP_W );
+
+ LOCK_CHANGELOG();
+
+ if( RtlEqualUnicodeString(
+ &ServersGroup, ObjectName, (BOOLEAN)TRUE ) ) {
+
+ //
+ // Handle a new LM BDC.
+ //
+
+ if( DeltaType == SecurityDbChangeMemberAdd ) {
+
+ NlLmBdcListAdd( MemberId->GroupMemberId.MemberRid );
+
+
+ //
+ // Handle an LM BDC being deleted.
+ //
+
+ } else if( DeltaType == SecurityDbChangeMemberDel ) {
+
+ NlLmBdcListDel( MemberId->GroupMemberId.MemberRid );
+ }
+
+ } else {
+
+ if( IsChangeLogWorkerRunning() ) {
+
+ //
+ // Change log work is running. If the global groups
+ // list watched is empty, add this delta in the
+ // queue anyway, otherwise add this delta to entry
+ // only if this group is monitored.
+ //
+
+ if( IsListEmpty( &NlGlobalSpecialServerGroupList ) ||
+
+ ( NlGetGroupEntry(
+ &NlGlobalSpecialServerGroupList,
+ ObjectRid ) != NULL ) ) {
+
+
+ (VOID) NlAddWorkerQueueEntry(
+ ChangeLogGroupMembership,
+ MemberId->GroupMemberId.MemberRid );
+
+ }
+ }
+ }
+
+ UNLOCK_CHANGELOG();
+
+ break;
+ }
+
+ //
+ // unknown delta type
+ //
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+ case SecurityDbObjectSamAlias:
+
+ switch (DeltaType) {
+ case SecurityDbNew:
+ case SecurityDbChange:
+ NetlogonDeltaType = AddOrChangeAlias;
+ break;
+
+ case SecurityDbDelete:
+ NetlogonDeltaType = DeleteAlias;
+ break;
+
+ case SecurityDbRename:
+ NetlogonDeltaType = RenameAlias;
+ break;
+
+ case SecurityDbChangeMemberAdd:
+ case SecurityDbChangeMemberSet:
+ case SecurityDbChangeMemberDel:
+
+ NetlogonDeltaType = ChangeAliasMembership;
+
+ LOCK_CHANGELOG();
+
+ //
+ // if this delta is BUILTIN domain delta and the group
+ // modified is special group then add this delta to
+ // workers queue if it is running.
+ //
+
+ if ( (ChangeLogEntry.DBIndex == BUILTIN_DB) &&
+ ( IsChangeLogWorkerRunning() ) &&
+ ( IsSpecialLocalGroup( ObjectRid ) ) ) {
+
+ ULONG Rid;
+ PUCHAR SubAuthorityCount;
+ BOOLEAN EqualSid;
+
+ //
+ // if the member modified belongs to the local SAM
+ // database.
+ //
+
+ SubAuthorityCount =
+ RtlSubAuthorityCountSid(
+ MemberId->AliasMemberId.MemberSid);
+
+ (*SubAuthorityCount)--;
+
+ if( NlGlobalChWorkerSamDomainSid != NULL ) {
+
+ EqualSid = RtlEqualSid(
+ NlGlobalChWorkerSamDomainSid,
+ MemberId->AliasMemberId.MemberSid);
+ } else {
+ EqualSid = FALSE;
+ }
+
+ (*SubAuthorityCount)++;
+
+ if( EqualSid ) {
+
+ Rid = *RtlSubAuthoritySid(
+ MemberId->AliasMemberId.MemberSid,
+ (*SubAuthorityCount) -1 );
+
+ (VOID) NlAddWorkerQueueEntry(
+ ChangeLogAliasMembership,
+ Rid );
+
+
+ //
+ // add this member in the global group list,
+ // since this member may be a global group and we
+ // don't want to miss any delta made on this group.
+ // Worker thread will adjust the list and remove
+ // unwanted user entries from the list.
+ //
+
+ Status = NlAddGroupEntry(
+ &NlGlobalSpecialServerGroupList,
+ Rid );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlAddGroupEntry failed %lx\n",
+ Status ) );
+ }
+ }
+ }
+
+ UNLOCK_CHANGELOG();
+
+ break;
+
+ // unknown delta type
+ default:
+ return STATUS_SUCCESS;
+ }
+ break;
+
+ default:
+
+ // unknown object type
+ return STATUS_SUCCESS;
+
+ }
+
+
+ //
+ // Build the changelog entry and write it to the changelog
+ //
+
+ ChangeLogEntry.DeltaType = NetlogonDeltaType;
+ ChangeLogEntry.SerialNumber = SerialNumber;
+ ChangeLogEntry.ObjectRid = ObjectRid;
+ ChangeLogEntry.Flags = ReplicateImmediately ? CHANGELOG_REPLICATE_IMMEDIATELY : 0;
+ ChangeLogEntry.Flags |= Flags;
+
+ (VOID) NlWriteChangeLogEntry( &NlGlobalChangeLogDesc, &ChangeLogEntry, ObjectSid, ObjectName, TRUE );
+
+
+ //
+ // If this change requires immediate replication, do so
+ //
+
+ if( ReplicateImmediately ) {
+
+ LOCK_CHANGELOG();
+ NlGlobalChangeLogReplicateImmediately = TRUE;
+ UNLOCK_CHANGELOG();
+
+ if ( !SetEvent( NlGlobalChangeLogEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot set ChangeLog event: %lu\n",
+ GetLastError() ));
+ }
+
+
+ //
+ // If this change requires immediate replication to Lanman BDCs, do so
+ //
+
+ } else if( LanmanReplicateImmediately ) {
+
+ LOCK_CHANGELOG();
+ NlGlobalChangeLogLanmanReplicateImmediately = TRUE;
+ UNLOCK_CHANGELOG();
+
+ if ( !SetEvent( NlGlobalChangeLogEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot set ChangeLog event: %lu\n",
+ GetLastError() ));
+ }
+
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+
+
+NTSTATUS
+NlInitChangeLogBuffer(
+ VOID
+)
+/*++
+
+Routine Description:
+
+ Open the change log file (netlogon.chg) for reading or writing one or
+ more records. Create this file if it does not exist or is out of
+ sync with the SAM database (see note below).
+
+ This file must be opened for R/W (deny-none share mode) at the time
+ the cache is initialized. If the file already exists when NETLOGON
+ service started, its contents will be cached in its entirety
+ provided the last change log record bears the same serial number as
+ the serial number field in SAM database else this file will be
+ removed and a new one created. If the change log file did not exist
+ then it will be created.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+
+ UINT WindowsDirectoryLength;
+ WCHAR ChangeLogFile[PATHLEN+1];
+
+ LPNET_CONFIG_HANDLE SectionHandle = NULL;
+ DWORD NewChangeLogSize;
+
+ //
+ // Initialize
+ //
+
+ LOCK_CHANGELOG();
+
+
+ //
+ // Get the size of the changelog.
+
+
+ //
+ // Open the NetLogon configuration section.
+ //
+
+ NewChangeLogSize = DEFAULT_CHANGELOGSIZE;
+ NetStatus = NetpOpenConfigData(
+ &SectionHandle,
+ NULL, // no server name.
+#if defined(USE_WIN32_CONFIG)
+ SERVICE_NETLOGON,
+#else
+ SECT_NT_NETLOGON, // section name
+#endif
+ TRUE ); // we only want readonly access
+
+ if ( NetStatus == NO_ERROR ) {
+
+ (VOID) NlParseOne( SectionHandle,
+ NETLOGON_KEYWORD_CHANGELOGSIZE,
+ DEFAULT_CHANGELOGSIZE,
+ MIN_CHANGELOGSIZE,
+ MAX_CHANGELOGSIZE,
+ &NewChangeLogSize );
+
+ (VOID) NetpCloseConfigData( SectionHandle );
+ }
+
+ NewChangeLogSize = ROUND_UP_COUNT( NewChangeLogSize, ALIGN_WORST);
+
+ NlPrint((NL_INIT, "ChangeLogSize: 0x%lx\n", NewChangeLogSize ));
+
+
+ //
+ // Build the change log file name
+ //
+
+ WindowsDirectoryLength = GetWindowsDirectoryW(
+ NlGlobalChangeLogFilePrefix,
+ sizeof(NlGlobalChangeLogFilePrefix)/sizeof(WCHAR) );
+
+ if ( WindowsDirectoryLength == 0 ) {
+
+ NlPrint((NL_CRITICAL,"Unable to get changelog file directory name, "
+ "WinError = %ld \n", GetLastError() ));
+
+ NlGlobalChangeLogFilePrefix[0] = L'\0';
+ goto CleanChangeLogFile;
+ }
+
+ if ( WindowsDirectoryLength * sizeof(WCHAR) + sizeof(CHANGELOG_FILE_PREFIX) +
+ CHANGELOG_FILE_POSTFIX_LENGTH * sizeof(WCHAR)
+ > sizeof(NlGlobalChangeLogFilePrefix) ) {
+
+ NlPrint((NL_CRITICAL,"Changelog file directory name length is "
+ "too long \n" ));
+
+ NlGlobalChangeLogFilePrefix[0] = L'\0';
+ goto CleanChangeLogFile;
+ }
+
+ wcscat( NlGlobalChangeLogFilePrefix, CHANGELOG_FILE_PREFIX );
+
+
+ //
+ // Read in the existing changelog file.
+ //
+
+ wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( ChangeLogFile, CHANGELOG_FILE_POSTFIX );
+
+ InitChangeLogDesc( &NlGlobalChangeLogDesc );
+ Status = NlOpenChangeLogFile( ChangeLogFile, &NlGlobalChangeLogDesc, FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto CleanChangeLogFile;
+ }
+
+
+ //
+ // Convert the changelog file to the right size/version.
+ //
+
+ Status = NlResizeChangeLogFile( &NlGlobalChangeLogDesc, NewChangeLogSize );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto CleanChangeLogFile;
+ }
+
+ goto Cleanup;
+
+
+ //
+ // CleanChangeLogFile
+ //
+
+CleanChangeLogFile:
+
+ //
+ // If we just need to start with a newly initialized file,
+ // do it.
+ //
+
+ Status = NlResetChangeLog( &NlGlobalChangeLogDesc, NewChangeLogSize );
+
+Cleanup:
+
+ //
+ // start changelog worker thread
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+
+ if ( NlGlobalChangeLogRole == ChangeLogPrimary ) {
+ (VOID)NlStartChangeLogWorkerThread();
+ }
+
+ //
+ // Free any resources on error.
+ //
+
+ } else {
+ NlCloseChangeLogFile( &NlGlobalChangeLogDesc );
+ }
+
+ UNLOCK_CHANGELOG();
+
+ return Status;
+}
+
+
+NTSTATUS
+I_NetNotifyRole (
+ IN POLICY_LSA_SERVER_ROLE Role
+ )
+/*++
+
+Routine Description:
+
+ This function is called by the LSA service upon LSA initialization
+ and when LSA changes domain role. This routine will initialize the
+ change log cache if the role specified is PDC or delete the change
+ log cache if the role specified is other than PDC.
+
+ When this function initializing the change log if the change log
+ currently exists on disk, the cache will be initialized from disk.
+ LSA should treat errors from this routine as non-fatal. LSA should
+ log the errors so they may be corrected then continue
+ initialization. However, LSA should treat the system databases as
+ read-only in this case.
+
+Arguments:
+
+ Role - Current role of the server.
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // If the netlogon service is running,
+ // then we can't change role so simply return.
+ //
+
+ if( NlGlobalChangeLogNetlogonState != NetlogonStopped ) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // If this is a workstation, simply return.
+ //
+
+ if ( NlGlobalChangeLogRole == ChangeLogMemberWorkstation ) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Set our role to the new value.
+ //
+
+ if( Role == PolicyServerRolePrimary) {
+
+ NlGlobalChangeLogRole = ChangeLogPrimary;
+
+ } else {
+
+ NlGlobalChangeLogRole = ChangeLogBackup;
+ }
+
+ //
+ // Delete any previous change log buffer and initialize it again.
+ // (This allows the size to be changed on every role change.)
+ //
+
+ NlCloseChangeLogFile( &NlGlobalChangeLogDesc );
+
+ Status = NlInitChangeLogBuffer();
+
+ return Status;
+}
+
+
+
+NTSTATUS
+I_NetNotifyMachineAccount (
+ IN ULONG ObjectRid,
+ IN PSID DomainSid,
+ IN ULONG OldUserAccountControl,
+ IN ULONG NewUserAccountControl,
+ IN PUNICODE_STRING ObjectName
+ )
+/*++
+
+Routine Description:
+
+ This function is called by the SAM to indicate that the account type
+ of a machine account has changed. Specifically, if
+ USER_INTERDOMAIN_TRUST_ACCOUNT, USER_WORKSTATION_TRUST_ACCOUNT, or
+ USER_SERVER_TRUST_ACCOUNT change for a particular account, this
+ routine is called to let Netlogon know of the account change.
+
+ This function is called for both PDC and BDC.
+
+Arguments:
+
+ ObjectRid - The relative ID of the object that has been modified.
+
+ DomainSid - Specifies the SID of the Domain containing the object.
+
+ OldUserAccountControl - Specifies the previous value of the
+ UserAccountControl field of the user.
+
+ NewUserAccountControl - Specifies the new (current) value of the
+ UserAccountControl field of the user.
+
+ ObjectName - The name of the account being changed.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // If the netlogon service isn't running,
+ // Don't bother with the coming and going of accounts.
+ //
+
+ if( NlGlobalChangeLogNetlogonState == NetlogonStopped ) {
+ return(STATUS_SUCCESS);
+ }
+
+ //
+ // If this is windows NT,
+ // There is nothing to maintain.
+ //
+
+ if ( NlGlobalChangeLogRole == ChangeLogMemberWorkstation ) {
+ return(STATUS_SUCCESS);
+ }
+
+
+ //
+ // Make available just the machine account bits.
+ //
+
+ OldUserAccountControl &= USER_MACHINE_ACCOUNT_MASK;
+ NewUserAccountControl &= USER_MACHINE_ACCOUNT_MASK;
+ NlAssert( OldUserAccountControl == 0 || NewUserAccountControl == 0 );
+ NlAssert( OldUserAccountControl != 0 || NewUserAccountControl != 0 );
+
+
+ //
+ // Handle deletion of a Server Trust Account
+ //
+
+ if ( OldUserAccountControl == USER_SERVER_TRUST_ACCOUNT ) {
+
+ Status = NlSendChangeLogNotification( ChangeLogNtServerDeleted,
+ ObjectName,
+ NULL,
+ 0 );
+
+
+ //
+ // Handle deletion of a Domain Trust Account
+ //
+
+ } else if ( OldUserAccountControl == USER_INTERDOMAIN_TRUST_ACCOUNT ) {
+
+ Status = NlSendChangeLogNotification( ChangeLogTrustedDomainDeleted,
+ ObjectName,
+ NULL,
+ 0 );
+
+
+ //
+ // Handle deletion of a Workstation Trust Account
+ //
+
+ } else if ( OldUserAccountControl == USER_WORKSTATION_TRUST_ACCOUNT ) {
+
+ Status = NlSendChangeLogNotification( ChangeLogWorkstationDeleted,
+ ObjectName,
+ NULL,
+ 0 );
+
+ //
+ // Handle creation of a Server Trust Account
+ //
+
+ } else if ( NewUserAccountControl == USER_SERVER_TRUST_ACCOUNT ) {
+
+ if ( NlGlobalChangeLogRole == ChangeLogPrimary ) {
+ Status = NlSendChangeLogNotification( ChangeLogNtServerAdded,
+ ObjectName,
+ NULL,
+ ObjectRid );
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Ignore all other changes for now.
+ //
+
+ } else {
+
+ Status = STATUS_SUCCESS;
+ }
+
+ return Status;
+ UNREFERENCED_PARAMETER( DomainSid );
+}
+
+
+NTSTATUS
+NlInitChangeLog(
+ VOID
+)
+/*++
+
+Routine Description:
+
+ Do the portion of ChangeLog initialization which happens on process
+ attach for netlogon.dll.
+
+ Specifically, Initialize the NlGlobalChangeLogCritSect and several
+ other global variables.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ LARGE_INTEGER DomainPromotionIncrement = DOMAIN_PROMOTION_INCREMENT;
+ LARGE_INTEGER DomainPromotionMask = DOMAIN_PROMOTION_MASK;
+ NTSTATUS Status;
+
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ NT_PRODUCT_TYPE NtProductType;
+
+
+ //
+ // Initialize the critical section and anything process detach depends on.
+ //
+
+ InitializeCriticalSection( &NlGlobalChangeLogCritSect );
+#if DBG
+ InitializeCriticalSection( &NlGlobalLogFileCritSect );
+ NlGlobalTrace = 0xFFFFFFFF;
+ NlGlobalLogFile = INVALID_HANDLE_VALUE;
+ NlGlobalLogFileMaxSize = DEFAULT_MAXIMUM_LOGFILE_SIZE;
+#endif // DBG
+ InitChangeLogDesc( &NlGlobalChangeLogDesc );
+ NlGlobalChWorkerBuiltinDomainSid = NULL;
+ NlGlobalChWorkerSamDomainSid = NULL;
+
+ NlGlobalChangeLogNetlogonState = NetlogonStopped;
+ NlGlobalChangeLogEvent = NULL;
+ NlGlobalChangeLogReplicateImmediately = FALSE;
+ NlGlobalChangeLogLanmanReplicateImmediately = FALSE;
+ InitializeListHead( &NlGlobalChangeLogNotifications );
+
+
+ NlGlobalChWorkerSamServerHandle = NULL;
+ NlGlobalChWorkerPolicyHandle = NULL;
+ NlGlobalChWorkerSamDBHandle = NULL;
+ NlGlobalChWorkerBuiltinDBHandle = NULL;
+
+ NlGlobalChangeLogWorkerQueueEvent = NULL;
+ InitializeListHead(&NlGlobalChangeLogWorkerQueue);
+ InitializeListHead(&NlGlobalSpecialServerGroupList);
+
+ NlGlobalChangeLogWorkerThreadHandle = NULL;
+ NlGlobalChangeLogWorkInit = FALSE;
+
+ NlGlobalChangeLogWorkerTerminate = FALSE;
+ NlGlobalChangeLogFilePrefix[0] = L'\0';
+ NlGlobalChangeLogPromotionIncrement = DomainPromotionIncrement;
+ NlGlobalChangeLogPromotionMask = DomainPromotionMask.HighPart;
+
+ NlGlobalLmBdcRidArray = NULL;
+ NlGlobalLmBdcCount = 0;
+
+ //
+ // Initialize the Role.
+ //
+ // For Windows-NT, just set the role to member workstation once and for all.
+ //
+ // For LanMan-Nt initially set it to "unknown" to prevent the
+ // changelog from being maintained until LSA calls I_NetNotifyRole.
+ //
+
+ if ( !RtlGetNtProductType( &NtProductType ) ) {
+ NtProductType = NtProductWinNt;
+ }
+
+ if ( NtProductType == NtProductLanManNt ) {
+ NlGlobalChangeLogRole = ChangeLogUnknown;
+ } else {
+ NlGlobalChangeLogRole = ChangeLogMemberWorkstation;
+ }
+
+ //
+ // Initialize the events that are used by the LanmanNt PDC.
+ //
+
+ if ( NtProductType == NtProductLanManNt ) {
+
+ //
+ // Create special change log notify event.
+ //
+
+ NlGlobalChangeLogEvent =
+ CreateEvent( NULL, // No security attributes
+ FALSE, // Is automatically reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if ( NlGlobalChangeLogEvent == NULL ) {
+ NET_API_STATUS NetStatus;
+
+ NetStatus = GetLastError();
+ NlPrint((NL_CRITICAL, "Cannot create ChangeLog Event %lu\n",
+ NetStatus ));
+ return (int) NetpApiStatusToNtStatus(NetStatus);
+ }
+
+ //
+ // Create worker queue notify event.
+ //
+
+ NlGlobalChangeLogWorkerQueueEvent =
+ CreateEvent( NULL, // No security attributes
+ FALSE, // Is automatically reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if ( NlGlobalChangeLogWorkerQueueEvent == NULL ) {
+ NET_API_STATUS NetStatus;
+
+ NetStatus = GetLastError();
+ NlPrint((NL_CRITICAL,
+ "Cannot create Worker Queue Event %lu\n",
+ NetStatus ));
+ return (int) NetpApiStatusToNtStatus(NetStatus);
+ }
+
+ //
+ // Build a Sid for the SAM Builtin domain
+ //
+
+ Status = RtlAllocateAndInitializeSid(
+ &NtAuthority,
+ 1, // Sub Authority Count
+ SECURITY_BUILTIN_DOMAIN_RID,
+ 0, // Unused
+ 0, // Unused
+ 0, // Unused
+ 0, // Unused
+ 0, // Unused
+ 0, // Unused
+ 0, // Unused
+ &NlGlobalChWorkerBuiltinDomainSid);
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Success...
+ //
+
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup
+ //
+
+Cleanup:
+
+ return Status;
+}
+
+//
+// netlogon.dll never detaches
+//
+#ifdef NETLOGON_PROCESS_DETACH
+
+NTSTATUS
+NlCloseChangeLog(
+ VOID
+)
+/*++
+
+Routine Description:
+
+ Frees any resources consumed by NlInitChangeLog.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+
+ if ( (NlGlobalChangeLogDesc.FileHandle == INVALID_HANDLE_VALUE) &&
+ (NlGlobalChangeLogRole == ChangeLogPrimary) ) {
+
+ //
+ // try to save change log cache one last time.
+ //
+
+ (VOID)NlCreateChangeLogFile( &NlGlobalChangeLogDesc );
+ }
+
+ if ( NlGlobalChangeLogDesc.FileHandle != INVALID_HANDLE_VALUE ) {
+ CloseHandle( NlGlobalChangeLogDesc.FileHandle );
+ NlGlobalChangeLogDesc.FileHandle = INVALID_HANDLE_VALUE;
+ }
+ NlGlobalChangeLogFilePrefix[0] = L'\0';
+
+ if ( NlGlobalChangeLogDesc.Buffer != NULL ) {
+ NetpMemoryFree( NlGlobalChangeLogDesc.Buffer );
+ NlGlobalChangeLogDesc.Buffer = NULL;
+ }
+
+ if ( NlGlobalChWorkerBuiltinDomainSid != NULL ) {
+ RtlFreeSid( NlGlobalChWorkerBuiltinDomainSid );
+ NlGlobalChWorkerBuiltinDomainSid = NULL;
+ }
+
+ if ( NlGlobalChWorkerSamDomainSid != NULL ) {
+ NetpMemoryFree( NlGlobalChWorkerSamDomainSid );
+ NlGlobalChWorkerSamDomainSid = NULL;
+ }
+
+ if ( NlGlobalChangeLogEvent != NULL ) {
+ (VOID) CloseHandle(NlGlobalChangeLogEvent);
+ NlGlobalChangeLogEvent = NULL;
+ }
+
+ if ( NlGlobalChangeLogWorkerQueueEvent != NULL ) {
+ (VOID) CloseHandle(NlGlobalChangeLogWorkerQueueEvent);
+ NlGlobalChangeLogWorkerQueueEvent = NULL;
+ }
+
+ //
+ // if worker thread running, stop it.
+ //
+
+ NlStopChangeLogWorker();
+
+ LOCK_CHANGELOG();
+
+ NlAssert( IsListEmpty( &NlGlobalChangeLogNotifications ) );
+ NlAssert( IsListEmpty( &NlGlobalChangeLogWorkerQueue ) );
+
+ UNLOCK_CHANGELOG();
+
+ NlGlobalChangeLogWorkInit = FALSE;
+
+ //
+ // close all handles
+ //
+
+ if ( NlGlobalChWorkerSamServerHandle != NULL ) {
+
+ (VOID)SamrCloseHandle( &NlGlobalChWorkerSamServerHandle);
+ }
+
+ if ( NlGlobalChWorkerPolicyHandle != NULL ) {
+
+ (VOID)LsarClose( &NlGlobalChWorkerPolicyHandle);
+ }
+
+ if ( NlGlobalChWorkerSamDBHandle != NULL ) {
+
+ (VOID)SamrCloseHandle( &NlGlobalChWorkerSamDBHandle);
+ }
+
+ if ( NlGlobalChWorkerBuiltinDBHandle != NULL ) {
+
+ (VOID)SamrCloseHandle( &NlGlobalChWorkerBuiltinDBHandle);
+ }
+
+ DeleteCriticalSection( &NlGlobalChangeLogCritSect );
+#if DBG
+ DeleteCriticalSection( &NlGlobalLogFileCritSect );
+#endif // DBG
+
+ return STATUS_SUCCESS;
+
+}
+#endif // NETLOGON_PROCESS_DETACH
diff --git a/private/net/svcdlls/logonsrv/server/changelg.h b/private/net/svcdlls/logonsrv/server/changelg.h
new file mode 100644
index 000000000..6054b7a91
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/changelg.h
@@ -0,0 +1,224 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ changelg.h
+
+Abstract:
+
+ Defines and routines needed to interface with changelg.c.
+ Read the comments in the abstract for changelg.c to determine the
+ restrictions on the use of that module.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 07-May-1992
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+--*/
+
+#if ( _MSC_VER >= 800 )
+#pragma warning ( 3 : 4100 ) // enable "Unreferenced formal parameter"
+#pragma warning ( 3 : 4219 ) // enable "trailing ',' used for variable argument list"
+#endif
+
+//
+// changelg.c will #include this file with CHANGELOG_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef CHANGELOG_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+#define THREAD_STACKSIZE 8192
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Structures and variables describing the Change Log
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// Change log entry is a variable length record, the variable fields SID and
+// ObjectName will follow the structure.
+//
+
+typedef struct _CHANGELOG_ENTRY_V3 {
+ LARGE_INTEGER SerialNumber; // always align this on 8 byte boundary
+
+ DWORD Size;
+ USHORT DeltaType;
+ UCHAR DBIndex;
+ UCHAR ReplicateImmediately;
+
+ ULONG ObjectRid;
+ USHORT ObjectSidOffset;
+ USHORT ObjectNameOffset; // null terminated unicode string
+} CHANGELOG_ENTRY_V3, *PCHANGELOG_ENTRY_V3;
+
+typedef struct _CHANGELOG_ENTRY {
+ LARGE_INTEGER SerialNumber; // always align this on 8 byte boundary
+
+ ULONG ObjectRid;
+
+ USHORT Flags;
+#define CHANGELOG_REPLICATE_IMMEDIATELY 0x01
+#define CHANGELOG_PASSWORD_CHANGE 0x02
+#define CHANGELOG_SID_SPECIFIED 0x04
+#define CHANGELOG_NAME_SPECIFIED 0x08
+#define CHANGELOG_PDC_PROMOTION 0x10
+#define CHANGELOG_DOMAINUSERS_CHANGED 0x20
+ UCHAR DBIndex;
+ UCHAR DeltaType;
+
+} CHANGELOG_ENTRY, *PCHANGELOG_ENTRY;
+
+
+//
+// List of changes the netlogon needs to be aware of.
+//
+
+typedef struct _CHANGELOG_NOTIFICATION {
+ LIST_ENTRY Next;
+
+ enum CHANGELOG_NOTIFICATION_TYPE {
+ ChangeLogNtServerAdded, // ObjectName/ObjectRid specified
+ ChangeLogNtServerDeleted, // ObjectName specified
+ ChangeLogWorkstationDeleted, // ObjectName specified
+ ChangeLogTrustedDomainDeleted, // ObjectName specified
+ ChangeLogTrustAdded, // ObjectSid specified
+ ChangeLogTrustDeleted, // ObjectSid specified
+ ChangeLogLmServerAdded, // ObjectRid specified
+ ChangeLogLmServerDeleted // ObjectRid specified
+ } EntryType;
+
+ UNICODE_STRING ObjectName;
+
+ PSID ObjectSid;
+
+ ULONG ObjectRid;
+
+} CHANGELOG_NOTIFICATION, *PCHANGELOG_NOTIFICATION;
+
+//
+// To serialize change log access
+//
+
+EXTERN CRITICAL_SECTION NlGlobalChangeLogCritSect;
+
+#define LOCK_CHANGELOG() EnterCriticalSection( &NlGlobalChangeLogCritSect )
+#define UNLOCK_CHANGELOG() LeaveCriticalSection( &NlGlobalChangeLogCritSect )
+
+//
+// Index to supported data bases.
+//
+
+#define SAM_DB 0 // index to SAM database structure
+#define BUILTIN_DB 1 // index to BUILTIN database structure
+#define LSA_DB 2 // index to LSA database
+#define VOID_DB 3 // index to unused database (used to mark changelog
+ // entry as invalid)
+
+#define NUM_DBS 3 // number of databases supported
+
+
+//
+// Amount SAM/LSA increments serial number by on promotion.
+//
+EXTERN LARGE_INTEGER NlGlobalChangeLogPromotionIncrement;
+EXTERN LONG NlGlobalChangeLogPromotionMask;
+
+
+
+//
+// Netlogon started flag, used by the changelog to determine the
+// netlogon service is successfully started and initialization
+// completed.
+//
+
+EXTERN enum {
+ NetlogonStopped,
+ NetlogonStarting,
+ NetlogonStarted
+} NlGlobalChangeLogNetlogonState;
+
+
+
+//
+// Event to indicate that something interesting is being logged to the
+// change log. The booleans below (protected by NlGlobalChangeLogCritSect)
+// indicate the actual interesting event.
+//
+
+EXTERN HANDLE NlGlobalChangeLogEvent;
+
+//
+// Indicates that a "replicate immediately" event has happened.
+//
+
+EXTERN BOOL NlGlobalChangeLogReplicateImmediately;
+
+//
+// Indicates we need to "replicate immediately" to Lanman BDCs
+//
+
+EXTERN BOOL NlGlobalChangeLogLanmanReplicateImmediately;
+
+//
+// List of MachineAccount changes
+//
+
+EXTERN LIST_ENTRY NlGlobalChangeLogNotifications;
+
+//
+// List of Rids of Lanman BDC accounts.
+//
+
+EXTERN PULONG NlGlobalLmBdcRidArray;
+EXTERN ULONG NlGlobalLmBdcCount;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Procedure forwards
+//
+/////////////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+NlInitChangeLog(
+ VOID
+);
+
+#ifdef NETLOGON_PROCESS_DETACH
+NTSTATUS
+NlCloseChangeLog(
+ VOID
+);
+#endif // NETLOGON_PROCESS_DETACH
+
+DWORD
+NlBackupChangeLogFile(
+ VOID
+ );
+
+VOID
+NlLmBdcListSet(
+ IN ULONG LmBdcCount,
+ IN PULONG LmBdcRidArray
+ );
+
+#undef EXTERN
diff --git a/private/net/svcdlls/logonsrv/server/chutil.c b/private/net/svcdlls/logonsrv/server/chutil.c
new file mode 100644
index 000000000..16bf9493c
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/chutil.c
@@ -0,0 +1,4337 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ chutil.c
+
+Abstract:
+
+ Change Log utility routines.
+
+Author:
+
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 11-Jan-1994 (cliffv)
+ Split out from changelg.c
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <ntrtl.h> // LARGE_INTEGER definition
+#include <nturtl.h> // LARGE_INTEGER definition
+#include <ntlsa.h> // needed by changelg.h
+
+#define NOMINMAX // Avoid redefinition of min and max in stdlib.h
+#include <rpc.h> // Needed by logon.h
+#include <logon_s.h>// includes lmcons.h, lmaccess.h, netlogon.h,
+ // ssi.h, windef.h
+#include <winbase.h>
+#include <stdio.h> // sprintf ...
+
+//
+// Include files specific to this .c file
+//
+#include "iniparm.h" // defaults
+
+//
+// BEWARE: Be careful about adding netlogon.dll specific include files here.
+// This module is call by SAM and LSA. The netlogon service may not yet
+// be running. Therefore, guard against referencing netlogon.dll globals
+// other than those defined in changelg.h.
+//
+
+#include <samrpc.h> // Needed by samisrv.h
+#include <samisrv.h> // Needed by changelg.h
+#include <changelg.h> // Local procedure definitions
+
+#include <lmerrlog.h> // NELOG_* defined here ..
+#include <netlib.h> // NetpMemoryAllocate
+#include <netlibnt.h> // NetpNtStatusToApiStatus
+
+#include <debugfmt.h> // FORMAT_*
+#include <nldebug.h> // Netlogon debugging
+#include <align.h>
+#include <string.h> // strncmp
+#include <nlp.h> // NlpWriteEventlog defined here.
+
+#define CHUTIL_ALLOCATE
+#include "chutil.h" // Local data definitions
+#undef CHUTIL_ALLOCATE
+
+
+
+/* NlCreateChangeLogFile and NlWriteChangeLogBytes reference each other */
+NTSTATUS
+NlWriteChangeLogBytes(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LPBYTE Buffer,
+ IN DWORD BufferSize,
+ IN BOOLEAN FlushIt
+ );
+
+
+
+
+NTSTATUS
+NlCreateChangeLogFile(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+ )
+/*++
+
+Routine Description:
+
+ Try to create a change log file. If it is successful then it sets
+ the file handle in ChangeLogDesc, otherwise it leaves the handle invalid.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+{
+ NTSTATUS Status;
+ WCHAR ChangeLogFile[PATHLEN+1];
+
+ NlAssert( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE );
+
+ //
+ // if the change file name is unknown, terminate the operation.
+ //
+
+ if( NlGlobalChangeLogFilePrefix[0] == L'\0' ) {
+ return STATUS_NO_SUCH_FILE;
+ }
+
+ //
+ // Create change log file. If it exists already then truncate it.
+ //
+ // Note : if a valid change log file exists on the system, then we
+ // would have opened at initialization time.
+ //
+
+ wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( ChangeLogFile,
+ ChangeLogDesc->RedoLog ? REDO_FILE_POSTFIX : CHANGELOG_FILE_POSTFIX );
+
+ ChangeLogDesc->FileHandle = CreateFileW(
+ ChangeLogFile,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, // allow backups and debugging
+ NULL, // Supply better security ??
+ CREATE_ALWAYS, // Overwrites always
+ FILE_ATTRIBUTE_NORMAL,
+ NULL ); // No template
+
+ if (ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE) {
+
+ Status = NetpApiStatusToNtStatus( GetLastError());
+ NlPrint((NL_CRITICAL,"Unable to create changelog file: 0x%lx \n", Status));
+ return Status;
+ }
+
+ //
+ // Write cache in backup changelog file if the cache is valid.
+ //
+
+ if( ChangeLogDesc->Buffer != NULL ) {
+ Status = NlWriteChangeLogBytes(
+ ChangeLogDesc,
+ ChangeLogDesc->Buffer,
+ ChangeLogDesc->BufferSize,
+ TRUE ); // Flush the bytes to disk
+
+ return Status;
+
+ }
+
+ return STATUS_SUCCESS;
+
+}
+
+
+NTSTATUS
+NlFlushChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+ )
+/*++
+
+Routine Description:
+
+ Flush any dirty buffers to the change log file itself.
+ Ensure they are flushed to disk.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ OVERLAPPED Overlapped;
+ DWORD BytesWritten;
+ DWORD BufferSize;
+
+ //
+ // If there's nothing to do,
+ // just return.
+ //
+
+ if ( ChangeLogDesc->LastDirtyByte == 0 ) {
+ return STATUS_SUCCESS;
+ }
+
+
+ //
+ // Write to the file.
+ //
+
+ if ( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE ) {
+
+ Status = NlCreateChangeLogFile( ChangeLogDesc );
+
+ //
+ // This must have written entire buffer if it is successful
+ // creating the change log file.
+ //
+
+ goto Cleanup;
+ }
+
+ //
+ // if we are unable to create this into the changelog file, work
+ // with internal cache, but notify admin by sending admin alert.
+ //
+
+ if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
+
+#ifdef notdef
+ NlPrint((NL_CHANGELOG, "NlFlushChangeLog: %ld to %ld\n",
+ ChangeLogDesc->FirstDirtyByte,
+ ChangeLogDesc->LastDirtyByte ));
+#endif // notdef
+
+ //
+ // Seek to appropriate offset in the file.
+ //
+
+ RtlZeroMemory( &Overlapped, sizeof(Overlapped) );
+ Overlapped.Offset = ChangeLogDesc->FirstDirtyByte;
+
+ //
+ // Actually write to the file.
+ //
+
+ BufferSize = ChangeLogDesc->LastDirtyByte -
+ ChangeLogDesc->FirstDirtyByte + 1;
+
+ if ( !WriteFile( ChangeLogDesc->FileHandle,
+ &ChangeLogDesc->Buffer[ChangeLogDesc->FirstDirtyByte],
+ BufferSize,
+ &BytesWritten,
+ &Overlapped ) ) {
+
+ Status = NetpApiStatusToNtStatus( GetLastError() );
+ NlPrint((NL_CRITICAL, "Write to ChangeLog failed 0x%lx\n",
+ Status ));
+
+ //
+ // Recreate changelog file
+ //
+
+ CloseHandle( ChangeLogDesc->FileHandle );
+ ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
+
+ goto Cleanup;
+ }
+
+ //
+ // Ensure all the bytes made it.
+ //
+
+ if ( BytesWritten != BufferSize ) {
+ NlPrint((NL_CRITICAL,
+ "Write to ChangeLog bad byte count %ld s.b. %ld\n",
+ BytesWritten,
+ BufferSize ));
+
+ //
+ // Recreate changelog file
+ //
+
+ CloseHandle( ChangeLogDesc->FileHandle );
+ ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
+
+ Status = STATUS_BUFFER_TOO_SMALL;
+ goto Cleanup;
+ }
+
+ //
+ // Force the modifications to disk.
+ //
+
+ if ( !FlushFileBuffers( ChangeLogDesc->FileHandle ) ) {
+
+ Status = NetpApiStatusToNtStatus( GetLastError() );
+ NlPrint((NL_CRITICAL, "Flush to ChangeLog failed 0x%lx\n", Status ));
+
+ //
+ // Recreate changelog file
+ //
+
+ CloseHandle( ChangeLogDesc->FileHandle );
+ ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
+
+ goto Cleanup;
+ }
+
+ //
+ // Indicate these byte successfully made it out to disk.
+ //
+
+ ChangeLogDesc->FirstDirtyByte = 0;
+ ChangeLogDesc->LastDirtyByte = 0;
+ }
+
+Cleanup:
+
+ if( !NT_SUCCESS(Status) ) {
+
+ //
+ // Write event log.
+ //
+
+ NlpWriteEventlog (
+ NELOG_NetlogonChangeLogCorrupt,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ NULL,
+ 0 );
+ }
+
+ return Status;
+}
+
+NTSTATUS
+NlWriteChangeLogBytes(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LPBYTE Buffer,
+ IN DWORD BufferSize,
+ IN BOOLEAN FlushIt
+ )
+/*++
+
+Routine Description:
+
+ Write bytes from the changelog cache to the change log file.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ Buffer - Address within the changelog cache to write.
+
+ BufferSize - Number of bytes to write.
+
+ FlushIt - TRUE if the bytes are to be flushed to disk
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG FirstDirtyByte;
+ ULONG LastDirtyByte;
+
+ //
+ // Compute the new range of dirty bytes.
+ //
+
+ FirstDirtyByte = ((LPBYTE)Buffer) - ((LPBYTE)ChangeLogDesc->Buffer);
+ LastDirtyByte = FirstDirtyByte + BufferSize - 1;
+
+#ifdef notdef
+ NlPrint((NL_CHANGELOG, "NlWriteChangeLogBytes: %ld to %ld\n",
+ FirstDirtyByte,
+ LastDirtyByte ));
+#endif // notdef
+
+ if ( ChangeLogDesc->LastDirtyByte == 0 ) {
+ ChangeLogDesc->FirstDirtyByte = FirstDirtyByte;
+ ChangeLogDesc->LastDirtyByte = LastDirtyByte;
+ } else {
+ if ( ChangeLogDesc->FirstDirtyByte > FirstDirtyByte ) {
+ ChangeLogDesc->FirstDirtyByte = FirstDirtyByte;
+ }
+ if ( ChangeLogDesc->LastDirtyByte < LastDirtyByte ) {
+ ChangeLogDesc->LastDirtyByte = LastDirtyByte;
+ }
+ }
+
+ //
+ // If the bytes are to be flushed,
+ // do so.
+ //
+
+ if ( FlushIt ) {
+ Status = NlFlushChangeLog( ChangeLogDesc );
+ return Status;
+ }
+ return STATUS_SUCCESS;
+}
+
+
+
+
+PCHANGELOG_BLOCK_HEADER
+NlMoveToNextChangeLogBlock(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_BLOCK_HEADER BlockPtr
+ )
+
+/*++
+
+Routine Description:
+
+ This function accepts a pointer to a change log
+ block and returns the pointer to the next change log block in the
+ buffer. It however wraps around the change log cache.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ BlockPtr - pointer to a change log block.
+
+Return Value:
+
+ Returns the pointer to the next change log block in the list.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER ReturnPtr;
+
+ ReturnPtr = (PCHANGELOG_BLOCK_HEADER)
+ ((LPBYTE)BlockPtr + BlockPtr->BlockSize);
+
+
+ NlAssert( (LPBYTE)ReturnPtr <= ChangeLogDesc->BufferEnd );
+
+ if( (LPBYTE)ReturnPtr >= ChangeLogDesc->BufferEnd ) {
+
+ //
+ // wrap around
+ //
+
+ ReturnPtr = ChangeLogDesc->FirstBlock;
+ }
+
+ return ReturnPtr;
+
+}
+
+
+PCHANGELOG_BLOCK_HEADER
+NlMoveToPrevChangeLogBlock(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_BLOCK_HEADER BlockPtr
+ )
+
+/*++
+
+Routine Description:
+
+ This function accepts a pointer to a change log
+ block and returns the pointer to the next change log block in the
+ buffer. It however wraps around the change log cache.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ BlockPtr - pointer to a change log block.
+
+Return Value:
+
+ Returns the pointer to the next change log block in the list.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER ReturnPtr;
+ PCHANGELOG_BLOCK_TRAILER ReturnTrailer;
+
+ //
+ // If this is the first block in the buffer,
+ // return the last block in the buffer.
+ //
+
+ if ( BlockPtr == ChangeLogDesc->FirstBlock ) {
+ ReturnTrailer = (PCHANGELOG_BLOCK_TRAILER)
+ (ChangeLogDesc->BufferEnd - sizeof(CHANGELOG_BLOCK_TRAILER));
+
+ //
+ // Otherwise return the buffer immediately before this one.
+ //
+
+ } else {
+ ReturnTrailer = (PCHANGELOG_BLOCK_TRAILER)
+ (((LPBYTE)BlockPtr) - sizeof(CHANGELOG_BLOCK_TRAILER));
+ }
+
+
+ ReturnPtr = (PCHANGELOG_BLOCK_HEADER)
+ ((LPBYTE)ReturnTrailer -
+ ReturnTrailer->BlockSize +
+ sizeof(CHANGELOG_BLOCK_TRAILER) );
+
+
+ NlAssert( ReturnPtr >= ChangeLogDesc->FirstBlock );
+ NlAssert( (LPBYTE)ReturnPtr < ChangeLogDesc->BufferEnd );
+
+ return ReturnPtr;
+
+}
+
+
+
+NTSTATUS
+NlAllocChangeLogBlock(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD BlockSize,
+ OUT PCHANGELOG_BLOCK_HEADER *AllocatedBlock
+ )
+/*++
+
+Routine Description:
+
+ This function will allocate a change log block from the free block
+ at the tail of the change log circular list. If the available free
+ block size is less than the required size than it will enlarge the
+ free block by the freeing up change logs from the header. Once the
+ free block is larger then it will cut the block to the required size
+ and adjust the free block pointer.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ BlockSize - size of the change log block required.
+
+ AllocatedBlock - Returns the pointer to the block that is allocated.
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER FreeBlock;
+ PCHANGELOG_BLOCK_HEADER NewBlock;
+ DWORD ReqBlockSize;
+ DWORD AllocatedBlockSize;
+
+ //
+ // pump up the size to include block header, block trailer,
+ // and to align to DWORD.
+ //
+ // Add in the size of the new free block immediately following the new
+ // block.
+ //
+
+ AllocatedBlockSize =
+ ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_HEADER), ALIGN_WORST) +
+ ROUND_UP_COUNT( BlockSize+sizeof(CHANGELOG_BLOCK_TRAILER), ALIGN_WORST);
+
+ ReqBlockSize = AllocatedBlockSize +
+ ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_HEADER), ALIGN_WORST) +
+ ROUND_UP_COUNT( sizeof(CHANGELOG_BLOCK_TRAILER), ALIGN_WORST );
+
+ NlAssert( ReqBlockSize < ChangeLogDesc->BufferSize - 16 );
+
+
+ //
+ // If the current free block isn't big enough,
+ // make it big enough.
+ //
+
+ FreeBlock = ChangeLogDesc->Tail;
+
+ NlAssert( FreeBlock->BlockState == BlockFree );
+
+ while ( FreeBlock->BlockSize <= ReqBlockSize ) {
+
+ //
+ // If this is a re-do log,
+ // make the freeblock bigger by re-allocating the buffer.
+
+ if ( ChangeLogDesc->RedoLog ) {
+ NTSTATUS Status;
+
+ Status = NlResizeChangeLogFile(
+ ChangeLogDesc,
+ ChangeLogDesc->BufferSize + REDO_LOG_INCREMENT );
+
+
+ if ( !NT_SUCCESS(Status) ) {
+ return Status;
+ }
+
+ //
+ // The free block is in a different allocated buffer.
+ //
+
+ FreeBlock = ChangeLogDesc->Tail;
+
+ NlAssert( FreeBlock->BlockState == BlockFree );
+
+ //
+ // If this is a change log,
+ // make the free block bigger by wrapping around.
+ //
+
+ } else {
+ PCHANGELOG_BLOCK_HEADER NextFreeBlock;
+
+ NextFreeBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, FreeBlock );
+
+
+ //
+ // If this free block is the end block in the cache,
+ // so make this as a 'hole' block and wrap around for
+ // next free block.
+ //
+
+ if( (LPBYTE)NextFreeBlock !=
+ (LPBYTE)FreeBlock + FreeBlock->BlockSize ) {
+
+ NlAssert( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) ==
+ ChangeLogDesc->BufferEnd );
+
+ NlAssert( NextFreeBlock == ChangeLogDesc->FirstBlock );
+
+ FreeBlock->BlockState = BlockHole;
+
+ //
+ // Write the 'hole' block status in the file.
+ // (Write the entire block since the block size in the trailer
+ // may have changed on previous iterations of this loop.)
+ //
+
+ (VOID) NlWriteChangeLogBytes( ChangeLogDesc,
+ (LPBYTE) FreeBlock,
+ FreeBlock->BlockSize,
+ TRUE ); // Flush the bytes to disk
+
+ //
+ // The free block is now at the front of the cache.
+ //
+
+ FreeBlock = ChangeLogDesc->FirstBlock;
+ FreeBlock->BlockState = BlockFree;
+
+ //
+ // Otherwise, enlarge the current free block by merging the next
+ // block into it. The next free block is either a used block or
+ // the 'hole' block.
+ //
+ } else {
+
+ //
+ // If we've just deleted a used block,
+ // adjust the entry count.
+ //
+ // VOID_DB entries are "deleted" entries and have already adjusted
+ // the entry count.
+ //
+ if ( NextFreeBlock->BlockState == BlockUsed ) {
+ DWORD DBIndex = ((PCHANGELOG_ENTRY)(NextFreeBlock+1))->DBIndex;
+ if ( DBIndex != VOID_DB ) {
+ ChangeLogDesc->EntryCount[DBIndex] --;
+ }
+ }
+
+ FreeBlock->BlockSize += NextFreeBlock->BlockSize;
+ ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
+ }
+
+
+ //
+ // If we've consumed the head of the cache,
+ // move the head of the cache to the next block.
+ //
+
+ if ( NextFreeBlock == ChangeLogDesc->Head ) {
+
+ ChangeLogDesc->Head = NlMoveToNextChangeLogBlock( ChangeLogDesc,
+ NextFreeBlock );
+
+ //
+ // if we have moved the global header to hole block,
+ // skip and merge it to free block
+ //
+
+ NextFreeBlock = ChangeLogDesc->Head;
+
+ if (NextFreeBlock->BlockState == BlockHole ) {
+
+ FreeBlock->BlockSize += NextFreeBlock->BlockSize;
+ ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
+
+ ChangeLogDesc->Head =
+ NlMoveToNextChangeLogBlock( ChangeLogDesc, NextFreeBlock );
+ }
+ }
+ }
+
+
+ NlAssert(ChangeLogDesc->Head->BlockState == BlockUsed );
+
+ }
+
+ NlAssert( (FreeBlock >= ChangeLogDesc->FirstBlock) &&
+ (FreeBlock->BlockSize <= ChangeLogDesc->BufferSize) &&
+ ( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) <=
+ ChangeLogDesc->BufferEnd) );
+
+ //
+ // Cut the free block ...
+ //
+
+ NewBlock = FreeBlock;
+
+ FreeBlock = (PCHANGELOG_BLOCK_HEADER)
+ ((LPBYTE)FreeBlock + AllocatedBlockSize);
+
+ FreeBlock->BlockState = BlockFree;
+ FreeBlock->BlockSize = NewBlock->BlockSize - AllocatedBlockSize;
+ ChangeLogBlockTrailer(FreeBlock)->BlockSize = FreeBlock->BlockSize;
+
+ ChangeLogDesc->Tail = FreeBlock;
+
+ RtlZeroMemory( NewBlock, AllocatedBlockSize );
+ NewBlock->BlockState = BlockUsed;
+ NewBlock->BlockSize = AllocatedBlockSize;
+ ChangeLogBlockTrailer(NewBlock)->BlockSize = NewBlock->BlockSize;
+
+ NlAssert( (NewBlock >= ChangeLogDesc->FirstBlock) &&
+ ( ((LPBYTE)NewBlock + BlockSize) <= ChangeLogDesc->BufferEnd) );
+
+ *AllocatedBlock = NewBlock;
+
+ return STATUS_SUCCESS;
+
+}
+
+
+PCHANGELOG_ENTRY
+NlMoveToNextChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This function is a worker routine to scan the change log list. This
+ accepts a pointer to a change log structure and returns a pointer to
+ the next change log structure. It returns NULL pointer if the given
+ struct is the last change log structure in the list.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ ChangeLogEntry - pointer to a change log strcuture.
+
+Return Value:
+
+ Returns the pointer to the next change log structure in the list.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
+
+ ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
+ ( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
+
+ NlAssert( ChangeLogBlock->BlockState == BlockUsed );
+
+ ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
+
+ //
+ // If we're at the end of the list,
+ // return null
+ //
+ if ( ChangeLogBlock->BlockState == BlockFree ) {
+ return NULL;
+
+
+ //
+ // Skip this block, there will be only one 'Hole' block in the
+ // list.
+ //
+ } else if ( ChangeLogBlock->BlockState == BlockHole ) {
+
+
+ ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
+
+ if ( ChangeLogBlock->BlockState == BlockFree ) {
+ return NULL;
+ }
+
+ }
+
+ NlAssert( ChangeLogBlock->BlockState == BlockUsed );
+
+ return (PCHANGELOG_ENTRY)
+ ( (LPBYTE)ChangeLogBlock + sizeof(CHANGELOG_BLOCK_HEADER) );
+
+}
+
+
+PCHANGELOG_ENTRY
+NlMoveToPrevChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This function is a worker routine to scan the change log list. This
+ accepts a pointer to a change log structure and returns a pointer to
+ the previous change log structure. It returns NULL pointer if the given
+ struct is the first change log structure in the list.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ ChangeLogEntry - pointer to a change log strcuture.
+
+Return Value:
+
+ Returns the pointer to the next change log structure in the list.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
+
+ ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
+ ( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
+
+ NlAssert( ChangeLogBlock->BlockState == BlockUsed ||
+ ChangeLogBlock->BlockState == BlockFree );
+
+ ChangeLogBlock = NlMoveToPrevChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
+
+ //
+ // If we're at the end of the list,
+ // return null
+ //
+ if ( ChangeLogBlock->BlockState == BlockFree ) {
+ return NULL;
+
+
+ //
+ // Skip this block, there will be only one 'Hole' block in the
+ // list.
+ //
+ } else if ( ChangeLogBlock->BlockState == BlockHole ) {
+
+
+ ChangeLogBlock = NlMoveToPrevChangeLogBlock( ChangeLogDesc, ChangeLogBlock );
+
+ if ( ChangeLogBlock->BlockState == BlockFree ) {
+ return NULL;
+ }
+
+ }
+
+ NlAssert( ChangeLogBlock->BlockState == BlockUsed );
+
+ return (PCHANGELOG_ENTRY)
+ ( (LPBYTE)ChangeLogBlock + sizeof(CHANGELOG_BLOCK_HEADER) );
+
+}
+
+
+PCHANGELOG_ENTRY
+NlFindFirstChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD DBIndex
+ )
+/*++
+
+Routine Description:
+
+ Returns a pointer to the first change log entry for the specified
+ database.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+Return Value:
+
+ Non-NULL - change log entry found
+
+ NULL - No such entry exists.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+
+ //
+ // If nothing has ever been written to the change log,
+ // indicate nothing is available.
+ //
+
+ if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
+ return NULL;
+ }
+
+ for ( ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Head + 1);
+ ChangeLogEntry != NULL ;
+ ChangeLogEntry = NlMoveToNextChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) {
+
+ if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
+ break;
+ }
+ }
+
+ return ChangeLogEntry;
+}
+
+
+
+PCHANGELOG_ENTRY
+NlFindChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN BOOL DownLevel,
+ IN BOOL NeedExactMatch,
+ IN DWORD DBIndex
+ )
+/*++
+
+Routine Description:
+
+ Search the change log entry in change log cache for a given serial
+ number
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ SerialNumber - Serial number of the entry to find.
+
+ DownLevel - True if only the least significant portion of the serial
+ number needs to match.
+
+ NeedExactMatch - True if the caller wants us to exactly match the
+ specified serial number.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+Return Value:
+
+ Non-NULL - change log entry found
+
+ NULL - No such entry exists.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ PCHANGELOG_ENTRY PriorChangeLogEntry = NULL;
+
+ //
+ // If nothing has ever been written to the change log,
+ // indicate nothing is available.
+ //
+
+ if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
+ return NULL;
+ }
+
+ //
+ // Search from the tail of the changelog. For huge changelogs, this should
+ // reduce the working set size since we almost always search for one of
+ // the last few entries.
+ //
+
+ ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Tail + 1);
+
+
+ while ( ( ChangeLogEntry =
+ NlMoveToPrevChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) != NULL ) {
+
+ if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
+
+ if ( DownLevel ) {
+ if ( ChangeLogEntry->SerialNumber.LowPart ==
+ SerialNumber.LowPart ) {
+ return ChangeLogEntry;
+ }
+ } else {
+ if ( IsSerialNumberEqual( ChangeLogDesc, ChangeLogEntry, &SerialNumber) ){
+ if ( NeedExactMatch &&
+ ChangeLogEntry->SerialNumber.QuadPart != SerialNumber.QuadPart ) {
+ return NULL;
+ }
+ return ChangeLogEntry;
+ }
+
+ //
+ // For the redo log,
+ // find the smallest change log entry that is greater than or equal to
+ // the requested number.
+ //
+
+ if ( ChangeLogDesc->RedoLog && !NeedExactMatch) {
+ if ( ChangeLogEntry->SerialNumber.QuadPart < SerialNumber.QuadPart ) {
+ return PriorChangeLogEntry;
+ }
+
+ }
+ }
+
+ PriorChangeLogEntry = ChangeLogEntry;
+
+ }
+ }
+
+ if ( ChangeLogDesc->RedoLog && !NeedExactMatch ) {
+ return PriorChangeLogEntry;
+ } else {
+ return NULL;
+ }
+}
+
+
+PCHANGELOG_ENTRY
+NlDuplicateChangeLogEntry(
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ OUT LPDWORD ChangeLogEntrySize OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Duplicate the specified changelog entry into an allocated buffer.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogEntry -- points to the changelog entry to duplicate
+
+ ChangeLogEntrySize - Optionally returns the size (in bytes) of the
+ returned change log entry.
+
+Return Value:
+
+ NULL - Not enough memory to duplicate the change log entry
+
+ Non-NULL - returns a pointer to the duplicate change log entry. This buffer
+ must be freed via NetpMemoryFree.
+
+--*/
+{
+ PCHANGELOG_ENTRY TempChangeLogEntry;
+ ULONG Size;
+ PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
+
+ ChangeLogBlock = (PCHANGELOG_BLOCK_HEADER)
+ ( (LPBYTE) ChangeLogEntry - sizeof(CHANGELOG_BLOCK_HEADER) );
+
+ Size = ChangeLogBlock->BlockSize -
+ sizeof(CHANGELOG_BLOCK_HEADER) -
+ sizeof(CHANGELOG_BLOCK_TRAILER);
+
+ TempChangeLogEntry = (PCHANGELOG_ENTRY) NetpMemoryAllocate( Size );
+
+ if( TempChangeLogEntry == NULL ) {
+ return NULL;
+ }
+
+ RtlCopyMemory( TempChangeLogEntry, ChangeLogEntry, Size );
+
+ if ( ChangeLogEntrySize != NULL ) {
+ *ChangeLogEntrySize = Size;
+ }
+
+ return TempChangeLogEntry;
+}
+
+
+
+PCHANGELOG_ENTRY
+NlFindPromotionChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex
+ )
+/*++
+
+Routine Description:
+
+ Find the last change log entry with the same promotion count
+ as SerialNumber.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ SerialNumber - Serial number containing the promotion count to query.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+Return Value:
+
+ Non-NULL - returns a pointer to the duplicate change log entry. This buffer
+ must be freed via NetpMemoryFree.
+
+ NULL - No such entry exists.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ LONG GoalPromotionCount;
+ LONG PromotionCount;
+
+ //
+ // If nothing has ever been written to the change log,
+ // indicate nothing is available.
+ //
+
+ if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
+ return NULL;
+ }
+
+
+
+ //
+ // Search from the tail of the changelog. For huge changelogs, this should
+ // reduce the working set size since we almost always search for one of
+ // the last few entries.
+ //
+
+ ChangeLogEntry = (PCHANGELOG_ENTRY) (ChangeLogDesc->Tail + 1);
+ GoalPromotionCount = SerialNumber.HighPart & NlGlobalChangeLogPromotionMask;
+
+ while ( ( ChangeLogEntry =
+ NlMoveToPrevChangeLogEntry( ChangeLogDesc, ChangeLogEntry) ) != NULL ) {
+
+ if( ChangeLogEntry->DBIndex == (UCHAR) DBIndex ) {
+ PromotionCount = ChangeLogEntry->SerialNumber.HighPart & NlGlobalChangeLogPromotionMask;
+
+ //
+ // If the Current Change Log entry has a greater promotion count,
+ // continue searching backward.
+ //
+
+ if ( PromotionCount > GoalPromotionCount ) {
+ continue;
+ }
+
+ //
+ // If the current change log entry has a smaller promotion count,
+ // indicate we couldn't find a change log entry.
+ //
+
+ if ( PromotionCount < GoalPromotionCount ) {
+ break;
+ }
+
+ //
+ // Otherwise, success
+ //
+
+ return NlDuplicateChangeLogEntry( ChangeLogEntry, NULL );
+
+ }
+ }
+
+ return NULL;
+}
+
+
+PCHANGELOG_ENTRY
+NlGetNextDownlevelChangeLogEntry(
+ ULONG DownlevelSerialNumber
+ )
+/*++
+
+Routine Description:
+
+ Find the change log entry for the delta with a serial number greater
+ than the one specified.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ DownlevelSerialNumber - The downlevel serial number
+
+Return Value:
+
+ Non-NULL - change log entry found. This changelog entry must be
+ deallocated using NetpMemoryFree.
+
+ NULL - No such entry exists.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ LARGE_INTEGER SerialNumber;
+
+ SerialNumber.QuadPart = DownlevelSerialNumber + 1;
+
+ ChangeLogEntry = NlFindChangeLogEntry( &NlGlobalChangeLogDesc, SerialNumber, TRUE, TRUE, SAM_DB);
+
+ if ( ChangeLogEntry == NULL ||
+ ChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
+ return NULL;
+ }
+
+ return NlDuplicateChangeLogEntry( ChangeLogEntry, NULL );
+}
+
+
+PCHANGELOG_ENTRY
+NlFindNextChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY LastChangeLogEntry,
+ IN DWORD DBIndex
+ )
+/*++
+
+Routine Description:
+
+ Find the next change log entry in change log following a particular
+ changelog entry.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ LastChangeLogEntry - last found changelog entry.
+
+ DBIndex - database index of the next entry to find
+
+Return Value:
+
+ Non-null - change log entry found
+
+ NULL - No such entry exists.
+
+
+--*/
+{
+ PCHANGELOG_ENTRY NextChangeLogEntry = LastChangeLogEntry;
+ LARGE_INTEGER SerialNumber;
+
+ //
+ // Loop through the log finding this entry starting from the last
+ // found record.
+ //
+
+ SerialNumber.QuadPart = LastChangeLogEntry->SerialNumber.QuadPart + 1;
+ while ( ( NextChangeLogEntry =
+ NlMoveToNextChangeLogEntry( ChangeLogDesc, NextChangeLogEntry) ) != NULL ) {
+
+ if( NextChangeLogEntry->DBIndex == DBIndex ) {
+
+ //
+ // next log entry in the change log for
+ // this database. The serial number should match.
+ //
+
+ if ( !IsSerialNumberEqual( ChangeLogDesc, NextChangeLogEntry, &SerialNumber) ) {
+
+ //
+ // For the redo log, the serial numbers merely need to be ascending.
+ //
+
+ if ( !ChangeLogDesc->RedoLog ||
+ NextChangeLogEntry->SerialNumber.QuadPart < SerialNumber.QuadPart ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlFindNextChangeLogEntry: Serial numbers not contigous %lx %lx and %lx %lx\n",
+ NextChangeLogEntry->SerialNumber.HighPart,
+ NextChangeLogEntry->SerialNumber.LowPart,
+ SerialNumber.HighPart,
+ SerialNumber.LowPart ));
+
+ //
+ // write event log
+ //
+
+ NlpWriteEventlog (
+ NELOG_NetlogonChangeLogCorrupt,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE)&DBIndex,
+ sizeof(DBIndex),
+ NULL,
+ 0 );
+
+ return NULL;
+ }
+
+ }
+
+ return NextChangeLogEntry;
+
+ }
+ }
+
+ return NULL;
+}
+
+
+BOOLEAN
+NlCompareChangeLogEntries(
+ IN PCHANGELOG_ENTRY ChangeLogEntry1,
+ IN PCHANGELOG_ENTRY ChangeLogEntry2
+ )
+/*++
+
+Routine Description:
+
+ The two change log entries are compared to see if the are for the same
+ object. If
+
+Arguments:
+
+ ChangeLogEntry1 - First change log entry to compare.
+
+ ChangeLogEntry2 - Second change log entry to compare.
+
+Return Value:
+
+ TRUE - iff the change log entries are for the same object.
+
+--*/
+{
+ //
+ // Ensure the DbIndex is the same for both entries.
+ //
+
+ if ( ChangeLogEntry1->DBIndex != ChangeLogEntry2->DBIndex ) {
+ return FALSE;
+ }
+
+ //
+ // Ensure the entries both describe the same object type.
+ //
+
+ if ( ChangeLogEntry1->DeltaType >= MAX_DELETE_DELTA ) {
+ NlPrint(( NL_CRITICAL,
+ "NlCompateChangeLogEntries: invalid delta type %lx\n",
+ ChangeLogEntry1->DeltaType ));
+ return FALSE;
+ }
+
+ if ( ChangeLogEntry2->DeltaType >= MAX_DELETE_DELTA ) {
+ NlPrint(( NL_CRITICAL,
+ "NlCompateChangeLogEntries: invalid delta type %lx\n",
+ ChangeLogEntry2->DeltaType ));
+ return FALSE;
+ }
+
+ if ( NlGlobalDeleteDeltaType[ChangeLogEntry1->DeltaType] !=
+ NlGlobalDeleteDeltaType[ChangeLogEntry2->DeltaType] ) {
+ return FALSE;
+ }
+
+ //
+ // Depending on the delta type, ensure the entries refer to the same object.
+ //
+
+ switch(ChangeLogEntry1->DeltaType) {
+
+ case AddOrChangeGroup:
+ case DeleteGroup:
+ case RenameGroup:
+ case AddOrChangeUser:
+ case DeleteUser:
+ case RenameUser:
+ case ChangeGroupMembership:
+ case AddOrChangeAlias:
+ case DeleteAlias:
+ case RenameAlias:
+ case ChangeAliasMembership:
+
+ if (ChangeLogEntry1->ObjectRid == ChangeLogEntry2->ObjectRid ) {
+ return TRUE;
+ }
+ break;
+
+
+ case AddOrChangeLsaTDomain:
+ case DeleteLsaTDomain:
+ case AddOrChangeLsaAccount:
+ case DeleteLsaAccount:
+
+ NlAssert( ChangeLogEntry1->Flags & CHANGELOG_SID_SPECIFIED );
+ NlAssert( ChangeLogEntry2->Flags & CHANGELOG_SID_SPECIFIED );
+
+ if( (ChangeLogEntry1->Flags & CHANGELOG_SID_SPECIFIED) == 0 ||
+ (ChangeLogEntry2->Flags & CHANGELOG_SID_SPECIFIED) == 0) {
+ break;
+ }
+
+ if( RtlEqualSid(
+ (PSID)((LPBYTE)ChangeLogEntry1 + sizeof(CHANGELOG_ENTRY)),
+ (PSID)((LPBYTE)ChangeLogEntry2 + sizeof(CHANGELOG_ENTRY))) ) {
+
+ return TRUE;
+ }
+ break;
+
+ case AddOrChangeLsaSecret:
+ case DeleteLsaSecret:
+
+ NlAssert( ChangeLogEntry1->Flags & CHANGELOG_NAME_SPECIFIED );
+ NlAssert( ChangeLogEntry2->Flags & CHANGELOG_NAME_SPECIFIED );
+
+ if( (ChangeLogEntry1->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ||
+ (ChangeLogEntry2->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
+ break;
+ }
+
+ if( _wcsicmp(
+ (LPWSTR)((LPBYTE)ChangeLogEntry1 + sizeof(CHANGELOG_ENTRY)),
+ (LPWSTR)((LPBYTE)ChangeLogEntry2 + sizeof(CHANGELOG_ENTRY))
+ ) == 0 ) {
+
+ return TRUE;
+ }
+ break;
+
+ case AddOrChangeLsaPolicy:
+ case AddOrChangeDomain:
+ return TRUE;
+
+ default:
+ NlPrint((NL_CRITICAL,
+ "NlCompareChangeLogEntries: invalid delta type %lx\n",
+ ChangeLogEntry1->DeltaType ));
+ break;
+ }
+
+ return FALSE;
+}
+
+
+PCHANGELOG_ENTRY
+NlGetNextChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex,
+ OUT LPDWORD ChangeLogEntrySize OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Search the change log entry in change log cache for a given serial
+ number.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to use.
+
+ SerialNumber - Serial number preceeding that of the entry to find.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+ ChangeLogEntrySize - Optionally returns the size (in bytes) of the
+ returned change log entry.
+
+Return Value:
+
+ Non-NULL - returns a pointer to a duplicate of the found change log entry.
+ This buffer must be freed via NetpMemoryFree.
+
+ NULL - No such entry exists.
+
+
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+
+
+ //
+ // Increment the serial number, get the change log entry, duplicate it
+ //
+
+ LOCK_CHANGELOG();
+ SerialNumber.QuadPart += 1;
+ ChangeLogEntry = NlFindChangeLogEntry(
+ ChangeLogDesc,
+ SerialNumber,
+ FALSE,
+ FALSE,
+ DBIndex );
+
+ if ( ChangeLogEntry != NULL ) {
+ ChangeLogEntry = NlDuplicateChangeLogEntry(ChangeLogEntry, ChangeLogEntrySize );
+ }
+
+ UNLOCK_CHANGELOG();
+ return ChangeLogEntry;
+}
+
+
+PCHANGELOG_ENTRY
+NlGetNextUniqueChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex,
+ OUT LPDWORD ChangeLogEntrySize OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Search the change log entry in change log cache for a given serial
+ number. If there are more than one change log entry for the same
+ object then this routine will return the last log entry of that
+ object.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to use.
+
+ SerialNumber - Serial number preceeding that of the entry to find.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+ ChangeLogEntrySize - Optionally returns the size (in bytes) of the
+ returned change log entry.
+
+Return Value:
+
+ Non-NULL - returns a pointer to a duplicate of the found change log entry.
+ This buffer must be freed via NetpMemoryFree.
+
+ NULL - No such entry exists.
+
+
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ PCHANGELOG_ENTRY NextChangeLogEntry;
+ PCHANGELOG_ENTRY FoundChangeLogEntry;
+
+
+ //
+ // Get the first entry we want to deal with.
+ //
+ SerialNumber.QuadPart += 1;
+ ChangeLogEntry = NlFindChangeLogEntry(
+ ChangeLogDesc,
+ SerialNumber,
+ FALSE,
+ FALSE,
+ DBIndex );
+
+ if ( ChangeLogEntry == NULL ) {
+ return NULL;
+ }
+
+
+ //
+ // Skip over any leading dummy change log entries
+ //
+
+ while ( ChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
+
+ //
+ // Get the next change log entry to compare with.
+ //
+
+ NextChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
+ ChangeLogEntry,
+ DBIndex );
+
+ if( NextChangeLogEntry == NULL ) {
+ return NULL;
+ }
+
+ //
+ // skip 'ChangeLogEntry' entry
+ //
+
+ ChangeLogEntry = NextChangeLogEntry;
+ }
+
+
+ //
+ // Check to see if the next entry is a "duplicate" of this entry.
+ //
+
+ FoundChangeLogEntry = ChangeLogEntry;
+
+ for (;;) {
+
+ //
+ // Don't walk past a change log entry for a promotion.
+ // Promotions don't happen very often, but passing the BDC the
+ // change log entry will allow it to do a better job of building
+ // its own change log.
+ //
+
+ if ( FoundChangeLogEntry->Flags & CHANGELOG_PDC_PROMOTION ) {
+ break;
+ }
+
+ //
+ // Get the next change log entry to compare with.
+ //
+
+ NextChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
+ ChangeLogEntry,
+ DBIndex );
+
+ if( NextChangeLogEntry == NULL ) {
+ break;
+ }
+
+ //
+ // Just skip any dummy entries.
+ //
+
+ if ( NextChangeLogEntry->DeltaType == DummyChangeLogEntry ) {
+ ChangeLogEntry = NextChangeLogEntry;
+ continue;
+ }
+
+ //
+ // if 'FoundChangeLogEntry' and 'NextChangeLogEntry' entries are
+ // for different objects or are different delta types.
+ // then return 'FoundChangeLogEntry' to the caller.
+ //
+
+ if ( FoundChangeLogEntry->DeltaType != NextChangeLogEntry->DeltaType ||
+ !NlCompareChangeLogEntries( FoundChangeLogEntry, NextChangeLogEntry ) ){
+ break;
+
+ }
+
+
+ //
+ // Skip 'FoundChangeLogEntry' entry
+ // Mark this entry as the being the best one to return.
+ //
+
+ ChangeLogEntry = NextChangeLogEntry;
+ FoundChangeLogEntry = ChangeLogEntry;
+ }
+
+ return NlDuplicateChangeLogEntry(FoundChangeLogEntry, ChangeLogEntrySize );
+}
+
+
+BOOL
+NlRecoverChangeLog(
+ PCHANGELOG_ENTRY OrigChangeLogEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine traverses the change log list from current change log entry
+ determines whether the current change log can be ignored under
+ special conditions.
+
+Arguments:
+
+ OrigChangeLogEntry - pointer to log structure that is under investigation.
+
+Return Value:
+
+ TRUE - if the given change log can be ignored.
+
+ FALSE - otherwise.
+
+--*/
+{
+ PCHANGELOG_ENTRY NextChangeLogEntry;
+ BOOLEAN ReturnValue;
+
+ //
+ // Find the original change log entry.
+ //
+
+ LOCK_CHANGELOG();
+ NextChangeLogEntry = NlFindChangeLogEntry(
+ &NlGlobalChangeLogDesc,
+ OrigChangeLogEntry->SerialNumber,
+ FALSE, // Not downlevel
+ FALSE, // Not exact match
+ OrigChangeLogEntry->DBIndex );
+
+ if (NextChangeLogEntry == NULL) {
+ ReturnValue = FALSE;
+ goto Cleanup;
+ }
+
+ if ( OrigChangeLogEntry->DeltaType >= MAX_DELETE_DELTA ) {
+ NlPrint(( NL_CRITICAL,
+ "NlRecoverChangeLog: invalid delta type %lx\n",
+ OrigChangeLogEntry->DeltaType ));
+ ReturnValue = FALSE;
+ goto Cleanup;
+ }
+
+ //
+ // Loop for each entry with a greater serial number.
+ //
+
+ for (;;) {
+
+ NextChangeLogEntry = NlFindNextChangeLogEntry(
+ &NlGlobalChangeLogDesc,
+ NextChangeLogEntry,
+ OrigChangeLogEntry->DBIndex );
+
+ if (NextChangeLogEntry == NULL) {
+ break;
+ }
+
+ //
+ // If the delta we found is the type that deletes the original delta,
+ // and the objects described by the two deltas are the same,
+ // tell the caller to not worry about the original delta failing.
+ //
+
+ if ( NextChangeLogEntry->DeltaType ==
+ NlGlobalDeleteDeltaType[OrigChangeLogEntry->DeltaType] &&
+ NlCompareChangeLogEntries( OrigChangeLogEntry,
+ NextChangeLogEntry ) ) {
+ ReturnValue = TRUE;
+ goto Cleanup;
+ }
+
+ }
+
+ ReturnValue = FALSE;
+
+Cleanup:
+ UNLOCK_CHANGELOG();
+ return ReturnValue;
+
+}
+
+
+VOID
+NlVoidChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN BOOLEAN FlushIt
+ )
+/*++
+
+Routine Description:
+
+ Mark a changelog entry as void. If there are no more change log entries in the file,
+ the file is deleted.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to use.
+
+ ChangeLogEntry -- Change Log Entry to mark as void.
+
+ FlushIt - TRUE if the bytes are to be flushed to disk
+
+Return Value:
+
+ None.
+
+--*/
+{
+ DWORD DBIndex = ChangeLogEntry->DBIndex;
+
+
+ //
+ // Mark the changelog entry as being deleted.
+ // (and force the change to disk).
+ //
+
+ NlPrint((NL_CHANGELOG,
+ "NlVoidChangeLogEntry: %lx %lx: deleting change log entry.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+
+ ChangeLogDesc->EntryCount[DBIndex] --;
+
+ ChangeLogEntry->DBIndex = VOID_DB;
+
+ (VOID) NlWriteChangeLogBytes(
+ ChangeLogDesc,
+ &ChangeLogEntry->DBIndex,
+ sizeof(ChangeLogEntry->DBIndex),
+ FlushIt );
+
+ //
+ // If the changelog is now empty,
+ // delete it.
+ //
+ // Only delete a redo log.
+
+ if ( ChangeLogDesc->RedoLog && ChangeLogDesc->EntryCount[DBIndex] == 0 ) {
+ DWORD i;
+ for( i = 0; i < NUM_DBS; i++ ) {
+ if (ChangeLogDesc->EntryCount[i] != 0 ) {
+ break;
+ }
+ }
+
+ if ( i == NUM_DBS ) {
+ WCHAR ChangeLogFile[PATHLEN+1];
+
+ NlPrint(( NL_CHANGELOG,
+ "NlVoidChangeLogEntry: redo log is now empty. Delete it.\n" ));
+
+ //
+ // Close the file and delete the buffer.
+ //
+
+ NlCloseChangeLogFile( ChangeLogDesc );
+
+ //
+ // Delete the file itself.
+ //
+
+ wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( ChangeLogFile,
+ ChangeLogDesc->RedoLog ? REDO_FILE_POSTFIX : CHANGELOG_FILE_POSTFIX );
+ if ( !DeleteFile( ChangeLogFile ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlVoidChangeLogEntry: cannot delete redo log %ld.\n",
+ GetLastError() ));
+ }
+
+
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NlDeleteChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD DBIndex,
+ IN LARGE_INTEGER SerialNumber
+ )
+/*++
+
+Routine Description:
+
+ This routine deletes the change log entry with the particular serial number.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to use.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+ SerialNumber - Serial number of the entry to find.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+
+
+
+ //
+ // Find the specified change log entry.
+ //
+
+ LOCK_CHANGELOG();
+ ChangeLogEntry = NlFindChangeLogEntry(
+ ChangeLogDesc,
+ SerialNumber,
+ FALSE, // Not downlevel
+ TRUE, // Exact match
+ DBIndex );
+
+ if (ChangeLogEntry != NULL) {
+
+ //
+ // Mark the changelog entry as being deleted.
+ // (and force the change to disk).
+ //
+
+ NlVoidChangeLogEntry( ChangeLogDesc, ChangeLogEntry, TRUE );
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "NlDeleteChangeLogEntry: %lx %lx: couldn't find change log entry.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+ }
+
+ UNLOCK_CHANGELOG();
+ return;
+}
+
+
+NTSTATUS
+NlCopyChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR SourceChangeLogDesc,
+ IN PCHANGELOG_ENTRY SourceChangeLogEntry,
+ IN PCHANGELOG_DESCRIPTOR DestChangeLogDesc
+)
+/*++
+
+Routine Description:
+
+ Copies the specified change log entry for the specified "source" change log to
+ the specified "destination" change log. The caller is responsible for flushing the
+ entry to disk by calling NlFlushChangeLog.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ SourceChangeLogDesc -- a description of the Changelog buffer to copy from
+
+ SourceChangeLogEntry -- The particular entry to copy
+
+ DestChangeLogDesc -- a description of the ChangelogBuffer to copy to
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ NTSTATUS Status;
+ CHANGELOG_ENTRY DestChangeLogEntry;
+ PSID ObjectSid;
+ UNICODE_STRING ObjectNameString;
+ PUNICODE_STRING ObjectName;
+
+ //
+ // If this entry has been marked void, ignore it.
+ //
+
+ if ( SourceChangeLogEntry->DBIndex == VOID_DB ) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Build a version 4 changelog entry from a version 3 one.
+ //
+
+ ObjectSid = NULL;
+ ObjectName = NULL;
+
+ if ( SourceChangeLogDesc->Version3 ) {
+ PCHANGELOG_ENTRY_V3 Version3;
+
+ Version3 = (PCHANGELOG_ENTRY_V3)SourceChangeLogEntry;
+
+ DestChangeLogEntry.SerialNumber = Version3->SerialNumber;
+ DestChangeLogEntry.DeltaType = (BYTE) Version3->DeltaType;
+ DestChangeLogEntry.DBIndex = Version3->DBIndex;
+ DestChangeLogEntry.ObjectRid = Version3->ObjectRid;
+ DestChangeLogEntry.Flags = Version3->ReplicateImmediately ?
+ CHANGELOG_REPLICATE_IMMEDIATELY :
+ 0;
+ if ( Version3->ObjectSidOffset ) {
+ ObjectSid = (PSID)(((LPBYTE)Version3) +
+ Version3->ObjectSidOffset);
+ }
+ if ( Version3->ObjectNameOffset ) {
+ RtlInitUnicodeString( &ObjectNameString,
+ (LPWSTR)(((LPBYTE)Version3) +
+ Version3->ObjectNameOffset));
+ ObjectName = &ObjectNameString;
+ }
+
+ //
+ // Build a version 4 changelog entry from a version 4 one.
+ //
+ } else {
+
+ DestChangeLogEntry = *SourceChangeLogEntry;
+
+ if ( SourceChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+ ObjectSid = (PSID)(((LPBYTE)SourceChangeLogEntry) +
+ sizeof(CHANGELOG_ENTRY));
+ } else if ( SourceChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+ RtlInitUnicodeString( &ObjectNameString,
+ (LPWSTR)(((LPBYTE)SourceChangeLogEntry) +
+ sizeof(CHANGELOG_ENTRY)));
+ ObjectName = &ObjectNameString;
+ }
+
+
+ }
+
+
+ Status = NlWriteChangeLogEntry( DestChangeLogDesc,
+ &DestChangeLogEntry,
+ ObjectSid,
+ ObjectName,
+ FALSE ); // Don't flush to disk
+
+ return Status;
+}
+
+
+BOOLEAN
+NlFixChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD DBIndex,
+ IN LARGE_INTEGER SerialNumber,
+ IN BOOLEAN CopyEntriesToRedoLog
+ )
+/*++
+
+Routine Description:
+
+ This routine scans the change log and 'removes' all change log entries
+ with a serial number greater than the one specified.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to use.
+
+ DBIndex - Describes which database to find the changelog entry for.
+
+ SerialNumber - Serial number of the entry to find.
+
+ CopyEntriesToRedoLog - TRUE to indicate that all deleted entries need to be copied
+ to the redo log.
+
+Return Value:
+
+ TRUE -- if the entry specied by SerialNumber was found.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ BOOLEAN SkipFirstEntry = TRUE;
+
+ //
+ // In all cases,
+ // the new serial number of the change log is the one passed in.
+ //
+
+ ChangeLogDesc->SerialNumber[DBIndex] = SerialNumber;
+
+ //
+ // Find the specified change log entry.
+ //
+
+ ChangeLogEntry = NlFindChangeLogEntry(
+ ChangeLogDesc,
+ SerialNumber,
+ FALSE, // Not downlevel
+ TRUE, // exact match
+ DBIndex );
+
+ if (ChangeLogEntry == NULL) {
+
+ //
+ // If we can't find the entry,
+ // simply start from the beginning and delete all entries for this
+ // database.
+ //
+
+ if ( !CopyEntriesToRedoLog ) {
+ ChangeLogEntry = NlFindFirstChangeLogEntry( ChangeLogDesc, DBIndex );
+ SkipFirstEntry = FALSE;
+ }
+
+ if (ChangeLogEntry == NULL) {
+ return FALSE;
+ }
+ }
+
+
+ //
+ // Loop for each entry with a greater serial number.
+ //
+
+ for (;;) {
+
+ //
+ // Skip past the previous entry.
+ //
+ // Don't do this the first time if we want to start at the very beginning.
+ //
+
+ if ( SkipFirstEntry ) {
+ ChangeLogEntry = NlFindNextChangeLogEntry( ChangeLogDesc,
+ ChangeLogEntry,
+ DBIndex );
+ } else {
+ SkipFirstEntry = TRUE;
+ }
+
+
+ if (ChangeLogEntry == NULL) {
+ break;
+ }
+
+ //
+ // Write the entry to the redo log.
+ //
+
+ if ( CopyEntriesToRedoLog ) {
+ NTSTATUS TempStatus;
+ NlAssert( ChangeLogDesc != &NlGlobalRedoLogDesc );
+
+ TempStatus = NlCopyChangeLogEntry( ChangeLogDesc,
+ ChangeLogEntry,
+ &NlGlobalRedoLogDesc );
+
+ if ( !NT_SUCCESS(TempStatus) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlFixChangeLog: Cannot write redo log 0x%lx\n",
+ TempStatus ));
+ }
+ }
+
+ //
+ // Mark the changelog entry as being deleted.
+ // (but don't flush to disk yet).
+ //
+
+ NlVoidChangeLogEntry( ChangeLogDesc, ChangeLogEntry, FALSE );
+
+ //
+ // If deleteing the change log entry caused the changelog to be deleted,
+ // exit now since 'ChangeLogEntry' points to freed memory.
+ //
+
+ if ( ChangeLogDesc->EntryCount[DBIndex] == 0 ) {
+ break;
+ }
+
+ }
+
+ //
+ // Flush all the changes to disk.
+ //
+
+ (VOID) NlFlushChangeLog( ChangeLogDesc );
+ if ( CopyEntriesToRedoLog ) {
+ NlFlushChangeLog( &NlGlobalRedoLogDesc );
+ }
+
+
+ return TRUE;
+}
+
+
+BOOL
+NlValidateChangeLogEntry(
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN DWORD ChangeLogEntrySize
+ )
+/*++
+
+Routine Description:
+
+ Validate the a ChangeLogEntry is structurally sound.
+
+Arguments:
+
+ ChangeLogEntry: pointer to a change log entry.
+
+ ChangeLogEntrySize -- Size (in bytes) of the change log entry not including
+ header and trailer.
+
+Return Value:
+
+ TRUE: if the given entry is valid
+
+ FALSE: otherwise.
+
+--*/
+{
+
+ //
+ // Ensure the entry is big enough.
+ //
+
+ if ( ChangeLogEntrySize < sizeof(CHANGELOG_ENTRY) ) {
+ NlPrint((NL_CRITICAL,
+ "NlValidateChangeLogEntry: Entry size is too small: %ld\n",
+ ChangeLogEntrySize ));
+ return FALSE;
+ }
+
+ //
+ // Ensure strings are zero terminated.
+ //
+
+ if ( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+
+ LPWSTR ZeroTerminator = (LPWSTR)(ChangeLogEntry+1);
+ BOOLEAN ZeroTerminatorFound = FALSE;
+
+ if ( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+ NlPrint((NL_CRITICAL,
+ "NlValidateChangeLogEntry: %lx %lx: both Name and Sid specified.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+ return FALSE;
+ }
+
+ while ( (DWORD)((LPBYTE)ZeroTerminator - (LPBYTE) ChangeLogEntry) <
+ ChangeLogEntrySize - 1 ) {
+
+ if ( *ZeroTerminator == L'\0' ) {
+ ZeroTerminatorFound = TRUE;
+ break;
+ }
+ ZeroTerminator ++;
+ }
+
+ if ( !ZeroTerminatorFound ) {
+ NlPrint((NL_CRITICAL,
+ "NlValidateChangeLogEntry: %lx %lx: String not zero terminated. (no string)\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+ return FALSE;
+ }
+
+ }
+
+ //
+ // Ensure the sid is entirely within the block.
+ //
+
+ if ( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+
+ if ( GetSidLengthRequired(0) >
+ ChangeLogEntrySize - sizeof(*ChangeLogEntry) ||
+ RtlLengthSid( (PSID)(ChangeLogEntry+1) ) >
+ ChangeLogEntrySize - sizeof(*ChangeLogEntry) ) {
+ NlPrint((NL_CRITICAL,
+ "NlValidateChangeLogEntry: %lx %lx: Sid too large.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+ return FALSE;
+ }
+
+ }
+
+ //
+ // Ensure the database # is valid.
+ // ARGH! Allow VOID_DB.
+ //
+
+ if ( ChangeLogEntry->DBIndex > NUM_DBS ) {
+ NlPrint((NL_CRITICAL,
+ "NlValidateChangeLogEntry: %lx %lx: DBIndex is bad %ld.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart,
+ ChangeLogEntry->DBIndex ));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+BOOL
+ValidateThisEntry(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN OUT PLARGE_INTEGER NextSerialNumber,
+ IN BOOLEAN InitialCall
+ )
+/*++
+
+Routine Description:
+
+ Determine the given log entry is a valid next log in the change log
+ list.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to validate.
+
+ ChangeLogEntry: pointer to a new log entry.
+
+ NextSerialNumber: pointer to an array of serial numbers.
+ (NULL if serial numbers aren't to be validated.)
+
+ Initialcall: TRUE iff SerialNumber array should be initialized.
+
+Return Value:
+
+ TRUE: if the given entry is a valid next entry.
+
+ FALSE: otherwise.
+
+Assumed: non-empty ChangeLog list.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER Block = ((PCHANGELOG_BLOCK_HEADER)ChangeLogEntry) - 1;
+
+ //
+ // Do Version 3 specific things
+ //
+
+ if ( ChangeLogDesc->Version3 ) {
+
+ //
+ // Ensure the block is big enough.
+ //
+
+ if ( Block->BlockSize <
+ sizeof(CHANGELOG_ENTRY_V3) + sizeof(CHANGELOG_BLOCK_HEADER) ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateThisEntry: Block size is too small: %ld\n",
+ Block->BlockSize ));
+ return FALSE;
+ }
+
+ //
+ // Ensure the database # is valid.
+ //
+
+ if ( ChangeLogEntry->DBIndex > NUM_DBS ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateThisEntry: %lx %lx: DBIndex is bad %ld.\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart,
+ ChangeLogEntry->DBIndex ));
+ return FALSE;
+ }
+
+
+ //
+ // Do version 4 specific validation
+ //
+
+ } else {
+
+ //
+ // Ensure the block is big enough.
+ //
+
+ if ( Block->BlockSize <
+ sizeof(CHANGELOG_BLOCK_HEADER) +
+ sizeof(CHANGELOG_ENTRY) +
+ sizeof(CHANGELOG_BLOCK_TRAILER) ) {
+
+ NlPrint((NL_CRITICAL,
+ "ValidateThisEntry: Block size is too small: %ld\n",
+ Block->BlockSize ));
+ return FALSE;
+ }
+
+
+ //
+ // Validate the contents of the block itself.
+ //
+
+ if ( !NlValidateChangeLogEntry(
+ ChangeLogEntry,
+ Block->BlockSize -
+ sizeof(CHANGELOG_BLOCK_HEADER) -
+ sizeof(CHANGELOG_BLOCK_TRAILER) ) ) {
+
+ return FALSE;
+ }
+
+ }
+
+
+ //
+ // Validate the serial number sequence.
+ //
+
+ if ( ChangeLogEntry->DBIndex != VOID_DB && NextSerialNumber != NULL ) {
+
+ //
+ // If this is the first entry in the database,
+ // Save its serial number.
+ //
+
+ if ( NextSerialNumber[ChangeLogEntry->DBIndex].QuadPart == 0 ) {
+
+ //
+ // first entry for this database
+ //
+
+ NextSerialNumber[ChangeLogEntry->DBIndex] = ChangeLogEntry->SerialNumber;
+
+
+ //
+ // Otherwise ensure the serial number is the value expected.
+ //
+
+ } else {
+
+ if ( !IsSerialNumberEqual(
+ ChangeLogDesc,
+ ChangeLogEntry,
+ &NextSerialNumber[ChangeLogEntry->DBIndex] )){
+
+ //
+ // For the redo log, the serial numbers merely need to be ascending.
+ //
+
+ if ( !ChangeLogDesc->RedoLog ||
+ ChangeLogEntry->SerialNumber.QuadPart <
+ NextSerialNumber[ChangeLogEntry->DBIndex].QuadPart ){
+
+
+ NlPrint((NL_CRITICAL,
+ "ValidateThisEntry: %lx %lx: Serial number is bad. s.b. %lx %lx\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart,
+ NextSerialNumber[ChangeLogEntry->DBIndex].HighPart,
+ NextSerialNumber[ChangeLogEntry->DBIndex].LowPart ));
+ return FALSE;
+ }
+ }
+ }
+
+ //
+ // Increment next expected serial number
+ //
+
+ NextSerialNumber[ChangeLogEntry->DBIndex].QuadPart =
+ ChangeLogEntry->SerialNumber.QuadPart + 1;
+
+
+ //
+ // The current entry specifies the highest serial number for its
+ // database.
+ //
+
+ if ( InitialCall ) {
+ ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex] =
+ ChangeLogEntry->SerialNumber;
+ ChangeLogDesc->EntryCount[ChangeLogEntry->DBIndex] ++;
+ }
+
+ }
+
+
+ return TRUE;
+}
+
+
+BOOL
+ValidateBlock(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_BLOCK_HEADER Block,
+ IN OUT LARGE_INTEGER *NextSerialNumber,
+ IN BOOLEAN InitialCall
+ )
+/*++
+
+Routine Description:
+
+ Validate a changelog block.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to validate.
+
+ Block: pointer to the change log block to validate
+
+ NextSerialNumber: pointer to an array of serial numbers.
+ (NULL if serial numbers aren't to be validated.)
+
+ InitializeCall: TRUE iff SerialNumber array should be initialized.
+
+Return Value:
+
+ TRUE: if the given entry is a valid next entry.
+
+ FALSE: otherwise.
+
+--*/
+{
+ //
+ // Ensure Block size is properly aligned.
+ //
+
+ if ( Block->BlockSize != ROUND_UP_COUNT(Block->BlockSize, ALIGN_WORST) ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Block size alignment is bad.\n" ));
+ return FALSE;
+ }
+
+
+ //
+ // Ensure the block is contained in the cache.
+ //
+
+ if ( Block->BlockSize > ChangeLogDesc->BufferSize ||
+ ((LPBYTE)Block + Block->BlockSize) > ChangeLogDesc->BufferEnd ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Block extends beyond end of buffer.\n" ));
+ return FALSE;
+
+ }
+
+
+ //
+ // Do Version 3 specific things
+ //
+
+ if ( ChangeLogDesc->Version3 ) {
+
+ //
+ // Ensure the block is big enough.
+ //
+
+ if ( Block->BlockSize < sizeof(CHANGELOG_BLOCK_HEADER) ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Block size is too small: %ld\n",
+ Block->BlockSize ));
+ return FALSE;
+ }
+
+
+ //
+ // Do version 4 specific validation
+ //
+
+ } else {
+
+ //
+ // Ensure the block is big enough.
+ //
+
+ if ( Block->BlockSize <
+ sizeof(CHANGELOG_BLOCK_HEADER) +
+ sizeof(CHANGELOG_BLOCK_TRAILER) ) {
+
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Block size is too small: %ld\n",
+ Block->BlockSize ));
+ return FALSE;
+ }
+
+ //
+ // Ensure trailer and header match
+ //
+
+ if ( ChangeLogBlockTrailer(Block)->BlockSize != Block->BlockSize ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Header/Trailer block size mismatch: %ld %ld (Trailer fixed).\n",
+ Block->BlockSize,
+ ChangeLogBlockTrailer(Block)->BlockSize ));
+ ChangeLogBlockTrailer(Block)->BlockSize = Block->BlockSize;
+ }
+
+
+ }
+
+ //
+ // Free blocks have no other checking to do
+ //
+ switch ( Block->BlockState ) {
+ case BlockFree:
+
+ break;
+
+ //
+ // Used blocks have more checking to do.
+ //
+
+ case BlockUsed:
+
+ if ( !ValidateThisEntry( ChangeLogDesc,
+ (PCHANGELOG_ENTRY)(Block+1),
+ NextSerialNumber,
+ InitialCall )) {
+ return FALSE;
+ }
+ break;
+
+
+ //
+ // The hole is allowed only at the end of the buffer.
+ //
+
+ case BlockHole:
+ if ( (LPBYTE)Block + Block->BlockSize != ChangeLogDesc->BufferEnd ) {
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Hole block in middle of buffer (buffer truncated).\n" ));
+ Block->BlockSize = ChangeLogDesc->BufferEnd - (LPBYTE)Block;
+ }
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL,
+ "ValidateBlock: Invalid block type %ld.\n",
+ Block->BlockState ));
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+
+BOOL
+ValidateList(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN BOOLEAN InitialCall
+ )
+/*++
+
+Routine Description:
+
+ Determine the given header is a valid header. It is done by
+ traversing the circular buffer starting from the given header and
+ validate each entry.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to validate.
+
+ InitialCall: TRUE iff SerialNumber Array and EntryCount should
+ be initialized.
+
+Return Value:
+
+ TRUE: if the given header is valid.
+
+ FALSE: otherwise
+
+
+--*/
+{
+
+ LARGE_INTEGER NextSerialNumber[NUM_DBS];
+ PCHANGELOG_BLOCK_HEADER ChangeLogBlock;
+ DWORD j;
+
+ //
+ // setup a NextSerialNumber array first.
+ //
+
+ for( j = 0; j < NUM_DBS; j++ ) {
+
+ NextSerialNumber[j].QuadPart = 0;
+
+ if ( InitialCall ) {
+ ChangeLogDesc->SerialNumber[j].QuadPart = 0;
+ }
+ }
+
+ //
+ // The cache is valid if it is empty.
+ //
+
+ if ( ChangeLogIsEmpty(ChangeLogDesc) ) {
+ return TRUE;
+ }
+
+ //
+ // Validate each block
+ //
+
+ for ( ChangeLogBlock = ChangeLogDesc->Head;
+ ;
+ ChangeLogBlock = NlMoveToNextChangeLogBlock( ChangeLogDesc, ChangeLogBlock) ) {
+
+ //
+ // Validate the block.
+ //
+
+ if( !ValidateBlock( ChangeLogDesc,
+ ChangeLogBlock,
+ NextSerialNumber,
+ InitialCall) ) {
+ return FALSE;
+ }
+
+ //
+ // Stop when we get to the end.
+ //
+ if ( ChangeLogBlock->BlockState == BlockFree ) {
+ break;
+ }
+
+ }
+
+ return TRUE;
+
+}
+
+
+BOOL
+InitChangeLogHeadAndTail(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN BOOLEAN NewChangeLog
+ )
+
+/*++
+
+Routine Description:
+
+ This function initializes the global head and tail pointers of change
+ log block list. The change log cache is made up of variable length
+ blocks, each block has a header containing the length of the block
+ and the block state ( BlockFree, BlockUsed and BlockHole ). The
+ last block in the change log block list is always the free block,
+ all other blocks in the cache are used blocks except a block at the
+ end of the cache may be a unused block known as 'hole' block. So
+ the head of the change log block list is the block that is just next
+ to the free block and the tail is the free block.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer to analyze.
+ On entry, Buffer and BufferSize describe the allocated block containing
+ the change log read from disk.
+ On TRUE return, all the fields are filled in.
+
+ NewChangeLog -- True if no entries are in the change log
+
+Return Value:
+
+ TRUE: if valid head and tail are successfully initialized.
+
+ FALSE: if valid head and tail can't be determined. This may be due
+ to the corrupted change log file.
+
+--*/
+{
+ PCHANGELOG_BLOCK_HEADER Block;
+ PCHANGELOG_BLOCK_HEADER FreeBlock;
+ DWORD i;
+
+ ChangeLogDesc->BufferEnd =
+ ChangeLogDesc->Buffer + ChangeLogDesc->BufferSize;
+
+ //
+ // Compute the address of the first physical cache entry.
+ //
+ ChangeLogDesc->FirstBlock = (PCHANGELOG_BLOCK_HEADER)
+ (ChangeLogDesc->Buffer +
+ sizeof(CHANGELOG_SIG));
+
+ ChangeLogDesc->FirstBlock = (PCHANGELOG_BLOCK_HEADER)
+ ROUND_UP_POINTER ( ChangeLogDesc->FirstBlock, ALIGN_WORST );
+
+ //
+ // Clear the count of entries in the change log and the serial numbers
+ // (We'll compute them later when we call ValidateList().)
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+ ChangeLogDesc->EntryCount[i] = 0;
+ ChangeLogDesc->SerialNumber[i].QuadPart = 0;
+ }
+
+
+ //
+ // If this is a new change log,
+ // Initialize the Change Log Cache to zero.
+ //
+
+ Block = ChangeLogDesc->FirstBlock;
+
+ if ( NewChangeLog ) {
+
+ RtlZeroMemory(ChangeLogDesc->Buffer, ChangeLogDesc->BufferSize);
+ (VOID) lstrcpyA( (PCHAR)ChangeLogDesc->Buffer, CHANGELOG_SIG);
+
+ Block->BlockState = BlockFree;
+
+ Block->BlockSize =
+ (ChangeLogDesc->BufferEnd - (LPBYTE)ChangeLogDesc->FirstBlock);
+ ChangeLogBlockTrailer(Block)->BlockSize = Block->BlockSize;
+
+ ChangeLogDesc->Version3 = FALSE;
+ ChangeLogDesc->Head = ChangeLogDesc->Tail = ChangeLogDesc->FirstBlock;
+ return TRUE;
+ }
+
+ //
+ // If no entries have been written to the changelog,
+ // simply initialize the head and tail to the block start.
+ //
+
+ if ( ChangeLogIsEmpty( ChangeLogDesc ) ) {
+
+ ChangeLogDesc->Head = ChangeLogDesc->Tail = ChangeLogDesc->FirstBlock;
+
+ NlPrint((NL_CHANGELOG,
+ "InitChangeLogHeadAndTail: Change log is empty.\n" ));
+ return TRUE;
+ }
+
+ //
+ // Loop through the cache looking for a free block.
+ //
+
+ FreeBlock = NULL;
+
+ do {
+
+ //
+ // Validate the block's integrity.
+ //
+
+ if ( !ValidateBlock( ChangeLogDesc, Block, NULL, FALSE )) {
+ return FALSE;
+ }
+
+ //
+ // Just remember where the free block is.
+ //
+
+ if ( Block->BlockState == BlockFree ) {
+
+ if ( FreeBlock != NULL ) {
+ NlPrint((NL_CRITICAL,
+ "InitChangeLogHeadAndTail: Multiple free blocks found.\n" ));
+ return FALSE;
+ }
+
+ FreeBlock = Block;
+ }
+
+ //
+ // Move to next block
+ //
+
+ Block = (PCHANGELOG_BLOCK_HEADER) ((LPBYTE)Block + Block->BlockSize);
+
+ } while ( (LPBYTE)Block < ChangeLogDesc->BufferEnd );
+
+ //
+ // If we didn't find a free block,
+ // the changelog is corrupt.
+ //
+
+ if ( FreeBlock == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "InitChangeLogHeadAndTail: No Free block anywhere in buffer.\n" ));
+ return FALSE;
+ }
+
+ //
+ // We found the free block.
+ // (The tail pointer always points to the free block.)
+ //
+
+ ChangeLogDesc->Tail = FreeBlock;
+
+ //
+ // If free block is the last block in the change log block
+ // list, the head of the list is the first block in
+ // the list.
+ //
+ if( ((LPBYTE)FreeBlock + FreeBlock->BlockSize) >=
+ ChangeLogDesc->BufferEnd ) {
+
+ ChangeLogDesc->Head = ChangeLogDesc->FirstBlock;
+
+ //
+ //
+ // Otherwise, the head of the list is immediately after the tail.
+ //
+
+ } else {
+
+ //
+ // A redo log needs the free block at the end.
+ //
+ if ( ChangeLogDesc->RedoLog ) {
+ NlPrint((NL_CRITICAL,
+ "InitChangeLogHeadAndTail: Re-do log has Free block in middle of buffer.\n" ));
+ return FALSE;
+ }
+
+ ChangeLogDesc->Head = (PCHANGELOG_BLOCK_HEADER)
+ ((LPBYTE)FreeBlock + FreeBlock->BlockSize);
+ }
+
+
+ //
+ // Validate the list before returning from here.
+ //
+
+ if ( !ValidateList( ChangeLogDesc, TRUE) ) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+NTSTATUS
+NlResetChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD NewChangeLogSize
+ )
+/*++
+
+Routine Description:
+
+ This function resets the change log cache and change log file. This
+ function is called from InitChangeLog() function to afresh the
+ change log. This function may also be called from
+ I_NetNotifyDelta() function when the serial number of the new entry
+ is out of order.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ NewChangeLogSize -- Size (in bytes) of the new change log.
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // Start with a clean slate.
+ //
+
+ NlCloseChangeLogFile( ChangeLogDesc );
+
+ //
+ // Allocate a buffer.
+ //
+
+ ChangeLogDesc->BufferSize = NewChangeLogSize;
+
+ ChangeLogDesc->Buffer = NetpMemoryAllocate(ChangeLogDesc->BufferSize );
+
+ if ( ChangeLogDesc->Buffer == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+
+ //
+ // Initialize the Change Log Cache to zero.
+ //
+
+ (VOID) InitChangeLogHeadAndTail( ChangeLogDesc, TRUE );
+
+ //
+ // Write the cache to the file.
+ //
+
+ Status = NlWriteChangeLogBytes( ChangeLogDesc,
+ ChangeLogDesc->Buffer,
+ ChangeLogDesc->BufferSize,
+ TRUE ); // Flush the bytes to disk
+
+ return Status;
+}
+
+#if DBG
+
+VOID
+PrintChangeLogEntry(
+ PCHANGELOG_ENTRY ChangeLogEntry
+ )
+/*++
+
+Routine Description:
+
+ This routine print the content of the given changelog entry.
+
+Arguments:
+
+ ChangeLogEntry -- pointer to the change log entry to print
+
+Return Value:
+
+ none.
+
+--*/
+{
+ LPSTR DeltaName;
+
+ switch ( ChangeLogEntry->DeltaType ) {
+ case AddOrChangeDomain:
+ DeltaName = "AddOrChangeDomain";
+ break;
+ case AddOrChangeGroup:
+ DeltaName = "AddOrChangeGroup";
+ break;
+ case DeleteGroupByName:
+ case DeleteGroup:
+ DeltaName = "DeleteGroup";
+ break;
+ case RenameGroup:
+ DeltaName = "RenameGroup";
+ break;
+ case AddOrChangeUser:
+ DeltaName = "AddOrChangeUser";
+ break;
+ case DeleteUserByName:
+ case DeleteUser:
+ DeltaName = "DeleteUser";
+ break;
+ case RenameUser:
+ DeltaName = "RenameUser";
+ break;
+ case ChangeGroupMembership:
+ DeltaName = "ChangeGroupMembership";
+ break;
+ case AddOrChangeAlias:
+ DeltaName = "AddOrChangeAlias";
+ break;
+ case DeleteAlias:
+ DeltaName = "DeleteAlias";
+ break;
+ case RenameAlias:
+ DeltaName = "RenameAlias";
+ break;
+ case ChangeAliasMembership:
+ DeltaName = "ChangeAliasMembership";
+ break;
+ case AddOrChangeLsaPolicy:
+ DeltaName = "AddOrChangeLsaPolicy";
+ break;
+ case AddOrChangeLsaTDomain:
+ DeltaName = "AddOrChangeLsaTDomain";
+ break;
+ case DeleteLsaTDomain:
+ DeltaName = "DeleteLsaTDomain";
+ break;
+ case AddOrChangeLsaAccount:
+ DeltaName = "AddOrChangeLsaAccount";
+ break;
+ case DeleteLsaAccount:
+ DeltaName = "DeleteLsaAccount";
+ break;
+ case AddOrChangeLsaSecret:
+ DeltaName = "AddOrChangeLsaSecret";
+ break;
+ case DeleteLsaSecret:
+ DeltaName = "DeleteLsaSecret";
+ break;
+ case SerialNumberSkip:
+ DeltaName = "SerialNumberSkip";
+ break;
+ case DummyChangeLogEntry:
+ DeltaName = "DummyChangeLogEntry";
+ break;
+
+ default:
+ DeltaName ="(Unknown)";
+ break;
+ }
+
+ NlPrint((NL_CHANGELOG,
+ "DeltaType %s (%ld) SerialNumber: %lx %lx",
+ DeltaName,
+ ChangeLogEntry->DeltaType,
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+
+ if ( ChangeLogEntry->ObjectRid != 0 ) {
+ NlPrint((NL_CHANGELOG," Rid: 0x%lx", ChangeLogEntry->ObjectRid ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_REPLICATE_IMMEDIATELY ) {
+ NlPrint((NL_CHANGELOG," Immediately" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_PDC_PROMOTION ) {
+ NlPrint((NL_CHANGELOG," Promotion" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_PASSWORD_CHANGE ) {
+ NlPrint((NL_CHANGELOG," PasswordChanged" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_DOMAINUSERS_CHANGED ) {
+ NlPrint((NL_CHANGELOG," DomainUsersChanged" ));
+ }
+
+
+ if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+ NlPrint(( NL_CHANGELOG, " Name: '" FORMAT_LPWSTR "'",
+ (LPWSTR)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY))));
+ }
+
+ if( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+ NlPrint((NL_CHANGELOG," Sid: "));
+ NlpDumpSid( NL_CHANGELOG,
+ (PSID)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY)) );
+ } else {
+ NlPrint((NL_CHANGELOG,"\n" ));
+ }
+}
+#endif // DBG
+
+
+NTSTATUS
+NlWriteChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN PSID ObjectSid,
+ IN PUNICODE_STRING ObjectName,
+ IN BOOLEAN FlushIt
+ )
+/*++
+
+Routine Description:
+
+ This is the actual worker for the I_NetNotifyDelta(). This function
+ acquires the sufficient size memory block from the change log
+ buffer, writes the fixed and variable portions of the change log
+ delta in change log buffer and also writes the delta into change log
+ file.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ ChangeLogEntry - pointer to the fixed portion of the change log.
+
+ ObjectSid - pointer to the variable field SID.
+
+ ObjectName - pointer to the variable field Name.
+
+ FlushIt - True if the written bytes are to be flushed to disk
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+
+{
+ NTSTATUS Status;
+ DWORD LogSize;
+ PCHANGELOG_BLOCK_HEADER LogBlock;
+ PCHANGELOG_BLOCK_HEADER FreeBlock;
+ LPBYTE AllocatedChangeLogEntry;
+
+ //
+ // Make sure that the change log cache is available.
+ //
+
+ if ( ChangeLogDesc->Buffer == NULL ) {
+ WCHAR ChangeLogFile[PATHLEN+1];
+
+ if ( !ChangeLogDesc->RedoLog ) {
+ return STATUS_INTERNAL_ERROR;
+ }
+
+ //
+ // Read in the existing changelog file.
+ //
+
+ wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( ChangeLogFile, REDO_FILE_POSTFIX );
+
+ Status = NlOpenChangeLogFile( ChangeLogFile, ChangeLogDesc, FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ Status = NlResetChangeLog( ChangeLogDesc, REDO_LOG_INITIAL_SIZE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return Status;
+ }
+ }
+ }
+
+ //
+ // Fill in the serial number for redo log entries
+ //
+
+ if ( ChangeLogDesc->RedoLog ) {
+ ChangeLogEntry->SerialNumber.QuadPart =
+ ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex].QuadPart + 1;
+ }
+
+
+ //
+ // Determine the size of this change log entry.
+ //
+
+ LogSize = sizeof(CHANGELOG_ENTRY);
+
+ //
+ // Ensure we've got the right data for those deltas we care about
+ //
+
+ switch (ChangeLogEntry->DeltaType) {
+ case AddOrChangeLsaTDomain:
+ case DeleteLsaTDomain:
+ case AddOrChangeLsaAccount:
+ case DeleteLsaAccount:
+ NlAssert( ObjectSid != NULL );
+ if( ObjectSid != NULL ) {
+ ChangeLogEntry->Flags |= CHANGELOG_SID_SPECIFIED;
+ LogSize += RtlLengthSid( ObjectSid );
+ }
+ break;
+
+ case AddOrChangeLsaSecret:
+ case DeleteLsaSecret:
+ case DeleteGroup:
+ case RenameGroup:
+ case DeleteUser:
+ case RenameUser:
+
+ NlAssert( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 );
+ if( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 ) {
+ ChangeLogEntry->Flags |= CHANGELOG_NAME_SPECIFIED;
+ LogSize += ObjectName->Length + sizeof(WCHAR);
+ }
+ break;
+
+ //
+ // For all other delta types, save the data if it's there.
+ //
+ default:
+
+ if( ObjectName != NULL && ObjectName->Buffer != NULL && ObjectName->Length != 0 ) {
+ ChangeLogEntry->Flags |= CHANGELOG_NAME_SPECIFIED;
+ LogSize += ObjectName->Length + sizeof(WCHAR);
+ } else if( ObjectSid != NULL ) {
+ ChangeLogEntry->Flags |= CHANGELOG_SID_SPECIFIED;
+ LogSize += RtlLengthSid( ObjectSid );
+ }
+ break;
+
+ }
+
+
+
+ //
+ // Serialize access to the change log
+ //
+
+ LOCK_CHANGELOG();
+
+ //
+ // Validate the serial number order of this new entry
+ //
+ // If we're out of sync with the caller,
+ // clear the change log and start all over again.
+ //
+ // The global serial number array entry for this database must either
+ // be zero (indicating no entries for this database) or one less than
+ // the new serial number being added.
+ //
+
+ if ( ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex].QuadPart != 0 ) {
+ LARGE_INTEGER ExpectedSerialNumber;
+ LARGE_INTEGER OldSerialNumber;
+
+ ExpectedSerialNumber.QuadPart =
+ ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex].QuadPart + 1;
+
+ //
+ // If the serial number jumped by the promotion increment,
+ // set the flag in the change log entry indicating this is
+ // a promotion to PDC.
+ //
+
+ if ( ChangeLogEntry->SerialNumber.QuadPart ==
+ ExpectedSerialNumber.QuadPart +
+ NlGlobalChangeLogPromotionIncrement.QuadPart ) {
+
+ ChangeLogEntry->Flags |= CHANGELOG_PDC_PROMOTION;
+ }
+
+ if ( !IsSerialNumberEqual( ChangeLogDesc,
+ ChangeLogEntry,
+ &ExpectedSerialNumber )) {
+
+ NlPrint((NL_CRITICAL,
+ "NlWriteChangeLogEntry: Serial numbers not contigous %lx %lx and %lx %lx\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart,
+ ExpectedSerialNumber.HighPart,
+ ExpectedSerialNumber.LowPart ));
+
+ //
+ // write event log.
+ //
+
+ NlpWriteEventlog (
+ NELOG_NetlogonChangeLogCorrupt,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE)&(ChangeLogEntry->DBIndex),
+ sizeof(ChangeLogEntry->DBIndex),
+ NULL,
+ 0 );
+
+
+ //
+ // If the change log is merely newer than the SAM database,
+ // we truncate entries newer than what exists in SAM.
+ //
+
+ OldSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart - 1;
+
+ (VOID) NlFixChangeLog( ChangeLogDesc, ChangeLogEntry->DBIndex, OldSerialNumber, FALSE );
+ }
+
+ //
+ // If this is the first entry written to the change log for this database,
+ // mark it as a promotion.
+ //
+
+ } else {
+ //
+ // Only mark entries that might possibly be a promotion.
+ //
+ switch (ChangeLogEntry->DeltaType) {
+ case AddOrChangeDomain:
+ case AddOrChangeLsaPolicy:
+ ChangeLogEntry->Flags |= CHANGELOG_PDC_PROMOTION;
+ break;
+ }
+ }
+
+
+ //
+ // Validate the list before changing anything
+ //
+
+ NlAssert( ValidateList( ChangeLogDesc, FALSE) );
+
+
+ //
+ // copy fixed portion
+ //
+
+ Status = NlAllocChangeLogBlock( ChangeLogDesc, LogSize, &LogBlock );
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+ AllocatedChangeLogEntry = ((LPBYTE)LogBlock) + sizeof(CHANGELOG_BLOCK_HEADER);
+ RtlCopyMemory( AllocatedChangeLogEntry, ChangeLogEntry, sizeof(CHANGELOG_ENTRY) );
+
+
+ //
+ // copy variable fields
+ //
+
+ if( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+
+ RtlCopyMemory( AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY),
+ ObjectSid,
+ RtlLengthSid( ObjectSid ) );
+ } else if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+
+ RtlCopyMemory( AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY),
+ ObjectName->Buffer,
+ ObjectName->Length );
+
+ //
+ // terminate unicode string
+ //
+
+ *(WCHAR *)(AllocatedChangeLogEntry + sizeof(CHANGELOG_ENTRY) +
+ ObjectName->Length) = 0;
+ }
+
+ //
+ // Be verbose
+ //
+
+#if DBG
+ PrintChangeLogEntry( (PCHANGELOG_ENTRY)AllocatedChangeLogEntry );
+#endif // DBG
+
+
+
+ //
+ // Write the cache entry to the file.
+ //
+ // Actually, write this entry plus the header and trailer of the free
+ // block that follows. If the free block is huge, write the free
+ // block trailer separately.
+ //
+
+ FreeBlock =
+ (PCHANGELOG_BLOCK_HEADER)((LPBYTE)LogBlock + LogBlock->BlockSize);
+
+ if ( FreeBlock->BlockSize >= 4096 ) {
+
+ Status = NlWriteChangeLogBytes(
+ ChangeLogDesc,
+ (LPBYTE)LogBlock,
+ LogBlock->BlockSize + sizeof(CHANGELOG_BLOCK_HEADER),
+ FlushIt );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = NlWriteChangeLogBytes(
+ ChangeLogDesc,
+ (LPBYTE)ChangeLogBlockTrailer(FreeBlock),
+ sizeof(CHANGELOG_BLOCK_TRAILER),
+ FlushIt );
+ }
+
+ } else {
+
+ Status = NlWriteChangeLogBytes(
+ ChangeLogDesc,
+ (LPBYTE)LogBlock,
+ LogBlock->BlockSize + FreeBlock->BlockSize,
+ FlushIt );
+ }
+
+
+ //
+ // Done.
+ //
+
+ ChangeLogDesc->SerialNumber[ChangeLogEntry->DBIndex] = ChangeLogEntry->SerialNumber;
+ ChangeLogDesc->EntryCount[ChangeLogEntry->DBIndex] ++;
+
+ //
+ // Validate the list before returning from here.
+ //
+Cleanup:
+
+ NlAssert( ValidateList( ChangeLogDesc, FALSE) );
+
+
+ UNLOCK_CHANGELOG();
+ return Status;
+}
+
+NTSTATUS
+NlWriteDeltaToChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN ULONG DBIndex,
+ IN OUT PLARGE_INTEGER SerialNumber
+ )
+/*++
+
+Routine Description:
+
+ This routine convert the Delta returned from the PDC and converts it into
+ a change log entry on the BDC. The change log entry is written to the
+ change log on the BDC.
+
+ The change log is silently maintained but isn't used until the BDC is
+ promoted to a PDC.
+
+ The caller is responsible for flushing the change log to disk.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+ Delta - Delta returned from the PDC
+
+ DBIndex - Index to the database being modified.
+
+ SerialNumber - On input, this is the Serial Number of this delta.
+ On output, this is the Serial Number of the next delta.
+
+ This parameter isn't needed when writing the redo log.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS CumulativeStatus = STATUS_SUCCESS;
+
+ CHANGELOG_ENTRY Log;
+ PSID ObjectSid = NULL;
+ UNICODE_STRING ObjectNameString;
+ PUNICODE_STRING ObjectName = NULL;
+ LPWSTR DeltaName = NULL;
+ LARGE_INTEGER SerialNumberCount;
+ LARGE_INTEGER SerialNumberOfThisDelta;
+
+
+ static BOOLEAN SkipNextDelta = FALSE;
+
+ //
+ // Save the serial number of this delta.
+ // Compute the default value of the serial number of the next delta.
+ //
+ // Don't touch the serial number for the redo log.
+ //
+
+ if ( !ChangeLogDesc->RedoLog ) {
+ Log.SerialNumber = *SerialNumber;
+ SerialNumber->QuadPart += 1;
+ }
+
+
+ //
+ // If the previous delta was a SerialNumberSkip delta that decrement the
+ // Serial Number by one,
+ // and the current delta has the same serial number as the last one
+ // written to the change log,
+ // just ignore this delta.
+ //
+ // This handles the case where the PDC passed us two deltas for a single
+ // serial number. We log the "first" one passed to us.
+ //
+ // The PDC does this in the case it has to ship us two deltas to represent
+ // a single change (e.g., the group and group membership on a group rename).
+ //
+
+ if ( !ChangeLogDesc->RedoLog && SkipNextDelta ) {
+ SkipNextDelta = FALSE;
+ LOCK_CHANGELOG();
+ if ( ChangeLogDesc->SerialNumber[DBIndex].QuadPart ==
+ SerialNumber->QuadPart - 1 ) {
+
+
+ NlPrint((NL_CHANGELOG,
+ "NlWriteDeltaToChangeLog: Don't log this delta %lx %lx\n",
+ ChangeLogDesc->SerialNumber[DBIndex].HighPart,
+ ChangeLogDesc->SerialNumber[DBIndex].LowPart ));
+ UNLOCK_CHANGELOG();
+
+ return STATUS_SUCCESS;
+ }
+ UNLOCK_CHANGELOG();
+ }
+
+
+ //
+ // Build the change log entry.
+ //
+
+ Log.DeltaType = (UCHAR) Delta->DeltaType;
+
+ Log.DBIndex = (UCHAR) DBIndex;
+
+ //
+ // Clear the Rid for now. We'll set it to the right value later.
+ Log.ObjectRid = 0;
+
+ // We lose the REPLICATE_IMMEDIATELY and PASSWORD_CHANGE flags
+ // but they are for informational purposes only anyway.
+ Log.Flags = 0;
+
+
+
+ //
+ // Handle each delta type differently
+ //
+
+ switch ( Delta->DeltaType ) {
+ case DeleteGroupByName:
+
+ Log.DeltaType = DeleteGroup;
+ DeltaName = Delta->DeltaUnion.DeltaDeleteGroup->AccountName;
+ Log.ObjectRid = Delta->DeltaID.Rid;
+ break;
+
+ case DeleteUserByName:
+
+ Log.DeltaType = DeleteUser;
+ DeltaName = Delta->DeltaUnion.DeltaDeleteUser->AccountName;
+ Log.ObjectRid = Delta->DeltaID.Rid;
+ break;
+
+ case RenameGroup:
+ case RenameUser:
+
+ ObjectName = &Delta->DeltaUnion.DeltaRenameUser->OldName;
+ Log.ObjectRid = Delta->DeltaID.Rid;
+ break;
+
+ case AddOrChangeDomain:
+ case AddOrChangeGroup:
+ case AddOrChangeUser:
+ case ChangeGroupMembership:
+ case AddOrChangeAlias:
+ case DeleteAlias:
+ case RenameAlias:
+ case ChangeAliasMembership:
+
+ Log.ObjectRid = Delta->DeltaID.Rid;
+ break;
+
+ case AddOrChangeLsaTDomain:
+ case DeleteLsaTDomain:
+ case AddOrChangeLsaAccount:
+ case DeleteLsaAccount:
+
+ ObjectSid = (PSID)(Delta->DeltaID.Sid);
+ break;
+
+
+ // There is only one LSA Policy. It need not be further identified.
+ case AddOrChangeLsaPolicy:
+ break;
+
+ case AddOrChangeLsaSecret:
+ case DeleteLsaSecret:
+
+ DeltaName = Delta->DeltaID.Name;
+ break;
+
+ //
+ // The SerialNumberSkip delta tells us the serial number of the 'next'
+ // delta if it isn't the default.
+ //
+ // Notice that the SerialNumber on the change log entry is the serial number
+ // of the first missing delta.
+ //
+ case SerialNumberSkip:
+
+ //
+ // Ignore serial number deltas in the redo log.
+ //
+
+ if ( ChangeLogDesc->RedoLog ) {
+ break;
+ }
+
+ //
+ // Adjust the serial number of the next delta.
+ //
+
+ SerialNumberOfThisDelta = Log.SerialNumber;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ Delta->DeltaUnion.DeltaSerialNumberSkip->ModifiedCount,
+ *SerialNumber );
+
+ NlPrint((NL_CHANGELOG,
+ "NlWriteDeltaToChangeLog: Serial number skip from %lx %lx to %lx %lx: ",
+ SerialNumberOfThisDelta.HighPart,
+ SerialNumberOfThisDelta.LowPart,
+ SerialNumber->HighPart,
+ SerialNumber->LowPart ));
+
+ //
+ // If the serial number is being set forward,
+ // write several dummy deltas to the change log.
+ //
+
+ if ( SerialNumberOfThisDelta.QuadPart < SerialNumber->QuadPart ) {
+ //
+ // If the serial number of the NextDelta indicates PDC promotion,
+ // adjust what we think the serial number of this delta is.
+ //
+
+ if ( SerialNumberOfThisDelta.QuadPart +
+ NlGlobalChangeLogPromotionIncrement.QuadPart <=
+ SerialNumber->QuadPart ) {
+
+
+ NlPrint((NL_CHANGELOG, "PDC promotion " ));
+ SerialNumberOfThisDelta.QuadPart +=
+ NlGlobalChangeLogPromotionIncrement.QuadPart;
+ }
+
+
+ SerialNumberCount.QuadPart =
+ SerialNumber->QuadPart - SerialNumberOfThisDelta.QuadPart;
+
+ NlPrint(( NL_CHANGELOG,
+ "forward skip of %ld deltas\n", SerialNumberCount ));
+
+ //
+ // If the number of skipped deltas is ridiculously large,
+ // just skip the delta writes and let the next delta write
+ // reset the change log.
+ //
+
+ if ( SerialNumberCount.QuadPart <= 500 ) {
+ DWORD i;
+
+ Log.DeltaType = DummyChangeLogEntry;
+
+ for ( i=0; i<SerialNumberCount.LowPart ; i++ ) {
+ Status = NlWriteChangeLogEntry( ChangeLogDesc, &Log, NULL, NULL, FALSE );
+ if ( CumulativeStatus == STATUS_SUCCESS ) {
+ CumulativeStatus = Status;
+ }
+ Log.SerialNumber.QuadPart++;
+ }
+
+ }
+
+ //
+ // If the serial number isn't being changed,
+ // this is a no-op.
+ //
+
+ } else if ( SerialNumberOfThisDelta.QuadPart < SerialNumber->QuadPart ) {
+
+ /* Do Nothing Here */
+
+ //
+ // If the SerialNumber went backwards,
+ // differentiate between the case of multiple deltas per serial number and
+ // the serial number actually going backwards.
+ //
+ } else {
+
+ //
+ // If the serial number is being set back by one,
+ // and this delta is a duplicate of one we've just received from the PDC,
+ // see comment at the top of this routine,
+ // don't write any deltas at this time.
+
+ LOCK_CHANGELOG();
+ if ( SerialNumberOfThisDelta.QuadPart - 1 == SerialNumber->QuadPart &&
+ NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart == SerialNumber->QuadPart ) {
+
+ NlPrint((NL_CHANGELOG, "Back by one\n" ));
+ SkipNextDelta = TRUE;
+
+ //
+ // Otherwise, the serial number on the PDC is less than the serial number on the BDC.
+ //
+ // Recover by converting the Change Log Entries on this BDC to redo log entries.
+ // We'll reset the serial number on the database to match the PDC.
+ // The redo log will ensure the "extra" changes get undone.
+ //
+
+ } else {
+ NlPrint((NL_CHANGELOG, "backward skip (recovering)\n" ));
+
+ if ( !NlFixChangeLog( ChangeLogDesc, DBIndex, *SerialNumber, TRUE ) ) {
+ CumulativeStatus = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ //
+ // Set the expected serial number to match the change log.
+ //
+ // Above we deleted all change log entries "greater" than the
+ // reset serial number. Add to that the fact that the PDC
+ // doesn't actually send us the delta.
+ //
+ SerialNumber->QuadPart ++;
+ }
+
+ UNLOCK_CHANGELOG();
+ }
+
+ return CumulativeStatus;
+
+
+ //
+ // We should never get these two delta types from NT3.5 PDC
+ //
+ case DeleteGroup: // Needs name too
+ case DeleteUser: // Needs name too
+ default:
+ NlPrint((NL_CRITICAL, "NlWriteDeltaToChangeLog: invalid delta type %lx\n", Delta->DeltaType ));
+ NlAssert( FALSE );
+ return STATUS_INTERNAL_ERROR;
+ }
+
+ //
+ // If we found a name in the delta,
+ // put it in the changelog entry.
+ //
+
+ if ( DeltaName != NULL ) {
+ RtlInitUnicodeString( &ObjectNameString, DeltaName );
+ ObjectName = &ObjectNameString;
+ }
+
+ Status = NlWriteChangeLogEntry(
+ ChangeLogDesc,
+ &Log,
+ ObjectSid,
+ ObjectName,
+ ChangeLogDesc->RedoLog ); // If redo log, flush it to disk right now
+
+ return Status;
+
+}
+
+
+
+
+NTSTATUS
+NlOpenChangeLogFile(
+ IN LPWSTR ChangeLogFileName,
+ OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN BOOLEAN ReadOnly
+)
+/*++
+
+Routine Description:
+
+ Open the change log file (netlogon.chg) for reading or writing one or
+ more records. Create this file if it does not exist or is out of
+ sync with the SAM database (see note below).
+
+ This file must be opened for R/W (deny-none share mode) at the time
+ the cache is initialized. If the file already exists when NETLOGON
+ service started, its contents will be cached in its entirety
+ provided the last change log record bears the same serial number as
+ the serial number field in SAM database else this file will be
+ removed and a new one created. If the change log file did not exist
+ then it will be created.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogFileName - Name of the changelog file to open.
+
+ ChangeLogDesc -- On success, returns a description of the Changelog buffer
+ being used
+
+ ReadOnly -- True if the file should be openned read only.
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+
+ DWORD WinError;
+ DWORD BytesRead;
+ DWORD MinChangeLogSize;
+
+ //
+ // Open change log file if exists
+ //
+
+ ChangeLogDesc->FileHandle = CreateFileW(
+ ChangeLogFileName,
+ ReadOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE),
+ ReadOnly ? (FILE_SHARE_READ | FILE_SHARE_WRITE) : FILE_SHARE_READ, // allow backups and debugging
+ NULL, // Supply better security ??
+ OPEN_EXISTING, // Only open it if it exists
+ FILE_ATTRIBUTE_NORMAL,
+ NULL ); // No template
+
+ if ( ChangeLogDesc->FileHandle == INVALID_HANDLE_VALUE) {
+ WinError = GetLastError();
+
+ NlPrint(( ChangeLogDesc->RedoLog ? NL_CHANGELOG : NL_CRITICAL,
+ FORMAT_LPWSTR ": Unable to open. %ld\n",
+ ChangeLogFileName,
+ WinError ));
+
+ goto Cleanup;
+ }
+
+ //
+ // Get the size of the file.
+ //
+
+ ChangeLogDesc->BufferSize = GetFileSize( ChangeLogDesc->FileHandle, NULL );
+
+ // ?? consider aligning to ALIGN_WORST
+ if ( ChangeLogDesc->RedoLog ) {
+ MinChangeLogSize = REDO_LOG_INITIAL_SIZE;
+ } else {
+ MinChangeLogSize = MIN_CHANGELOGSIZE;
+ }
+
+ if ( ChangeLogDesc->BufferSize < MinChangeLogSize ||
+ ChangeLogDesc->BufferSize > MAX_CHANGELOGSIZE ) {
+
+ WinError = ERROR_INTERNAL_DB_CORRUPTION;
+
+ NlPrint((NL_CRITICAL, FORMAT_LPWSTR ": Changelog size is invalid. %ld.\n",
+ ChangeLogFileName,
+ ChangeLogDesc->BufferSize ));
+ goto Cleanup;
+ }
+
+ //
+ // Allocate and initialize the change log cache.
+ //
+
+ ChangeLogDesc->Buffer = NetpMemoryAllocate( ChangeLogDesc->BufferSize );
+ if (ChangeLogDesc->Buffer == NULL) {
+ WinError = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+
+ RtlZeroMemory(ChangeLogDesc->Buffer, ChangeLogDesc->BufferSize);
+
+
+ //
+ // Check the signature at the front of the change log.
+ //
+ // It won't be there if we just created the file.
+ //
+
+ if ( !ReadFile( ChangeLogDesc->FileHandle,
+ ChangeLogDesc->Buffer,
+ ChangeLogDesc->BufferSize,
+ &BytesRead,
+ NULL ) ) { // Not Overlapped
+
+ WinError = GetLastError();
+
+ NlPrint(( NL_CRITICAL,
+ FORMAT_LPWSTR ": Unable to read from changelog file. %ld\n",
+ ChangeLogFileName,
+ WinError ));
+
+ goto Cleanup;
+ }
+
+ if ( BytesRead != ChangeLogDesc->BufferSize ) {
+
+ WinError = ERROR_INTERNAL_DB_CORRUPTION;
+
+ NlPrint(( NL_CRITICAL,
+ FORMAT_LPWSTR ": Couldn't read entire file. %ld\n",
+ ChangeLogFileName,
+ WinError ));
+
+
+ goto Cleanup;
+ }
+
+ if ( strncmp((PCHAR)ChangeLogDesc->Buffer,
+ CHANGELOG_SIG, sizeof(CHANGELOG_SIG)) == 0) {
+ ChangeLogDesc->Version3 = FALSE;
+
+ } else if ( strncmp((PCHAR)ChangeLogDesc->Buffer,
+ CHANGELOG_SIG_V3, sizeof(CHANGELOG_SIG_V3)) == 0) {
+ ChangeLogDesc->Version3 = TRUE;
+ } else {
+ WinError = ERROR_INTERNAL_ERROR;
+
+ NlPrint(( NL_CRITICAL,
+ FORMAT_LPWSTR ": Invalid signature. %ld\n",
+ ChangeLogFileName,
+ WinError ));
+
+ goto Cleanup;
+ }
+
+
+ //
+ // Find the Head and Tail pointers of the circular log.
+ //
+
+ if( !InitChangeLogHeadAndTail( ChangeLogDesc, FALSE ) ) {
+ WinError = ERROR_INTERNAL_DB_CORRUPTION;
+
+ NlPrint(( NL_CRITICAL,
+ FORMAT_LPWSTR ": couldn't find head/tail. %ld\n",
+ ChangeLogFileName,
+ WinError ));
+
+ goto Cleanup;
+ }
+
+
+
+ WinError = NO_ERROR;
+
+ //
+ // Free any resources on error.
+ //
+Cleanup:
+
+ if ( WinError != NO_ERROR ) {
+ NlCloseChangeLogFile( ChangeLogDesc );
+ }
+
+ return NetpApiStatusToNtStatus(WinError);
+}
+
+
+
+VOID
+NlCloseChangeLogFile(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+)
+/*++
+
+Routine Description:
+
+ This function closes the change log file and frees up the resources
+ consumed by the change log desriptor.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+
+ LOCK_CHANGELOG();
+
+ //
+ // free up the change log cache.
+ //
+
+ if ( ChangeLogDesc->Buffer != NULL ) {
+ NetpMemoryFree( ChangeLogDesc->Buffer );
+ ChangeLogDesc->Buffer = NULL;
+ }
+
+ ChangeLogDesc->Head = NULL;
+ ChangeLogDesc->Tail = NULL;
+
+ ChangeLogDesc->FirstBlock = NULL;
+ ChangeLogDesc->BufferEnd = NULL;
+
+ ChangeLogDesc->LastDirtyByte = 0;
+ ChangeLogDesc->FirstDirtyByte = 0;
+
+ //
+ // Close the change log file
+ //
+
+ if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
+ CloseHandle( ChangeLogDesc->FileHandle );
+ ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
+ }
+
+ UNLOCK_CHANGELOG();
+
+ return;
+}
+
+
+
+NTSTATUS
+NlResizeChangeLogFile(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD NewChangeLogSize
+)
+/*++
+
+Routine Description:
+
+ The buffer described by ChageLogDesc is converted to
+ the size requested by NewChangeLogSize and is converted from any
+ old format change log to the latest format.
+
+ NOTE: This function must be called with the change log locked.
+
+Arguments:
+
+ ChangeLogDesc -- a description of the Changelog buffer.
+
+ NewChangeLogSize -- Size (in bytes) of the new change log.
+
+Return Value:
+
+ NT Status code
+
+ On error, the ChangeLogDesc will still be intact. Merely the size
+ changes will not have happened
+
+--*/
+{
+ CHANGELOG_DESCRIPTOR OutChangeLogDesc;
+ NTSTATUS Status;
+
+ //
+ // If the current buffer is perfect,
+ // just use it.
+ //
+
+ if ( !ChangeLogDesc->Version3 &&
+ ChangeLogDesc->BufferSize == NewChangeLogSize ) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Initialize the template change log descriptor
+ //
+
+ InitChangeLogDesc( &OutChangeLogDesc );
+ OutChangeLogDesc.RedoLog = ChangeLogDesc->RedoLog;
+
+ //
+ // Close the file so we can resize it.
+ //
+
+ if ( ChangeLogDesc->FileHandle != INVALID_HANDLE_VALUE ) {
+ CloseHandle( ChangeLogDesc->FileHandle );
+ ChangeLogDesc->FileHandle = INVALID_HANDLE_VALUE;
+ }
+
+ //
+ // Start with a newly initialized change log,
+ //
+
+ Status = NlResetChangeLog( &OutChangeLogDesc, NewChangeLogSize );
+
+ if ( !NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ //
+ // We're done if the old change log is empty.
+ //
+
+ if ( !ChangeLogIsEmpty(ChangeLogDesc) ) {
+
+ //
+ // Loop through the old change log copying it to the new changelog,
+ //
+
+ PCHANGELOG_ENTRY SourceChangeLogEntry = (PCHANGELOG_ENTRY)
+ (ChangeLogDesc->Head + 1);
+
+ do {
+ Status = NlCopyChangeLogEntry( ChangeLogDesc,
+ SourceChangeLogEntry,
+ &OutChangeLogDesc );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlCloseChangeLogFile( &OutChangeLogDesc );
+ return Status;
+ }
+
+ } while ( (SourceChangeLogEntry =
+ NlMoveToNextChangeLogEntry( ChangeLogDesc, SourceChangeLogEntry )) != NULL );
+
+ //
+ // Flsuh all the changes to the change log file now.
+ //
+
+ Status = NlFlushChangeLog( &OutChangeLogDesc );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlCloseChangeLogFile( &OutChangeLogDesc );
+ return Status;
+ }
+
+ }
+
+ //
+ // Free the old change log buffer.
+ //
+
+ NlCloseChangeLogFile( ChangeLogDesc );
+
+ //
+ // Copy the new descriptor over the old descriptor
+ //
+
+ *ChangeLogDesc = OutChangeLogDesc;
+
+ return STATUS_SUCCESS;
+}
+
+
+#if DBG
+
+DWORD
+NlBackupChangeLogFile(
+ )
+/*++
+
+Routine Description:
+
+ Backup change log content. Since the cache and the change log file
+ content are identical, write cache content to the backup file.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+--*/
+{
+ HANDLE BackupChangeLogHandle;
+
+ WCHAR BackupChangelogFile[PATHLEN+1];
+ DWORD WinError;
+
+ if( NlGlobalChangeLogFilePrefix[0] == L'\0' ) {
+
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ //
+ // make backup file name.
+ //
+
+ wcscpy( BackupChangelogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( BackupChangelogFile, BACKUP_CHANGELOG_FILE_POSTFIX );
+
+
+
+ //
+ // Create change log file. If it exists already then truncate it.
+ //
+ // Note : if a valid change log file exists on the system, then we
+ // would have opened at initialization time.
+ //
+
+ BackupChangeLogHandle = CreateFileW(
+ BackupChangelogFile,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, // allow backups and debugging
+ NULL, // Supply better security ??
+ CREATE_ALWAYS, // Overwrites always
+ FILE_ATTRIBUTE_NORMAL,
+ NULL ); // No template
+
+ if (BackupChangeLogHandle == INVALID_HANDLE_VALUE) {
+
+
+ NlPrint((NL_CRITICAL,"Unable to create backup changelog file "
+ "WinError = %ld \n", WinError = GetLastError() ));
+
+ return WinError;
+ }
+
+ //
+ // Write cache in changelog file if the cache is valid.
+ //
+
+ if( NlGlobalChangeLogDesc.Buffer != NULL ) {
+
+ OVERLAPPED Overlapped;
+ DWORD BytesWritten;
+
+ //
+ // Seek to appropriate offset in the file.
+ //
+
+ RtlZeroMemory( &Overlapped, sizeof(Overlapped) );
+
+ LOCK_CHANGELOG();
+
+ if ( !WriteFile( BackupChangeLogHandle,
+ NlGlobalChangeLogDesc.Buffer,
+ NlGlobalChangeLogDesc.BufferSize,
+ &BytesWritten,
+ &Overlapped ) ) {
+
+ UNLOCK_CHANGELOG();
+ NlPrint((NL_CRITICAL, "Write to Backup ChangeLog failed %ld\n",
+ WinError = GetLastError() ));
+
+ goto Cleanup;
+ }
+
+ UNLOCK_CHANGELOG();
+
+ //
+ // Ensure all the bytes made it.
+ //
+
+ if ( BytesWritten != NlGlobalChangeLogDesc.BufferSize ) {
+ NlPrint((NL_CRITICAL,
+ "Write to Backup ChangeLog bad byte count %ld s.b. %ld\n",
+ BytesWritten,
+ NlGlobalChangeLogDesc.BufferSize ));
+
+ goto Cleanup;
+ }
+ }
+
+Cleanup:
+
+ CloseHandle( BackupChangeLogHandle );
+ return ERROR_SUCCESS;
+}
+
+#endif // DBG
+
diff --git a/private/net/svcdlls/logonsrv/server/chutil.h b/private/net/svcdlls/logonsrv/server/chutil.h
new file mode 100644
index 000000000..09e98ee23
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/chutil.h
@@ -0,0 +1,495 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ chutil.h
+
+Abstract:
+
+ Definitions of the internals of the changelog.
+
+ Currently only included sparingly.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 07-May-1992
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+--*/
+
+#if ( _MSC_VER >= 800 )
+#pragma warning ( 3 : 4100 ) // enable "Unreferenced formal parameter"
+#pragma warning ( 3 : 4219 ) // enable "trailing ',' used for variable argument list"
+#endif
+
+//
+// chutil.c will #include this file with CHUTIL_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef CHUTIL_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Structures and variables describing the Change Log.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// All of the following data is private to changelg.c and nltest1.c
+//
+
+//
+// change log file name
+//
+
+#define CHANGELOG_FILE_PREFIX L"\\NETLOGON"
+
+#define CHANGELOG_FILE_POSTFIX_LENGTH 4 // Length of all the following postfixes
+#define CHANGELOG_FILE_POSTFIX L".CHG"
+#define BACKUP_CHANGELOG_FILE_POSTFIX L".BKP"
+#define REDO_FILE_POSTFIX L".RDO"
+
+//
+// Initial size and size increment of a redo log.
+//
+#define REDO_LOG_INITIAL_SIZE 1024
+#define REDO_LOG_INCREMENT 1024
+
+//
+// Signature at front of changelog file
+//
+
+#define CHANGELOG_SIG_V3 "NT CHANGELOG 3"
+#define CHANGELOG_SIG "NT CHANGELOG 4"
+
+//
+// Change log block state
+//
+
+typedef enum _CHANGELOG_BLOCK_STATE {
+ BlockFree = 1,
+ BlockUsed,
+ BlockHole
+} CHANGELOG_BLOCK_STATE, *PCHANGELOG_BLOCK_STATE;
+
+//
+// change log memory block header
+//
+
+typedef struct _CHANGELOG_BLOCK_HEADER {
+ DWORD BlockSize;
+ CHANGELOG_BLOCK_STATE BlockState;
+} CHANGELOG_BLOCK_HEADER, *PCHANGELOG_BLOCK_HEADER;
+
+typedef struct _CHANGELOG_BLOCK_TRAILER {
+ DWORD BlockSize;
+} CHANGELOG_BLOCK_TRAILER, *PCHANGELOG_BLOCK_TRAILER;
+
+//
+// Macro to find a trailer (given a header)
+//
+
+#define ChangeLogBlockTrailer( _Header ) ( (PCHANGELOG_BLOCK_TRAILER)(\
+ ((LPBYTE)(_Header)) + \
+ (_Header)->BlockSize - \
+ sizeof(CHANGELOG_BLOCK_TRAILER) ))
+
+//
+// Macro to find if the change log describe be a particular
+// changelog descriptor is empty.
+//
+//
+
+#define ChangeLogIsEmpty( _Desc ) \
+( \
+ (_Desc)->FirstBlock == NULL || \
+ ((_Desc)->FirstBlock->BlockState == BlockFree && \
+ (_Desc)->FirstBlock->BlockSize >= \
+ (DWORD)((_Desc)->BufferEnd - (LPBYTE)(_Desc)->FirstBlock) ) \
+)
+
+//
+// Macro to initialize a changelog desriptor.
+//
+
+#define InitChangeLogDesc( _Desc ) \
+ RtlZeroMemory( (_Desc), sizeof( *(_Desc) ) ); \
+ (_Desc)->FileHandle = INVALID_HANDLE_VALUE;
+
+//
+// Macro to determine if the serial number on the change log entry matches
+// the serial number specified.
+//
+// The serial numbers match if there is an exact match or
+// if the changelog entry contains the serial number at the instant of promotion and the
+// requested serial number is the corresponding pre-promotion value.
+//
+
+#define IsSerialNumberEqual( _ChangeLogDesc, _ChangeLogEntry, _SerialNumber ) \
+( \
+ (_ChangeLogEntry)->SerialNumber.QuadPart == (_SerialNumber)->QuadPart || \
+ (((_ChangeLogEntry)->Flags & CHANGELOG_PDC_PROMOTION) && \
+ (_ChangeLogEntry)->SerialNumber.QuadPart == \
+ (_SerialNumber)->QuadPart + NlGlobalChangeLogPromotionIncrement.QuadPart ) \
+)
+
+
+//
+// variables describing the change log
+//
+
+typedef struct _CHANGELOG_DESCRIPTOR {
+
+ //
+ // Start and end of the allocated block.
+ //
+ LPBYTE Buffer; // Cache of the changelog contents
+ ULONG BufferSize; // Size (in bytes) of the buffer
+ LPBYTE BufferEnd; // Address of first byte beyond the end of the buffer
+
+ //
+ // Offset of the first and last dirty bytes
+ //
+
+ ULONG FirstDirtyByte;
+ ULONG LastDirtyByte;
+
+ //
+ // Address of the first physical block in the change log
+ //
+ PCHANGELOG_BLOCK_HEADER FirstBlock; // where delta buffer starts
+
+ //
+ // Description of the circular list of change log entries.
+ //
+ PCHANGELOG_BLOCK_HEADER Head; // start reading logs from here
+ PCHANGELOG_BLOCK_HEADER Tail; // where next log is written
+
+ //
+ // Serial Number of each database.
+ //
+ // Access is serialized via NlGlobalChangeLogCritSect
+ //
+
+ LARGE_INTEGER SerialNumber[NUM_DBS];
+
+ //
+ // Number of change log entries in the log for the specified database
+ //
+
+ DWORD EntryCount[NUM_DBS];
+
+ //
+ // Handle to file acting as backing store for the buffer.
+ //
+
+ HANDLE FileHandle; // handle for change log file
+
+ //
+ // Version 3: True to indicate this is a version 3 buffer.
+ //
+
+ BOOLEAN Version3;
+
+ //
+ // True if this is a re-do log and not a change log
+ //
+
+ BOOLEAN RedoLog;
+
+} CHANGELOG_DESCRIPTOR, *PCHANGELOG_DESCRIPTOR;
+
+//
+// The change log is a log of ALL changes made to the SAM/LSA databases. The
+// change log is maintained in serial number order.
+//
+// The redo log is a log of changes that need to be re-applied to a BDC. The
+// redo log is not maintained in any particular order.
+//
+EXTERN CHANGELOG_DESCRIPTOR NlGlobalChangeLogDesc;
+EXTERN CHANGELOG_DESCRIPTOR NlGlobalRedoLogDesc;
+EXTERN WCHAR NlGlobalChangeLogFilePrefix[PATHLEN+1]; // Changelog file name. (w/o postfix)
+
+
+//
+// Tables of related delta types
+//
+
+//
+// Table of delete delta types.
+// Index into the table with a delta type,
+// the entry is the delta type that is used to delete the object.
+//
+// There are some objects that can't be deleted. In that case, this table
+// contains a delta type that uniquely identifies the object. That allows
+// this table to be used to see if two deltas describe the same object type.
+//
+
+EXTERN const NETLOGON_DELTA_TYPE NlGlobalDeleteDeltaType[]
+#ifdef CHUTIL_ALLOCATE
+= {
+ AddOrChangeDomain, // 0 is an invalid delta type
+ AddOrChangeDomain, // AddOrChangeDomain,
+ DeleteGroup, // AddOrChangeGroup,
+ DeleteGroup, // DeleteGroup,
+ DeleteGroup, // RenameGroup,
+ DeleteUser, // AddOrChangeUser,
+ DeleteUser, // DeleteUser,
+ DeleteUser, // RenameUser,
+ DeleteGroup, // ChangeGroupMembership,
+ DeleteAlias, // AddOrChangeAlias,
+ DeleteAlias, // DeleteAlias,
+ DeleteAlias, // RenameAlias,
+ DeleteAlias, // ChangeAliasMembership,
+ AddOrChangeLsaPolicy, // AddOrChangeLsaPolicy,
+ DeleteLsaTDomain, // AddOrChangeLsaTDomain,
+ DeleteLsaTDomain, // DeleteLsaTDomain,
+ DeleteLsaAccount, // AddOrChangeLsaAccount,
+ DeleteLsaAccount, // DeleteLsaAccount,
+ DeleteLsaSecret, // AddOrChangeLsaSecret,
+ DeleteLsaSecret, // DeleteLsaSecret,
+ DeleteGroup, // DeleteGroupByName,
+ DeleteUser, // DeleteUserByName,
+ SerialNumberSkip, // SerialNumberSkip,
+ DummyChangeLogEntry // DummyChangeLogEntry
+}
+#endif // CHUTIL_ALLOCATE
+;
+
+#define MAX_DELETE_DELTA \
+ (sizeof(NlGlobalDeleteDeltaType)/sizeof(NlGlobalDeleteDeltaType[0]))
+
+
+//
+// Table of add delta types.
+// Index into the table with a delta type,
+// the entry is the delta type that is used to add the object.
+//
+// There are some objects that can't be added. In that case, this table
+// contains a delta type that uniquely identifies the object. That allows
+// this table to be used to see if two deltas describe the same object type.
+//
+// In the table, Groups and Aliases are represented as renames. This causes
+// NlPackSingleDelta to return both the group attributes and the group
+// membership.
+//
+
+EXTERN const NETLOGON_DELTA_TYPE NlGlobalAddDeltaType[]
+#ifdef CHUTIL_ALLOCATE
+= {
+ AddOrChangeDomain, // 0 is an invalid delta type
+ AddOrChangeDomain, // AddOrChangeDomain,
+ RenameGroup, // AddOrChangeGroup,
+ RenameGroup, // DeleteGroup,
+ RenameGroup, // RenameGroup,
+ AddOrChangeUser, // AddOrChangeUser,
+ AddOrChangeUser, // DeleteUser,
+ AddOrChangeUser, // RenameUser,
+ RenameGroup, // ChangeGroupMembership,
+ RenameAlias, // AddOrChangeAlias,
+ RenameAlias, // DeleteAlias,
+ RenameAlias, // RenameAlias,
+ RenameAlias, // ChangeAliasMembership,
+ AddOrChangeLsaPolicy, // AddOrChangeLsaPolicy,
+ AddOrChangeLsaTDomain, // AddOrChangeLsaTDomain,
+ AddOrChangeLsaTDomain, // DeleteLsaTDomain,
+ AddOrChangeLsaAccount, // AddOrChangeLsaAccount,
+ AddOrChangeLsaAccount, // DeleteLsaAccount,
+ AddOrChangeLsaSecret, // AddOrChangeLsaSecret,
+ AddOrChangeLsaSecret, // DeleteLsaSecret,
+ RenameGroup, // DeleteGroupByName,
+ AddOrChangeUser, // DeleteUserByName,
+ SerialNumberSkip, // SerialNumberSkip,
+ DummyChangeLogEntry // DummyChangeLogEntry
+}
+#endif // CHUTIL_ALLOCATE
+;
+
+#define MAX_ADD_DELTA DummyChangeLogEntry
+
+
+
+//
+// Table of Status Codes indicating the object doesn't exist.
+// Index into the table with a delta type.
+//
+// Map to STATUS_SUCCESS for the invalid cases to explicitly avoid other error
+// codes.
+
+EXTERN const NTSTATUS NlGlobalObjectNotFoundStatus[]
+#ifdef CHUTIL_ALLOCATE
+= {
+ STATUS_SUCCESS, // 0 is an invalid delta type
+ STATUS_NO_SUCH_DOMAIN, // AddOrChangeDomain,
+ STATUS_NO_SUCH_GROUP, // AddOrChangeGroup,
+ STATUS_NO_SUCH_GROUP, // DeleteGroup,
+ STATUS_NO_SUCH_GROUP, // RenameGroup,
+ STATUS_NO_SUCH_USER, // AddOrChangeUser,
+ STATUS_NO_SUCH_USER, // DeleteUser,
+ STATUS_NO_SUCH_USER, // RenameUser,
+ STATUS_NO_SUCH_GROUP, // ChangeGroupMembership,
+ STATUS_NO_SUCH_ALIAS, // AddOrChangeAlias,
+ STATUS_NO_SUCH_ALIAS, // DeleteAlias,
+ STATUS_NO_SUCH_ALIAS, // RenameAlias,
+ STATUS_NO_SUCH_ALIAS, // ChangeAliasMembership,
+ STATUS_SUCCESS, // AddOrChangeLsaPolicy,
+ STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaTDomain,
+ STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaTDomain,
+ STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaAccount,
+ STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaAccount,
+ STATUS_OBJECT_NAME_NOT_FOUND, // AddOrChangeLsaSecret,
+ STATUS_OBJECT_NAME_NOT_FOUND, // DeleteLsaSecret,
+ STATUS_NO_SUCH_GROUP, // DeleteGroupByName,
+ STATUS_NO_SUCH_USER, // DeleteUserByName,
+ STATUS_SUCCESS, // SerialNumberSkip,
+ STATUS_SUCCESS // DummyChangeLogEntry
+}
+#endif // CHUTIL_ALLOCATE
+;
+
+#define MAX_OBJECT_NOT_FOUND_STATUS DummyChangeLogEntry
+
+#define IsObjectNotFoundStatus( _DeltaType, _NtStatus ) \
+ (((ULONG)(_DeltaType) > MAX_OBJECT_NOT_FOUND_STATUS ) ? \
+ FALSE : \
+ (NlGlobalObjectNotFoundStatus[ (_DeltaType) ] == (_NtStatus)) )
+
+//
+// chutil.c
+//
+
+NTSTATUS
+NlCreateChangeLogFile(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+ );
+
+NTSTATUS
+NlFlushChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+ );
+
+PCHANGELOG_ENTRY
+NlMoveToNextChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry
+ );
+
+VOID
+PrintChangeLogEntry(
+ IN PCHANGELOG_ENTRY ChangeLogEntry
+ );
+
+NTSTATUS
+NlResetChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD NewChangeLogSize
+ );
+
+NTSTATUS
+NlOpenChangeLogFile(
+ IN LPWSTR ChangeLogFileName,
+ OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN BOOLEAN ReadOnly
+ );
+
+VOID
+NlCloseChangeLogFile(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc
+);
+
+NTSTATUS
+NlResizeChangeLogFile(
+ IN OUT PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD NewChangeLogSize
+);
+
+NTSTATUS
+NlWriteChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN PSID ObjectSid,
+ IN PUNICODE_STRING ObjectName,
+ IN BOOLEAN FlushIt
+ );
+
+NTSTATUS
+NlWriteDeltaToChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN ULONG DBIndex,
+ IN OUT PLARGE_INTEGER SerialNumber
+ );
+
+PCHANGELOG_ENTRY
+NlGetNextDownlevelChangeLogEntry(
+ ULONG DownlevelSerialNumber
+ );
+
+PCHANGELOG_ENTRY
+NlFindPromotionChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex
+ );
+
+PCHANGELOG_ENTRY
+NlGetNextChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex,
+ OUT LPDWORD ChangeLogEntrySize OPTIONAL
+ );
+
+PCHANGELOG_ENTRY
+NlGetNextUniqueChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN LARGE_INTEGER SerialNumber,
+ IN DWORD DBIndex,
+ OUT LPDWORD ChangeLogEntrySize OPTIONAL
+ );
+
+BOOL
+NlRecoverChangeLog(
+ PCHANGELOG_ENTRY ChangeLogEntry
+ );
+
+VOID
+NlDeleteChangeLogEntry(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD DBIndex,
+ IN LARGE_INTEGER SerialNumber
+ );
+
+BOOLEAN
+NlFixChangeLog(
+ IN PCHANGELOG_DESCRIPTOR ChangeLogDesc,
+ IN DWORD DBIndex,
+ IN LARGE_INTEGER SerialNumber,
+ IN BOOLEAN CopyEntriesToRedoLog
+ );
+
+BOOL
+NlValidateChangeLogEntry(
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN DWORD ChangeLogEntrySize
+ );
+
+#undef EXTERN
+
diff --git a/private/net/svcdlls/logonsrv/server/chworker.c b/private/net/svcdlls/logonsrv/server/chworker.c
new file mode 100644
index 000000000..2b567796d
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/chworker.c
@@ -0,0 +1,1714 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ worker.c
+
+Abstract:
+
+ Special Local groups replication to down level system
+ implementation.
+
+ This file contains the code required for the change log worker
+ thread. The worker thread maintains the list special local groups of
+ the BUILTIN database system and the list global groups that are
+ members of the special local group. Once a membership change is
+ found in one of the maintained groups, this thread simulate a user
+ delta for the new member and thus a change is reflected to the DL
+ system.
+
+Author:
+
+ Madan Appiah
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 13-Dec-1992 (Madana)
+ Created this file
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <ntrtl.h> // LARGE_INTEGER definition
+#include <nturtl.h> // LARGE_INTEGER definition
+#include <ntlsa.h> // needed by changelg.h
+
+#define NOMINMAX // Avoid redefinition of min and max in stdlib.h
+#include <rpc.h> // Needed by logon.h
+#include <logon_s.h>// includes lmcons.h, lmaccess.h, netlogon.h,
+ // ssi.h, windef.h
+#include <winbase.h>
+#include <stdio.h> // sprintf ...
+
+//
+// BEWARE: Be careful about adding netlogon.dll specific include files here.
+// This module is call by SAM and LSA. The netlogon service may not yet
+// be running. Therefore, guard against referencing netlogon.dll globals
+// other than those defined in changelg.h.
+//
+
+#include <samrpc.h> // Needed by samisrv.h
+#include <samisrv.h> // Needed by changelg.h
+#include <lsarpc.h> // Needed by lsrvdata.h and logonsrv.h
+#include <lsaisrv.h> // LsaI routines
+#include <changelg.h> // Local procedure definitions
+
+#include <lmerr.h> // NERR_Success defined here ..
+#include <lmerrlog.h> // NELOG_* defined here ..
+#include <lmapibuf.h> // NetApiBufferFree defined here ..
+#include <netlib.h> // NetpMemoryAllocate
+#include <netlibnt.h> // NetpNtStatusToApiStatus
+
+#define DEBUG_ALLOCATE
+#include <nldebug.h> // Netlogon debugging
+#undef DEBUG_ALLOCATE
+#include <align.h>
+#include <string.h> // strncmp
+#include <nlp.h> // NlpWriteEventlog defined here.
+
+#include <nlrepl.h> // I_Net* definitions
+
+#define WORKER_ALLOCATE
+#include <chworker.h>
+#undef WORKER_ALLOCATE
+
+//
+// Special Local ID array
+//
+
+ULONG NlGlobalSpecialLocalGroupIDs[] = {
+ DOMAIN_ALIAS_RID_ADMINS,
+ DOMAIN_ALIAS_RID_USERS,
+ DOMAIN_ALIAS_RID_GUESTS,
+ DOMAIN_ALIAS_RID_ACCOUNT_OPS,
+ DOMAIN_ALIAS_RID_SYSTEM_OPS,
+ DOMAIN_ALIAS_RID_PRINT_OPS } ;
+
+#define NUM_SP_LOCAL_GROUPS \
+ (sizeof(NlGlobalSpecialLocalGroupIDs) / \
+ sizeof(NlGlobalSpecialLocalGroupIDs[0]))
+
+
+
+BOOLEAN
+IsSpecialLocalGroup(
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ This procedure determines that the given RID is one among the
+ special local group RID.
+
+Arguments:
+
+ Rid : Rid to test.
+
+Return Value:
+
+ TRUE : if the given RID is special local group.
+
+ FALSE : otherwise.
+
+--*/
+{
+ DWORD i;
+
+ for (i = 0; i < NUM_SP_LOCAL_GROUPS; i++ ) {
+
+ if( NlGlobalSpecialLocalGroupIDs[i] == Rid ) {
+
+ return( TRUE );
+ }
+ }
+
+ return( FALSE );
+
+}
+
+
+VOID
+NlSimulateUserDelta(
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ This procedure calls SAM service to generate change to a user record
+ and increment the serial number.
+
+Arguments:
+
+ Rid : Rid of the new delta.
+
+Return Value:
+
+ none
+
+--*/
+{
+ NTSTATUS Status;
+
+ Status = SamINotifyDelta(
+ NlGlobalChWorkerSamDBHandle,
+ SecurityDbChange,
+ SecurityDbObjectSamUser,
+ Rid,
+ NULL,
+ FALSE,
+ NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL, "SamINotifyDelta failed %lx\n", Status ) );
+ }
+}
+
+
+NTSTATUS
+NlAddWorkerQueueEntry(
+ enum WORKER_QUEUE_ENTRY_TYPE EntryType,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ This procedure adds a new entry to worker queue. It sets queue event
+ so that worker thread wakes up to process this new entry.
+
+ Enter with NlGlobalChangeLogCritSect locked.
+
+Arguments:
+
+ EntryType : type of the new entry.
+
+ Rid : Rid of the member that caused this new entry.
+
+Return Value:
+
+ STATUS_NO_MEMORY - if no memory available for the new entry.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PWORKER_QUEUE_ENTRY Entry;
+
+ Entry = (PWORKER_QUEUE_ENTRY)NetpMemoryAllocate(
+ sizeof(WORKER_QUEUE_ENTRY) );
+
+ if( Entry == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ Entry->EntryType = EntryType;
+ Entry->Rid = Rid;
+
+ //
+ // add to list
+ //
+
+ InsertTailList( &NlGlobalChangeLogWorkerQueue, &Entry->Next );
+
+ //
+ // awake worker thread.
+ //
+
+ if ( !SetEvent( NlGlobalChangeLogWorkerQueueEvent ) ) {
+ DWORD WinError;
+
+ WinError = GetLastError();
+
+ NlPrint((NL_CRITICAL,
+ "Cannot set ChangeLog worker queue event: %lu\n",
+ WinError ));
+
+ Status = NetpApiStatusToNtStatus( WinError );
+ }
+
+Cleanup:
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL, "NlAddWorkerQueueEntry failed %lx\n",Status ) );
+ }
+
+ return( Status );
+
+}
+
+
+PGLOBAL_GROUP_ENTRY
+NlGetGroupEntry(
+ PLIST_ENTRY GroupList,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ This procedure returns a group entry from the list. It returns NULL
+ if the requested entry is not in the list.
+
+ Enter with NlGlobalChangeLogCritSect locked if GroupList points to
+ NlGlobalSpecialServerGroupList.
+
+Arguments:
+
+ GroupList : list to search.
+
+ Rid : Rid of the entry wanted.
+
+Return Value:
+
+ NULL : if there is no entry.
+ otherwise : pointer to the entry.
+
+--*/
+{
+ PLIST_ENTRY ListEntry;
+ PGLOBAL_GROUP_ENTRY GroupEntry;
+
+ for ( ListEntry = GroupList->Flink;
+ ListEntry != GroupList;
+ ListEntry = ListEntry->Flink ) {
+
+ GroupEntry =
+ CONTAINING_RECORD( ListEntry, GLOBAL_GROUP_ENTRY, Next );
+
+ if( GroupEntry->Rid == Rid ) {
+
+ return( GroupEntry );
+ }
+ }
+
+ return( NULL );
+}
+
+
+NTSTATUS
+NlAddGroupEntry(
+ PLIST_ENTRY GroupList,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ This procedure adds a group entry to a global group list.
+
+ Enter with NlGlobalChangeLogCritSect locked if GroupList points to
+ NlGlobalSpecialServerGroupList.
+
+Arguments:
+
+ GroupList : List to modify.
+
+ Rid : Rid of the new group entry.
+
+ Name : Name of the new group entry.
+
+Return Value:
+
+ STATUS_NO_MEMORY - if no memory available for the new entry.
+
+--*/
+{
+ PGLOBAL_GROUP_ENTRY Entry;
+
+ //
+ // If the entry already exists,
+ // don't add it again.
+ //
+ Entry = NlGetGroupEntry( GroupList, Rid );
+
+ if ( Entry != NULL) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // get memory for this entry
+ //
+
+ Entry = (PGLOBAL_GROUP_ENTRY)NetpMemoryAllocate(
+ sizeof(GLOBAL_GROUP_ENTRY) );
+
+ if( Entry == NULL ) {
+
+ return( STATUS_NO_MEMORY );
+ }
+
+ Entry->Rid = Rid;
+
+ //
+ // add to list
+ //
+
+ InsertTailList( GroupList, &Entry->Next );
+
+ return( STATUS_SUCCESS );
+
+}
+
+
+NTSTATUS
+NlAddGlobalGroupsToList(
+ PLIST_ENTRY GroupList,
+ ULONG LocalGroupID
+ )
+/*++
+
+Routine Description:
+
+ This procedure adds the global groups that are member of the given
+ alias.
+
+Arguments:
+
+ GroupList : List to munch.
+
+ LocalGroupId : Rid of the local group.
+
+Return Value:
+
+ Return NT Status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle = NULL;
+
+ SAMPR_PSID_ARRAY Members = {0, NULL};
+ PULONG RidArray = NULL;
+ DWORD RidArrayLength = 0;
+ SAMPR_RETURNED_USTRING_ARRAY Names = {0, NULL};
+ SAMPR_ULONG_ARRAY Use = {0, NULL};
+ DWORD i;
+
+ //
+ // Open Local Group
+ //
+
+ Status = SamrOpenAlias(
+ NlGlobalChWorkerBuiltinDBHandle,
+ 0, // No desired access
+ LocalGroupID,
+ &AliasHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Enumerate members in this local group.
+ //
+
+ Status = SamrGetMembersInAlias(
+ AliasHandle,
+ &Members );
+
+ if (!NT_SUCCESS(Status)) {
+ Members.Sids = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Determine the SIDs that belong to the Account Domain and get the
+ // RIDs of them.
+ //
+
+ if( Members.Count == 0) {
+
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ //
+ // Allocate the maximum size RID array required.
+ //
+
+ RidArray = (PULONG)NetpMemoryAllocate( Members.Count * sizeof(ULONG) );
+
+ if( RidArray == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ for( i = 0; i < Members.Count; i++) {
+
+ PUCHAR SubAuthorityCount;
+ BOOLEAN EqualSid;
+
+ SubAuthorityCount =
+ RtlSubAuthorityCountSid(Members.Sids[i].SidPointer);
+
+ (*SubAuthorityCount)--;
+ EqualSid = RtlEqualSid( NlGlobalChWorkerSamDomainSid,
+ Members.Sids[i].SidPointer );
+ (*SubAuthorityCount)++;
+
+ if( EqualSid ) {
+
+ RidArray[RidArrayLength] =
+ *RtlSubAuthoritySid( Members.Sids[i].SidPointer,
+ (*SubAuthorityCount) -1 );
+ RidArrayLength++;
+ }
+ }
+
+ if( RidArrayLength == 0) {
+
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ //
+ // Get Group RIDs and add them to list.
+ //
+
+ Status = SamrLookupIdsInDomain( NlGlobalChWorkerSamDBHandle,
+ RidArrayLength,
+ RidArray,
+ &Names,
+ &Use );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ Names.Element = NULL;
+ Use.Element = NULL;
+
+ if( Status == STATUS_NONE_MAPPED ) {
+
+ //
+ // if no SID is mapped, we can't do much here.
+ //
+
+ NlPrint((NL_CRITICAL,
+ "NlAddGlobalGroupsToList could not map any SID from "
+ "local group, RID = %lx \n", LocalGroupID ));
+
+ Status = STATUS_SUCCESS;
+ }
+
+ goto Cleanup;
+ }
+
+ NlAssert( Names.Count == RidArrayLength );
+ NlAssert( Names.Element != NULL );
+ NlAssert( Use.Count == RidArrayLength );
+ NlAssert( Use.Element != NULL );
+
+ //
+ // Find groups and add them to list.
+ //
+
+ for( i = 0; i < RidArrayLength; i++ ) {
+
+ if( Use.Element[i] == SidTypeGroup ) {
+
+ //
+ // we found a group, add it to the list if it is not there
+ // already.
+ //
+
+ if( NlGetGroupEntry( GroupList, RidArray[i] ) != NULL ) {
+
+ //
+ // entry already in the list.
+ //
+
+ continue;
+ }
+
+ //
+ // add an entry to the list.
+ //
+
+ Status = NlAddGroupEntry( GroupList, RidArray[i] );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+ }
+ }
+
+Cleanup:
+
+ if( Names.Element != NULL ) {
+ SamIFree_SAMPR_RETURNED_USTRING_ARRAY( &Names );
+ }
+
+ if( Use.Element != NULL ) {
+ SamIFree_SAMPR_ULONG_ARRAY( &Use );
+ }
+
+ if( RidArray != NULL ) {
+ NetpMemoryFree( RidArray );
+ }
+
+ if ( Members.Sids != NULL ) {
+ SamIFree_SAMPR_PSID_ARRAY( (PSAMPR_PSID_ARRAY)&Members );
+ }
+
+ if( AliasHandle != NULL ) {
+ SamrCloseHandle( &AliasHandle );
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL, "NlAddGlobalGroupsToList failed %lx\n",
+ Status ));
+ }
+
+ return( Status );
+}
+
+
+NTSTATUS
+NlInitSpecialGroupList(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine browses through the following special local groups and
+ forms a list global groups that are members of the local groups.
+
+ Special Local groups :
+
+ 1. DOMAIN_ALIAS_RID_ADMINS
+ 2. DOMAIN_ALIAS_RID_USERS
+ 3. DOMAIN_ALIAS_RID_GUESTS
+ 4. DOMAIN_ALIAS_RID_ACCOUNT_OPS
+ 5. DOMAIN_ALIAS_RID_SYSTEM_OPS
+ 6. DOMAIN_ALIAS_RID_PRINT_OPS
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Return NT Status code.
+
+--*/
+{
+
+ NTSTATUS Status;
+ LIST_ENTRY SpecialServerGroupList;
+ DWORD i;
+
+ InitializeListHead(&SpecialServerGroupList);
+
+ for (i = 0; i < NUM_SP_LOCAL_GROUPS; i++ ) {
+
+ Status = NlAddGlobalGroupsToList(
+ &SpecialServerGroupList,
+ NlGlobalSpecialLocalGroupIDs[i] );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+ }
+
+ //
+ // install new list in global data.
+ //
+
+ LOCK_CHANGELOG();
+
+ NlAssert( IsListEmpty(&NlGlobalSpecialServerGroupList) );
+
+ //
+ // install list in global data
+ //
+
+ NlGlobalSpecialServerGroupList = SpecialServerGroupList;
+ (SpecialServerGroupList.Flink)->Blink = &NlGlobalSpecialServerGroupList;
+ (SpecialServerGroupList.Blink)->Flink = &NlGlobalSpecialServerGroupList;
+
+ UNLOCK_CHANGELOG();
+
+ return( Status );
+}
+
+
+BOOL
+NlIsServersGroupEmpty(
+ ULONG ServersGroupRid
+ )
+/*++
+
+Routine Description:
+
+ This procedure determines whether the Servers group is empty or not.
+
+Arguments:
+
+ Rid : Rid of the SERVERS group. If it is zero then determine the RID
+ by lookup.
+
+Return Value:
+
+ FALSE : If the servers group exist and it is non-empty.
+ TRUE : otherwise
+
+--*/
+{
+ NTSTATUS Status;
+
+ SAMPR_ULONG_ARRAY RelativeIdArray = {0, NULL};
+ SAMPR_ULONG_ARRAY UseArray = {0, NULL};
+ RPC_UNICODE_STRING GroupNameString;
+ SAMPR_HANDLE GroupHandle = NULL;
+ BOOL ReturnValue = TRUE;
+
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+
+ if ( ServersGroupRid == 0 ) {
+
+ //
+ // Convert the group name to a RelativeId.
+ //
+
+ RtlInitUnicodeString( (PUNICODE_STRING)&GroupNameString,
+ SSI_SERVER_GROUP_W );
+
+ Status = SamrLookupNamesInDomain(
+ NlGlobalChWorkerSamDBHandle,
+ 1,
+ &GroupNameString,
+ &RelativeIdArray,
+ &UseArray );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ RelativeIdArray.Element = NULL;
+ UseArray.Element = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // we should get back exactly one entry of info back.
+ //
+
+ NlAssert( UseArray.Count == 1 );
+ NlAssert( UseArray.Element != NULL );
+ NlAssert( RelativeIdArray.Count == 1 );
+ NlAssert( RelativeIdArray.Element != NULL );
+
+ if ( UseArray.Element[0] != SidTypeGroup ) {
+ goto Cleanup;
+ }
+
+ ServersGroupRid = RelativeIdArray.Element[0];
+ }
+
+ //
+ // Open the SERVERS group
+ //
+
+ Status = SamrOpenGroup( NlGlobalChWorkerSamDBHandle,
+ 0, // No desired access
+ ServersGroupRid,
+ &GroupHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // enumerate members in the group.
+ //
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ MembersBuffer = NULL;
+ goto Cleanup;
+ }
+
+ if ( MembersBuffer->MemberCount != 0 ) {
+
+ //
+ // atleast a member in there.
+ //
+
+ ReturnValue = FALSE;
+
+ //
+ // Save the list of LmBdcs
+
+ NlLmBdcListSet( MembersBuffer->MemberCount,
+ MembersBuffer->Members );
+ }
+
+Cleanup:
+
+ SamIFree_SAMPR_ULONG_ARRAY( &RelativeIdArray );
+ SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ return ReturnValue;
+}
+
+
+BOOLEAN
+NlProcessQueueEntry(
+ PWORKER_QUEUE_ENTRY Entry
+ )
+/*++
+
+Routine Description:
+
+ This procedure processes an entry that has come from the worker
+ queue.
+
+Arguments:
+
+ WorkerQueueEntry : pointer to worker structure.
+
+Return Value:
+
+ TRUE : if we need to continue processing more entries.
+ FALSE : if we need to terminate the worker.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG Rid = Entry->Rid;
+ BOOLEAN ReturnValue = TRUE;
+ SAMPR_RETURNED_USTRING_ARRAY Names = {0, NULL};
+ SAMPR_ULONG_ARRAY Use = {0, NULL};
+ SAMPR_HANDLE GroupHandle = NULL;
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+ SAMPR_HANDLE UserHandle = NULL;
+
+ PSAMPR_GET_GROUPS_BUFFER GroupsBuffer = NULL;
+ PGLOBAL_GROUP_ENTRY GroupEntry;
+
+ DWORD i;
+
+
+ //
+ // The membership of a special local group is being changed,
+ // force each lanman BDC to re-sync with each user that's being
+ // added-to/removed-from the local group.
+ //
+ switch ( Entry->EntryType ) {
+ case ChangeLogAliasMembership :
+
+ //
+ // determine Rid Type.
+ //
+
+ Status = SamrLookupIdsInDomain(
+ NlGlobalChWorkerSamDBHandle,
+ 1,
+ &Rid,
+ &Names,
+ &Use );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Names.Element = NULL;
+ Use.Element = NULL;
+ goto Cleanup;
+ }
+
+ NlAssert( Names.Count == 1 );
+ NlAssert( Names.Element != NULL );
+ NlAssert( Use.Count == 1 );
+ NlAssert( Use.Element != NULL );
+
+ if( Use.Element[0] == SidTypeUser ) {
+
+ NlSimulateUserDelta( Rid );
+
+ //
+ // if this users is added unknowingly to the global group
+ // list, remove it now.
+ //
+
+ LOCK_CHANGELOG();
+
+ GroupEntry = NlGetGroupEntry (
+ &NlGlobalSpecialServerGroupList,
+ Rid );
+
+ if( GroupEntry != NULL ) {
+
+ RemoveEntryList( &GroupEntry->Next );
+ NetpMemoryFree( GroupEntry );
+ }
+
+ UNLOCK_CHANGELOG();
+
+
+ } else if( Use.Element[0] == SidTypeGroup ) {
+
+ DWORD i;
+
+ //
+ // simulate deltas for all members in this group.
+ //
+
+ Status = SamrOpenGroup( NlGlobalChWorkerSamDBHandle,
+ 0, // No desired access
+ Rid,
+ &GroupHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ MembersBuffer = NULL;
+ goto Cleanup;
+ }
+
+ for( i = 0; i < MembersBuffer->MemberCount; i++) {
+
+ NlSimulateUserDelta( MembersBuffer->Members[i] );
+ }
+
+#if DBG
+ //
+ // Ensure the change log thread already added this group
+ //
+
+ LOCK_CHANGELOG();
+
+ GroupEntry = NlGetGroupEntry (
+ &NlGlobalSpecialServerGroupList,
+ Rid );
+
+ UNLOCK_CHANGELOG();
+
+ NlAssert( GroupEntry != NULL );
+#endif // DBG
+
+ } else {
+
+ //
+ // ignore any other changes
+ //
+
+ NlAssert( FALSE );
+ }
+
+ break;
+
+ //
+ // The group membership of the special group has changed.
+ // Force Lanman BDCs to re-sync the user being added-to/removed-from
+ // the domain.
+ //
+ case ChangeLogGroupMembership :
+
+ //
+ // determine Rid Type.
+ //
+
+ Status = SamrLookupIdsInDomain(
+ NlGlobalChWorkerSamDBHandle,
+ 1,
+ &Rid,
+ &Names,
+ &Use );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Names.Element = NULL;
+ Use.Element = NULL;
+ goto Cleanup;
+ }
+
+ NlAssert( Names.Count == 1 );
+ NlAssert( Names.Element != NULL );
+ NlAssert( Use.Count == 1 );
+ NlAssert( Use.Element != NULL );
+
+ NlAssert( Use.Element[0] == SidTypeUser );
+
+
+ if( Use.Element[0] == SidTypeUser ) {
+
+ NlSimulateUserDelta( Rid );
+ }
+
+ break;
+
+
+ //
+ // A member was deleted from the SERVERS group.
+ // Check to see if this thread can terminate.
+ //
+ case ServersGroupDel :
+
+ //
+ // if the server group is empty then terminate worker thread.
+ //
+
+ if ( NlGlobalLmBdcCount == 0 ) {
+ ReturnValue = FALSE;
+ }
+
+ break;
+
+
+ //
+ // Rename user is handled as multiple deltas:
+ // 1) Delete old user and
+ // 2) Add new user.
+ // 3) Update membership of each group the user is a member of
+ //
+ case ChangeLogRenameUser :
+
+ //
+ // simulate a user change so that an user account with
+ // new name will be created on the down level system.
+ //
+
+ NlSimulateUserDelta( Rid );
+
+ //
+ // create deltas to make his group membership correct on the
+ // down level machine.
+ //
+
+ Status = SamrOpenUser( NlGlobalChWorkerSamDBHandle,
+ 0, // No desired access
+ Rid,
+ &UserHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ UserHandle = NULL;
+ goto Cleanup;
+ }
+
+ Status = SamrGetGroupsForUser( UserHandle, &GroupsBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupsBuffer = NULL;
+ goto Cleanup;
+ }
+
+ for( i = 0; i < GroupsBuffer->MembershipCount; i++) {
+
+ Status = SamINotifyDelta(
+ NlGlobalChWorkerSamDBHandle,
+ SecurityDbChangeMemberAdd,
+ SecurityDbObjectSamGroup,
+ GroupsBuffer->Groups[i].RelativeId,
+ NULL,
+ FALSE,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+
+ break;
+
+ //
+ // A newly added user needs to have it's membership in "Domain Users" updated, too.
+ //
+ // Here we simply supply a corresponding change user membership delta which
+ // ends up as a AddOrChangeUser delta with the CHANGELOG_DOMAINUSERS_CHANGED
+ // flag set. NetrAccountDeltas interprets that flag to mean
+ // "send the membership of this user".
+ //
+ case ChangeLogAddUser:
+
+ Status = SamINotifyDelta(
+ NlGlobalChWorkerSamDBHandle,
+ SecurityDbChangeMemberAdd,
+ SecurityDbObjectSamUser,
+ Rid,
+ NULL,
+ FALSE,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ break;
+
+
+ //
+ // Rename group is handled as three deltas:
+ // 1) Delete old group,
+ // 2) Add new group and
+ // 3) Changemembership of new group.
+ //
+ case ChangeLogRenameGroup :
+
+ //
+ // simulate a group change so that a group account with
+ // new name will be created on the down level system. Also
+ // simulate a changemembership delta so that the members are
+ // added to the new group appropriately.
+ //
+
+ Status = SamINotifyDelta(
+ NlGlobalChWorkerSamDBHandle,
+ SecurityDbChange,
+ SecurityDbObjectSamGroup,
+ Rid,
+ NULL,
+ FALSE,
+ NULL );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ Status = SamINotifyDelta(
+ NlGlobalChWorkerSamDBHandle,
+ SecurityDbChangeMemberAdd,
+ SecurityDbObjectSamGroup,
+ Rid,
+ NULL,
+ FALSE,
+ NULL );
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL, "SamINotifyDelta failed %lx\n", Status ) );
+ }
+
+ break;
+
+ default:
+
+ NlPrint((NL_CRITICAL,
+ "NlProcessQueueEntry found unknown queue entry : %lx\n",
+ Entry->EntryType ));
+ break;
+
+ }
+
+Cleanup:
+
+ if( Names.Element != NULL ) {
+ SamIFree_SAMPR_RETURNED_USTRING_ARRAY( &Names );
+ }
+
+ if( Use.Element != NULL ) {
+ SamIFree_SAMPR_ULONG_ARRAY( &Use );
+ }
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ if ( GroupsBuffer != NULL ) {
+ SamIFree_SAMPR_GET_GROUPS_BUFFER( GroupsBuffer );
+ }
+
+ if( UserHandle != NULL ) {
+ (VOID) SamrCloseHandle( &UserHandle );
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlProcessQueueEntry failed : %lx\n",
+ Status ));
+
+ }
+
+ return ReturnValue;
+
+}
+
+
+VOID
+NlChangeLogWorker(
+ IN LPVOID ChangeLogWorkerParam
+ )
+/*++
+
+Routine Description:
+
+ This thread performs the special operations that are required to
+ replicate the special local groups such as Administrator, Server
+ Operartors, etc., in the NT BUILTIN database to the
+ down level systems.
+
+ This thread comes up first time during system bootup and initializes
+ required global data. If this NT (PDC) System is replicating to any
+ down level system then it stays back, otherwise it terminates. Also
+ when a down level system is added to the domain, this thread is
+ created if it is not running on the system before.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Return when there is no down level system on the domain.
+
+--*/
+{
+ NTSTATUS Status;
+
+#if DBG
+ DWORD Count;
+#endif
+
+
+ NlPrint((NL_CHANGELOG, "ChangeLogWorker Thread is starting \n"));
+
+ //
+ // check if have initialize the global data before
+ //
+
+ if ( !NlGlobalChangeLogWorkInit ) {
+
+ PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL;
+ DWORD DomainSidLength;
+
+ //
+ // wait for SAM service to start.
+ //
+
+ if( !NlWaitForSamService(FALSE) ) {
+
+ NlPrint((NL_CRITICAL, "Sam server failed start."));
+ goto Cleanup;
+ }
+
+ //
+ // Open Sam Server
+ //
+
+ Status = SamIConnect( NULL, // No server name
+ &NlGlobalChWorkerSamServerHandle,
+ 0, // Ignore desired access
+ (BOOLEAN) TRUE );
+ // Indicate we are privileged
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to connect to SAM server %lx\n", Status ));
+
+ NlGlobalChWorkerSamServerHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Open Policy Domain
+ //
+
+ Status = LsaIOpenPolicyTrusted( &NlGlobalChWorkerPolicyHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to Open LSA database %lx\n", Status ));
+
+ NlGlobalChWorkerPolicyHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Open BuiltIn Domain database
+ //
+ // Note, build in domain SID is made during dll init time.
+ //
+
+
+ Status = SamrOpenDomain( NlGlobalChWorkerSamServerHandle,
+ DOMAIN_ALL_ACCESS,
+ NlGlobalChWorkerBuiltinDomainSid,
+ &NlGlobalChWorkerBuiltinDBHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to Open BUILTIN database %lx\n", Status ));
+ NlGlobalChWorkerBuiltinDBHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Query account domain SID.
+ //
+
+ Status = LsarQueryInformationPolicy(
+ NlGlobalChWorkerPolicyHandle,
+ PolicyAccountDomainInformation,
+ &PolicyAccountDomainInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to Query Account domain Sid from LSA %lx\n",
+ Status ));
+
+ goto Cleanup;
+ }
+
+ if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAccountDomainInformation,
+ PolicyAccountDomainInfo );
+
+ NlPrint((NL_CRITICAL, "Account domain info from LSA invalid.\n"));
+ goto Cleanup;
+ }
+
+ //
+ // copy domain SID to global data.
+ //
+
+ DomainSidLength = RtlLengthSid( PolicyAccountDomainInfo->
+ PolicyAccountDomainInfo.DomainSid );
+
+ NlGlobalChWorkerSamDomainSid = (PSID)NetpMemoryAllocate( DomainSidLength );
+
+ if( NlGlobalChWorkerSamDomainSid == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+
+ NlPrint((NL_CRITICAL,
+ "NlChangeLogWorker is out of memory.\n"));
+
+ goto Cleanup;
+ }
+
+ Status = RtlCopySid(
+ DomainSidLength,
+ NlGlobalChWorkerSamDomainSid,
+ PolicyAccountDomainInfo->
+ PolicyAccountDomainInfo.DomainSid );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to copy SAM Domain sid %lx\n", Status ));
+ goto Cleanup;
+ }
+
+ //
+ // Free up Account domain info, we don't need any more.
+ //
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAccountDomainInformation,
+ PolicyAccountDomainInfo );
+
+ //
+ // Open Account domain
+ //
+
+ Status = SamrOpenDomain( NlGlobalChWorkerSamServerHandle,
+ DOMAIN_ALL_ACCESS,
+ NlGlobalChWorkerSamDomainSid,
+ &NlGlobalChWorkerSamDBHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to Open SAM database %lx\n", Status ));
+ NlGlobalChWorkerSamDBHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Initialization done. Never do it again.
+ //
+
+ NlGlobalChangeLogWorkInit = TRUE;
+
+ }
+
+ //
+ // If SERVERS global group is empty then it implies that we don't
+ // have any down level system on this domain. so we can stop this
+ // thread.
+ //
+
+ if ( NlIsServersGroupEmpty( 0 ) ) {
+
+ NlPrint((NL_CHANGELOG, "Servers Group is empty \n "));
+
+ goto Cleanup;
+ }
+
+ //
+ // Initialize NlGlobalSpecialServerGroupList.
+ //
+
+ Status = NlInitSpecialGroupList();
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "Failed to initialize Special group list %lx\n", Status ));
+ goto Cleanup;
+ }
+
+ //
+ // process worker queue forever, terminate when we are asked to do
+ // so or when the SERVERS group goes empty.
+ //
+
+ for( ;; ) {
+
+ DWORD WaitStatus;
+
+ //
+ // wait on the queue to become non-empty
+ //
+
+ WaitStatus = WaitForSingleObject(
+ NlGlobalChangeLogWorkerQueueEvent,
+ (DWORD)(-1) );
+
+ if ( WaitStatus != 0 ) {
+
+ NlPrint((NL_CRITICAL,
+ "Change log worker failed, "
+ "WaitForSingleObject error: %ld\n",
+ WaitStatus));
+ break;
+ }
+
+ //
+ // empty worker queue.
+ //
+
+#if DBG
+ Count = 0;
+#endif
+ for (;;) {
+
+ PLIST_ENTRY ListEntry;
+ PWORKER_QUEUE_ENTRY WorkerQueueEntry;
+
+ //
+ // if we are asked to leave, do so.
+ //
+
+ if( NlGlobalChangeLogWorkerTerminate ) {
+
+ NlPrint((NL_CHANGELOG,
+ "ChangeLogWorker is asked to leave \n"));
+
+ goto Cleanup;
+ }
+
+ LOCK_CHANGELOG();
+
+ if( IsListEmpty( &NlGlobalChangeLogWorkerQueue ) ) {
+
+ UNLOCK_CHANGELOG();
+ break;
+ }
+
+ ListEntry = RemoveHeadList( &NlGlobalChangeLogWorkerQueue );
+
+ UNLOCK_CHANGELOG();
+
+ WorkerQueueEntry = CONTAINING_RECORD( ListEntry,
+ WORKER_QUEUE_ENTRY,
+ Next );
+
+ //
+ // process an queue entry.
+ //
+
+ if( !NlProcessQueueEntry( WorkerQueueEntry ) ) {
+
+ NlPrint((NL_CHANGELOG, "Servers group becomes empty \n"));
+
+ NetpMemoryFree( WorkerQueueEntry );
+ goto Cleanup;
+ }
+
+ //
+ // Free this entry.
+ //
+
+ NetpMemoryFree( WorkerQueueEntry );
+#if DBG
+ Count++;
+#endif
+ }
+
+ NlPrint((NL_CHANGELOG,
+ "Changelog worker processed %lu entries.\n", Count) );
+ }
+
+Cleanup:
+
+ //
+ // empty worker queue and group list
+ //
+
+ LOCK_CHANGELOG();
+
+#if DBG
+ Count = 0;
+#endif
+ while ( !IsListEmpty( &NlGlobalChangeLogWorkerQueue ) ) {
+ PLIST_ENTRY ListEntry;
+ PWORKER_QUEUE_ENTRY WorkerQueueEntry;
+
+ ListEntry = RemoveHeadList( &NlGlobalChangeLogWorkerQueue );
+
+ WorkerQueueEntry = CONTAINING_RECORD( ListEntry,
+ WORKER_QUEUE_ENTRY,
+ Next );
+ NetpMemoryFree( WorkerQueueEntry );
+#if DBG
+ Count++;
+#endif
+ }
+
+#if DBG
+ if ( Count != 0 ) {
+ NlPrint((NL_CHANGELOG,
+ "Changelog worker did not process %lu entries.\n", Count) );
+ }
+#endif
+
+ while ( !IsListEmpty( &NlGlobalSpecialServerGroupList ) ) {
+ PLIST_ENTRY ListEntry;
+ PGLOBAL_GROUP_ENTRY ServerEntry;
+
+ ListEntry = RemoveHeadList( &NlGlobalSpecialServerGroupList );
+
+ ServerEntry = CONTAINING_RECORD( ListEntry,
+ GLOBAL_GROUP_ENTRY,
+ Next );
+ NetpMemoryFree( ServerEntry );
+ }
+
+ UNLOCK_CHANGELOG();
+
+ NlPrint((NL_CHANGELOG, "ChangeLogWorker Thread is exiting \n"));
+
+ return;
+ UNREFERENCED_PARAMETER( ChangeLogWorkerParam );
+}
+
+
+
+BOOL
+NlStartChangeLogWorkerThread(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Start the Change Log Worker thread if it is not already running.
+
+ Enter with NlGlobalChangeLogCritSect locked.
+
+Arguments:
+
+ None.
+
+Return Value:
+ None.
+
+--*/
+{
+ DWORD ThreadHandle;
+
+ //
+ // If the worker thread is already running, do nothing.
+ //
+
+ if ( IsChangeLogWorkerRunning() ) {
+ return FALSE;
+ }
+
+ NlGlobalChangeLogWorkerTerminate = FALSE;
+
+ NlGlobalChangeLogWorkerThreadHandle = CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE)
+ NlChangeLogWorker,
+ NULL,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( NlGlobalChangeLogWorkerThreadHandle == NULL ) {
+
+ //
+ // ?? Shouldn't we do something in non-debug case
+ //
+
+ NlPrint((NL_CRITICAL, "Can't create change log worker thread %lu\n",
+ GetLastError() ));
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+
+VOID
+NlStopChangeLogWorker(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Stops the worker thread if it is running and waits for it to stop.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ //
+ // Ask the worker to stop running.
+ //
+
+ NlGlobalChangeLogWorkerTerminate = TRUE;
+
+ //
+ // Determine if the worker thread is already running.
+ //
+
+ if ( NlGlobalChangeLogWorkerThreadHandle != NULL ) {
+
+ //
+ // awake worker thread.
+ //
+
+ if ( !SetEvent( NlGlobalChangeLogWorkerQueueEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot set ChangeLog worker queue event: %lu\n",
+ GetLastError() ));
+ }
+
+ //
+ // We've asked the worker to stop. It should do so soon.
+ // Wait for it to stop.
+ //
+
+ NlWaitForSingleObject( "Wait for worker to stop",
+ NlGlobalChangeLogWorkerThreadHandle );
+
+
+ CloseHandle( NlGlobalChangeLogWorkerThreadHandle );
+ NlGlobalChangeLogWorkerThreadHandle = NULL;
+
+ }
+
+ NlGlobalChangeLogWorkerTerminate = FALSE;
+
+ return;
+}
+
+
+BOOL
+IsChangeLogWorkerRunning(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Test if the change log worker thread is running
+
+ Enter with NlGlobalChangeLogCritSect locked.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ TRUE - if the worker thread is running.
+
+ FALSE - if the worker thread is not running.
+
+--*/
+{
+ DWORD WaitStatus;
+
+ //
+ // Determine if the worker thread is already running.
+ //
+
+ if ( NlGlobalChangeLogWorkerThreadHandle != NULL ) {
+
+ //
+ // Time out immediately if the worker is still running.
+ //
+
+ WaitStatus = WaitForSingleObject(
+ NlGlobalChangeLogWorkerThreadHandle, 0 );
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ return TRUE;
+
+ } else if ( WaitStatus == 0 ) {
+ CloseHandle( NlGlobalChangeLogWorkerThreadHandle );
+ NlGlobalChangeLogWorkerThreadHandle = NULL;
+ return FALSE;
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "Cannot WaitFor Change Log Worker thread: %ld\n",
+ WaitStatus ));
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
diff --git a/private/net/svcdlls/logonsrv/server/chworker.h b/private/net/svcdlls/logonsrv/server/chworker.h
new file mode 100644
index 000000000..b2bed81d9
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/chworker.h
@@ -0,0 +1,215 @@
+/*++
+
+Copyright (c) 1991-1992 Microsoft Corporation
+
+Module Name:
+
+ worker.h
+
+Abstract:
+
+ Defines and routines needed to interface with worker.c.
+
+Author:
+
+ Madan Appiah (madana) 13-Dec-1992
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 13-Dec-1992 (madana)
+ Created this file.
+
+--*/
+
+//
+// worker.c will #include this file with WORKER_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+
+#ifdef WORKER_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Structures and variables describing the Change Log
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// Global Group list entry.
+//
+
+typedef struct _GLOBAL_GROUP_ENTRY {
+ LIST_ENTRY Next;
+
+ ULONG Rid;
+} GLOBAL_GROUP_ENTRY, *PGLOBAL_GROUP_ENTRY;
+
+//
+// ChangeLog Worker queue entry.
+//
+
+typedef struct _WORKER_QUEUE_ENTRY {
+ LIST_ENTRY Next;
+
+ enum WORKER_QUEUE_ENTRY_TYPE {
+ ChangeLogAliasMembership,
+ ChangeLogGroupMembership,
+ ServersGroupDel,
+ ChangeLogRenameUser,
+ ChangeLogRenameGroup,
+ ChangeLogAddUser
+ } EntryType;
+
+ ULONG Rid;
+
+} WORKER_QUEUE_ENTRY, *PWORKER_QUEUE_ENTRY;
+
+//
+// Changelog worker thread variables
+//
+
+EXTERN SAMPR_HANDLE NlGlobalChWorkerSamServerHandle; // Handle to Sam Server database
+EXTERN LSAPR_HANDLE NlGlobalChWorkerPolicyHandle; // Handle to Policy Database
+
+EXTERN SAM_HANDLE NlGlobalChWorkerSamDBHandle; // database handle to access SAM database
+EXTERN SAM_HANDLE NlGlobalChWorkerBuiltinDBHandle; // database handle to access BUILTIN database
+
+EXTERN PSID NlGlobalChWorkerBuiltinDomainSid; // Sid of builtin domain
+EXTERN PSID NlGlobalChWorkerSamDomainSid; // Sid of sam domain
+
+//
+// Event to indicate that an entry is added to the changelog
+// worker queue.
+//
+
+EXTERN HANDLE NlGlobalChangeLogWorkerQueueEvent;
+
+//
+// Queue containing the change logs for down level special group
+// coversions. ChangeLog threads write entries to this queue and
+// the worker thread reads entries from this thread. This queue is
+// protected by NlGlobalChangeLogCritSect.
+//
+
+EXTERN LIST_ENTRY NlGlobalChangeLogWorkerQueue;
+
+//
+// List containing list of Global Groups that are members of special
+// local groups such Administrator, Server Operators etc., This list
+// initially built by the worker thread and then updated by the
+// changelog threads. This list is also protected by
+// NlGlobalChangeLogCritSect.
+//
+
+EXTERN LIST_ENTRY NlGlobalSpecialServerGroupList;
+
+//
+// Change log worker thread handle. This is projected by the
+// NlGlobalChangeLogCritSect.
+//
+
+EXTERN HANDLE NlGlobalChangeLogWorkerThreadHandle;
+
+//
+// Flag to indicate the Global data that are required for change log
+// worker have been initialized successfully.
+//
+
+EXTERN BOOL NlGlobalChangeLogWorkInit;
+
+//
+// Flag to stop the change log worker thread.
+// Protected by the NlGlobalChangeLogCritSect.
+//
+
+EXTERN BOOL NlGlobalChangeLogWorkerTerminate;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Procedure forwards
+//
+/////////////////////////////////////////////////////////////////////////////
+
+BOOLEAN
+IsSpecialLocalGroup(
+ ULONG Rid
+ );
+
+VOID
+NlSimulateUserDelta(
+ ULONG Rid
+ );
+
+NTSTATUS
+NlAddWorkerQueueEntry(
+ enum WORKER_QUEUE_ENTRY_TYPE EntryType,
+ ULONG Rid
+ );
+
+PGLOBAL_GROUP_ENTRY
+NlGetGroupEntry(
+ PLIST_ENTRY GroupList,
+ ULONG Rid
+ );
+
+NTSTATUS
+NlAddGroupEntry(
+ PLIST_ENTRY GroupList,
+ ULONG Rid
+ );
+
+NTSTATUS
+NlAddGlobalGroupsToList(
+ PLIST_ENTRY GroupList,
+ ULONG LocalGroupID
+ );
+
+NTSTATUS
+NlInitSpecialGroupList(
+ VOID
+ );
+
+BOOL
+NlIsServersGroupEmpty(
+ ULONG ServersGroupRid
+ );
+
+BOOLEAN
+NlProcessQueueEntry(
+ PWORKER_QUEUE_ENTRY Entry
+ );
+
+VOID
+NlChangeLogWorker(
+ IN LPVOID ChangeLogWorkerParam
+ );
+
+BOOL
+NlStartChangeLogWorkerThread(
+ VOID
+ );
+
+VOID
+NlStopChangeLogWorker(
+ VOID
+ );
+
+BOOL
+IsChangeLogWorkerRunning(
+ VOID
+ );
+
+#undef EXTERN
+
+
diff --git a/private/net/svcdlls/logonsrv/server/error.c b/private/net/svcdlls/logonsrv/server/error.c
new file mode 100644
index 000000000..0b2d377d1
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/error.c
@@ -0,0 +1,820 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ error.c
+
+Abstract:
+
+ Error routines for Netlogon service
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 29-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+--*/
+
+//
+// Common include files.
+//
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <lmalert.h> // LAN Manager alert routines
+
+#include <lmerr.h> // NERR_Success
+#include <nlsecure.h> // NlGlobalNetlogonSecurityDescriptor ...
+#include <ntrpcp.h> // RpcpDeleteInterface ...
+#include <tstring.h> // Transitional string routines.
+#include <Secobj.h> // need for NetpDeleteSecurityObject
+
+
+
+NET_API_STATUS
+NlCleanup(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Cleanup all global resources.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PLIST_ENTRY ListEntry;
+ DWORD i;
+ BOOLEAN WaitForMsv;
+
+ //
+ // Let the ChangeLog routines know that Netlogon is not started.
+ //
+
+ NlGlobalChangeLogNetlogonState = NetlogonStopped;
+
+ //
+ // Timeout any async discoveries.
+ //
+ // The MainLoop thread is no longer running to complete them.
+ //
+
+ if ( NlGlobalSSICritSectInit ) {
+ NlDcDiscoveryExpired( TRUE );
+ }
+
+
+ //
+ // Indicate to external waiters that we're not running.
+ //
+
+ if ( NlGlobalStartedEvent != NULL ) {
+ //
+ // Reset it first in case some other process is preventing its deletion.
+ //
+ (VOID) NtResetEvent( NlGlobalStartedEvent, NULL );
+ (VOID) NtClose( NlGlobalStartedEvent );
+ NlGlobalStartedEvent = NULL;
+ }
+
+
+ //
+ // Stop the RPC server (Wait for outstanding calls to complete)
+ //
+
+ if ( NlGlobalRpcServerStarted ) {
+ Status = RpcServerUnregisterIf ( logon_ServerIfHandle, 0, TRUE );
+ NlAssert( Status == RPC_S_OK );
+ NlGlobalRpcServerStarted = FALSE;
+ }
+
+
+ //
+ // Tell all the MSV threads to leave netlogon.dll.
+ //
+
+ EnterCriticalSection( &NlGlobalMsvCritSect );
+ if ( NlGlobalMsvEnabled ) {
+ NlGlobalMsvEnabled = FALSE;
+ WaitForMsv = (NlGlobalMsvThreadCount > 0 );
+ } else {
+ WaitForMsv = FALSE;
+ }
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+
+ //
+ // Wait for the MSV threads to leave netlogon.dll
+ //
+
+ if ( NlGlobalMsvTerminateEvent != NULL ) {
+
+ if ( WaitForMsv ) {
+ WaitForSingleObject( NlGlobalMsvTerminateEvent, INFINITE );
+ }
+
+ (VOID) CloseHandle( NlGlobalMsvTerminateEvent );
+ NlGlobalMsvTerminateEvent = NULL;
+
+ }
+
+
+ //
+ // Cleanup scavenger thread.
+ // Wait for the scavenger thread to exit.
+ //
+
+ EnterCriticalSection( &NlGlobalScavengerCritSect );
+ NlStopScavenger();
+ LeaveCriticalSection( &NlGlobalScavengerCritSect );
+
+ DeleteCriticalSection( &NlGlobalScavengerCritSect );
+
+
+ //
+ // Need to cleanup replicator if only the thread started up successfully.
+ //
+
+ if( NlGlobalSSICritSectInit == TRUE ) {
+
+ //
+ // Wait for the replicator thread to exit.
+ //
+
+ EnterCriticalSection( &NlGlobalReplicatorCritSect );
+ NlStopReplicator();
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+
+ //
+ // Delete the Event used to ask the replicator to exit.
+ //
+
+ if( !CloseHandle( NlGlobalReplicatorTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "CloseHandle NlGlobalReplicatorTerminateEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+
+
+ //
+ // Free the server session table.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ while ( (ListEntry = NlGlobalServerSessionTable.Flink) !=
+ &NlGlobalServerSessionTable ) {
+
+ PSERVER_SESSION ServerSession;
+
+ ServerSession =
+ CONTAINING_RECORD(ListEntry, SERVER_SESSION, SsSeqList);
+
+ // Indicate we no longer need the server session anymore.
+ ServerSession->SsLmBdcAccountRid = 0;
+ ServerSession->SsNtBdcAccountRid = 0;
+
+ NlFreeServerSession( ServerSession );
+ }
+
+
+ NlAssert( IsListEmpty( &NlGlobalBdcServerSessionList ) );
+ NlAssert( IsListEmpty( &NlGlobalPendingBdcList ) );
+
+
+ if ( NlGlobalServerSessionHashTable != NULL ) {
+ NetpMemoryFree( NlGlobalServerSessionHashTable );
+ NlGlobalServerSessionHashTable = NULL;
+ }
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+
+
+ //
+ // Free the Trust List
+ //
+
+ LOCK_TRUST_LIST();
+
+ while ( (ListEntry = NlGlobalTrustList.Flink) != &NlGlobalTrustList ) {
+ PCLIENT_SESSION ClientSession;
+
+ ClientSession =
+ CONTAINING_RECORD(ListEntry, CLIENT_SESSION, CsNext );
+
+ NlAssert( ClientSession->CsReferenceCount == 0 );
+
+ NlFreeClientSession( ClientSession );
+ }
+
+ InitializeListHead( &NlGlobalTrustList );
+ NlGlobalTrustListLength = 0;
+
+ if ( NlGlobalTrustedDomainList != NULL ) {
+ NetpMemoryFree( NlGlobalTrustedDomainList );
+ NlGlobalTrustedDomainList = NULL;
+ NlGlobalTrustedDomainCount = 0;
+ NlGlobalTrustedDomainListKnown = FALSE;
+ NlGlobalTrustedDomainListTime.QuadPart = 0;
+ }
+
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // Free the misc SSI critical sections.
+ //
+
+ DeleteCriticalSection( &NlGlobalServerSessionTableCritSect );
+ NlGlobalSSICritSectInit = FALSE;
+
+ //
+ // Free the transport list
+ //
+
+ NlTransportClose();
+
+ }
+
+
+ //
+ // Free the Global Client Session structure.
+ //
+
+ if ( NlGlobalClientSession != NULL ) {
+ NlAssert( NlGlobalClientSession->CsReferenceCount == 1 );
+ NlUnrefClientSession( NlGlobalClientSession );
+ NlFreeClientSession( NlGlobalClientSession );
+ NlGlobalClientSession = NULL;
+ }
+
+ DeleteCriticalSection( &NlGlobalReplicatorCritSect );
+ DeleteCriticalSection( &NlGlobalTrustListCritSect );
+ DeleteCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+
+ //
+ // Free up resources
+ //
+
+ if ( NlGlobalAnsiComputerName != NULL ) {
+ NetpMemoryFree( NlGlobalAnsiComputerName );
+ NlGlobalAnsiComputerName = NULL;
+ }
+
+ if ( NlGlobalAnsiDomainName != NULL ) {
+ NetpMemoryFree( NlGlobalAnsiDomainName );
+ NlGlobalAnsiDomainName = NULL;
+ }
+
+ if ( NlGlobalPrimaryDomainId != NULL ) {
+ NetpMemoryFree( NlGlobalPrimaryDomainId );
+ NlGlobalPrimaryDomainId = NULL;
+ }
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+ if ( NlGlobalDBInfoArray[i].DBId != NULL ) {
+ NetpMemoryFree( NlGlobalDBInfoArray[i].DBId );
+ NlGlobalDBInfoArray[i].DBId = NULL;
+ }
+ }
+ DeleteCriticalSection( &NlGlobalDbInfoCritSect );
+
+ if ( NlGlobalNetlogonSecurityDescriptor != NULL ) {
+ NetpDeleteSecurityObject( &NlGlobalNetlogonSecurityDescriptor );
+ NlGlobalNetlogonSecurityDescriptor = NULL;
+ }
+
+ //
+ // Close the redo log if it's open
+ //
+
+ if ( NlGlobalRole == RoleBackup ) {
+ NlCloseChangeLogFile( &NlGlobalRedoLogDesc );
+ }
+
+ //
+ // delete well known SIDs if they are allocated already.
+ //
+
+ NetpFreeWellKnownSids();
+
+
+ //
+ // Close the Sam Handles
+ //
+
+ if ( NlGlobalSamServerHandle != NULL ) {
+ Status = SamrCloseHandle( &NlGlobalSamServerHandle);
+ NlAssert( NT_SUCCESS(Status) );
+ }
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+
+ if ( NlGlobalDBInfoArray[i].DBIndex == LSA_DB) {
+
+ //
+ // this handle is same as NlGlobalPolicyHandle, so
+ // don't close it here.
+ //
+
+ continue;
+ }
+
+ if ( NlGlobalDBInfoArray[i].DBHandle != NULL ) {
+
+ Status = SamrCloseHandle( &NlGlobalDBInfoArray[i].DBHandle );
+ NlAssert( NT_SUCCESS(Status) );
+
+ }
+
+ }
+
+
+ //
+ // Close the LsaHandles
+ //
+
+ if ( NlGlobalPolicyHandle != NULL ) {
+ Status = LsarClose( &NlGlobalPolicyHandle );
+ NlAssert( NT_SUCCESS(Status) );
+ }
+
+
+
+ //
+ // Close the browser
+ //
+
+ NlBrowserClose();
+
+
+ //
+ // Delete the timer event
+ //
+
+ if ( NlGlobalTimerEvent != NULL ) {
+ (VOID) CloseHandle( NlGlobalTimerEvent );
+ NlGlobalTimerEvent = NULL;
+ }
+
+
+ //
+ // Set the service state to uninstalled, and tell the service controller.
+ //
+
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_STOPPED;
+ NlGlobalServiceStatus.dwCheckPoint = 0;
+ NlGlobalServiceStatus.dwWaitHint = 0;
+
+ if( !SetServiceStatus( NlGlobalServiceHandle,
+ &NlGlobalServiceStatus ) ) {
+
+ NlPrint((NL_CRITICAL, "SetServiceStatus error: %lu\n",
+ GetLastError() ));
+ }
+
+ //
+ // Close service handle, we need not to close this handle.
+ //
+
+#ifdef notdef
+ // This service handle can not be closed
+ CloseServiceHandle( NlGlobalServiceHandle );
+#endif // notdef
+
+
+ //
+ // Close the handle to the debug file.
+ //
+
+#if DBG
+ EnterCriticalSection( &NlGlobalLogFileCritSect );
+ if ( NlGlobalLogFile != INVALID_HANDLE_VALUE ) {
+ CloseHandle( NlGlobalLogFile );
+ NlGlobalLogFile = INVALID_HANDLE_VALUE;
+ }
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+
+ if( NlGlobalDebugSharePath != NULL ) {
+ NetpMemoryFree( NlGlobalDebugSharePath );
+ NlGlobalDebugSharePath = NULL;
+ }
+#endif // DBG
+
+ //
+ // Delete the Event used to ask Netlogon to exit.
+ //
+
+ if( !CloseHandle( NlGlobalTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL,
+ "CloseHandle NlGlobalTerminateEvent error: %lu\n",
+ GetLastError() ));
+ }
+
+
+
+ //
+ // Return an exit status to our caller.
+ //
+ return (NET_API_STATUS)
+ ((NlGlobalServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR) ?
+ NlGlobalServiceStatus.dwServiceSpecificExitCode :
+ NlGlobalServiceStatus.dwWin32ExitCode);
+
+}
+
+
+
+
+VOID
+NlExit(
+ IN DWORD ServiceError,
+ IN DWORD Data,
+ IN NL_EXIT_CODE ExitCode,
+ IN LPWSTR ErrorString
+ )
+/*++
+
+Routine Description:
+
+ Registers service as uninstalled with error code.
+
+Arguments:
+
+ ServiceError - Service specific error code
+
+ Data - a DWORD of data to be logged with the message.
+ No data is logged if this is zero.
+
+ ExitCode - Indicates whether the message should be logged to the eventlog
+ and whether Data is a status code that should be appended to the bottom
+ of the message:
+
+ ErrorString - Error string, used to print it on debugger.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ IF_DEBUG( MISC ) {
+
+ NlPrint((NL_MISC, "NlExit: Netlogon exiting %lu 0x%lx",
+ ServiceError,
+ ServiceError ));
+
+ if ( Data ) {
+ NlPrint((NL_MISC, " Data: %lu 0x%lx", Data, Data ));
+ }
+
+ if( ErrorString != NULL ) {
+ NlPrint((NL_MISC, " '" FORMAT_LPWSTR "'", ErrorString ));
+ }
+
+ NlPrint(( NL_MISC, "\n"));
+
+ }
+
+ //
+ // Record our exit in the event log.
+ //
+
+ if ( ExitCode != DontLogError ) {
+ LPWSTR MsgStrings[2];
+ ULONG MessageCount = 0;
+
+ if ( ErrorString != NULL ) {
+ MsgStrings[MessageCount] = ErrorString;
+ MessageCount ++;
+ }
+
+ if ( ExitCode == LogErrorAndNtStatus ) {
+ MsgStrings[MessageCount] = (LPWSTR) Data;
+ MessageCount ++;
+ MessageCount |= LAST_MESSAGE_IS_NTSTATUS;
+ } else if ( ExitCode == LogErrorAndNetStatus ) {
+ MsgStrings[MessageCount] = (LPWSTR) Data;
+ MessageCount ++;
+ MessageCount |= LAST_MESSAGE_IS_NETSTATUS;
+ }
+
+
+ NlpWriteEventlog( ServiceError,
+ EVENTLOG_ERROR_TYPE,
+ (Data) ? (LPBYTE) &Data : NULL,
+ (Data) ? sizeof(Data) : 0,
+ (MessageCount != 0) ? MsgStrings : NULL,
+ MessageCount );
+ }
+
+ //
+ // Set the service state to stop pending.
+ //
+
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
+ NlGlobalServiceStatus.dwWaitHint = NETLOGON_INSTALL_WAIT;
+ NlGlobalServiceStatus.dwCheckPoint = 0;
+
+ SET_SERVICE_EXITCODE(
+ ServiceError,
+ NlGlobalServiceStatus.dwWin32ExitCode,
+ NlGlobalServiceStatus.dwServiceSpecificExitCode
+ );
+
+ //
+ // Tell the service controller what our state is.
+ //
+
+ if( !SetServiceStatus( NlGlobalServiceHandle,
+ &NlGlobalServiceStatus ) ) {
+
+ NlPrint((NL_CRITICAL, "SetServiceStatus error: %lu\n",
+ GetLastError() ));
+ }
+
+ //
+ // Indicate that all threads should exit.
+ //
+
+ NlGlobalTerminate = TRUE;
+
+ if ( !SetEvent( NlGlobalTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL, "Cannot set termination event: %lu\n",
+ GetLastError() ));
+ }
+
+}
+
+
+
+BOOL
+GiveInstallHints(
+ IN BOOL Started
+ )
+/*++
+
+Routine Description:
+
+ Give hints to the installer of the service that installation is progressing.
+
+Arguments:
+
+ Started -- Set true to tell the service controller that we're done starting.
+
+Return Value:
+
+ TRUE -- iff install hint was accepted.
+
+--*/
+{
+
+ //
+ // If we're not installing,
+ // we don't need this install hint.
+ //
+
+ if ( NlGlobalServiceStatus.dwCurrentState != SERVICE_START_PENDING ) {
+ return TRUE;
+ }
+
+
+ //
+ // If we've been asked to exit,
+ // return FALSE immediately asking the caller to exit.
+ //
+
+ if ( NlGlobalTerminate ) {
+ return FALSE;
+ }
+
+
+ //
+ // Tell the service controller our current state.
+ //
+
+ if ( Started ) {
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
+ NlGlobalServiceStatus.dwCheckPoint = 0;
+ NlGlobalServiceStatus.dwWaitHint = 0;
+ } else {
+ NlGlobalServiceStatus.dwCheckPoint++;
+ }
+
+ if( !SetServiceStatus( NlGlobalServiceHandle, &NlGlobalServiceStatus ) ) {
+ NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+
+VOID
+NlControlHandler(
+ IN DWORD opcode
+ )
+/*++
+
+Routine Description:
+
+ Process and respond to a control signal from the service controller.
+
+Arguments:
+
+ opcode - Supplies a value which specifies the action for the Netlogon
+ service to perform.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ NlPrint((NL_MISC, "In control handler (Opcode: %ld)\n", opcode ));
+
+ //
+ // Handle an uninstall request.
+ //
+
+ switch (opcode) {
+ case SERVICE_CONTROL_STOP: /* Uninstall required */
+
+ //
+ // Request the service to exit.
+ //
+ // NlExit also sets the service status to UNINSTALL_PENDING
+ // and tells the service controller.
+ //
+
+ NlExit( NERR_Success, 0, DontLogError, NULL);
+ return;
+
+ //
+ // Pause the service.
+ //
+
+ case SERVICE_CONTROL_PAUSE:
+
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_PAUSED;
+ break;
+
+ //
+ // Continute the service.
+ //
+
+ case SERVICE_CONTROL_CONTINUE:
+
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
+ break;
+
+ //
+ // By default, just return the current status.
+ //
+
+ case SERVICE_CONTROL_INTERROGATE:
+ default:
+ break;
+ }
+
+ //
+ // Always respond with the current status.
+ //
+
+ if( !SetServiceStatus( NlGlobalServiceHandle,
+ &NlGlobalServiceStatus ) ) {
+
+ NlPrint((NL_CRITICAL, "SetServiceStatus error: %lu\n",
+ GetLastError() ));
+ }
+
+ return;
+}
+
+
+VOID
+RaiseAlert(
+ IN DWORD alert_no,
+ IN LPWSTR *string_array
+ )
+/*++
+
+Routine Description:
+
+ Raise NETLOGON specific Admin alerts.
+
+Arguments:
+
+ alert_no - The alert to be raised, text in alertmsg.h
+
+ string_array - array of strings terminated by NULL string.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ LPWSTR *SArray;
+ PCHAR Next;
+ PCHAR End;
+
+ char message[ALERTSZ + sizeof(ADMIN_OTHER_INFO)];
+ PADMIN_OTHER_INFO admin = (PADMIN_OTHER_INFO) message;
+
+ IF_DEBUG( MISC ) {
+ DWORD i;
+
+ NlPrint((NL_CRITICAL,"Alert: %ld ", alert_no ));
+
+ for( SArray = string_array, i = 0; *SArray != NULL; SArray++, i++ ) {
+ NlPrint((NL_CRITICAL,"\"" FORMAT_LPWSTR "\" ", *SArray ));
+ }
+
+ NlPrint((NL_CRITICAL,"\n" ));
+ }
+
+ //
+ // Build the variable data
+ //
+ admin->alrtad_errcode = alert_no;
+ admin->alrtad_numstrings = 0;
+
+ Next = (PCHAR) ALERT_VAR_DATA(admin);
+ End = Next + ALERTSZ;
+
+ //
+ // now take care of (optional) char strings
+ //
+
+ for( SArray = string_array; *SArray != NULL; SArray++ ) {
+ DWORD StringLen;
+
+ StringLen = (wcslen(*SArray) + 1) * sizeof(WCHAR);
+
+ if( Next + StringLen < End ) {
+
+ //
+ // copy next string.
+ //
+
+ RtlCopyMemory(Next, *SArray, StringLen);
+ Next += StringLen;
+ admin->alrtad_numstrings++;
+ }
+ else {
+
+ NlPrint((NL_CRITICAL,"Error raising alert, Can't fit all "
+ "message strings in the alert buffer \n" ));
+
+ return;
+ }
+ }
+
+ //
+ // Call alerter.
+ //
+
+ NetStatus = NetAlertRaiseEx(
+ ALERT_ADMIN_EVENT,
+ message,
+ (DWORD)((PCHAR)Next - (PCHAR)message),
+ SERVICE_NETLOGON );
+
+ if ( NetStatus != NERR_Success ) {
+ NlPrint((NL_CRITICAL,"Error raising alert %lu\n", NetStatus));
+ }
+
+ return;
+}
diff --git a/private/net/svcdlls/logonsrv/server/iniparm.h b/private/net/svcdlls/logonsrv/server/iniparm.h
new file mode 100644
index 000000000..22e197d45
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/iniparm.h
@@ -0,0 +1,325 @@
+/*++
+
+Copyright (c) 1987-92 Microsoft Corporation
+
+Module Name:
+
+ iniparm.h
+
+Abstract:
+
+ Initiail values of startup parameters.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Revision History:
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+ 07-May-1992 JohnRo
+ Use net config helpers for NetLogon.
+
+--*/
+
+
+#ifndef _INIPARM_
+#define _INIPARM_
+
+//
+// Pulse period (in seconds):
+//
+// Defines the typical pulse frequency. All SAM/LSA changes made within this
+// time are collected together. After this time, a pulse is sent to each BDC
+// needing the changes. No pulse is sent to a BDC that is up to date.
+//
+#define DEFAULT_PULSE (5*60) // 5 mins
+#define MAX_PULSE (48*60*60) // 2 days
+#define MIN_PULSE 60 // 1 min
+
+//
+// Pulse concurrency (in number of concurrent mailslot messages).
+//
+// Netlogon sends pulses to individual BDCs. The BDCs respond asking for any
+// database changes. To control the maximum load these responses place on the
+// PDC, the PDC will only have this many pulses "pending" at once. The PDC
+// should be sufficiently powerful to support this many concurrent replication
+// RPC calls.
+//
+// Increasing this number increases the load on the PDC.
+// Decreasing this number increases the time it takes for a domain with a
+// large number of BDC to get a SAM/LSA change.
+
+#define DEFAULT_PULSECONCURRENCY 10
+#define MAX_PULSECONCURRENCY 500
+#define MIN_PULSECONCURRENCY 1
+
+//
+// Maximum pulse period (in seconds):
+//
+// Defines the maximum pulse frequency. Every BDC will be sent at least one
+// pulse at this frequency regardless of whether its database is up to date.
+//
+
+#define DEFAULT_PULSEMAXIMUM (2*60*60) // 2 hours
+#define MAX_PULSEMAXIMUM (48*60*60) // 2 days
+#define MIN_PULSEMAXIMUM 60 // 1 min
+
+//
+// Pulse timeout period (in seconds):
+//
+// When a BDC is sent a pulse, it must respond within this time period. If
+// not, the BDC is considered to be non-responsive. A non-responsive BDC is
+// not counted against the "Pulse Concurrency" limit allowing the PDC to
+// send a pulse to another BDC in the domain.
+//
+// If this number is too large, a domain with a large number of non-responsive
+// BDCs will take a long time to complete a partial replication.
+//
+// If this number is too small, a slow BDC may be falsely accused of being
+// non-responsive. When the BDC finally does respond, it will partial
+// replicate from the PDC unduly increasing the load on the PDC.
+//
+#define DEFAULT_PULSETIMEOUT1 10 // 10 seconds
+#define MAX_PULSETIMEOUT1 (2*60) // 2 min
+#define MIN_PULSETIMEOUT1 1 // 1 second
+
+//
+// Maximum Partial replication timeout (in seconds):
+//
+// Even though a BDC initially responds to a pulse (as described for
+// PULSETIMEOUT1), it must continue making replication progress or the
+// BDC will be considered non-responsive. Each time the BDC calls the PDC,
+// the BDC is given another PULSETIMEOUT2 seconds to be considered responsive.
+//
+// If this number is too large, a slow BDC (or one which has its replication
+// rate artificially governed) will consume one of the PULSECONCURRENCY slots.
+//
+// If this number is too small, the load on the PDC will be unduly increased
+// because of the large number of BDC doing a partial sync.
+//
+// NOTE: This parameter only affect the cases where a BDC cannot retrieve all the
+// changes to the SAM/LSA database in a single RPC call. This will only
+// happen if a large number of changes are made to the database.
+
+#define DEFAULT_PULSETIMEOUT2 (5*60) // 5 minutes
+#define MAX_PULSETIMEOUT2 (1*60*60) // 1 hour
+#define MIN_PULSETIMEOUT2 (1*60) // 1 minute
+
+//
+// BDC random backoff (in seconds):
+//
+// When the BDC receives a pulse, it will back off between zero and RANDOMIZE
+// seconds before calling the PDC. In Lanman and NT 1.0, the pulse was
+// broadcast to all BDCs simultaneously and the BDCs used this mechanism to
+// ensure they didn't overload the PDC. As of NT 1.0A, the pulse is sent
+// to individual BDCs so this parameter should be minimized.
+//
+// This parameter should be smaller than PULSETIMEOUT1.
+//
+// Consider that the time to replicate a SAM/LSA change to all the BDCs in a
+// domain will be greater than:
+//
+// ((RANDOMIZE/2) * NumberOfBdcsInDomain) / PULSECONCURRENCY
+//
+#define DEFAULT_RANDOMIZE 1 // 1 secs
+#define MAX_RANDOMIZE 120 // 2 mins
+#define MIN_RANDOMIZE 0 // 0 secs
+
+//
+// BDC Replication Governor (in percent)
+//
+// If the BDC is connected to the PDC via a slow WAN link, the amount of
+// replication load on the WAN link can be adjusted. Lowering this percentage
+// reduces both the size of the data transferred on each call to the PDC and
+// frequency of those calls. For instance, setting ReplicationGovernor to 50%
+// will use a 64Kb buffer rather than a 128Kb buffer and will only have a
+// replication call outstanding on the net a maximum of 50% of the time.
+//
+// Don't be tempted to set the ReplicationGovernor too low. Otherwise,
+// replication may never complete.
+//
+// A value of 0 will cause netlogon to NEVER replicate. The SAM/LSA database
+// will be allowed to get completely out of sync.
+//
+// This parameter must be set individually on each BDC.
+//
+
+#define DEFAULT_GOVERNOR 100
+#define MAX_GOVERNOR 100
+#define MIN_GOVERNOR 0
+
+//
+// ChangeLogSize (in bytes)
+//
+// This is the size of the Change Log file. Each change to the SAM/LSA database
+// is represented by an entry in the change log. The changelog is maintained
+// as a circular buffer with the oldest entry being overwritten by the newest
+// entry. If a BDC does a partial sync and requests an entry that has been
+// overwritten, the BDC is forced to do a full sync.
+//
+// The minimum (and typical) size of an entry is 32 bytes. Some entries are
+// larger. (e.g., a 64K changelog holds about 2000 changes)
+//
+// This parameter need only be set larger if:
+//
+// a) full syncs are prohibitively expensive, AND
+// b) one or more BDCs are expected to not request a partial sync within 2000
+// changes.
+//
+// For instance, if a BDC dials in nightly to do a partial sync and on some
+// days 4000 changes are made to the SAM/LSA database, this parameter should
+// be set to 128K.
+//
+// This parameter need only be set on the PDC. If a different PDC is promoted,
+// it should be set on that PDC also.
+//
+
+#define DEFAULT_CHANGELOGSIZE (64*1024)
+#define MAX_CHANGELOGSIZE (4*1024*1024)
+#define MIN_CHANGELOGSIZE (64*1024)
+
+//
+// MaximumMailslotMessages (in number of messages)
+//
+// This parameter determines the maximum number of mailslot messages that will
+// be queued to the netlogon service. Even though the Netlogon service is
+// designed to process incoming mailslot messages immediately, the netlogon
+// service can get backed up processing requests.
+//
+// Each mailslot message consumes about 1500 bytes of non-paged pool until it
+// is process. By setting this parameter low, you can govern the maximum
+// amount of non-paged pool that can be consumed.
+//
+// If you set this parameter too low, netlogon may miss important incoming
+// mailslot messages.
+//
+
+#define DEFAULT_MAXIMUMMAILSLOTMESSAGES 500
+#define MAX_MAXIMUMMAILSLOTMESSAGES 0xFFFFFFFF
+#define MIN_MAXIMUMMAILSLOTMESSAGES 1
+
+//
+// MailslotMessageTimeout (in seconds)
+//
+// This parameter specifies the maximum acceptable age of an incoming
+// mailslot message. If netlogon receives a mailslot messages that arrived
+// longer ago than this, it will ignore the message. This allows netlogon
+// to process messages that are more recent. The theory is that the client
+// that originally sent the older mailslot message is no longer waiting for
+// the response so we shouldn't bother sending a response.
+//
+// If you set this parameter too low, netlogon will ignore important incoming
+// mailslot messages.
+//
+// Ideally, netlogon processes each mailslot message in a fraction of a second.
+// This parameter is only significant if the NTAS server is overloaded.
+//
+
+#define DEFAULT_MAILSLOTMESSAGETIMEOUT 10
+#define MAX_MAILSLOTMESSAGETIMEOUT 0xFFFFFFFF
+#define MIN_MAILSLOTMESSAGETIMEOUT 5
+
+//
+// MailslotDuplicateTimeout (in seconds)
+//
+// This parameter specifies the interval over which duplicate incoming
+// mailslot messages will be ignored. Netlogon compares each mailslot
+// message received with the previous mailslot message received. If the
+// previous message was received within this many seconds and the messages
+// are identical, this message will be ignored. The theory is that the
+// duplicate messages are caused by clients sending on multiple transports and
+// that netlogon needs to only reply on one of those transports saving network
+// bandwidth.
+//
+// Set this parameter to zero to disable this feature. You should disable this
+// feature if your network is configured such that this machine can see
+// certain incoming mailslot messages but can't respond to them. For instance,
+// a PDC may be separated from an NT workstation by a bridge/router.
+// The bridge/router might filter outgoing NBF broadcasts, but allow incoming
+// one. As such, netlogon might respond to an NBF mailslot message (only to
+// be filtered out by the bridge/router) and not respond to a subsequent NBT
+// mailslot message. Disabling this feature (or preferably reconfiguring the
+// bridge/router) solves this problem.
+//
+// If you set this parameter too high, netlogon will ignore retry attempts
+// from a client.
+//
+
+#define DEFAULT_MAILSLOTDUPLICATETIMEOUT 2
+#define MAX_MAILSLOTDUPLICATETIMEOUT 5
+#define MIN_MAILSLOTDUPLICATETIMEOUT 0
+
+//
+// ExpectedDialupDelay (in seconds)
+//
+// This parameter specifies the time it takes for a dialup router to dial when
+// sending a message from this client machine to a domain trusted by this client
+// machine. Typically, netlogon assumes a domain controller is reachable in a
+// short (e.g., 15 seconds) time period. Setting ExpectedDialupDelay informs
+// Netlogon to expect an ADDITIONAL delay of the time specified.
+//
+// Currently, netlogon adjusts the following two times based on the
+// ExpectedDialupDelay:
+//
+// 1) When discovering a DC in a trusted domain, Netlogon sends a 3 mailslot
+// messages to the trusted domain at ( 5 + ExpectedDialupDelay/3 ) second
+// intervals Synchronous discoveries will not be timed out for 3 times that
+// interval.
+// 2) An API call over a secure channel to a discovered DC will timeout only
+// after (45 + ExpectedDialupDelay) seconds.
+//
+// This parameter should remain zero unless a dialup router exists between this
+// machine and its trusted domain.
+//
+// If this parameter is set too high, legitimate cases where no DC is available in
+// a trusted domain will take an extraordinary amount of time to detect.
+//
+
+
+#define DEFAULT_EXPECTEDDIALUPDELAY 0
+#define MAX_EXPECTEDDIALUPDELAY (10*60) // 10 minutes
+#define MIN_EXPECTEDDIALUPDELAY 0
+
+//
+// ScavengeInterval (in seconds)
+//
+// This parameter adjusts the interval at which netlogon performs the following
+// scavenging operations:
+//
+// * Checks to see if a password on a secure channel needs to be changed.
+//
+// * Checks to see if a secure channel has been idle for a long time.
+//
+// * On DCs, sends a mailslot message to each trusted domain for a DC hasn't been
+// discovered.
+//
+// * On PDC, attempts to add the <DomainName>[1B] netbios name if it hasn't
+// already been successfully added.
+//
+// None of these operations are critical. 15 minutes is optimal in all but extreme
+// cases. For instance, if a DC is separated from a trusted domain by an
+// expensive (e.g., ISDN) line, this parameter might be adjusted upward to avoid
+// frequent automatic discovery of DCs in a trusted domain.
+//
+
+#define DEFAULT_SCAVENGEINTERVAL (15*60) // 15 minutes
+#define MAX_SCAVENGEINTERVAL (48*60*60) // 2 days
+#define MIN_SCAVENGEINTERVAL 60 // 1 minute
+
+
+//
+// How frequently we scavenge the LogonTable.
+//
+#define LOGON_INTERROGATE_PERIOD (15*60*1000) // make it 15 mins
+
+
+#define DEFAULT_SYNCHRONIZE FALSE
+
+#define DEFAULT_DISABLE_PASSWORD_CHANGE 0
+#define DEFAULT_REFUSE_PASSWORD_CHANGE 0
+
+#define DEFAULT_SCRIPTS TEXT("REPL\\IMPORT\\SCRIPTS")
+
+#endif // _INIPARM_
diff --git a/private/net/svcdlls/logonsrv/server/logonapi.c b/private/net/svcdlls/logonsrv/server/logonapi.c
new file mode 100644
index 000000000..bcc31ada6
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/logonapi.c
@@ -0,0 +1,3426 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ logonapi.c
+
+Abstract:
+
+ Remote Logon API routines.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 28-Jun-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ Madana - Fixed several bugs.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+
+#include <accessp.h> // Routines shared with NetUser Apis
+#include <align.h> // ROUND_UP_COUNT ...
+#include <lmaudit.h> // AE_*
+#include <lmerr.h>
+#include <nlsecure.h> // Security Descriptor for APIs
+#include <secobj.h> // NetpAccessCheck
+#include <stddef.h> // offsetof()
+#include <rpcutil.h> // NetpRpcStatusToApiStatus()
+#include <align.h> // ROUND_UP_COUTN ...
+
+
+NET_API_STATUS
+NlEnsureClientIsNamedUser(
+ IN LPWSTR UserName
+ )
+/*++
+
+Routine Description:
+
+ Ensure the client is the named user.
+
+Arguments:
+
+ UserName - name of the user to check.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ RPC_STATUS RpcStatus;
+ NTSTATUS Status;
+ HANDLE TokenHandle = NULL;
+ PTOKEN_USER TokenUserInfo = NULL;
+ ULONG TokenUserInfoSize;
+ ULONG UserId;
+ PSID UserSid;
+ SAMPR_HANDLE UserHandle = NULL;
+
+ //
+ // Get the relative ID of the specified user.
+ //
+
+ Status = NlSamOpenNamedUser( UserName, &UserHandle, &UserId );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: NlSamOpenNamedUser failed 0x%lx\n",
+ UserName,
+ Status ));
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+
+ //
+ // Impersonate the client while we check him out.
+ //
+
+ RpcStatus = RpcImpersonateClient( NULL );
+
+ if ( RpcStatus != RPC_S_OK ) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: RpcImpersonateClient failed 0x%lx\n",
+ UserName,
+ RpcStatus ));
+ NetStatus = NetpRpcStatusToApiStatus( RpcStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Compare the username specified with that in
+ // the impersonation token to ensure the caller isn't bogus.
+ //
+ // Do this by opening the token,
+ // querying the token user info,
+ // and ensuring the returned SID is for this user.
+ //
+
+ Status = NtOpenThreadToken(
+ NtCurrentThread(),
+ TOKEN_QUERY,
+ (BOOLEAN) TRUE, // Use the logon service's security context
+ // to open the token
+ &TokenHandle );
+
+ if ( !NT_SUCCESS( Status )) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: NtOpenThreadToken failed 0x%lx\n",
+ UserName,
+ Status ));
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ //
+ // Get the user's SID for the token.
+ //
+
+ Status = NtQueryInformationToken(
+ TokenHandle,
+ TokenUser,
+ &TokenUserInfo,
+ 0,
+ &TokenUserInfoSize );
+
+ if ( Status != STATUS_BUFFER_TOO_SMALL ) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: NtOpenQueryInformationThread failed 0x%lx\n",
+ UserName,
+ Status ));
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ TokenUserInfo = NetpMemoryAllocate( TokenUserInfoSize );
+
+ if ( TokenUserInfo == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+ Status = NtQueryInformationToken(
+ TokenHandle,
+ TokenUser,
+ TokenUserInfo,
+ TokenUserInfoSize,
+ &TokenUserInfoSize );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: NtOpenQueryInformationThread (again) failed 0x%lx\n",
+ UserName,
+ Status ));
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ UserSid = TokenUserInfo->User.Sid;
+
+
+ //
+ // Ensure the last subauthority matches the UserId
+ //
+
+ if ( UserId !=
+ *RtlSubAuthoritySid( UserSid, (*RtlSubAuthorityCountSid(UserSid))-1 )){
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: UserId mismatch 0x%lx\n",
+ UserName,
+ UserId ));
+
+ NlpDumpSid( NL_CRITICAL, UserSid );
+
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Convert the User's sid to a DomainId and ensure it is our domain Id.
+ //
+
+ (*RtlSubAuthorityCountSid(UserSid)) --;
+ if ( !RtlEqualSid( (PSID) NlGlobalDBInfoArray[SAM_DB].DBId, UserSid ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlEnsureClientIsNamedUser: %ws: DomainId mismatch 0x%lx\n",
+ UserName,
+ UserId ));
+
+ NlpDumpSid( NL_CRITICAL, UserSid );
+ NlpDumpSid( NL_CRITICAL, (PSID) NlGlobalDBInfoArray[SAM_DB].DBId );
+
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Done
+ //
+
+ NetStatus = NERR_Success;
+Cleanup:
+
+ //
+ // Clean up locally used resources.
+ //
+
+ if ( TokenHandle != NULL ) {
+ (VOID) NtClose( TokenHandle );
+ }
+
+ if ( TokenUserInfo != NULL ) {
+ NetpMemoryFree( TokenUserInfo );
+ }
+
+ //
+ // revert to system, so that we can close
+ // the user handle properly.
+ //
+
+ (VOID) RpcRevertToSelf();
+
+ if ( UserHandle != NULL ) {
+ SamrCloseHandle( &UserHandle );
+ }
+
+ return NetStatus;
+}
+
+
+NET_API_STATUS
+NetrLogonUasLogon (
+ IN LPWSTR ServerName,
+ IN LPWSTR UserName,
+ IN LPWSTR Workstation,
+ OUT PNETLOGON_VALIDATION_UAS_INFO *ValidationInformation
+)
+/*++
+
+Routine Description:
+
+ Server side of I_NetLogonUasLogon.
+
+ This function is called by the XACT server when processing a
+ I_NetWkstaUserLogon XACT SMB. This feature allows a UAS client to
+ logon to a SAM domain controller.
+
+Arguments:
+
+ ServerName -- Server to perform this operation on. Must be NULL.
+
+ UserName -- Account name of the user logging on.
+
+ Workstation -- The workstation from which the user is logging on.
+
+ ValidationInformation -- Returns the requested validation
+ information.
+
+
+Return Value:
+
+ NERR_SUCCESS if there was no error. Otherwise, the error code is
+ returned.
+
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+
+ NETLOGON_INTERACTIVE_INFO LogonInteractive;
+ PNETLOGON_VALIDATION_SAM_INFO SamInfo = NULL;
+
+
+ PNETLOGON_VALIDATION_UAS_INFO usrlog1 = NULL;
+ DWORD ValidationSize;
+ LPWSTR EndOfVariableData;
+ BOOLEAN Authoritative;
+ BOOLEAN BadPasswordCountZeroed;
+
+ LARGE_INTEGER TempTime;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return ERROR_NOT_SUPPORTED;
+ }
+
+
+ //
+ // This API can only be called locally. (By the XACT server).
+ //
+
+ if ( ServerName != NULL ) {
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ //
+ // Initialization
+ //
+
+ *ValidationInformation = NULL;
+
+
+ //
+ // Perform access validation on the caller.
+ //
+
+ NetStatus = NetpAccessCheck(
+ NlGlobalNetlogonSecurityDescriptor, // Security descriptor
+ NETLOGON_UAS_LOGON_ACCESS, // Desired access
+ &NlGlobalNetlogonInfoMapping ); // Generic mapping
+
+ if ( NetStatus != NERR_Success) {
+
+ NlPrint((NL_CRITICAL,"NetrLogonUasLogon of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " failed NetpAccessCheck\n",
+ UserName, Workstation));
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Ensure the client is actually the named user.
+ //
+ // The server has already validated the password.
+ // The XACT server has already verified that the workstation name is
+ // correct.
+ //
+
+ NetStatus = NlEnsureClientIsNamedUser( UserName );
+
+ if ( NetStatus != NERR_Success ) {
+ NlPrint((NL_CRITICAL,"NetrLogonUasLogon of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " failed NlEnsureClientIsNamedUser\n",
+ UserName, Workstation));
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+ //
+ // Validate the user against the local SAM database.
+ //
+
+ RtlInitUnicodeString( &LogonInteractive.Identity.LogonDomainName, NULL );
+ LogonInteractive.Identity.ParameterControl = 0;
+ RtlZeroMemory( &LogonInteractive.Identity.LogonId,
+ sizeof(LogonInteractive.Identity.LogonId) );
+ RtlInitUnicodeString( &LogonInteractive.Identity.UserName, UserName );
+ RtlInitUnicodeString( &LogonInteractive.Identity.Workstation, Workstation );
+
+ Status = MsvSamValidate( NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ NlGlobalUasCompatibilityMode,
+ NullSecureChannel, // Skip password check
+ &NlGlobalUnicodeComputerNameString,
+ &NlGlobalAccountDomainName,
+ NlGlobalDBInfoArray[SAM_DB].DBId,
+ NetlogonInteractiveInformation,
+ &LogonInteractive,
+ NetlogonValidationSamInfo,
+ (PVOID *)&SamInfo,
+ &Authoritative,
+ &BadPasswordCountZeroed,
+ MSVSAM_SPECIFIED );
+
+ if ( !NT_SUCCESS( Status )) {
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+
+ //
+ // Allocate a return buffer
+ //
+
+ ValidationSize = sizeof( NETLOGON_VALIDATION_UAS_INFO ) +
+ SamInfo->EffectiveName.Length + sizeof(WCHAR) +
+ (wcslen( NlGlobalUncUnicodeComputerName ) +1) * sizeof(WCHAR) +
+ NlGlobalAccountDomainName.Length + sizeof(WCHAR) +
+ SamInfo->LogonScript.Length + sizeof(WCHAR);
+
+ ValidationSize = ROUND_UP_COUNT( ValidationSize, ALIGN_WCHAR );
+
+ usrlog1 = MIDL_user_allocate( ValidationSize );
+
+ if ( usrlog1 == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // Convert the SAM information to the right format for LM 2.0
+ //
+
+ EndOfVariableData = (LPWSTR) (((PCHAR)usrlog1) + ValidationSize);
+
+ if ( !NetpCopyStringToBuffer(
+ SamInfo->EffectiveName.Buffer,
+ SamInfo->EffectiveName.Length / sizeof(WCHAR),
+ (LPBYTE) (usrlog1 + 1),
+ &EndOfVariableData,
+ &usrlog1->usrlog1_eff_name ) ) {
+
+ NetStatus = NERR_InternalError ;
+ goto Cleanup;
+ }
+
+ Status = NlGetUserPriv(
+ SamInfo->GroupCount,
+ (PGROUP_MEMBERSHIP) SamInfo->GroupIds,
+ SamInfo->UserId,
+ &usrlog1->usrlog1_priv,
+ &usrlog1->usrlog1_auth_flags );
+
+ if ( !NT_SUCCESS( Status )) {
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ usrlog1->usrlog1_num_logons = 0;
+ usrlog1->usrlog1_bad_pw_count = SamInfo->BadPasswordCount;
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->LogonTime, TempTime);
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_last_logon) ) {
+ usrlog1->usrlog1_last_logon = 0;
+ }
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->LogoffTime, TempTime);
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_last_logoff) ) {
+ usrlog1->usrlog1_last_logoff = TIMEQ_FOREVER;
+ }
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->KickOffTime, TempTime);
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_logoff_time) ) {
+ usrlog1->usrlog1_logoff_time = TIMEQ_FOREVER;
+ }
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_kickoff_time) ) {
+ usrlog1->usrlog1_kickoff_time = TIMEQ_FOREVER;
+ }
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordLastSet, TempTime);
+
+ usrlog1->usrlog1_password_age =
+ NetpGetElapsedSeconds( &TempTime );
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordCanChange, TempTime);
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_pw_can_change) ) {
+ usrlog1->usrlog1_pw_can_change = TIMEQ_FOREVER;
+ }
+
+ OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordMustChange, TempTime);
+
+ if ( !RtlTimeToSecondsSince1970( &TempTime,
+ &usrlog1->usrlog1_pw_must_change) ) {
+ usrlog1->usrlog1_pw_must_change = TIMEQ_FOREVER;
+ }
+
+
+ usrlog1->usrlog1_computer = NlGlobalUncUnicodeComputerName;
+ if ( !NetpPackString(
+ &usrlog1->usrlog1_computer,
+ (LPBYTE) (usrlog1 + 1),
+ &EndOfVariableData )) {
+
+ NetStatus = NERR_InternalError ;
+ goto Cleanup;
+ }
+
+ if ( !NetpCopyStringToBuffer(
+ NlGlobalAccountDomainName.Buffer,
+ NlGlobalAccountDomainName.Length / sizeof(WCHAR),
+ (LPBYTE) (usrlog1 + 1),
+ &EndOfVariableData,
+ &usrlog1->usrlog1_domain ) ) {
+
+ NetStatus = NERR_InternalError ;
+ goto Cleanup;
+ }
+
+ if ( !NetpCopyStringToBuffer(
+ SamInfo->LogonScript.Buffer,
+ SamInfo->LogonScript.Length / sizeof(WCHAR),
+ (LPBYTE) (usrlog1 + 1),
+ &EndOfVariableData,
+ &usrlog1->usrlog1_script_path ) ) {
+
+ NetStatus = NERR_InternalError ;
+ goto Cleanup;
+ }
+
+ NetStatus = NERR_Success;
+
+ //
+ // Done
+ //
+
+Cleanup:
+
+ //
+ // Clean up locally used resources.
+ //
+
+ if ( SamInfo != NULL ) {
+ MIDL_user_free( SamInfo );
+ }
+
+ if ( NetStatus != NERR_Success ) {
+ if ( usrlog1 != NULL ) {
+ MIDL_user_free( usrlog1 );
+ usrlog1 = NULL;
+ }
+ }
+
+ NlPrint((NL_LOGON,"NetrLogonUasLogon of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " returns %lu\n",
+ UserName, Workstation, NetStatus ));
+
+ *ValidationInformation = usrlog1;
+
+ return(NetStatus);
+}
+
+
+NET_API_STATUS
+NetrLogonUasLogoff (
+ IN LPWSTR ServerName OPTIONAL,
+ IN LPWSTR UserName,
+ IN LPWSTR Workstation,
+ OUT PNETLOGON_LOGOFF_UAS_INFO LogoffInformation
+)
+/*++
+
+Routine Description:
+
+ This function is called by the XACT server when processing a
+ I_NetWkstaUserLogoff XACT SMB. This feature allows a UAS client to
+ logoff from a SAM domain controller. The request is authenticated,
+ the entry is removed for this user from the logon session table
+ maintained by the Netlogon service for NetLogonEnum, and logoff
+ information is returned to the caller.
+
+ The server portion of I_NetLogonUasLogoff (in the Netlogon service)
+ compares the user name and workstation name specified in the
+ LogonInformation with the user name and workstation name from the
+ impersonation token. If they don't match, I_NetLogonUasLogoff fails
+ indicating the access is denied.
+
+ Group SECURITY_LOCAL is refused access to this function. Membership
+ in SECURITY_LOCAL implies that this call was made locally and not
+ through the XACT server.
+
+ The Netlogon service cannot be sure that this function was called by
+ the XACT server. Therefore, the Netlogon service will not simply
+ delete the entry from the logon session table. Rather, the logon
+ session table entry will be marked invisible outside of the Netlogon
+ service (i.e., it will not be returned by NetLogonEnum) until a valid
+ LOGON_WKSTINFO_RESPONSE is received for the entry. The Netlogon
+ service will immediately interrogate the client (as described above
+ for LOGON_WKSTINFO_RESPONSE) and temporarily increase the
+ interrogation frequency to at least once a minute. The logon session
+ table entry will reappear as soon as a function of interrogation if
+ this isn't a true logoff request.
+
+Arguments:
+
+ ServerName -- Reserved. Must be NULL.
+
+ UserName -- Account name of the user logging off.
+
+ Workstation -- The workstation from which the user is logging
+ off.
+
+ LogoffInformation -- Returns the requested logoff information.
+
+Return Value:
+
+ The Net status code.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+
+ NETLOGON_INTERACTIVE_INFO LogonInteractive;
+
+ PNETLOGON_LOGOFF_UAS_INFO usrlog1 = NULL;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return ERROR_NOT_SUPPORTED;
+ }
+
+
+ //
+ // This API can only be called locally. (By the XACT server).
+ //
+
+ if ( ServerName != NULL ) {
+ return ERROR_INVALID_PARAMETER;
+ }
+
+
+
+ //
+ // Perform access validation on the caller.
+ //
+
+ NetStatus = NetpAccessCheck(
+ NlGlobalNetlogonSecurityDescriptor, // Security descriptor
+ NETLOGON_UAS_LOGOFF_ACCESS, // Desired access
+ &NlGlobalNetlogonInfoMapping ); // Generic mapping
+
+ if ( NetStatus != NERR_Success) {
+ NlPrint((NL_CRITICAL,"NetrLogonUasLogoff of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " failed NetpAccessCheck\n",
+ UserName, Workstation));
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Ensure the client is actually the named user.
+ //
+ // The server has already validated the password.
+ // The XACT server has already verified that the workstation name is
+ // correct.
+ //
+
+#ifdef notdef // Some clients (WFW 3.11) can call this over the null session
+ NetStatus = NlEnsureClientIsNamedUser( UserName );
+
+ if ( NetStatus != NERR_Success ) {
+ NlPrint((NL_CRITICAL,"NetrLogonUasLogoff of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " failed NlEnsureClientIsNamedUser\n",
+ UserName, Workstation));
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+#endif // notdef
+
+
+ //
+ // Build the LogonInformation to return
+ //
+
+ LogoffInformation->Duration = 0;
+ LogoffInformation->LogonCount = 0;
+
+
+ //
+ // Update the LastLogoff time in the SAM database.
+ //
+
+ RtlInitUnicodeString( &LogonInteractive.Identity.LogonDomainName, NULL );
+ LogonInteractive.Identity.ParameterControl = 0;
+ RtlZeroMemory( &LogonInteractive.Identity.LogonId,
+ sizeof(LogonInteractive.Identity.LogonId) );
+ RtlInitUnicodeString( &LogonInteractive.Identity.UserName, UserName );
+ RtlInitUnicodeString( &LogonInteractive.Identity.Workstation, Workstation );
+
+ Status = MsvSamLogoff(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ NetlogonInteractiveInformation,
+ &LogonInteractive );
+
+ if (!NT_SUCCESS(Status)) {
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ //
+ // Cleanup
+ //
+
+Cleanup:
+
+ //
+ // Clean up locally used resources.
+ //
+
+ NlPrint((NL_LOGON,"NetrLogonUasLogoff of " FORMAT_LPWSTR " from "
+ FORMAT_LPWSTR " returns %lu\n",
+ UserName, Workstation, NetStatus));
+ return NetStatus;
+}
+
+
+VOID
+NlpDecryptLogonInformation (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN OUT LPBYTE LogonInformation,
+ IN PSESSION_INFO SessionInfo
+)
+/*++
+
+Routine Description:
+
+ This function decrypts the sensitive information in the LogonInformation
+ structure. The decryption is done in place.
+
+Arguments:
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ SessionInfo -- The session key to encrypt with and negotiate flags
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // Only the interactive and service logon information is encrypted.
+ //
+
+ if ( LogonLevel == NetlogonInteractiveInformation ||
+ LogonLevel == NetlogonServiceInformation ) {
+
+ PNETLOGON_INTERACTIVE_INFO LogonInteractive;
+
+ LogonInteractive =
+ (PNETLOGON_INTERACTIVE_INFO) LogonInformation;
+
+
+ //
+ // If both sides support RC4 encryption,
+ // decrypt both the LM OWF and NT OWF passwords using RC4.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ NlDecryptRC4( &LogonInteractive->LmOwfPassword,
+ sizeof(LogonInteractive->LmOwfPassword),
+ SessionInfo );
+
+ NlDecryptRC4( &LogonInteractive->NtOwfPassword,
+ sizeof(LogonInteractive->NtOwfPassword),
+ SessionInfo );
+
+
+ //
+ // If the other side is running NT 1.0,
+ // use the slower DES based encryption.
+ //
+
+ } else {
+
+ NTSTATUS Status;
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+
+ //
+ // Decrypt the LM_OWF password.
+ //
+
+ NlAssert( ENCRYPTED_LM_OWF_PASSWORD_LENGTH ==
+ LM_OWF_PASSWORD_LENGTH );
+ NlAssert(LM_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
+ EncryptedLmOwfPassword =
+ * ((PENCRYPTED_LM_OWF_PASSWORD) &LogonInteractive->LmOwfPassword);
+
+ Status = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ &EncryptedLmOwfPassword,
+ (PLM_OWF_PASSWORD) &SessionInfo->SessionKey,
+ &LogonInteractive->LmOwfPassword );
+ NlAssert( NT_SUCCESS(Status) );
+
+ //
+ // Decrypt the NT_OWF password.
+ //
+
+ NlAssert( ENCRYPTED_NT_OWF_PASSWORD_LENGTH ==
+ NT_OWF_PASSWORD_LENGTH );
+ NlAssert(NT_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
+ EncryptedNtOwfPassword =
+ * ((PENCRYPTED_NT_OWF_PASSWORD) &LogonInteractive->NtOwfPassword);
+
+ Status = RtlDecryptNtOwfPwdWithNtOwfPwd(
+ &EncryptedNtOwfPassword,
+ (PNT_OWF_PASSWORD) &SessionInfo->SessionKey,
+ &LogonInteractive->NtOwfPassword );
+ NlAssert( NT_SUCCESS(Status) );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NlpEncryptLogonInformation (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN OUT LPBYTE LogonInformation,
+ IN PSESSION_INFO SessionInfo
+)
+/*++
+
+Routine Description:
+
+ This function encrypts the sensitive information in the LogonInformation
+ structure. The encryption is done in place.
+
+Arguments:
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ SessionInfo -- The session key to encrypt with and negotiate flags
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+
+ //
+ // Only the interactive and service logon information is encrypted.
+ //
+
+ if ( LogonLevel == NetlogonInteractiveInformation ||
+ LogonLevel == NetlogonServiceInformation ) {
+
+ PNETLOGON_INTERACTIVE_INFO LogonInteractive;
+
+ LogonInteractive =
+ (PNETLOGON_INTERACTIVE_INFO) LogonInformation;
+
+
+ //
+ // If both sides support RC4 encryption, use it.
+ // encrypt both the LM OWF and NT OWF passwords using RC4.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ NlEncryptRC4( &LogonInteractive->LmOwfPassword,
+ sizeof(LogonInteractive->LmOwfPassword),
+ SessionInfo );
+
+ NlEncryptRC4( &LogonInteractive->NtOwfPassword,
+ sizeof(LogonInteractive->NtOwfPassword),
+ SessionInfo );
+
+
+ //
+ // If the other side is running NT 1.0,
+ // use the slower DES based encryption.
+ //
+
+ } else {
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+
+ //
+ // Encrypt the LM_OWF password.
+ //
+
+ NlAssert( ENCRYPTED_LM_OWF_PASSWORD_LENGTH ==
+ LM_OWF_PASSWORD_LENGTH );
+ NlAssert(LM_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
+
+ Status = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ &LogonInteractive->LmOwfPassword,
+ (PLM_OWF_PASSWORD) &SessionInfo->SessionKey,
+ &EncryptedLmOwfPassword );
+
+ NlAssert( NT_SUCCESS(Status) );
+
+ *((PENCRYPTED_LM_OWF_PASSWORD) &LogonInteractive->LmOwfPassword) =
+ EncryptedLmOwfPassword;
+
+ //
+ // Encrypt the NT_OWF password.
+ //
+
+ NlAssert( ENCRYPTED_NT_OWF_PASSWORD_LENGTH ==
+ NT_OWF_PASSWORD_LENGTH );
+ NlAssert(NT_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
+
+ Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ &LogonInteractive->NtOwfPassword,
+ (PNT_OWF_PASSWORD) &SessionInfo->SessionKey,
+ &EncryptedNtOwfPassword );
+
+ NlAssert( NT_SUCCESS(Status) );
+
+ *((PENCRYPTED_NT_OWF_PASSWORD) &LogonInteractive->NtOwfPassword) =
+ EncryptedNtOwfPassword;
+ }
+ }
+
+ return;
+
+}
+
+
+
+VOID
+NlpDecryptValidationInformation (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ IN OUT LPBYTE ValidationInformation,
+ IN PSESSION_INFO SessionInfo
+)
+/*++
+
+Routine Description:
+
+ This function decrypts the sensitive information in the
+ ValidationInformation structure. The decryption is done in place.
+
+Arguments:
+
+ LogonLevel -- Specifies the Logon level used to obtain
+ ValidationInformation.
+
+ ValidationLevel -- Specifies the level of information given in
+ ValidationInformation.
+
+ ValidationInformation -- Specifies the description for the user
+ logging on.
+
+ SessionInfo -- The session key to encrypt with and negotiated flags.
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
+
+ //
+ // Check the validation level.
+ //
+
+ if ( (ValidationLevel != NetlogonValidationSamInfo) &&
+ (ValidationLevel != NetlogonValidationSamInfo2) ) {
+ return;
+ }
+
+ //
+ // Only network logons contain information which is sensitive.
+ //
+
+ if ( LogonLevel != NetlogonNetworkInformation ) {
+ return;
+ }
+
+ ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO) ValidationInformation;
+
+
+
+ //
+ // If we're suppossed to use RC4,
+ // Decrypt both the NT and LM session keys using RC4.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ NlDecryptRC4( &ValidationInfo->UserSessionKey,
+ sizeof(ValidationInfo->UserSessionKey),
+ SessionInfo );
+
+ NlDecryptRC4( &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY],
+ SAMINFO_LM_SESSION_KEY_SIZE,
+ SessionInfo );
+
+ //
+ // If the other side is running NT1.0,
+ // be compatible.
+ //
+ } else {
+
+ NTSTATUS Status;
+ CLEAR_BLOCK ClearBlock;
+ DWORD i;
+ LPBYTE DataBuffer =
+ (LPBYTE) &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY];
+
+ //
+ // Decrypt the LmSessionKey
+ //
+
+ NlAssert( CLEAR_BLOCK_LENGTH == CYPHER_BLOCK_LENGTH );
+ NlAssert( (SAMINFO_LM_SESSION_KEY_SIZE % CLEAR_BLOCK_LENGTH) == 0 );
+
+ //
+ // Loop decrypting a block at a time
+ //
+
+ for (i=0; i<SAMINFO_LM_SESSION_KEY_SIZE/CLEAR_BLOCK_LENGTH; i++ ) {
+ Status = RtlDecryptBlock(
+ (PCYPHER_BLOCK)DataBuffer,
+ (PBLOCK_KEY)&SessionInfo->SessionKey,
+ &ClearBlock );
+ NlAssert( NT_SUCCESS( Status ) );
+
+ //
+ // Copy the clear text back into the original buffer.
+ //
+
+ RtlCopyMemory( DataBuffer, &ClearBlock, CLEAR_BLOCK_LENGTH );
+ DataBuffer += CLEAR_BLOCK_LENGTH;
+ }
+
+ }
+
+
+ return;
+}
+
+
+VOID
+NlpEncryptValidationInformation (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ IN OUT LPBYTE ValidationInformation,
+ IN PSESSION_INFO SessionInfo
+)
+/*++
+
+Routine Description:
+
+ This function encrypts the sensitive information in the
+ ValidationInformation structure. The encryption is done in place.
+
+Arguments:
+
+ LogonLevel -- Specifies the Logon level used to obtain
+ ValidationInformation.
+
+ ValidationLevel -- Specifies the level of information given in
+ ValidationInformation.
+
+ ValidationInformation -- Specifies the description for the user
+ logging on.
+
+ SessionInfo -- The session key to encrypt with and negotiated flags.
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
+
+ //
+ // Check the validation level.
+ //
+
+ if ( (ValidationLevel != NetlogonValidationSamInfo) &&
+ (ValidationLevel != NetlogonValidationSamInfo2) ) {
+ return;
+ }
+
+ //
+ // Only network logons contain information which is sensitive.
+ //
+
+ if ( LogonLevel != NetlogonNetworkInformation ) {
+ return;
+ }
+
+ ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO) ValidationInformation;
+
+
+ //
+ // If we're suppossed to use RC4,
+ // Encrypt both the NT and LM session keys using RC4.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ NlEncryptRC4( &ValidationInfo->UserSessionKey,
+ sizeof(ValidationInfo->UserSessionKey),
+ SessionInfo );
+
+ NlEncryptRC4( &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY],
+ SAMINFO_LM_SESSION_KEY_SIZE,
+ SessionInfo );
+
+ //
+ // If the other side is running NT1.0,
+ // be compatible.
+ //
+ } else {
+
+ NTSTATUS Status;
+ CLEAR_BLOCK ClearBlock;
+ DWORD i;
+ LPBYTE DataBuffer =
+ (LPBYTE) &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY];
+
+
+ //
+ // Encrypt the LmSessionKey
+ //
+ // Loop decrypting a block at a time
+ //
+
+ for (i=0; i<SAMINFO_LM_SESSION_KEY_SIZE/CLEAR_BLOCK_LENGTH; i++ ) {
+
+ //
+ // Copy the clear text onto the stack
+ //
+
+ RtlCopyMemory( &ClearBlock, DataBuffer, CLEAR_BLOCK_LENGTH );
+
+ Status = RtlEncryptBlock(
+ &ClearBlock,
+ (PBLOCK_KEY)&SessionInfo->SessionKey,
+ (PCYPHER_BLOCK)DataBuffer );
+
+ NlAssert( NT_SUCCESS( Status ) );
+
+ DataBuffer += CLEAR_BLOCK_LENGTH;
+ }
+
+ }
+
+ return;
+
+}
+
+
+
+
+NTSTATUS
+NlpConvertSamInfoToSamInfo2 (
+ IN OUT LPBYTE * ValidationInformation
+)
+/*++
+
+Routine Description:
+
+ This function converts a NETLOGON_VALIDATION_SAM_INFO from a NT1.0 server
+ into a NETLOGON_VALIDATION_SAM_INFO2. This is necessary because it
+ is not possible to tell RPC what kind of structure is being returned.
+
+Arguments:
+
+
+ ValidationInformation -- Specifies the NETLOGON_VALIDATION_SAM_INFO
+ to convert.
+ logging on.
+
+ SessionInfo -- The session key to encrypt with and negotiated flags.
+
+Return Value:
+
+ STATUS_INSUFFICIENT_RESOURCES: not enough memory to allocate the new
+ structure.
+
+--*/
+{
+ ULONG Length;
+ PNETLOGON_VALIDATION_SAM_INFO SamInfo = (PNETLOGON_VALIDATION_SAM_INFO) *ValidationInformation;
+ PNETLOGON_VALIDATION_SAM_INFO2 SamInfo2;
+ PBYTE Where;
+
+ //
+ // Calculate the size of the new structure
+ //
+
+ Length = sizeof( NETLOGON_VALIDATION_SAM_INFO2 )
+ + SamInfo->GroupCount * sizeof(GROUP_MEMBERSHIP)
+ + RtlLengthSid( SamInfo->LogonDomainId );
+
+ //
+ // Round up now to take into account the round up in the
+ // middle of marshalling
+ //
+
+ Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ + SamInfo->LogonDomainName.Length + sizeof(WCHAR)
+ + SamInfo->LogonServer.Length + sizeof(WCHAR)
+ + SamInfo->EffectiveName.Length + sizeof(WCHAR)
+ + SamInfo->FullName.Length + sizeof(WCHAR)
+ + SamInfo->LogonScript.Length + sizeof(WCHAR)
+ + SamInfo->ProfilePath.Length + sizeof(WCHAR)
+ + SamInfo->HomeDirectory.Length + sizeof(WCHAR)
+ + SamInfo->HomeDirectoryDrive.Length + sizeof(WCHAR);
+
+
+ Length = ROUND_UP_COUNT( Length, sizeof(WCHAR) );
+
+ SamInfo2 = (PNETLOGON_VALIDATION_SAM_INFO2) MIDL_user_allocate( Length );
+
+ if ( !SamInfo2 ) {
+ *ValidationInformation = NULL;
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // First copy the whole structure, since most parts are the same
+ //
+
+ RtlCopyMemory(SamInfo2,SamInfo,sizeof(NETLOGON_VALIDATION_SAM_INFO));
+
+ SamInfo2->SidCount = 0;
+ SamInfo2->ExtraSids = NULL;
+
+ //
+ // Copy all the variable length data
+ //
+
+ Where = (PBYTE) (SamInfo2 + 1);
+
+ RtlCopyMemory(
+ Where,
+ SamInfo->GroupIds,
+ SamInfo->GroupCount * sizeof( GROUP_MEMBERSHIP) );
+
+ SamInfo2->GroupIds = (PGROUP_MEMBERSHIP) Where;
+ Where += SamInfo->GroupCount * sizeof( GROUP_MEMBERSHIP );
+
+ RtlCopyMemory(
+ Where,
+ SamInfo->LogonDomainId,
+ RtlLengthSid( SamInfo->LogonDomainId ) );
+
+ SamInfo2->LogonDomainId = (PSID) Where;
+ Where += RtlLengthSid( SamInfo->LogonDomainId );
+
+ //
+ // Copy the WCHAR-aligned data
+ //
+ Where = ROUND_UP_POINTER(Where, sizeof(WCHAR) );
+
+ NlpPutString( &SamInfo2->EffectiveName,
+ &SamInfo->EffectiveName,
+ &Where );
+
+ NlpPutString( &SamInfo2->FullName,
+ &SamInfo->FullName,
+ &Where );
+
+ NlpPutString( &SamInfo2->LogonScript,
+ &SamInfo->LogonScript,
+ &Where );
+
+ NlpPutString( &SamInfo2->ProfilePath,
+ &SamInfo->ProfilePath,
+ &Where );
+
+ NlpPutString( &SamInfo2->HomeDirectory,
+ &SamInfo->HomeDirectory,
+ &Where );
+
+ NlpPutString( &SamInfo2->HomeDirectoryDrive,
+ &SamInfo->HomeDirectoryDrive,
+ &Where );
+
+ NlpPutString( &SamInfo2->LogonServer,
+ &SamInfo->LogonServer,
+ &Where );
+
+ NlpPutString( &SamInfo2->LogonDomainName,
+ &SamInfo->LogonDomainName,
+ &Where );
+
+
+
+ MIDL_user_free(SamInfo);
+
+ *ValidationInformation = (LPBYTE) SamInfo2;
+
+ return STATUS_SUCCESS;
+
+}
+
+
+NTSTATUS
+NlpUserValidateHigher (
+ IN PCLIENT_SESSION ClientSession,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ OUT LPBYTE * ValidationInformation,
+ OUT PBOOLEAN Authoritative
+)
+/*++
+
+Routine Description:
+
+ This function sends a user validation request to a higher authority.
+
+Arguments:
+
+ ClientSession -- Secure channel to send this request over. The Client
+ Session should be referenced.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation. Has already been validated.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ ValidationLevel -- Specifies the level of information returned in
+ ValidationInformation. Must be NetlogonValidationSamInfo or
+ NetlogonValidationSamInfo2.
+
+ ValidationInformation -- Returns the requested validation
+ information. This buffer must be freed using MIDL_user_free.
+
+ Authoritative -- Returns whether the status returned is an
+ authoritative status which should be returned to the original
+ caller. If not, this logon request may be tried again on another
+ domain controller. This parameter is returned regardless of the
+ status code.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+
+ STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
+
+ STATUS_NO_TRUST_LSA_SECRET:
+ STATUS_TRUSTED_DOMAIN_FAILURE:
+ STATUS_TRUSTED_RELATIONSHIP_FAILURE:
+ can't authenticate with higer authority
+
+ Otherwise, the error code is returned.
+
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+ BOOLEAN FirstTry = TRUE;
+ SESSION_INFO SessionInfo;
+ NETLOGON_VALIDATION_INFO_CLASS RemoteValidationLevel;
+
+ //
+ // Mark us as a writer of the ClientSession
+ //
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlpUserValidateHigher: Can't become writer of client session.\n" ));
+ *Authoritative = TRUE;
+ return STATUS_NO_LOGON_SERVERS;
+ }
+
+ //
+ // If we don't currently have a session set up to the higher authority,
+ // set one up.
+ //
+
+FirstTryFailed:
+ if ( ClientSession->CsState != CS_AUTHENTICATED ) {
+
+ //
+ // If we've tried to authenticate recently,
+ // don't bother trying again.
+ //
+
+ if ( !NlTimeToReauthenticate( ClientSession ) ) {
+ Status = ClientSession->CsConnectionStatus;
+ NlAssert( !NT_SUCCESS(Status) );
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ *Authoritative = TRUE;
+ goto Cleanup;
+
+ }
+
+ //
+ // Try to set up the session.
+ //
+
+ Status = NlSessionSetup( ClientSession );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ switch(Status) {
+
+ case STATUS_NO_TRUST_LSA_SECRET:
+ case STATUS_NO_TRUST_SAM_ACCOUNT:
+ case STATUS_ACCESS_DENIED:
+ case STATUS_NO_LOGON_SERVERS:
+ break;
+
+ default:
+ Status = STATUS_NO_LOGON_SERVERS;
+ break;
+ }
+
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ *Authoritative = TRUE;
+ goto Cleanup;
+ }
+ }
+
+ SessionInfo.SessionKey = ClientSession->CsSessionKey;
+ SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags;
+
+ //
+ // If we are talking to a DC that doesn't support returning multiple
+ // SIDs, make sure to only ask for NetlogonValidationSamInfo
+ //
+
+ if (!(SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_MULTIPLE_SIDS)) {
+ RemoteValidationLevel = NetlogonValidationSamInfo;
+ } else {
+ RemoteValidationLevel = ValidationLevel;
+ }
+ //
+ // Build the Authenticator for this request on the secure channel
+ //
+
+ NlBuildAuthenticator(
+ &ClientSession->CsAuthenticationSeed,
+ &ClientSession->CsSessionKey,
+ &OurAuthenticator );
+
+
+ //
+ // Make the request across the secure channel.
+ //
+
+ NlpEncryptLogonInformation( LogonLevel, LogonInformation, &SessionInfo );
+
+ Status = NlStartApiClientSession( ClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = I_NetLogonSamLogon(
+ ClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ LogonLevel,
+ LogonInformation,
+ RemoteValidationLevel,
+ ValidationInformation,
+ Authoritative );
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ }
+
+ // NOTE: This call may drop the secure channel behind our back
+ (VOID) NlFinishApiClientSession( ClientSession, TRUE );
+
+ NlpDecryptLogonInformation( LogonLevel, LogonInformation, &SessionInfo );
+
+ if ( NT_SUCCESS(Status) ) {
+ NlAssert( *ValidationInformation != NULL );
+ }
+
+
+ //
+ // Verify authenticator of the server on the other side and update our seed.
+ //
+ // If the server denied access or the server's authenticator is wrong,
+ // Force a re-authentication.
+ //
+ //
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: Seed = %lx %lx\n",
+ ((DWORD *) (&ClientSession->CsAuthenticationSeed))[0],
+ ((DWORD *) (&ClientSession->CsAuthenticationSeed))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: SessionKey = %lx %lx\n",
+ ((DWORD *) (&ClientSession->CsSessionKey))[0],
+ ((DWORD *) (&ClientSession->CsSessionKey))[1]));
+
+ NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: Return Authenticator = %lx %lx\n",
+ ((DWORD *) (&ReturnAuthenticator.Credential))[0],
+ ((DWORD *) (&ReturnAuthenticator.Credential))[1]));
+#endif // BAD_ALIGNMENT
+
+ if ( Status == STATUS_ACCESS_DENIED ||
+ !NlUpdateSeed(
+ &ClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &ClientSession->CsSessionKey) ) {
+
+
+ Status = STATUS_ACCESS_DENIED;
+ NlSetStatusClientSession( ClientSession, Status );
+
+ //
+ // Perhaps the netlogon service on the server has just restarted.
+ // Try just once to set up a session to the server again.
+ //
+ if ( FirstTry ) {
+ FirstTry = FALSE;
+ goto FirstTryFailed;
+ }
+
+ *Authoritative = TRUE;
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ goto Cleanup;
+ }
+
+ //
+ // Clean up after a successful call to higher authority.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ PNETLOGON_VALIDATION_SAM_INFO2 ValidationInfo;
+
+
+ //
+ // The server encrypted the validation information before sending it
+ // over the wire. Decrypt it.
+ //
+
+ NlpDecryptValidationInformation (
+ LogonLevel,
+ RemoteValidationLevel,
+ *ValidationInformation,
+ &SessionInfo );
+
+
+ //
+ // If the returned data was a VALIDATION_SAM_INFO and the caller
+ // wanted a VALIDATION_SAM_INFO2 convert it.
+ //
+
+ if ( RemoteValidationLevel != ValidationLevel) {
+
+ NlAssert( ValidationLevel == NetlogonValidationSamInfo2 );
+ NlAssert( RemoteValidationLevel == NetlogonValidationSamInfo );
+
+ if (!NT_SUCCESS( NlpConvertSamInfoToSamInfo2( ValidationInformation ) ) ) {
+ *ValidationInformation = NULL;
+ *Authoritative = FALSE;
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Ensure the returned SID and domain name are correct.
+ //
+
+ ValidationInfo =
+ (PNETLOGON_VALIDATION_SAM_INFO2) *ValidationInformation;
+
+ //
+ // If we validated on a trusted domain,
+ // the higher authority must have returned his own domain name,
+ // and must have returned his own domain sid.
+ //
+
+ if ( ClientSession->CsSecureChannelType == TrustedDomainSecureChannel ){
+
+ if ( !RtlEqualDomainName( &ValidationInfo->LogonDomainName,
+ &ClientSession->CsDomainName ) ||
+ !RtlEqualSid( ValidationInfo->LogonDomainId,
+ ClientSession->CsDomainId ) ) {
+
+ Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
+ MIDL_user_free( *ValidationInformation );
+ *ValidationInformation = NULL;
+ *Authoritative = TRUE;
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ }
+
+ //
+ // If we validated on our primary domain,
+ // only verify the domain sid if the primary domain itself validated
+ // the logon.
+ //
+
+ } else if ( ClientSession->CsSecureChannelType ==
+ WorkstationSecureChannel ){
+
+ if ( RtlEqualDomainName( &ValidationInfo->LogonDomainName,
+ &ClientSession->CsDomainName ) &&
+ !RtlEqualSid( ValidationInfo->LogonDomainId,
+ ClientSession->CsDomainId ) ) {
+
+ Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
+ MIDL_user_free( *ValidationInformation );
+ *ValidationInformation = NULL;
+ *Authoritative = TRUE;
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+ }
+ }
+ }
+
+Cleanup:
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+
+ //
+ // We are no longer a writer of the client session.
+ //
+ NlResetWriterClientSession( ClientSession );
+ return Status;
+
+}
+
+
+NTSTATUS
+NlpUserLogoffHigher (
+ IN PCLIENT_SESSION ClientSession,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation
+)
+/*++
+
+Routine Description:
+
+ This function sends a user validation request to a higher authority.
+
+Arguments:
+
+ ClientSession -- Secure channel to send this request over. The Client
+ Session should be referenced.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation. Has already been validated.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+ STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
+
+ STATUS_NO_TRUST_LSA_SECRET:
+ STATUS_TRUSTED_DOMAIN_FAILURE:
+ STATUS_TRUSTED_RELATIONSHIP_FAILURE:
+ can't authenticate with higer authority
+
+ Otherwise, the error code is returned.
+
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+ BOOLEAN FirstTry = TRUE;
+
+ //
+ // Mark us as a writer of the ClientSession
+ //
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlpUserLogoffHigher: Can't become writer of client session.\n" ));
+ return STATUS_NO_LOGON_SERVERS;
+ }
+
+ //
+ // If we don't currently have a session set up to the higher authority,
+ // set one up.
+ //
+
+FirstTryFailed:
+ if ( ClientSession->CsState != CS_AUTHENTICATED ) {
+
+ //
+ // If we've tried to authenticate recently,
+ // don't bother trying again.
+ //
+
+ if ( !NlTimeToReauthenticate( ClientSession ) ) {
+ Status = ClientSession->CsConnectionStatus;
+ goto Cleanup;
+
+ }
+
+ Status = NlSessionSetup( ClientSession );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ switch(Status) {
+
+ case STATUS_NO_TRUST_LSA_SECRET:
+ case STATUS_NO_TRUST_SAM_ACCOUNT:
+ case STATUS_ACCESS_DENIED:
+ case STATUS_NO_LOGON_SERVERS:
+ break;
+
+ default:
+ Status = STATUS_NO_LOGON_SERVERS;
+ break;
+ }
+
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Build the Authenticator for this request on the secure channel
+ //
+
+ NlBuildAuthenticator(
+ &ClientSession->CsAuthenticationSeed,
+ &ClientSession->CsSessionKey,
+ &OurAuthenticator );
+
+ //
+ // Make the request across the secure channel.
+ //
+
+ Status = NlStartApiClientSession( ClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = I_NetLogonSamLogoff(
+ ClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ LogonLevel,
+ LogonInformation );
+ }
+
+ // NOTE: This call may drop the secure channel behind our back
+ (VOID) NlFinishApiClientSession( ClientSession, TRUE );
+
+
+ //
+ // Verify authenticator of the server on the other side and update our seed.
+ //
+ // If the server denied access or the server's authenticator is wrong,
+ // Force a re-authentication.
+ //
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ||
+ !NlUpdateSeed(
+ &ClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &ClientSession->CsSessionKey) ) {
+
+ Status = STATUS_ACCESS_DENIED;
+ NlSetStatusClientSession( ClientSession, Status );
+
+ //
+ // Perhaps the netlogon service in the server has just restarted.
+ // Try just once to set up a session to the server again.
+ //
+ if ( FirstTry ) {
+ FirstTry = FALSE;
+ goto FirstTryFailed;
+ }
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ //
+ // We are no longer a writer of the client session.
+ //
+ NlResetWriterClientSession( ClientSession );
+ return Status;
+
+}
+
+
+NTSTATUS
+NlpUserValidateOnPdc (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ OUT LPBYTE * ValidationInformation,
+ OUT PBOOLEAN Authoritative
+)
+/*++
+
+Routine Description:
+
+ This function sends a user validation request to the PDC in this same
+ domain. Currently, this is called from a BDC after getting a password
+ mismatch. The theory is that the password might be right on the PDC but
+ it merely hasn't replicated yet.
+
+Arguments:
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation. Has already been validated.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ ValidationLevel -- Specifies the level of information returned in
+ ValidationInformation. Must be NetlogonValidationSamInfo or
+ NetlogonValidationSamInfo2.
+
+ ValidationInformation -- Returns the requested validation
+ information. This buffer must be freed using MIDL_user_free.
+
+ Authoritative -- Returns whether the status returned is an
+ authoritative status which should be returned to the original
+ caller. If not, this logon request may be tried again on another
+ domain controller. This parameter is returned regardless of the
+ status code.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+
+ STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
+
+ STATUS_NO_TRUST_LSA_SECRET:
+ STATUS_TRUSTED_DOMAIN_FAILURE:
+ STATUS_TRUSTED_RELATIONSHIP_FAILURE:
+ can't authenticate with higer authority
+
+ Otherwise, the error code is returned.
+
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // If this isn't a BDC,
+ // There's nothing to do here.
+ //
+
+ if ( NlGlobalRole != RoleBackup ) {
+ return STATUS_INVALID_DOMAIN_ROLE;
+ }
+
+ //
+ // The normal pass-thru authentication logic handles this quite nicely.
+ //
+
+ Status = NlpUserValidateHigher(
+ NlGlobalClientSession,
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ ValidationInformation,
+ Authoritative );
+
+#if DBG
+ if ( NT_SUCCESS(Status) ) {
+
+ IF_DEBUG( LOGON ) {
+ PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
+ LPWSTR LogonType;
+
+
+ LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
+ &((PNETLOGON_LEVEL)LogonInformation)->LogonInteractive;
+
+ if ( LogonLevel == NetlogonInteractiveInformation ) {
+ LogonType = L"Interactive";
+ } else if ( LogonLevel == NetlogonNetworkInformation ) {
+ LogonType = L"Network";
+ } else if ( LogonLevel == NetlogonServiceInformation ) {
+ LogonType = L"Service";
+ } else {
+ LogonType = L"[Unknown]";
+ }
+
+ NlPrint((NL_LOGON,
+ "SamLogon: " FORMAT_LPWSTR " logon of %wZ\\%wZ "
+ "from %wZ successfully handled on PDC.\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation ));
+ }
+ }
+#endif // DBG
+
+ return Status;
+
+}
+
+
+
+VOID
+NlpZeroBadPasswordCountOnPdc (
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation
+)
+/*++
+
+Routine Description:
+
+ This function zeros the BadPasswordCount field for the specified user
+ on the PDC.
+
+Arguments:
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation. Has already been validated.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+ BOOLEAN Authoritative;
+ LPBYTE ValidationInformation = NULL;
+
+ //
+ // We only call this function on a BDC and if the BDC has just zeroed
+ // the BadPasswordCount because of successful logon. Therefore,
+ // we can zero the BadPasswordCount on the PDC by doing the logon over
+ // again on the PDC.
+ //
+
+ Status = NlpUserValidateOnPdc (
+ LogonLevel,
+ LogonInformation,
+ NetlogonValidationSamInfo,
+ &ValidationInformation,
+ &Authoritative );
+
+ if ( NT_SUCCESS(Status) ) {
+ MIDL_user_free( ValidationInformation );
+ }
+}
+
+
+NTSTATUS
+NlpUserValidate (
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN LPBYTE LogonInformation,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ OUT LPBYTE * ValidationInformation,
+ OUT PBOOLEAN Authoritative
+)
+/*++
+
+Routine Description:
+
+ This function processes an interactive or network logon.
+ It is a worker routine for I_NetSamLogon. I_NetSamLogon handles the
+ details of validating the caller. This function handles the details
+ of whether to validate locally or pass the request on. MsvValidateSam
+ does the actual local validation.
+
+ session table only in the domain defining the specified user's
+ account.
+
+ This service is also used to process a re-logon request.
+
+
+Arguments:
+
+ SecureChannelType -- Type of secure channel this request was made over.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation. Has already been validated.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ ValidationLevel -- Specifies the level of information returned in
+ ValidationInformation. Must be NetlogonValidationSamInfo or
+ NetlogonValidationSamInfo2.
+
+ ValidationInformation -- Returns the requested validation
+ information. This buffer must be freed using MIDL_user_free.
+
+ Authoritative -- Returns whether the status returned is an
+ authoritative status which should be returned to the original
+ caller. If not, this logon request may be tried again on another
+ domain controller. This parameter is returned regardless of the
+ status code.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+ Otherwise, the error code is
+ returned.
+
+
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS DefaultStatus = STATUS_NO_SUCH_USER;
+
+ PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
+ PCLIENT_SESSION ClientSession;
+ DWORD AccountsToTry = MSVSAM_SPECIFIED | MSVSAM_GUEST;
+ BOOLEAN BadPasswordCountZeroed;
+ BOOLEAN LogonToLocalDomain;
+
+ //
+ // Initialization
+ //
+
+ LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
+ *Authoritative = FALSE;
+ LogonToLocalDomain = RtlEqualDomainName( &LogonInfo->LogonDomainName,
+ &NlGlobalAccountDomainName );
+
+
+
+ //
+ // Check to see if the account is in the local SAM database.
+ //
+ // The Theory:
+ // If a particular database is absolutely requested,
+ // we only try the account in the requested database.
+ //
+ // In the event that an account exists in multiple places in the hierarchy,
+ // we want to find the version of the account that is closest to the
+ // logged on machine (i.e., workstation first, primary domain, then
+ // trusted domain.). So we always try to local database before going
+ // to a higher authority.
+ //
+ // Finally, handle the case that this call is from a BDC in our own domain
+ // just checking to see if the PDC (us) has a better copy of the account
+ // than it does.
+ //
+
+ if ( LogonInfo->LogonDomainName.Length == 0 ||
+ LogonToLocalDomain ||
+ SecureChannelType == ServerSecureChannel ) {
+
+ //
+ // Indicate we've already tried the specified account and
+ // we won't need to try it again locally.
+ //
+
+ AccountsToTry &= ~MSVSAM_SPECIFIED;
+
+ Status = MsvSamValidate( NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ NlGlobalUasCompatibilityMode,
+ SecureChannelType,
+ &NlGlobalUnicodeComputerNameString,
+ &NlGlobalAccountDomainName,
+ NlGlobalDBInfoArray[SAM_DB].DBId,
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ (PVOID *)ValidationInformation,
+ Authoritative,
+ &BadPasswordCountZeroed,
+ MSVSAM_SPECIFIED );
+
+ //
+ // If this is a BDC and we zeroed the BadPasswordCount field,
+ // allow the PDC to do the same thing.
+ //
+
+ if ( BadPasswordCountZeroed ) {
+ NlpZeroBadPasswordCountOnPdc ( LogonLevel, LogonInformation );
+ }
+
+
+ //
+ // If the request is explicitly for this domain,
+ // The STATUS_NO_SUCH_USER answer is authoritative.
+ //
+
+ if ( LogonToLocalDomain && Status == STATUS_NO_SUCH_USER ) {
+ *Authoritative = TRUE;
+ }
+
+
+ //
+ // If this is one of our BDCs calling,
+ // return with whatever answer we got locally.
+ //
+
+ if ( SecureChannelType == ServerSecureChannel ) {
+ DefaultStatus = Status;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // If the local SAM database authoritatively handled the logon attempt,
+ // just return.
+ //
+
+ if ( *Authoritative ) {
+ DefaultStatus = Status;
+
+ //
+ // If the problem is just that the password is wrong,
+ // try again on the PDC where the password may already be changed.
+ //
+
+ if ( BAD_PASSWORD(Status) ) {
+
+ BOOLEAN TempAuthoritative;
+
+ Status = NlpUserValidateOnPdc (
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ ValidationInformation,
+ &TempAuthoritative );
+
+ // Ignore failures from the PDC
+ if ( NT_SUCCESS(Status) ) {
+ DefaultStatus = Status;
+ *Authoritative = TempAuthoritative;
+ }
+ }
+
+ goto Cleanup;
+ }
+
+ DefaultStatus = Status;
+ }
+
+
+ //
+ // If the request in not for this domain,
+ // or the domain name isn't specified (and we haven't found the account yet)
+ // send the request to a higher authority.
+ //
+
+ if ( LogonInfo->LogonDomainName.Length == 0 || !LogonToLocalDomain ) {
+
+
+ //
+ // If this machine is a workstation,
+ // send the request to the Primary Domain.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+
+ Status = NlpUserValidateHigher(
+ NlGlobalClientSession,
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ ValidationInformation,
+ Authoritative );
+
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+
+
+ //
+ // return more appropriate error
+ //
+
+ if( (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
+ (Status == STATUS_ACCESS_DENIED) ) {
+
+ Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ //
+ // If the primary domain authoritatively handled the logon attempt,
+ // just return.
+ //
+
+ if ( *Authoritative ) {
+
+ //
+ // If we didn't actually talk to the primary domain,
+ // check locally if the domain requested is a trusted domain.
+ // (This list is only a cache so we had to try to contact the
+ // primary domain.)
+
+ if ( Status == STATUS_NO_LOGON_SERVERS ) {
+
+ //
+ // If the domain specified is trusted,
+ // then return the status to the caller.
+ // otherwise just press on.
+
+ if ( NlIsDomainTrusted ( &LogonInfo->LogonDomainName ) ) {
+ DefaultStatus = Status;
+ goto Cleanup;
+ } else {
+ //
+ // Set the return codes to look as though the primary
+ // determine this is an untrusted domain.
+ //
+ *Authoritative = FALSE;
+ Status = STATUS_NO_SUCH_USER;
+ }
+ } else {
+ DefaultStatus = Status;
+ goto Cleanup;
+ }
+ }
+
+
+ if ( Status != STATUS_NO_SUCH_USER ) {
+ DefaultStatus = Status;
+ }
+
+
+ //
+ // The machine is a Domain Controller.
+ //
+ // If this request was passed to us as a trusted domain request,
+ // There is no higher authority to pass the request to.
+ //
+
+ } else if ( SecureChannelType == TrustedDomainSecureChannel ) {
+
+ // DefaultStatus = STATUS_NO_SUCH_USER;
+
+
+ //
+ // This machine is a Domain Controller.
+ //
+ // This request is either a pass-thru request by a workstation in
+ // our domain, or this request came directly from the MSV
+ // authentication package.
+ //
+ // In either case, pass the request to the trusted domain.
+ //
+
+ } else {
+
+
+ //
+ // If this is the LanMan 2.0 case,
+ // Try to find the domain name by asking all the trusted
+ // domains if they define the account
+ //
+
+ if ( LogonInfo->LogonDomainName.Length == 0 ) {
+ LPWSTR UserName;
+
+
+ UserName = NlStringToLpwstr( &LogonInfo->UserName );
+ if ( UserName == NULL ) {
+ *Authoritative = FALSE;
+ DefaultStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto Cleanup;
+ }
+
+ ClientSession = NlPickDomainWithAccount( UserName,
+ USER_NORMAL_ACCOUNT );
+
+ NetpMemoryFree( UserName );
+
+
+ //
+ // It the domain is explicitly given,
+ // simply find the client session for that domain.
+ //
+
+ } else {
+
+ ClientSession =
+ NlFindNamedClientSession( &LogonInfo->LogonDomainName );
+
+ }
+
+ //
+ // If a trusted domain was determined,
+ // pass the logon request to the trusted domain.
+ //
+
+ if ( ClientSession != NULL ) {
+
+ Status = NlpUserValidateHigher(
+ ClientSession,
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ ValidationInformation,
+ Authoritative );
+
+
+ NlUnrefClientSession( ClientSession );
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+
+
+ //
+ // return more appropriate error
+ //
+
+ if( (Status == STATUS_NO_TRUST_LSA_SECRET) ||
+ (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
+ (Status == STATUS_ACCESS_DENIED) ) {
+
+ Status = STATUS_TRUSTED_DOMAIN_FAILURE;
+ }
+
+ //
+ // Since the request is explicitly for a trusted domain,
+ // The STATUS_NO_SUCH_USER answer is authoritative.
+ //
+
+ if ( Status == STATUS_NO_SUCH_USER ) {
+ *Authoritative = TRUE;
+ }
+
+ //
+ // If the trusted domain authoritatively handled the
+ // logon attempt, just return.
+ //
+
+ if ( *Authoritative ) {
+ DefaultStatus = Status;
+ goto Cleanup;
+ }
+
+ DefaultStatus = Status;
+
+ }
+
+ }
+ }
+
+
+ //
+ // We have no authoritative answer from a higher authority and
+ // DefaultStatus is the higher authority's response.
+ //
+
+ NlAssert( ! *Authoritative );
+
+
+Cleanup:
+ NlAssert( !NT_SUCCESS(DefaultStatus) || DefaultStatus == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(DefaultStatus) || *ValidationInformation != NULL );
+ //
+ // If this is a network logon and this call in non-passthru,
+ // Try one last time to log on.
+ //
+
+ if ( LogonLevel == NetlogonNetworkInformation &&
+ SecureChannelType == MsvApSecureChannel ) {
+
+ //
+ // If the only reason we can't log the user on is that he has
+ // no user account, logging him on as guest is OK.
+ //
+ // There are actaully two cases here:
+ // * If the response is Authoritative, then the specified domain
+ // is trusted but the user has no account in the domain.
+ //
+ // * If the response in non-authoritative, then the specified domain
+ // is an untrusted domain.
+ //
+ // In either case, then right thing to do is to try the guest account.
+ //
+
+ if ( DefaultStatus != STATUS_NO_SUCH_USER ) {
+ AccountsToTry &= ~MSVSAM_GUEST;
+ }
+
+ //
+ // If this is not an authoritative response,
+ // then the domain specified isn't a trusted domain.
+ // try the specified account name too.
+ //
+ // The specified account name will probably be a remote account
+ // with the same username and password.
+ //
+
+ if ( *Authoritative ) {
+ AccountsToTry &= ~MSVSAM_SPECIFIED;
+ }
+
+
+ //
+ // Validate against the Local Sam database.
+ //
+
+ if ( AccountsToTry != 0 ) {
+ BOOLEAN TempAuthoritative;
+
+ Status = MsvSamValidate(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ NlGlobalUasCompatibilityMode,
+ SecureChannelType,
+ &NlGlobalUnicodeComputerNameString,
+ &NlGlobalAccountDomainName,
+ NlGlobalDBInfoArray[SAM_DB].DBId,
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ (PVOID *)ValidationInformation,
+ &TempAuthoritative,
+ &BadPasswordCountZeroed,
+ AccountsToTry );
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
+
+ //
+ // If this is a BDC and we zeroed the BadPasswordCount field,
+ // allow the PDC to do the same thing.
+ //
+
+ if ( BadPasswordCountZeroed ) {
+ NlpZeroBadPasswordCountOnPdc ( LogonLevel, LogonInformation );
+ }
+
+ //
+ // If the local SAM database authoritatively handled the
+ // logon attempt,
+ // just return.
+ //
+
+ if ( TempAuthoritative ) {
+ DefaultStatus = Status;
+ *Authoritative = TRUE;
+
+ //
+ // If the problem is just that the password is wrong,
+ // try again on the PDC where the password may already be
+ // changed.
+ //
+
+ if ( BAD_PASSWORD(Status) ) {
+
+ Status = NlpUserValidateOnPdc (
+ LogonLevel,
+ LogonInformation,
+ ValidationLevel,
+ ValidationInformation,
+ &TempAuthoritative );
+
+ // Ignore failures from the PDC
+ if ( NT_SUCCESS(Status) ) {
+ DefaultStatus = Status;
+ *Authoritative = TempAuthoritative;
+ }
+ }
+
+ //
+ // Here we must choose between the non-authoritative status in
+ // DefaultStatus and the non-authoritative status from the local
+ // SAM lookup. Use the one from the higher authority unless it
+ // isn't interesting.
+ //
+
+ } else {
+ if ( DefaultStatus == STATUS_NO_SUCH_USER ) {
+ DefaultStatus = Status;
+ }
+ }
+ }
+ }
+
+ return DefaultStatus;
+
+}
+
+
+NTSTATUS
+NetrLogonSamLogon (
+ IN LPWSTR LogonServer OPTIONAL,
+ IN LPWSTR ComputerName OPTIONAL,
+ IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN PNETLOGON_LEVEL LogonInformation,
+ IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
+ OUT PNETLOGON_VALIDATION ValidationInformation,
+ OUT PBOOLEAN Authoritative
+)
+/*++
+
+Routine Description:
+
+ This function is called by an NT client to process an interactive or
+ network logon. This function passes a domain name, user name and
+ credentials to the Netlogon service and returns information needed to
+ build a token. It is called in three instances:
+
+ * It is called by the LSA's MSV1_0 authentication package for any
+ NT system that has LanMan installed. The MSV1_0 authentication
+ package calls SAM directly if LanMan is not installed. In this
+ case, this function is a local function and requires the caller
+ to have SE_TCB privilege. The local Netlogon service will
+ either handle this request directly (validating the request with
+ the local SAM database) or will forward this request to the
+ appropriate domain controller as documented in sections 2.4 and
+ 2.5.
+
+ * It is called by a Netlogon service on a workstation to a DC in
+ the Primary Domain of the workstation as documented in section
+ 2.4. In this case, this function uses a secure channel set up
+ between the two Netlogon services.
+
+ * It is called by a Netlogon service on a DC to a DC in a trusted
+ domain as documented in section 2.5. In this case, this
+ function uses a secure channel set up between the two Netlogon
+ services.
+
+ The Netlogon service validates the specified credentials. If they
+ are valid, adds an entry for this LogonId, UserName, and Workstation
+ into the logon session table. The entry is added to the logon
+ session table only in the domain defining the specified user's
+ account.
+
+ This service is also used to process a re-logon request.
+
+
+Arguments:
+
+ LogonServer -- Supplies the name of the logon server to process
+ this logon request. This field should be null to indicate
+ this is a call from the MSV1_0 authentication package to the
+ local Netlogon service.
+
+ ComputerName -- Name of the machine making the call. This field
+ should be null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ Authenticator -- supplied by the client. This field should be
+ null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the
+ server. This field should be null to indicate this is a call
+ from the MSV1_0 authentication package to the local Netlogon
+ service.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the description for the user
+ logging on.
+
+ ValidationLevel -- Specifies the level of information returned in
+ ValidationInformation. Must be NetlogonValidationSamInfo or
+ NetlogonValidationSamInfo2
+
+ ValidationInformation -- Returns the requested validation
+ information. This buffer must be freed using MIDL_user_free.
+
+ Authoritative -- Returns whether the status returned is an
+ authoritative status which should be returned to the original
+ caller. If not, this logon request may be tried again on another
+ domain controller. This parameter is returned regardless of the
+ status code.
+
+Return Value:
+
+ STATUS_SUCCESS: if there was no error.
+
+ STATUS_NO_LOGON_SERVERS -- no domain controller in the requested
+ domain is currently available to validate the logon request.
+
+ STATUS_NO_TRUST_LSA_SECRET -- there is no secret account in the
+ local LSA database to establish a secure channel to a DC.
+
+ STATUS_TRUSTED_DOMAIN_FAILURE -- the secure channel setup between
+ the domain controllers of the trust domains to pass-through
+ validate the logon request failed.
+
+ STATUS_TRUSTED_RELATIONSHIP_FAILURE -- the secure channel setup
+ between the workstation and the DC failed.
+
+ STATUS_INVALID_INFO_CLASS -- Either LogonLevel or ValidationLevel is
+ invalid.
+
+ STATUS_INVALID_PARAMETER -- Another Parameter is invalid.
+
+ STATUS_ACCESS_DENIED -- The caller does not have access to call this
+ API.
+
+ STATUS_NO_SUCH_USER -- Indicates that the user specified in
+ LogonInformation does not exist. This status should not be returned
+ to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
+
+ STATUS_WRONG_PASSWORD -- Indicates that the password information in
+ LogonInformation was incorrect. This status should not be returned
+ to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
+
+ STATUS_INVALID_LOGON_HOURES -- The user is not authorized to logon
+ at this time.
+
+ STATUS_INVALID_WORKSTATION -- The user is not authorized to logon
+ from the specified workstation.
+
+ STATUS_PASSWORD_EXPIRED -- The password for the user has expired.
+
+ STATUS_ACCOUNT_DISABLED -- The user's account has been disabled.
+
+ .
+ .
+ .
+
+--*/
+{
+ NTSTATUS Status;
+
+ PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
+
+ PSERVER_SESSION ServerSession;
+ NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType;
+ SESSION_INFO SessionInfo;
+#if DBG
+ LPWSTR LogonType;
+#endif // DBG
+
+
+ //
+ // Check the LogonLevel
+ //
+
+ *Authoritative = TRUE;
+ ValidationInformation->ValidationSam = NULL;
+ SessionInfo.NegotiatedFlags = NETLOGON_SUPPORTS_MASK;
+
+ switch ( LogonLevel ) {
+ case NetlogonInteractiveInformation:
+ case NetlogonNetworkInformation:
+ case NetlogonServiceInformation:
+ break;
+
+ default:
+ *Authoritative = TRUE;
+ return STATUS_INVALID_INFO_CLASS;
+ }
+
+ LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
+ LogonInformation->LogonInteractive;
+
+#if DBG
+ if ( LogonLevel == NetlogonInteractiveInformation ) {
+ LogonType = L"Interactive";
+ } else if ( LogonLevel == NetlogonNetworkInformation ) {
+ LogonType = L"Network";
+ } else if ( LogonLevel == NetlogonServiceInformation ) {
+ LogonType = L"Service";
+ } else {
+ LogonType = L"[Unknown]";
+ }
+
+ IF_DEBUG( LOGON ) {
+ if ( ComputerName != NULL ) {
+ NlPrint((NL_LOGON,
+ "SamLogon: " FORMAT_LPWSTR " logon of %wZ\\%wZ "
+ "from %wZ (via " FORMAT_LPWSTR ") Entered\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation,
+ ComputerName ));
+ } else {
+ NlPrint((NL_LOGON,
+ "SamLogon: " FORMAT_LPWSTR " logon of %wZ\\%wZ "
+ "from %wZ Entered\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation ));
+ }
+ }
+#endif // DBG
+
+ //
+ // Check the ValidationLevel
+ //
+
+ switch (ValidationLevel) {
+ case NetlogonValidationSamInfo:
+ case NetlogonValidationSamInfo2:
+ break;
+
+ default:
+ *Authoritative = TRUE;
+ return STATUS_INVALID_INFO_CLASS;
+ }
+
+
+ //
+ // If MSV is calling when the netlogon service isn't running,
+ // tell it so.
+ //
+
+ EnterCriticalSection( &NlGlobalMsvCritSect );
+ if ( !NlGlobalMsvEnabled ) {
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+ return STATUS_NETLOGON_NOT_STARTED;
+ }
+ NlGlobalMsvThreadCount ++;
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+
+
+ //
+ // If we're being called from the MSV Authentication Package,
+ // require SE_TCB privilege.
+ //
+
+ if ( LogonServer == NULL &&
+ ComputerName == NULL &&
+ Authenticator == NULL &&
+ ReturnAuthenticator == NULL ) {
+
+ //
+ // ?? Do as I said
+ //
+
+ SecureChannelType = MsvApSecureChannel;
+
+
+ //
+ // If we're being called from another Netlogon Server,
+ // Verify the secure channel information.
+ //
+
+ } else {
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ Status = STATUS_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Arguments are no longer optional.
+ //
+
+ if ( LogonServer == NULL ||
+ ComputerName == NULL ||
+ Authenticator == NULL ||
+ ReturnAuthenticator == NULL ) {
+
+ *Authoritative = TRUE;
+ Status = STATUS_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+
+ //
+ // Check the LogonServer name.
+ //
+
+ Status = NlVerifyWorkstation( LogonServer );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ *Authoritative = FALSE;
+ goto Cleanup;
+ }
+
+ //
+ // Find the server session entry for this session.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ *Authoritative = FALSE;
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // now verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ *Authoritative = FALSE;
+ goto Cleanup;
+ }
+
+ SecureChannelType = ServerSession->SsSecureChannelType;
+ SessionInfo.SessionKey = ServerSession->SsSessionKey;
+ SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Decrypt the password information
+ //
+
+ NlpDecryptLogonInformation ( LogonLevel, (LPBYTE) LogonInfo, &SessionInfo );
+
+ }
+
+
+
+
+
+ //
+ // If the logon service is paused then don't process this logon
+ // request any further.
+ //
+
+ if ( (NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED) ||
+ ( NlGlobalFirstTimeFullSync == TRUE ) ) {
+
+ //
+ // Don't reject logons originating inside this
+ // machine. Such requests aren't really pass-thru requests.
+ //
+ // Don't reject logons from a BDC in our own domain. These logons
+ // support account lockout and authentication of users whose password
+ // has been updated on the PDC but not the BDC. Such pass-thru
+ // requests can only be handled by the PDC of the domain.
+ //
+
+ if ( SecureChannelType != MsvApSecureChannel &&
+ SecureChannelType != ServerSecureChannel ) {
+
+ //
+ // Return STATUS_ACCESS_DENIED to convince the caller to drop the
+ // secure channel to this logon server and reconnect to some other
+ // logon server.
+ //
+ *Authoritative = FALSE;
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Validate the Request.
+ //
+
+ Status = NlpUserValidate( SecureChannelType,
+ LogonLevel,
+ (LPBYTE) LogonInfo,
+ ValidationLevel,
+ (LPBYTE *)&ValidationInformation->ValidationSam,
+ Authoritative );
+
+ if ( !NT_SUCCESS(Status) ) {
+ //
+ // If this is an NT 3.1 client,
+ // map NT 3.5 status codes to their NT 3.1 equivalents.
+ //
+ // The NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT bit is really the wrong bit
+ // to be using, but all NT3.5 clients have it set and all NT3.1 clients
+ // don't, so it'll work for our purposes.
+ //
+
+ if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT) == 0 ) {
+ switch ( Status ) {
+ case STATUS_PASSWORD_MUST_CHANGE:
+ Status = STATUS_PASSWORD_EXPIRED;
+ break;
+ case STATUS_ACCOUNT_LOCKED_OUT:
+ Status = STATUS_ACCOUNT_DISABLED;
+ break;
+ }
+ }
+ goto Cleanup;
+ }
+
+ NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
+ NlAssert( !NT_SUCCESS(Status) || ValidationInformation->ValidationSam != NULL );
+
+
+
+ //
+ // If the validation information is being returned to a client on another
+ // machine, encrypt it before sending it over the wire.
+ //
+
+ if ( SecureChannelType != MsvApSecureChannel ) {
+ NlpEncryptValidationInformation (
+ LogonLevel,
+ ValidationLevel,
+ *((LPBYTE *) ValidationInformation),
+ &SessionInfo );
+ }
+
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup up before returning.
+ //
+
+Cleanup:
+ if ( !NT_SUCCESS(Status) ) {
+ if (ValidationInformation->ValidationSam != NULL) {
+ MIDL_user_free( ValidationInformation->ValidationSam );
+ ValidationInformation->ValidationSam = NULL;
+ }
+ }
+
+
+#if DBG
+ IF_DEBUG( LOGON ) {
+ if ( ComputerName != NULL ) {
+ NlPrint((NL_LOGON,
+ "SamLogon: " FORMAT_LPWSTR " logon of %wZ\\%wZ "
+ "from %wZ (via " FORMAT_LPWSTR ") Returns 0x%lX\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation,
+ ComputerName,
+ Status ));
+ } else {
+ NlPrint((NL_LOGON,
+ "SamLogon: " FORMAT_LPWSTR
+ " logon of %wZ\\%wZ from %wZ Returns 0x%lX\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation,
+ Status ));
+ }
+ }
+#endif // DBG
+
+
+ //
+ // Indicate that the MSV thread has left netlogon.dll
+ //
+
+ EnterCriticalSection( &NlGlobalMsvCritSect );
+ NlGlobalMsvThreadCount --;
+ if ( NlGlobalMsvThreadCount == 0 && !NlGlobalMsvEnabled ) {
+ if ( !SetEvent( NlGlobalMsvTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL, "Cannot set MSV termination event: %lu\n",
+ GetLastError() ));
+ }
+ }
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+
+ return Status;
+}
+
+
+NTSTATUS
+NetrLogonSamLogoff (
+ IN LPWSTR LogonServer OPTIONAL,
+ IN LPWSTR ComputerName OPTIONAL,
+ IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
+ IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
+ IN PNETLOGON_LEVEL LogonInformation
+)
+/*++
+
+Routine Description:
+
+ This function is called by an NT client to process an interactive
+ logoff. It is not called for the network logoff case since the
+ Netlogon service does not maintain any context for network logons.
+
+ This function does the following. It authenticates the request. It
+ updates the logon statistics in the SAM database on whichever machine
+ or domain defines this user account. It updates the logon session
+ table in the primary domain of the machine making the request. And
+ it returns logoff information to the caller.
+
+ This function is called in same scenarios that I_NetLogonSamLogon is
+ called:
+
+ * It is called by the LSA's MSV1_0 authentication package to
+ support LsaApLogonTerminated. In this case, this function is a
+ local function and requires the caller to have SE_TCB privilege.
+ The local Netlogon service will either handle this request
+ directly (if LogonDomainName indicates this request was
+ validated locally) or will forward this request to the
+ appropriate domain controller as documented in sections 2.4 and
+ 2.5.
+
+ * It is called by a Netlogon service on a workstation to a DC in
+ the Primary Domain of the workstation as documented in section
+ 2.4. In this case, this function uses a secure channel set up
+ between the two Netlogon services.
+
+ * It is called by a Netlogon service on a DC to a DC in a trusted
+ domain as documented in section 2.5. In this case, this
+ function uses a secure channel set up between the two Netlogon
+ services.
+
+ When this function is a remote function, it is sent to the DC over a
+ NULL session.
+
+Arguments:
+
+ LogonServer -- Supplies the name of the logon server which logged
+ this user on. This field should be null to indicate this is
+ a call from the MSV1_0 authentication package to the local
+ Netlogon service.
+
+ ComputerName -- Name of the machine making the call. This field
+ should be null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ Authenticator -- supplied by the client. This field should be
+ null to indicate this is a call from the MSV1_0
+ authentication package to the local Netlogon service.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the
+ server. This field should be null to indicate this is a call
+ from the MSV1_0 authentication package to the local Netlogon
+ service.
+
+ LogonLevel -- Specifies the level of information given in
+ LogonInformation.
+
+ LogonInformation -- Specifies the logon domain name, logon Id,
+ user name and workstation name of the user logging off.
+
+Return Value:
+
+--*/
+{
+ NTSTATUS Status;
+ PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
+
+ PSERVER_SESSION ServerSession;
+ NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType;
+ PCLIENT_SESSION ClientSession;
+#if DBG
+ LPWSTR LogonType;
+#endif // DBG
+
+ //
+ // Check the LogonLevel
+ //
+
+ if ( LogonLevel != NetlogonInteractiveInformation ) {
+ return STATUS_INVALID_INFO_CLASS;
+ }
+
+ LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
+ LogonInformation->LogonInteractive;
+
+#if DBG
+ if ( LogonLevel == NetlogonInteractiveInformation ) {
+ LogonType = L"Interactive";
+ } else {
+ LogonType = L"[Unknown]";
+ }
+
+ NlPrint((NL_LOGON,
+ "NetrLogonSamLogoff: " FORMAT_LPWSTR
+ " logoff of %wZ\\%wZ from %wZ Entered\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation ));
+#endif // DBG
+
+
+ //
+ // Sanity check the username and domain name.
+ //
+
+ if ( LogonInfo->UserName.Length == 0 ||
+ LogonInfo->LogonDomainName.Length == 0 ) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // If MSV is calling when the netlogon service isn't running,
+ // tell it so.
+ //
+
+ EnterCriticalSection( &NlGlobalMsvCritSect );
+ if ( !NlGlobalMsvEnabled ) {
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+ return STATUS_NETLOGON_NOT_STARTED;
+ }
+ NlGlobalMsvThreadCount ++;
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+
+
+
+ //
+ // If we've been called from the local msv1_0,
+ // special case the secure channel type.
+ //
+
+ if ( LogonServer == NULL &&
+ ComputerName == NULL &&
+ Authenticator == NULL &&
+ ReturnAuthenticator == NULL ) {
+
+ SecureChannelType = MsvApSecureChannel;
+
+ //
+ // If we're being called from another Netlogon Server,
+ // Verify the secure channel information.
+ //
+
+ } else {
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ Status = STATUS_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Arguments are no longer optional.
+ //
+
+ if ( LogonServer == NULL ||
+ ComputerName == NULL ||
+ Authenticator == NULL ||
+ ReturnAuthenticator == NULL ) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+
+ //
+ // Check the LogonServer name.
+ //
+
+ Status = NlVerifyWorkstation( LogonServer );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+ //
+ // Find the server session entry for this secure channel.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Now verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator(
+ ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ goto Cleanup;
+ }
+
+ SecureChannelType = ServerSession->SsSecureChannelType;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+ }
+
+ //
+ // If this is the domain that logged this user on,
+ // update the logon statistics.
+ //
+
+ if ( RtlEqualDomainName( &LogonInfo->LogonDomainName,
+ &NlGlobalAccountDomainName) ) {
+
+ Status = MsvSamLogoff(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ LogonLevel,
+ LogonInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ //
+ // If this is not the domain that logged this user on,
+ // pass the request to a higher authority.
+ //
+
+ } else {
+
+ //
+ // If this machine is a workstation,
+ // send the request to the Primary Domain.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+
+ Status = NlpUserLogoffHigher(
+ NlGlobalClientSession,
+ LogonLevel,
+ (LPBYTE) LogonInfo );
+
+ //
+ // return more appropriate error
+ //
+
+ if( (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
+ (Status == STATUS_ACCESS_DENIED) ) {
+
+ Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ goto Cleanup;
+
+
+ //
+ // The machine is a Domain Controller.
+ //
+ // If this request was passed to us as a trusted domain request,
+ // There is no higher authority to pass the request to.
+ //
+
+ } else if ( SecureChannelType == TrustedDomainSecureChannel ) {
+
+ Status = STATUS_NO_SUCH_DOMAIN;
+ goto Cleanup;
+
+
+ //
+ // This machine is a Domain Controller.
+ //
+ // This request is either a pass-thru request by a workstation in
+ // our domain, or this request came directly from the MSV
+ // authentication package.
+ //
+ // In either case, pass the request to the trusted domain.
+ //
+
+ } else {
+
+
+ //
+ // Send the request to the appropriate Trusted Domain.
+ //
+ // Find the ClientSession structure for the domain.
+ //
+
+ ClientSession =
+ NlFindNamedClientSession( &LogonInfo->LogonDomainName );
+
+ if ( ClientSession == NULL ) {
+ Status = STATUS_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+
+ Status = NlpUserLogoffHigher(
+ ClientSession,
+ LogonLevel,
+ (LPBYTE) LogonInfo );
+
+ NlUnrefClientSession( ClientSession );
+
+ //
+ // return more appropriate error
+ //
+
+ if( (Status == STATUS_NO_TRUST_LSA_SECRET) ||
+ (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
+ (Status == STATUS_ACCESS_DENIED) ) {
+
+ Status = STATUS_TRUSTED_DOMAIN_FAILURE;
+ }
+
+ }
+ }
+
+Cleanup:
+
+ //
+ // If the request failed, be carefull to not leak authentication
+ // information.
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ) {
+ if ( ReturnAuthenticator != NULL ) {
+ RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
+ }
+
+ }
+
+
+#if DBG
+ NlPrint((NL_LOGON,
+ "NetrLogonSamLogoff: " FORMAT_LPWSTR
+ " logoff of %wZ\\%wZ from %wZ returns %lX\n",
+ LogonType,
+ &LogonInfo->LogonDomainName,
+ &LogonInfo->UserName,
+ &LogonInfo->Workstation,
+ Status ));
+#endif // DBG
+
+ //
+ // Indicate that the MSV thread has left netlogon.dll
+ //
+
+ EnterCriticalSection( &NlGlobalMsvCritSect );
+ NlGlobalMsvThreadCount --;
+ if ( NlGlobalMsvThreadCount == 0 && !NlGlobalMsvEnabled ) {
+ if ( !SetEvent( NlGlobalMsvTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL, "Cannot set MSV termination event: %lu\n",
+ GetLastError() ));
+ }
+ }
+ LeaveCriticalSection( &NlGlobalMsvCritSect );
+
+ return Status;
+}
+
+
+NET_API_STATUS
+NetrGetDCName (
+ IN LPWSTR ServerName OPTIONAL,
+ IN LPWSTR DomainName OPTIONAL,
+ OUT LPWSTR *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of the primary domain controller for a domain.
+
+Arguments:
+
+ ServerName - name of remote server (null for local)
+
+ DomainName - name of domain (null for primary)
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of the PDC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ NERR_Success - Success. Buffer contains PDC name prefixed by \\.
+ NERR_DCNotFound No DC found for this domain.
+ ERROR_INVALID_NAME Badly formed domain name
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ UNREFERENCED_PARAMETER( ServerName );
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return ERROR_NOT_SUPPORTED;
+ }
+
+ //
+ // Simply call the API which handles the local case specially.
+ //
+
+ NetStatus = NetGetDCName( NULL, DomainName, (LPBYTE *)Buffer );
+
+ return NetStatus;
+}
diff --git a/private/net/svcdlls/logonsrv/server/logonsrv.h b/private/net/svcdlls/logonsrv/server/logonsrv.h
new file mode 100644
index 000000000..fd152e82a
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/logonsrv.h
@@ -0,0 +1,392 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ logonsrv.h
+
+Abstract:
+
+ Netlogon service internal constants and definitions.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Revision History:
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+ 09-Apr-1992 JohnRo
+ Prepare for WCHAR.H (_wcsicmp vs _wcscmpi, etc).
+
+--*/
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Common include files needed by ALL netlogon server files
+//
+////////////////////////////////////////////////////////////////////////////
+
+#if ( _MSC_VER >= 800 )
+#pragma warning ( 3 : 4100 ) // enable "Unreferenced formal parameter"
+#pragma warning ( 3 : 4219 ) // enable "trailing ',' used for variable argument list"
+#endif
+
+#include <nt.h> // LARGE_INTEGER definition
+#include <ntrtl.h> // LARGE_INTEGER definition
+#include <nturtl.h> // LARGE_INTEGER definition
+#include <ntlsa.h> // Needed by lsrvdata.h
+
+#define NOMINMAX // Avoid redefinition of min and max in stdlib.h
+#include <rpc.h> // Needed by logon.h
+#include <logon_s.h> // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
+
+#include <winbase.h>
+
+#include <lmerrlog.h> // NELOG_*
+#include <lmsname.h> // Needed for NETLOGON service name
+#include <winsvc.h> // Needed for new service controller APIs
+#include <logonp.h> // NetpLogon routines
+#include <samrpc.h> // Needed by lsrvdata.h and logonsrv.h
+#include <samisrv.h> // SamIFree routines
+#include "changelg.h" // Change log support
+#include "chutil.h" // Change log support
+#include <lsarpc.h> // Needed by lsrvdata.h and logonsrv.h
+#include <lsaisrv.h> // LsaI routines
+#include "ssiinit.h" // Misc global definitions
+#include <icanon.h> // NAMETYPE_* defines
+#include "lsrvdata.h" // Globals
+#include <debugfmt.h> // FORMAT_*
+#include <netlib.h> // NetpCopy...
+#include <netlibnt.h> // NetpNtStatusToApiStatus
+#include "nldebug.h" // Netlogon debugging
+#include "nlp.h" // Nlp routines
+#include <stdlib.h> // wcs routines
+
+
+
+//
+// On x86, allow bad alignment in debug statements.
+//
+
+#ifdef _X86_
+#define BAD_ALIGNMENT
+#endif // _X86_
+
+
+
+
+
+#define NETLOGON_SCRIPTS_SHARE TEXT( "NETLOGON" )
+#define IPC_SHARE TEXT( "IPC$" )
+
+#define THREAD_STACKSIZE 8192
+#define MAX_LOGONREQ_COUNT 3
+
+
+#define NETLOGON_INSTALL_WAIT 30000 // 30 secs
+
+
+
+////////////////////////////////////////////////////////////////////////
+//
+// NlNameCompare
+//
+// I_NetNameCompare but always takes UNICODE strings
+//
+////////////////////////////////////////////////////////////////////////
+
+#define NlNameCompare( _name1, _name2, _nametype ) \
+ I_NetNameCompare(NULL, (_name1), (_name2), (_nametype), 0 )
+
+
+//
+// Exit codes for NlExit
+//
+
+typedef enum {
+ DontLogError,
+ LogError,
+ LogErrorAndNtStatus,
+ LogErrorAndNetStatus
+} NL_EXIT_CODE;
+
+////////////////////////////////////////////////////////////////////////
+//
+// Procedure Forwards
+//
+////////////////////////////////////////////////////////////////////////
+
+//
+// error.c
+//
+
+NET_API_STATUS
+NlCleanup(
+ VOID
+ );
+
+VOID
+NlExit(
+ IN DWORD ServiceError,
+ IN DWORD Data,
+ IN NL_EXIT_CODE ExitCode,
+ IN LPWSTR ErrorString
+ );
+
+BOOL
+GiveInstallHints(
+ IN BOOL Started
+ );
+
+VOID
+NlControlHandler(
+ IN DWORD opcode
+ );
+
+VOID
+RaiseAlert(
+ IN DWORD alert_no,
+ IN LPWSTR *string_array
+ );
+
+//
+// Nlparse.c
+//
+
+BOOL
+Nlparse(
+ VOID
+ );
+
+//
+// announce.c
+//
+
+VOID
+NlRemovePendingBdc(
+ IN PSERVER_SESSION ServerSession
+ );
+
+VOID
+NlPrimaryAnnouncementFinish(
+ IN PSERVER_SESSION ServerSession,
+ IN DWORD DatabaseId,
+ IN PLARGE_INTEGER SerialNumber
+ );
+
+VOID
+NlPrimaryAnnouncementTimeout(
+ VOID
+ );
+
+VOID
+NlPrimaryAnnouncement(
+ IN DWORD AnnounceFlags
+ );
+
+#define ANNOUNCE_FORCE 0x01
+#define ANNOUNCE_CONTINUE 0x02
+#define ANNOUNCE_IMMEDIATE 0x04
+
+
+VOID
+NlLanmanPrimaryAnnouncement(
+ VOID
+ );
+
+VOID
+NlAnnouncePrimaryStart(
+ VOID
+ );
+
+
+
+//
+// lsrvutil.c
+//
+
+BOOL
+NlSetPrimaryName(
+ IN LPWSTR PrimaryName
+ );
+
+BOOL
+NlResetFirstTimeFullSync(
+ IN DWORD DBIndex
+ );
+
+NTSTATUS
+NlSessionSetup(
+ IN OUT PCLIENT_SESSION ClientSession
+ );
+
+BOOLEAN
+NlTimeHasElapsed(
+ IN LARGE_INTEGER StartTime,
+ IN DWORD Timeout
+ );
+
+BOOLEAN
+NlTimeToReauthenticate(
+ IN PCLIENT_SESSION ClientSession
+ );
+
+NTSTATUS
+NlNewSessionSetup(
+ IN LPWSTR primary
+ );
+
+NTSTATUS
+NlAuthenticate(
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential,
+ IN ULONG NegotiatedFlags
+ );
+
+NET_API_STATUS
+NlCreateShare(
+ LPWSTR SharePath,
+ LPWSTR ShareName
+ );
+
+NTSTATUS
+NlForceStartupSync(
+ PDB_INFO DBInfo
+ );
+
+BOOL
+NlCheckUpdateNotices(
+ IN PNETLOGON_DB_CHANGE UasChange,
+ IN DWORD UasChangeSize
+ );
+
+VOID
+NlStopReplicator(
+ VOID
+ );
+
+BOOL
+IsReplicatorRunning(
+ VOID
+ );
+
+BOOL
+NlStartReplicatorThread(
+ IN DWORD RandomSleep
+ );
+
+NTSTATUS
+NlSamOpenNamedUser(
+ IN LPWSTR UserName,
+ OUT SAMPR_HANDLE *UserHandle OPTIONAL,
+ OUT PULONG UserId OPTIONAL
+ );
+
+NTSTATUS
+NlChangePassword(
+ PCLIENT_SESSION ClientSession
+ );
+
+NTSTATUS
+NlCheckMachineAccount(
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType
+ );
+
+NTSTATUS
+NlOpenSecret(
+ IN PCLIENT_SESSION ClientSession,
+ IN ULONG DesiredAccess,
+ OUT PLSAPR_HANDLE SecretHandle
+ );
+
+NTSTATUS
+NlGetUserPriv(
+ IN ULONG GroupCount,
+ IN PGROUP_MEMBERSHIP Groups,
+ IN ULONG UserRelativeId,
+ OUT LPDWORD Priv,
+ OUT LPDWORD AuthFlags
+ );
+
+//
+// netlogon.c
+//
+
+int
+NlNetlogonMain(
+ IN DWORD argc,
+ IN LPWSTR *argv
+ );
+
+VOID
+NlScavenger(
+ IN LPVOID ScavengerParam
+ );
+
+BOOL
+IsScavengerRunning(
+ VOID
+ );
+
+VOID
+NlStopScavenger(
+ VOID
+ );
+
+BOOL
+NlStartScavengerThread(
+ );
+
+//
+// mailslot.c
+//
+
+BOOL
+NlBrowserOpen(
+ VOID
+ );
+
+VOID
+NlBrowserClose(
+ VOID
+ );
+
+NTSTATUS
+NlBrowserSendDatagram(
+ IN LPSTR OemServerName,
+ IN LPWSTR TransportName,
+ IN LPSTR OemMailslotName,
+ IN PVOID Buffer,
+ IN ULONG BufferSize
+ );
+
+VOID
+NlBrowserAddName(
+ VOID
+ );
+
+VOID
+NlMailslotPostRead(
+ IN BOOLEAN IgnoreDuplicatesOfPreviousMessage
+ );
+
+BOOL
+NlMailslotOverlappedResult(
+ OUT LPBYTE *Message,
+ OUT PULONG BytesRead,
+ OUT LPWSTR *Transport,
+ OUT PBOOLEAN IgnoreDuplicatesOfPreviousMessage
+ );
+
+//
+// oldstub.c
+//
+
+void _fgs__NETLOGON_DELTA_ENUM (NETLOGON_DELTA_ENUM * _source);
diff --git a/private/net/svcdlls/logonsrv/server/lsarepl.c b/private/net/svcdlls/logonsrv/server/lsarepl.c
new file mode 100644
index 000000000..ba140f4d6
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/lsarepl.c
@@ -0,0 +1,2184 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ lsarepl.c
+
+Abstract:
+
+ Low level LSA Replication functions.
+
+Author:
+
+ 06-Apr-1992 (madana)
+ Created for LSA replication.
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <align.h>
+#include <logonsrv.h> // Include files common to entire service
+
+#include <replutil.h>
+#include <lsarepl.h>
+
+
+NTSTATUS
+NlPackLsaPolicy(
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize )
+/*++
+
+Routine Description:
+
+ Pack a description of the LSA policy info into the specified buffer.
+
+Arguments:
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ ULONG i;
+
+ PLSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+
+ PLSAPR_POLICY_INFORMATION PolicyAuditLogInfo = NULL;
+ PLSAPR_POLICY_INFORMATION PolicyAuditEventsInfo = NULL;
+ PLSAPR_POLICY_INFORMATION PolicyPrimaryDomainInfo = NULL;
+ PLSAPR_POLICY_INFORMATION PolicyDefaultQuotaInfo = NULL;
+ PLSAPR_POLICY_INFORMATION PolicyModificationInfo = NULL;
+
+ PNETLOGON_DELTA_POLICY DeltaPolicy = NULL;
+
+ DEFPACKTIMER;
+ DEFLSATIMER;
+
+ INITPACKTIMER;
+ INITLSATIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Policy Object\n"));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeLsaPolicy;
+ Delta->DeltaUnion.DeltaPolicy = NULL;
+
+ QUERY_LSA_SECOBJ_INFO(DBInfo->DBHandle);
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyAuditLogInformation,
+ &PolicyAuditLogInfo);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ PolicyAuditLogInfo = NULL;
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyAuditEventsInformation,
+ &PolicyAuditEventsInfo);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ PolicyAuditEventsInfo = NULL;
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyPrimaryDomainInformation,
+ &PolicyPrimaryDomainInfo);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ PolicyPrimaryDomainInfo = NULL;
+ goto Cleanup;
+ }
+
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyDefaultQuotaInformation,
+ &PolicyDefaultQuotaInfo);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ PolicyDefaultQuotaInfo = NULL;
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyModificationInformation,
+ &PolicyModificationInfo);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ PolicyModificationInfo = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Fill in the delta structure
+ //
+
+ //
+ // copy SID info (There is only one policy database. It has no SID).
+ //
+
+ Delta->DeltaID.Sid = NULL;
+
+ //
+ // allocate delta buffer
+ //
+
+ DeltaPolicy = (PNETLOGON_DELTA_POLICY)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_POLICY) );
+
+ if( DeltaPolicy == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaPolicy, sizeof(NETLOGON_DELTA_POLICY) );
+
+ Delta->DeltaUnion.DeltaPolicy = DeltaPolicy;
+ *BufferSize += sizeof(NETLOGON_DELTA_POLICY);
+
+ DeltaPolicy->MaximumLogSize =
+ PolicyAuditLogInfo->PolicyAuditLogInfo.MaximumLogSize;
+ DeltaPolicy->AuditRetentionPeriod;
+ PolicyAuditLogInfo->PolicyAuditLogInfo.AuditRetentionPeriod;
+
+ DeltaPolicy->AuditingMode =
+ PolicyAuditEventsInfo->
+ PolicyAuditEventsInfo.AuditingMode;
+ DeltaPolicy->MaximumAuditEventCount =
+ PolicyAuditEventsInfo->
+ PolicyAuditEventsInfo.MaximumAuditEventCount;
+
+ *BufferSize += NlCopyData(
+ (LPBYTE *)&(PolicyAuditEventsInfo->
+ PolicyAuditEventsInfo.EventAuditingOptions),
+ (LPBYTE *)&(DeltaPolicy->EventAuditingOptions),
+ (DeltaPolicy->MaximumAuditEventCount + 1) *
+ sizeof(ULONG));
+
+ // Tell the BDC to 'set' these bits and not just 'or' them in to the current ones
+ for ( i=0; i<DeltaPolicy->MaximumAuditEventCount; i++ ) {
+ DeltaPolicy->EventAuditingOptions[i] |= POLICY_AUDIT_EVENT_NONE;
+ }
+
+ //
+ // sanitity check, EventAuditingOptions size is ULONG size.
+ //
+
+ NlAssert(sizeof(*(PolicyAuditEventsInfo->
+ PolicyAuditEventsInfo.EventAuditingOptions)) ==
+ sizeof(ULONG) );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&PolicyPrimaryDomainInfo->
+ PolicyPrimaryDomainInfo.Name,
+ &DeltaPolicy->PrimaryDomainName );
+
+ *BufferSize += NlCopyData(
+ (LPBYTE *)&(PolicyPrimaryDomainInfo->
+ PolicyPrimaryDomainInfo.Sid),
+ (LPBYTE *)&(DeltaPolicy->PrimaryDomainSid),
+ RtlLengthSid((PSID)(PolicyPrimaryDomainInfo->
+ PolicyPrimaryDomainInfo.Sid) ));
+
+ DeltaPolicy->QuotaLimits.PagedPoolLimit =
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.PagedPoolLimit;
+ DeltaPolicy->QuotaLimits.NonPagedPoolLimit =
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.NonPagedPoolLimit;
+ DeltaPolicy->QuotaLimits.MinimumWorkingSetSize =
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.MinimumWorkingSetSize;
+ DeltaPolicy->QuotaLimits.MaximumWorkingSetSize =
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.MaximumWorkingSetSize;
+ DeltaPolicy->QuotaLimits.PagefileLimit =
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.PagefileLimit;
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ PolicyDefaultQuotaInfo->PolicyDefaultQuotaInfo.QuotaLimits.TimeLimit,
+ DeltaPolicy->QuotaLimits.TimeLimit );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ PolicyModificationInfo->PolicyModificationInfo.ModifiedId,
+ DeltaPolicy->ModifiedId );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ PolicyModificationInfo->PolicyModificationInfo.DatabaseCreationTime,
+ DeltaPolicy->DatabaseCreationTime );
+
+
+ DELTA_SECOBJ_INFO(DeltaPolicy);
+
+ INIT_PLACE_HOLDER(DeltaPolicy);
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTLSATIMER;
+
+ if ( SecurityDescriptor != NULL ) {
+ LsaIFree_LSAPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if ( PolicyAuditLogInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAuditLogInformation,
+ PolicyAuditLogInfo );
+ }
+
+ if ( PolicyAuditEventsInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAuditEventsInformation,
+ PolicyAuditEventsInfo );
+ }
+
+ if ( PolicyPrimaryDomainInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ PolicyPrimaryDomainInfo );
+ }
+
+ if ( PolicyDefaultQuotaInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyDefaultQuotaInformation,
+ PolicyDefaultQuotaInfo );
+ }
+
+ if ( PolicyModificationInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyModificationInformation,
+ PolicyModificationInfo );
+ }
+
+ STOPLSATIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack POLICY object:\n"));
+ PRINTPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlPackLsaTDomain(
+ IN PSID Sid,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified trusted domain info into the
+ specified buffer.
+
+Arguments:
+
+ Sid - The SID of the trusted domain.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSAPR_HANDLE TrustedDomainHandle = NULL;
+ PLSAPR_TRUSTED_DOMAIN_INFO TrustedControllersInfo = NULL;
+ PLSAPR_TRUSTED_DOMAIN_INFO TrustedDomainNameInfo = NULL;
+ PLSAPR_TRUSTED_DOMAIN_INFO TrustedPosixOffsetInfo = NULL;
+
+ PLSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+
+ PNETLOGON_DELTA_TRUSTED_DOMAINS DeltaTDomain = NULL;
+
+ DWORD i;
+ DWORD Entries;
+ DWORD Size = 0;
+ PLSAPR_UNICODE_STRING UnicodeControllerName;
+
+ DEFPACKTIMER;
+ DEFLSATIMER;
+
+ INITPACKTIMER;
+ INITLSATIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Trusted Domain Object\n"));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeLsaTDomain;
+ Delta->DeltaID.Sid = NULL;
+ Delta->DeltaUnion.DeltaTDomains = NULL;
+
+ //
+ // open trusted domain
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenTrustedDomain(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &TrustedDomainHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ TrustedDomainHandle = NULL;
+ goto Cleanup;
+ }
+
+ QUERY_LSA_SECOBJ_INFO(TrustedDomainHandle);
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInfoTrustedDomain(
+ TrustedDomainHandle,
+ TrustedControllersInformation,
+ &TrustedControllersInfo );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ TrustedControllersInfo = NULL;
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInfoTrustedDomain(
+ TrustedDomainHandle,
+ TrustedDomainNameInformation,
+ &TrustedDomainNameInfo );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ TrustedDomainNameInfo = NULL;
+ goto Cleanup;
+ }
+
+ NlPrint((NL_SYNC_MORE,
+ "\t Trusted Domain Object name %wZ\n",
+ (PUNICODE_STRING)&TrustedDomainNameInfo->
+ TrustedDomainNameInfo.Name ));
+
+ STARTLSATIMER;
+
+ Status = LsarQueryInfoTrustedDomain(
+ TrustedDomainHandle,
+ TrustedPosixOffsetInformation,
+ &TrustedPosixOffsetInfo );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ TrustedPosixOffsetInfo = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Fill in the delta structure
+ //
+
+ //
+ // copy SID info
+ //
+
+ Delta->DeltaID.Sid = MIDL_user_allocate( RtlLengthSid(Sid) );
+
+
+ if( Delta->DeltaID.Sid == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( Delta->DeltaID.Sid, Sid, RtlLengthSid(Sid) );
+
+ //
+ // allocate delta buffer
+ //
+
+ DeltaTDomain = (PNETLOGON_DELTA_TRUSTED_DOMAINS)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_TRUSTED_DOMAINS) );
+
+ if( DeltaTDomain == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaTDomain, sizeof(NETLOGON_DELTA_TRUSTED_DOMAINS) );
+
+ Delta->DeltaUnion.DeltaTDomains = DeltaTDomain;
+ *BufferSize += sizeof(NETLOGON_DELTA_TRUSTED_DOMAINS);
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&TrustedDomainNameInfo->
+ TrustedDomainNameInfo.Name,
+ &DeltaTDomain->DomainName );
+
+ Entries = DeltaTDomain->NumControllerEntries =
+ TrustedControllersInfo->TrustedControllersInfo.Entries;
+
+ //
+ // compute size of controller names.
+ //
+
+ for( i = 0, UnicodeControllerName
+ = TrustedControllersInfo->TrustedControllersInfo.Names;
+ i < Entries;
+ i++, UnicodeControllerName++ ) {
+
+ Size += (sizeof(LSAPR_UNICODE_STRING) +
+ UnicodeControllerName->Length);
+ }
+
+ *BufferSize += NlCopyData(
+ (LPBYTE *)&(TrustedControllersInfo->
+ TrustedControllersInfo.Names),
+ (LPBYTE *)&(DeltaTDomain->ControllerNames),
+ Size);
+
+ DELTA_SECOBJ_INFO(DeltaTDomain);
+ INIT_PLACE_HOLDER(DeltaTDomain);
+
+ //
+ // send Posix Offset info across using place holder.
+ //
+
+ DeltaTDomain->DummyLong1 =
+ TrustedPosixOffsetInfo->TrustedPosixOffsetInfo.Offset;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTLSATIMER;
+
+ if ( TrustedDomainHandle != NULL ) {
+ LsarClose( &TrustedDomainHandle );
+ }
+
+ if ( SecurityDescriptor != NULL ) {
+ LsaIFree_LSAPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if ( TrustedControllersInfo != NULL ) {
+ LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
+ TrustedControllersInformation,
+ TrustedControllersInfo );
+ }
+
+ if ( TrustedDomainNameInfo != NULL ) {
+ LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
+ TrustedDomainNameInformation,
+ TrustedDomainNameInfo );
+ }
+
+ if ( TrustedPosixOffsetInfo != NULL ) {
+ LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
+ TrustedPosixOffsetInformation,
+ TrustedPosixOffsetInfo );
+ }
+
+ STOPLSATIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack TDOMAIN object:\n"));
+ PRINTPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlPackLsaAccount(
+ IN PSID Sid,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified LSA account info into the
+ specified buffer.
+
+Arguments:
+
+ Sid - The SID of the LSA account.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+ SessionInfo: Info describing BDC that's calling us
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PLSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+
+ PNETLOGON_DELTA_ACCOUNTS DeltaAccount = NULL;
+ LSAPR_HANDLE AccountHandle = NULL;
+
+ PLSAPR_PRIVILEGE_SET Privileges = NULL;
+ QUOTA_LIMITS QuotaLimits;
+ ULONG SystemAccessFlags;
+
+ PULONG PrivilegeAttributes;
+ PUNICODE_STRING PrivilegeNames;
+ LUID MachineAccountPrivilegeLuid;
+ DWORD CopiedPrivilegeCount;
+
+ DWORD i;
+ DWORD Size;
+
+ DEFPACKTIMER;
+ DEFLSATIMER;
+
+ INITPACKTIMER;
+ INITLSATIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Lsa Account Object\n"));
+
+ *BufferSize = 0;
+ MachineAccountPrivilegeLuid = RtlConvertLongToLuid(SE_MACHINE_ACCOUNT_PRIVILEGE);
+
+ Delta->DeltaType = AddOrChangeLsaAccount;
+ Delta->DeltaID.Sid = NULL;
+ Delta->DeltaUnion.DeltaAccounts = NULL;
+
+ //
+ // open lsa account
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenAccount(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &AccountHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ AccountHandle = NULL;
+ goto Cleanup;
+ }
+
+ QUERY_LSA_SECOBJ_INFO(AccountHandle);
+
+ STARTLSATIMER;
+
+ Status = LsarEnumeratePrivilegesAccount(
+ AccountHandle,
+ &Privileges );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ Privileges = NULL;
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarGetQuotasForAccount(
+ AccountHandle,
+ &QuotaLimits );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarGetSystemAccessAccount(
+ AccountHandle,
+ &SystemAccessFlags );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // Fill in the delta structure
+ //
+
+ //
+ // copy SID info
+ //
+
+ Delta->DeltaID.Sid = MIDL_user_allocate( RtlLengthSid(Sid) );
+
+
+ if( Delta->DeltaID.Sid == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( Delta->DeltaID.Sid, Sid, RtlLengthSid(Sid) );
+
+ //
+ // allocate delta buffer
+ //
+
+ DeltaAccount = (PNETLOGON_DELTA_ACCOUNTS)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ACCOUNTS) );
+
+ if( DeltaAccount == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaAccount, sizeof(NETLOGON_DELTA_ACCOUNTS) );
+
+ Delta->DeltaUnion.DeltaAccounts = DeltaAccount;
+ *BufferSize += sizeof(NETLOGON_DELTA_ACCOUNTS);
+
+ DeltaAccount->PrivilegeControl = Privileges->Control;
+
+ DeltaAccount->PrivilegeEntries = 0;
+ DeltaAccount->PrivilegeAttributes = NULL;
+ DeltaAccount->PrivilegeNames = NULL;
+
+ Size = Privileges->PrivilegeCount * sizeof(ULONG);
+
+ PrivilegeAttributes = MIDL_user_allocate( Size );
+
+ if( PrivilegeAttributes == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ DeltaAccount->PrivilegeAttributes = PrivilegeAttributes;
+ *BufferSize += Size;
+
+ Size = Privileges->PrivilegeCount * sizeof(UNICODE_STRING);
+
+ PrivilegeNames = MIDL_user_allocate( Size );
+
+ if( PrivilegeNames == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ DeltaAccount->PrivilegeNames = PrivilegeNames;
+ *BufferSize += Size;
+
+ //
+ // now fill up Privilege Attributes and Names
+ //
+
+ CopiedPrivilegeCount = 0;
+ for( i = 0; i < Privileges->PrivilegeCount; i++ ) {
+
+ //
+ // Don't replicate SeMachineAccount privilege to NT 1.0. It can't handle it.
+ // (Use the SUPPORTS_ACCOUNT_LOCKOUT bit so we don't have to consume
+ // another bit.)
+ //
+ if ( (SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT) ||
+ (!RtlEqualLuid((PLUID)(&Privileges->Privilege[i].Luid),
+ &MachineAccountPrivilegeLuid ))) {
+
+ PLSAPR_UNICODE_STRING PrivName = NULL;
+
+ *PrivilegeAttributes = Privileges->Privilege[i].Attributes;
+
+
+ //
+ // convert LUID to Name
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarLookupPrivilegeName(
+ DBInfo->DBHandle,
+ (PLUID)&Privileges->Privilege[i].Luid,
+ &PrivName );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)PrivName,
+ PrivilegeNames );
+
+ LsaIFree_LSAPR_UNICODE_STRING( PrivName );
+ CopiedPrivilegeCount ++;
+ PrivilegeAttributes++;
+ PrivilegeNames++;
+ } else {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackLsaAccount: ignored privilege %ld %ld\n",
+ (PLUID)(&Privileges->Privilege[i].Luid)->HighPart,
+ (PLUID)(&Privileges->Privilege[i].Luid)->LowPart ));
+ }
+ }
+ DeltaAccount->PrivilegeEntries = CopiedPrivilegeCount;
+
+ DeltaAccount->QuotaLimits.PagedPoolLimit = QuotaLimits.PagedPoolLimit;
+ DeltaAccount->QuotaLimits.NonPagedPoolLimit = QuotaLimits.NonPagedPoolLimit;
+ DeltaAccount->QuotaLimits.MinimumWorkingSetSize = QuotaLimits.MinimumWorkingSetSize;
+ DeltaAccount->QuotaLimits.MaximumWorkingSetSize = QuotaLimits.MaximumWorkingSetSize;
+ DeltaAccount->QuotaLimits.PagefileLimit = QuotaLimits.PagefileLimit;
+ NEW_TO_OLD_LARGE_INTEGER(
+ QuotaLimits.TimeLimit,
+ DeltaAccount->QuotaLimits.TimeLimit );
+
+ DeltaAccount->SystemAccessFlags = SystemAccessFlags;
+
+ DELTA_SECOBJ_INFO(DeltaAccount);
+ INIT_PLACE_HOLDER(DeltaAccount);
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTLSATIMER;
+
+ if ( AccountHandle != NULL ) {
+ LsarClose( &AccountHandle );
+ }
+
+ if ( SecurityDescriptor != NULL ) {
+ LsaIFree_LSAPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if ( Privileges != NULL ) {
+ LsaIFree_LSAPR_PRIVILEGE_SET( Privileges );
+ }
+
+ STOPLSATIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack LSAACCOUNT object:\n"));
+ PRINTPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+
+}
+
+
+
+NTSTATUS
+NlPackLsaSecret(
+ IN PUNICODE_STRING Name,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified LSA secret info into the
+ specified buffer.
+
+Arguments:
+
+ Name - Name of the secret.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+ SessionInfo: Information shared between BDC and PDC
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PLSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+
+ LSAPR_HANDLE SecretHandle = NULL;
+
+ PNETLOGON_DELTA_SECRET DeltaSecret = NULL;
+
+ PLSAPR_CR_CIPHER_VALUE CurrentValue = NULL;
+ PLSAPR_CR_CIPHER_VALUE OldValue = NULL;
+ LARGE_INTEGER CurrentValueSetTime;
+ LARGE_INTEGER OldValueSetTime;
+
+ DEFPACKTIMER;
+ DEFLSATIMER;
+
+ INITPACKTIMER;
+ INITLSATIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Secret Object: %wZ\n", Name));
+
+ //
+ // we should be packing only GLOBAL secrets
+ //
+
+ NlAssert(
+ (Name->Length / sizeof(WCHAR) >
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) &&
+ (_wcsnicmp( Name->Buffer,
+ LSA_GLOBAL_SECRET_PREFIX,
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0) );
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeLsaSecret;
+ Delta->DeltaID.Name = NULL;
+ Delta->DeltaUnion.DeltaPolicy = NULL;
+
+ //
+ // open lsa account
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenSecret(
+ DBInfo->DBHandle,
+ (PLSAPR_UNICODE_STRING)Name,
+ 0,
+ &SecretHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ SecretHandle = NULL;
+ goto Cleanup;
+ }
+
+ QUERY_LSA_SECOBJ_INFO(SecretHandle);
+
+ STARTLSATIMER;
+
+ Status = LsarQuerySecret(
+ SecretHandle,
+ &CurrentValue,
+ &CurrentValueSetTime,
+ &OldValue,
+ &OldValueSetTime );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ CurrentValue = NULL;
+ OldValue = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Fill in the delta structure
+ //
+
+ //
+ // copy ID field
+ //
+
+ Delta->DeltaID.Name =
+ MIDL_user_allocate( Name->Length + sizeof(WCHAR) );
+
+ if( Delta->DeltaID.Name == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ wcsncpy( Delta->DeltaID.Name,
+ Name->Buffer,
+ Name->Length / sizeof(WCHAR) );
+
+ //
+ // terminate string
+ //
+
+ Delta->DeltaID.Name[ Name->Length / sizeof(WCHAR) ] = L'\0';
+
+
+ DeltaSecret = (PNETLOGON_DELTA_SECRET)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_SECRET) );
+
+ if( DeltaSecret == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaSecret, sizeof(NETLOGON_DELTA_SECRET) );
+
+ Delta->DeltaUnion.DeltaSecret = DeltaSecret;
+ *BufferSize += sizeof(NETLOGON_DELTA_SECRET);
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ CurrentValueSetTime,
+ DeltaSecret->CurrentValueSetTime );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ OldValueSetTime,
+ DeltaSecret->OldValueSetTime );
+
+ if( CurrentValue != NULL && CurrentValue->Buffer != NULL && CurrentValue->Length != 0) {
+
+ //
+ // Copy the secret into an allocated buffer and encrypt it in place.
+ // Don't use the LSA's buffer since it a ALLOCATE_ALL_NODES.
+ //
+
+ DeltaSecret->CurrentValue.Buffer =
+ MIDL_user_allocate( CurrentValue->Length );
+
+ if( DeltaSecret->CurrentValue.Buffer == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ DeltaSecret->CurrentValue.Length =
+ DeltaSecret->CurrentValue.MaximumLength = CurrentValue->Length;
+ RtlCopyMemory( DeltaSecret->CurrentValue.Buffer,
+ CurrentValue->Buffer,
+ CurrentValue->Length );
+
+
+ //
+ // secret values are encrypted using session keys.
+ //
+
+ Status = NlEncryptSensitiveData(
+ (PCRYPT_BUFFER) &DeltaSecret->CurrentValue,
+ SessionInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ } else {
+
+ DeltaSecret->CurrentValue.Length = 0;
+ DeltaSecret->CurrentValue.MaximumLength = 0;
+ DeltaSecret->CurrentValue.Buffer = NULL;
+ }
+
+ *BufferSize += DeltaSecret->CurrentValue.MaximumLength;
+
+ if( OldValue != NULL && OldValue->Buffer != NULL && OldValue->Length != 0 ) {
+
+ //
+ // Copy the secret into an allocated buffer and encrypt it in place.
+ // Don't use the LSA's buffer since it a ALLOCATE_ALL_NODES.
+ //
+
+ DeltaSecret->OldValue.Buffer =
+ MIDL_user_allocate( OldValue->Length );
+
+ if( DeltaSecret->OldValue.Buffer == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ DeltaSecret->OldValue.Length =
+ DeltaSecret->OldValue.MaximumLength = OldValue->Length;
+ RtlCopyMemory( DeltaSecret->OldValue.Buffer,
+ OldValue->Buffer,
+ OldValue->Length );
+
+
+ //
+ // secret values are encrypted using session keys.
+ //
+
+ Status = NlEncryptSensitiveData(
+ (PCRYPT_BUFFER) &DeltaSecret->OldValue,
+ SessionInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ } else {
+
+ DeltaSecret->OldValue.Length = 0;
+ DeltaSecret->OldValue.MaximumLength = 0;
+ DeltaSecret->OldValue.Buffer = NULL;
+ }
+
+ *BufferSize += DeltaSecret->OldValue.MaximumLength;
+
+ DELTA_SECOBJ_INFO(DeltaSecret);
+ INIT_PLACE_HOLDER(DeltaSecret);
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTLSATIMER;
+
+ if ( SecretHandle != NULL ) {
+ LsarClose( &SecretHandle );
+ }
+
+ if ( SecurityDescriptor != NULL ) {
+ LsaIFree_LSAPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if( CurrentValue != NULL ) {
+ LsaIFree_LSAPR_CR_CIPHER_VALUE( CurrentValue );
+ }
+
+ if( OldValue != NULL ) {
+ LsaIFree_LSAPR_CR_CIPHER_VALUE( OldValue );
+ }
+
+ STOPLSATIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack SECRET object:\n"));
+ PRINTPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+
+}
+
+
+NTSTATUS
+NlUnpackLsaPolicy(
+ IN PNETLOGON_DELTA_POLICY DeltaLsaPolicy,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Set the LSA policy info to look like the specified buffer.
+
+Arguments:
+
+ DeltaLsaPolicy - Description of the LSA policy.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ ULONG i;
+
+ LSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+
+ LSAPR_POLICY_INFORMATION PolicyInformation;
+
+ DEFUNPACKTIMER;
+ DEFLSATIMER;
+
+ INITUNPACKTIMER;
+ INITLSATIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Policy Object\n"));
+
+ SET_LSA_SECOBJ_INFO(DeltaLsaPolicy, DBInfo->DBHandle);
+
+ //
+ // Set the audit log information
+ //
+ // ?? Query PolicyAuditLogInformation before set.
+ //
+
+
+ PolicyInformation.PolicyAuditLogInfo.AuditLogPercentFull = 0;
+ // ignored for set
+ PolicyInformation.PolicyAuditLogInfo.MaximumLogSize =
+ DeltaLsaPolicy->MaximumLogSize;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaLsaPolicy->AuditRetentionPeriod,
+ PolicyInformation.PolicyAuditLogInfo.AuditRetentionPeriod );
+
+ PolicyInformation.PolicyAuditLogInfo.AuditLogFullShutdownInProgress = 0;
+ // ignored for set
+ // PolicyInformation.PolicyAuditLogInfo.TimeToShutdown = 0;
+ // ignored for set
+
+ STARTLSATIMER;
+
+ Status = LsarSetInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyAuditLogInformation,
+ &PolicyInformation );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // set audit event info.
+ //
+
+ // 'set' these bits and not just 'or' them in to the current ones
+ for ( i=0; i<DeltaLsaPolicy->MaximumAuditEventCount; i++ ) {
+ DeltaLsaPolicy->EventAuditingOptions[i] |= POLICY_AUDIT_EVENT_NONE;
+ }
+ PolicyInformation.PolicyAuditEventsInfo.AuditingMode =
+ DeltaLsaPolicy->AuditingMode;
+ PolicyInformation.PolicyAuditEventsInfo.MaximumAuditEventCount =
+ DeltaLsaPolicy->MaximumAuditEventCount;
+ PolicyInformation.PolicyAuditEventsInfo.EventAuditingOptions =
+ DeltaLsaPolicy->EventAuditingOptions;
+
+ STARTLSATIMER;
+
+ Status = LsarSetInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyAuditEventsInformation,
+ &PolicyInformation);
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // Don't set the Primary Domain information.
+ //
+ // The UI (i.e., setup) originally set this information correctly.
+ // Later, the UI (i.e., NCPA) on the PDC can be used to change the domain
+ // name. We don't want the new domain name to replicate accidentally.
+ // Rather, we want the admin to change the domain name individually on
+ // each BDC.
+ //
+
+ //
+ // set quoto limit
+ //
+
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.PagedPoolLimit =
+ DeltaLsaPolicy->QuotaLimits.PagedPoolLimit;
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.NonPagedPoolLimit =
+ DeltaLsaPolicy->QuotaLimits.NonPagedPoolLimit;
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.MinimumWorkingSetSize =
+ DeltaLsaPolicy->QuotaLimits.MinimumWorkingSetSize;
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.MaximumWorkingSetSize =
+ DeltaLsaPolicy->QuotaLimits.MaximumWorkingSetSize;
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.PagefileLimit =
+ DeltaLsaPolicy->QuotaLimits.PagefileLimit;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaLsaPolicy->QuotaLimits.TimeLimit,
+ PolicyInformation.PolicyDefaultQuotaInfo.QuotaLimits.TimeLimit );
+
+ STARTLSATIMER;
+
+ Status = LsarSetInformationPolicy(
+ DBInfo->DBHandle,
+ PolicyDefaultQuotaInformation,
+ &PolicyInformation);
+
+ STOPLSATIMER;
+
+ //
+ // Don't unpack ModifiedId and DatabaseCreationTime !! These will
+ // be handled separately during a full sync.
+ //
+
+Cleanup:
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack POLICY object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlUnpackLsaTDomain(
+ IN PISID Sid,
+ IN PNETLOGON_DELTA_TRUSTED_DOMAINS DeltaLsaTDomain,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Set the specified Trusted Domain info to look like the specified
+ buffer.
+
+Arguments:
+
+ Sid - The Sid of the trusted domain.
+
+ DeltaLsaPolicy - Description of the truted domain.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ LSAPR_HANDLE TrustedDomainHandle = NULL;
+
+ LSAPR_TRUSTED_DOMAIN_INFO TrustedInfo;
+
+ DEFUNPACKTIMER;
+ DEFLSATIMER;
+
+ INITUNPACKTIMER;
+ INITLSATIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Trusted Domain Object: %wZ\n",
+ &DeltaLsaTDomain->DomainName ));
+
+ //
+ // open trusted domain
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenTrustedDomain(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &TrustedDomainHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ LSAPR_TRUST_INFORMATION DomainInfo;
+
+ if( Status != STATUS_OBJECT_NAME_NOT_FOUND ) {
+ goto Cleanup;
+ }
+
+ //
+ // if this account is not there then this may be a new one added
+ // so just create it.
+ //
+
+ DomainInfo.Name.Length =
+ DeltaLsaTDomain->DomainName.Length;
+ DomainInfo.Name.MaximumLength =
+ DeltaLsaTDomain->DomainName.MaximumLength;
+ DomainInfo.Name.Buffer =
+ DeltaLsaTDomain->DomainName.Buffer;
+
+ DomainInfo.Sid = (PLSAPR_SID)Sid;
+
+ STARTLSATIMER;
+
+ Status = LsarCreateTrustedDomain(
+ DBInfo->DBHandle,
+ &DomainInfo,
+ 0,
+ &TrustedDomainHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // give up ...
+ //
+
+ goto Cleanup;
+ }
+ }
+
+ SET_LSA_SECOBJ_INFO(DeltaLsaTDomain, TrustedDomainHandle);
+
+ //
+ // set controller information
+ //
+
+ TrustedInfo.TrustedControllersInfo.Entries =
+ DeltaLsaTDomain->NumControllerEntries;
+ TrustedInfo.TrustedControllersInfo.Names =
+ (PLSAPR_UNICODE_STRING)DeltaLsaTDomain->ControllerNames;
+
+ STARTLSATIMER;
+
+ Status = LsarSetInformationTrustedDomain(
+ TrustedDomainHandle,
+ TrustedControllersInformation,
+ &TrustedInfo );
+
+ STOPLSATIMER;
+
+ //
+ // set PosixOffset information
+ //
+
+ TrustedInfo.TrustedPosixOffsetInfo.Offset =
+ DeltaLsaTDomain->DummyLong1;
+
+ STARTLSATIMER;
+
+ Status = LsarSetInformationTrustedDomain(
+ TrustedDomainHandle,
+ TrustedPosixOffsetInformation,
+ &TrustedInfo );
+
+ STOPLSATIMER;
+
+ //
+ // The BDC needs to keep its internal trust list up to date.
+ //
+
+ NlUpdateTrustListBySid( (PSID)Sid, &DeltaLsaTDomain->DomainName );
+
+Cleanup:
+
+ if(TrustedDomainHandle != NULL) {
+
+ STARTLSATIMER;
+
+ LsarClose(&TrustedDomainHandle);
+
+ STOPLSATIMER;
+
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack TDOMAIN object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlUnpackLsaAccount(
+ IN PISID Sid,
+ IN PNETLOGON_DELTA_ACCOUNTS DeltaLsaAccount,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Set the LSA account info to look like the specified buffer.
+
+Arguments:
+
+ Sid - The Sid of the LSA account.
+
+ DeltaLsaPolicy - Description of the LSA account.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+
+ LSAPR_HANDLE AccountHandle = NULL;
+ PLSAPR_PRIVILEGE_SET NewPrivSet = NULL;
+
+ DWORD i;
+ DWORD NewPrivIndex;
+ PULONG PrivAttr;
+ PUNICODE_STRING PrivName;
+ QUOTA_LIMITS QuotaLimits;
+
+ DEFUNPACKTIMER;
+ DEFLSATIMER;
+
+ INITUNPACKTIMER;
+ INITLSATIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Lsa Account Object\n"));
+
+ //
+ // open lsa account
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenAccount(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &AccountHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ if( Status != STATUS_OBJECT_NAME_NOT_FOUND ) {
+ goto Cleanup;
+ }
+
+ //
+ // if this account is not there then this may be a new one added
+ // so just create it.
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarCreateAccount(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &AccountHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // give up ...
+ //
+
+ goto Cleanup;
+ }
+ }
+
+ SET_LSA_SECOBJ_INFO(DeltaLsaAccount, AccountHandle);
+
+ //
+ // remove all old privileges of the account
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarRemovePrivilegesFromAccount(
+ AccountHandle,
+ (BOOLEAN) TRUE,
+ NULL );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+
+ //
+ // make new privilege set
+ //
+
+ NewPrivSet = (PLSAPR_PRIVILEGE_SET)MIDL_user_allocate(
+ sizeof(LSAPR_PRIVILEGE_SET) +
+ (DeltaLsaAccount->PrivilegeEntries *
+ sizeof(LUID_AND_ATTRIBUTES)) );
+
+ if( NewPrivSet == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ NewPrivSet->Control = DeltaLsaAccount->PrivilegeControl;
+
+ PrivAttr = DeltaLsaAccount->PrivilegeAttributes;
+ PrivName = DeltaLsaAccount->PrivilegeNames;
+ NewPrivIndex = 0;
+
+ for (i = 0; i < DeltaLsaAccount->PrivilegeEntries;
+ i++, PrivAttr++, PrivName++ ) {
+
+ NewPrivSet->Privilege[NewPrivIndex].Attributes = *PrivAttr;
+
+ //
+ // convert privilege name to LUID
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarLookupPrivilegeValue(
+ DBInfo->DBHandle,
+ (PLSAPR_UNICODE_STRING)PrivName,
+ (PLUID)&NewPrivSet->Privilege[NewPrivIndex].Luid );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ //
+ // If the PDC has a privilege we don't understand,
+ // silently ignore that privilege.
+ //
+ if ( Status == STATUS_NO_SUCH_PRIVILEGE ) {
+ NlPrint((NL_SYNC_MORE,
+ "Lsa Account replication ignored %wZ privilege\n",
+ PrivName ));
+
+ continue;
+ }
+ goto Cleanup;
+ }
+
+ NewPrivIndex ++;
+
+ }
+
+ NewPrivSet->PrivilegeCount = NewPrivIndex;
+
+ //
+ // set new privileges of the account
+ //
+
+ if ( NewPrivSet->PrivilegeCount > 0 ) {
+ STARTLSATIMER;
+
+ Status = LsarAddPrivilegesToAccount(
+ AccountHandle,
+ NewPrivSet );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+
+ STARTLSATIMER;
+
+ QuotaLimits.PagedPoolLimit = DeltaLsaAccount->QuotaLimits.PagedPoolLimit;
+ QuotaLimits.NonPagedPoolLimit = DeltaLsaAccount->QuotaLimits.NonPagedPoolLimit;
+ QuotaLimits.MinimumWorkingSetSize = DeltaLsaAccount->QuotaLimits.MinimumWorkingSetSize;
+ QuotaLimits.MaximumWorkingSetSize = DeltaLsaAccount->QuotaLimits.MaximumWorkingSetSize;
+ QuotaLimits.PagefileLimit = DeltaLsaAccount->QuotaLimits.PagefileLimit;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaLsaAccount->QuotaLimits.TimeLimit,
+ QuotaLimits.TimeLimit );
+
+ Status = LsarSetQuotasForAccount(
+ AccountHandle,
+ &QuotaLimits );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarSetSystemAccessAccount(
+ AccountHandle,
+ DeltaLsaAccount->SystemAccessFlags );
+
+ STOPLSATIMER;
+
+Cleanup:
+
+ if(AccountHandle != NULL) {
+
+ STARTLSATIMER;
+
+ LsarClose(&AccountHandle);
+
+ STOPLSATIMER;
+
+ }
+
+ if( NewPrivSet != NULL ) {
+ MIDL_user_free( NewPrivSet );
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack LSAACCOUNT object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlUnpackLsaSecret(
+ IN LPWSTR Name,
+ IN PNETLOGON_DELTA_SECRET DeltaLsaSecret,
+ IN PDB_INFO DBInfo,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Set the LSA secret info to look like the specified buffer.
+
+Arguments:
+
+ Name - The Sid of the secret.
+
+ DeltaLsaPolicy - Description of the user.
+
+ DBInfo - pointer to the database info structure.
+
+ SessionInfo - Info shared between PDC and BDC
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+
+ NTSTATUS Status;
+
+ LSAPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+
+ LSAPR_HANDLE SecretHandle = NULL;
+ UNICODE_STRING UnicodeName;
+
+ LSAPR_CR_CIPHER_VALUE CrCurrentValue = {0, 0, NULL };
+ LSAPR_CR_CIPHER_VALUE CrOldValue = {0, 0, NULL };
+
+ LARGE_INTEGER CurrentValueSetTime;
+ LARGE_INTEGER OldValueSetTime;
+
+ DEFUNPACKTIMER;
+ DEFLSATIMER;
+
+ NlPrint((NL_SYNC_MORE,
+ "UnPacking Secret Object: " FORMAT_LPWSTR "\n", Name));
+
+ //
+ // we should be unpacking only the GLOBAL secrets
+ //
+
+ NlAssert(
+ (wcslen( Name ) >
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) &&
+ (_wcsnicmp( Name,
+ LSA_GLOBAL_SECRET_PREFIX,
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0) );
+
+
+
+
+ INITUNPACKTIMER;
+ INITLSATIMER;
+
+ STARTUNPACKTIMER;
+
+ RtlInitUnicodeString(&UnicodeName, Name);
+
+ //
+ // open trusted domain
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarOpenSecret(
+ DBInfo->DBHandle,
+ (PLSAPR_UNICODE_STRING)&UnicodeName,
+ 0,
+ &SecretHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ if( Status != STATUS_OBJECT_NAME_NOT_FOUND ) {
+ goto Cleanup;
+ }
+
+ //
+ // if this account is not there then this may be a new one added
+ // so just create it.
+ //
+
+ STARTLSATIMER;
+
+ Status = LsarCreateSecret(
+ DBInfo->DBHandle,
+ (PLSAPR_UNICODE_STRING)&UnicodeName,
+ 0,
+ &SecretHandle );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // give up ...
+ //
+
+ goto Cleanup;
+ }
+ }
+
+ SET_LSA_SECOBJ_INFO(DeltaLsaSecret, SecretHandle);
+
+ //
+ // set secret values
+ //
+
+ //
+ // decrypt secret values.
+ //
+
+ if( DeltaLsaSecret->CurrentValue.Buffer != NULL ) {
+
+ Status = NlDecryptSensitiveData(
+ (PCRYPT_BUFFER) &DeltaLsaSecret->CurrentValue,
+ (PCRYPT_BUFFER) &CrCurrentValue,
+ SessionInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ } else {
+
+ CrCurrentValue.Length = 0;
+ CrCurrentValue.MaximumLength = 0;
+ CrCurrentValue.Buffer = NULL;
+ }
+
+ if( DeltaLsaSecret->OldValue.Buffer != NULL ) {
+
+ Status = NlDecryptSensitiveData(
+ (PCRYPT_BUFFER) &DeltaLsaSecret->OldValue,
+ (PCRYPT_BUFFER) &CrOldValue,
+ SessionInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ } else {
+
+ CrOldValue.Length = 0;
+ CrOldValue.MaximumLength = 0;
+ CrOldValue.Buffer = NULL;
+ }
+
+ STARTLSATIMER;
+
+ Status = LsarSetSecret(
+ SecretHandle,
+ &CrCurrentValue,
+ &CrOldValue );
+
+ STOPLSATIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // set secret times
+ //
+
+ STARTLSATIMER;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaLsaSecret->CurrentValueSetTime,
+ CurrentValueSetTime );
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaLsaSecret->OldValueSetTime,
+ OldValueSetTime );
+
+
+ Status = LsaISetTimesSecret(
+ SecretHandle,
+ &CurrentValueSetTime,
+ &OldValueSetTime );
+
+ STOPLSATIMER;
+
+Cleanup:
+
+ //
+ // clean up decrypt buffers
+ //
+
+ if( CrCurrentValue.Buffer != NULL ) {
+
+ MIDL_user_free( CrCurrentValue.Buffer );
+ }
+
+ if( CrOldValue.Buffer != NULL ) {
+
+ MIDL_user_free( CrOldValue.Buffer );
+ }
+
+ if(SecretHandle != NULL) {
+
+ STARTLSATIMER;
+
+ LsarClose(&SecretHandle);
+
+ STOPLSATIMER;
+
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack SECRET object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTLSATIMER;
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlDeleteLsaTDomain(
+ IN PISID Sid,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Delete the specified trusted domain account from LSA.
+
+Arguments:
+
+ Sid - The Sid of the trusted domain.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSAPR_HANDLE TrustedDomainHandle = NULL;
+
+ NlPrint((NL_SYNC_MORE, "Delete Trusted Domain Object\n"));
+
+ //
+ // open trusted domain
+ //
+
+ Status = LsarOpenTrustedDomain(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &TrustedDomainHandle );
+
+ if( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
+
+ return(STATUS_SUCCESS);
+ }
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = LsarDelete(TrustedDomainHandle);
+ }
+
+ //
+ // The BDC needs to keep its internal trust list up to date.
+ //
+
+ NlUpdateTrustListBySid( (PSID) Sid, NULL );
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlDeleteLsaAccount(
+ IN PISID Sid,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Delete the specified LSA account.
+
+Arguments:
+
+ Sid - The Sid of the LSA account.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSAPR_HANDLE AccountHandle = NULL;
+
+ NlPrint((NL_SYNC_MORE, "Delete Lsa Account Object\n"));
+
+ //
+ // open trusted domain
+ //
+
+ Status = LsarOpenAccount(
+ DBInfo->DBHandle,
+ (PLSAPR_SID)Sid,
+ 0,
+ &AccountHandle );
+
+ if( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
+
+ return(STATUS_SUCCESS);
+ }
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = LsarDelete(AccountHandle);
+ }
+
+ return(Status);
+
+}
+
+
+NTSTATUS
+NlDeleteLsaSecret(
+ IN LPWSTR Name,
+ IN PDB_INFO DBInfo )
+/*++
+
+Routine Description:
+
+ Delete the specified LSA secret.
+
+Arguments:
+
+ Name - The Sid of the secret.
+
+ DBInfo - pointer to the database info structure.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+
+ NTSTATUS Status;
+
+ LSAPR_HANDLE SecretHandle = NULL;
+ UNICODE_STRING UnicodeName;
+
+ //
+ // SPECIAL CASE:
+ //
+ // DONOT REPLICATE machine account secret, which is
+ // not replicated and is specific to the machine.
+ //
+
+ if( !_wcsicmp( Name, SSI_SECRET_NAME ) ) {
+
+ return STATUS_SUCCESS;
+ }
+
+ RtlInitUnicodeString(&UnicodeName, Name);
+
+ NlPrint((NL_SYNC_MORE, "Delete Secret Object: %wZ\n", &UnicodeName));
+
+ //
+ // open trusted domain
+ //
+
+ Status = LsarOpenSecret(
+ DBInfo->DBHandle,
+ (PLSAPR_UNICODE_STRING)&UnicodeName,
+ 0,
+ &SecretHandle );
+
+ if( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
+
+ return(STATUS_SUCCESS);
+ }
+
+ if (NT_SUCCESS(Status)) {
+
+ Status = LsarDelete(SecretHandle);
+ }
+
+ return(Status);
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/lsarepl.h b/private/net/svcdlls/logonsrv/server/lsarepl.h
new file mode 100644
index 000000000..53854e133
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/lsarepl.h
@@ -0,0 +1,100 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ lsarepl.h
+
+Abstract:
+
+ Function prototypes for Low level LSA Replication functions
+
+Author:
+
+ 06-Apr-1992 (madana)
+ Created for LSA replication.
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//
+// lsarepl.c
+//
+
+NTSTATUS
+NlPackLsaPolicy(
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize );
+
+NTSTATUS
+NlPackLsaTDomain(
+ IN PSID Sid,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize );
+
+NTSTATUS
+NlPackLsaAccount(
+ IN PSID Sid,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ );
+
+NTSTATUS
+NlPackLsaSecret(
+ IN PUNICODE_STRING Name,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo );
+
+NTSTATUS
+NlUnpackLsaPolicy(
+ IN PNETLOGON_DELTA_POLICY DeltaLsaPolicy,
+ IN PDB_INFO DBInfo );
+
+NTSTATUS
+NlUnpackLsaTDomain(
+ IN PISID Sid,
+ IN PNETLOGON_DELTA_TRUSTED_DOMAINS DeltaLsaTDomain,
+ IN PDB_INFO DBInfo );
+
+NTSTATUS
+NlUnpackLsaAccount(
+ IN PISID Sid,
+ IN PNETLOGON_DELTA_ACCOUNTS DeltaLsaAccount,
+ IN PDB_INFO DBInfo );
+
+NTSTATUS
+NlUnpackLsaSecret(
+ IN LPWSTR Name,
+ IN PNETLOGON_DELTA_SECRET DeltaLsaSecret,
+ IN PDB_INFO DBInfo,
+ IN PSESSION_INFO SessionInfo
+ );
+
+NTSTATUS
+NlDeleteLsaTDomain(
+ IN PISID Sid,
+ IN PDB_INFO DBInfo );
+
+NTSTATUS
+NlDeleteLsaAccount(
+ IN PISID Sid,
+ IN PDB_INFO DBInfo );
+
+NTSTATUS
+NlDeleteLsaSecret(
+ IN LPWSTR Name,
+ IN PDB_INFO DBInfo );
diff --git a/private/net/svcdlls/logonsrv/server/lsrvdata.h b/private/net/svcdlls/logonsrv/server/lsrvdata.h
new file mode 100644
index 000000000..dc091b7e6
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/lsrvdata.h
@@ -0,0 +1,228 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ lsrvdata.h
+
+Abstract:
+
+ Netlogon service global variable external and definitions
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Revision History:
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+ 07-May-1992 JohnRo
+ Use net config helpers for NetLogon.
+
+--*/
+
+
+#ifndef _LSRVDATA_
+#define _LSRVDATA_
+
+
+//
+// netlogon.c will #include this file with LSRVDATA_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef LSRVDATA_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Modifiable Variables: these variables change over time.
+//
+///////////////////////////////////////////////////////////////////////////
+
+//
+// Global NetStatus of the Netlogon service
+//
+
+EXTERN SERVICE_STATUS NlGlobalServiceStatus;
+EXTERN SERVICE_STATUS_HANDLE NlGlobalServiceHandle;
+
+//
+// The server name of the current PDC.
+//
+
+EXTERN CHAR NlGlobalAnsiPrimaryName[CNLEN+1];
+EXTERN WCHAR NlGlobalUncPrimaryName[UNCLEN+1];
+EXTERN LPWSTR NlGlobalUnicodePrimaryName;
+
+//
+// Global SAM Modes.
+//
+// We track these values as SAM tells us that they have changed.
+//
+
+EXTERN BOOLEAN NlGlobalUasCompatibilityMode;
+
+//
+// Boolean so that we only warn the user once about having too many global
+// groups.
+
+EXTERN BOOLEAN NlGlobalTooManyGlobalGroups;
+
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Read-only variables after initialization.
+//
+///////////////////////////////////////////////////////////////////////////
+
+
+//
+// Handle to wait on for mailslot reads
+//
+
+EXTERN HANDLE NlGlobalMailslotHandle;
+
+//
+// Flag to indicate when RPC has been started
+//
+
+EXTERN BOOL NlGlobalRpcServerStarted;
+
+//
+// Service Termination event.
+//
+
+EXTERN HANDLE NlGlobalTerminateEvent;
+EXTERN BOOL NlGlobalTerminate;
+EXTERN HANDLE NlGlobalReplicatorTerminateEvent;
+
+//
+// Service Started Event
+//
+
+EXTERN HANDLE NlGlobalStartedEvent;
+
+//
+// Timers need attention event.
+//
+
+EXTERN HANDLE NlGlobalTimerEvent;
+
+
+
+//
+// The computername of the local system.
+//
+
+EXTERN LPSTR NlGlobalAnsiComputerName;
+EXTERN LPWSTR NlGlobalUnicodeComputerName;
+EXTERN WCHAR NlGlobalUncUnicodeComputerName[UNCLEN + 1];
+EXTERN UNICODE_STRING NlGlobalUnicodeComputerNameString;
+
+//
+// Primary Domain Information:
+//
+// The Domain Name is maintained in Ansi and Unicode.
+//
+EXTERN LPSTR NlGlobalAnsiDomainName;
+EXTERN WCHAR NlGlobalUnicodeDomainName[DNLEN+1];
+EXTERN UNICODE_STRING NlGlobalUnicodeDomainNameString;
+EXTERN PSID NlGlobalPrimaryDomainId;
+
+
+//
+// Account Domain Information
+//
+EXTERN UNICODE_STRING NlGlobalAccountDomainName;
+
+//
+// Global DB Info array
+//
+EXTERN DB_INFO NlGlobalDBInfoArray[NUM_DBS];
+
+
+
+EXTERN SAMPR_HANDLE NlGlobalSamServerHandle; // Handle to Sam Server database
+EXTERN LSAPR_HANDLE NlGlobalPolicyHandle; // Handle to Policy Database
+
+typedef enum _NETLOGON_ROLE {
+ RolePrimary = 1,
+ RoleBackup,
+ RoleMemberWorkstation
+} NETLOGON_ROLE, * PNETLOGON_ROLE;
+
+EXTERN NETLOGON_ROLE NlGlobalRole;
+
+
+EXTERN WCHAR NlGlobalUnicodeScriptPath[PATHLEN + 1];
+
+
+//
+// Command line arguments.
+//
+
+EXTERN ULONG NlGlobalPulseParameter;
+EXTERN ULONG NlGlobalPulseMaximumParameter;
+EXTERN ULONG NlGlobalPulseConcurrencyParameter;
+EXTERN ULONG NlGlobalPulseTimeout1Parameter;
+EXTERN ULONG NlGlobalPulseTimeout2Parameter;
+EXTERN ULONG NlGlobalGovernorParameter;
+EXTERN BOOL NlGlobalDisablePasswordChangeParameter;
+EXTERN BOOL NlGlobalRefusePasswordChangeParameter;
+EXTERN ULONG NlGlobalRandomizeParameter;
+EXTERN BOOL NlGlobalSynchronizeParameter;
+EXTERN ULONG NlGlobalMaximumMailslotMessagesParameter;
+EXTERN ULONG NlGlobalMailslotMessageTimeoutParameter;
+EXTERN ULONG NlGlobalMailslotDuplicateTimeoutParameter;
+EXTERN ULONG NlGlobalExpectedDialupDelayParameter;
+EXTERN ULONG NlGlobalScavengeIntervalParameter;
+
+
+//
+// Parameters represented in 100ns units
+//
+EXTERN LARGE_INTEGER NlGlobalPulseMaximum;
+EXTERN LARGE_INTEGER NlGlobalPulseTimeout1;
+EXTERN LARGE_INTEGER NlGlobalPulseTimeout2;
+EXTERN LARGE_INTEGER NlGlobalMailslotMessageTimeout;
+EXTERN LARGE_INTEGER NlGlobalMailslotDuplicateTimeout;
+EXTERN ULONG NlGlobalShortApiCallPeriod;
+
+
+//
+// global flags used to pause the netlogon service when the database is
+// full synced first time.
+//
+
+EXTERN BOOL NlGlobalFirstTimeFullSync;
+
+
+//
+// Global variables required for scavenger thread.
+//
+
+EXTERN CRITICAL_SECTION NlGlobalScavengerCritSect;
+EXTERN HANDLE NlGlobalScavengerThreadHandle;
+EXTERN BOOL NlGlobalScavengerTerminate;
+
+//
+// Variables for cordinating MSV threads running in netlogon.dll
+//
+
+EXTERN CRITICAL_SECTION NlGlobalMsvCritSect;
+EXTERN HANDLE NlGlobalMsvTerminateEvent;
+EXTERN BOOL NlGlobalMsvEnabled;
+EXTERN ULONG NlGlobalMsvThreadCount;
+
+#undef EXTERN
+
+
+#endif // _LSRVDATA_
diff --git a/private/net/svcdlls/logonsrv/server/lsrvrepl.c b/private/net/svcdlls/logonsrv/server/lsrvrepl.c
new file mode 100644
index 000000000..621f5f34c
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/lsrvrepl.c
@@ -0,0 +1,4700 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ lsrvrepl.c
+
+Abstract:
+
+ Utility functions for the netlogon replication service.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 00-Jun-1989 (PradyM)
+ modified lm10 code for new NETLOGON service
+
+ 00-Feb-1990 (PradyM)
+ bugfixes
+
+ 00-Aug-1990 (t-RichE)
+ added alerts for auth failure due to time slippage
+
+ 11-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+ 09-Apr-1992 JohnRo
+ Prepare for WCHAR.H (_wcsicmp vs _wcscmpi, etc).
+
+ 21-Apr-1992 (madana)
+ spilt the lsrvutil.c into two files as:
+ lsrvutil.c - has general util functions
+ lsrvrepl.c - has netlogon replication functions
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+// #include <accessp.h> // NetpAliasMemberToPriv
+#include <alertmsg.h> // Alert message text.
+#include <lmapibuf.h>
+#include <lmerr.h> // System Error Log definitions
+#include <lmserver.h> // server API functions and prototypes
+#include <lmshare.h> // share API functions and prototypes
+#include <msgtext.h> // MTXT_* defines
+#include <replutil.h> // UnpackSamXXX()
+#include <secobj.h> // NetpDomainIdToSid
+#include <ssiapi.h> // I_NetSamDeltas()
+#include <stddef.h> // offsetof
+#include <stdlib.h> // C library functions (rand, etc)
+#include <tstring.h> // IS_PATH_SEPARATOR ...
+#include <winreg.h> // registry API
+#include <wingdi.h> // LoadString()
+#include <winuser.h> // LoadString()
+
+#define MAX_LSA_PREF_LENGTH 0xFFFFFFFF // to get all objects
+#define MAX_SAM_PREF_LENGTH 0xFFFFFFFF // to get all objects
+
+//
+// Structure used to pass arguments to the replicator thread.
+//
+typedef struct _REPL_PARAM {
+ DWORD RandomSleep; // Number of millseconds to delay before working
+} REPL_PARAM, *PREPL_PARAM;
+
+//
+// enum typdef for SAM objects
+//
+
+typedef enum _LOCAL_SAM_ACCOUNT_TYPE {
+ UserAccount,
+ GroupAccount,
+ AliasAccount
+} LOCAL_SAM_ACCOUNT_TYPE;
+
+typedef enum _LOCAL_LSA_ACCOUNT_TYPE {
+ LsaAccount,
+ LsaTDomain,
+ LsaSecret
+} LOCAL_LSA_ACCOUNT_TYPE;
+
+//
+// The following variables are protected by the NlGlobalReplicatorCritSect
+//
+HANDLE NlGlobalReplicatorThreadHandle = NULL;
+BOOL NlGlobalReplicatorTerminate = FALSE;
+BOOL NlGlobalReplicatorIsRunning = FALSE;
+
+//
+// The following variable is only modified under the
+// NlGlobalReplicatorCritSect and when the replicator thread is not
+// running. It is referenced by the replicator thread.
+//
+
+REPL_PARAM NlGlobalReplParam; // Parameters to the replicator thread
+
+PULONG NlGlobalSamUserRids = NULL;
+ULONG NlGlobalSamUserCount = 0;
+PULONG NlGlobalSamGroupRids = NULL;
+ULONG NlGlobalSamGroupCount = 0;
+PSAMPR_ENUMERATION_BUFFER NlGlobalSamAliasesEnumBuffer = NULL;
+
+LSAPR_ACCOUNT_ENUM_BUFFER NlGlobalLsaAccountsEnumBuffer = {0, NULL};
+LSAPR_TRUSTED_ENUM_BUFFER NlGlobalLsaTDomainsEnumBuffer = {0, NULL};
+PVOID NlGlobalLsaSecretsEnumBuffer = NULL;
+ULONG NlGlobalLsaSecretCountReturned = 0;
+
+BOOLEAN NlGlobalLsaAccountsHack = FALSE;
+
+
+VOID
+NlLogSyncError(
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN NTSTATUS ReplicationStatus
+ )
+/*++
+
+Routine Description:
+
+ Logs an error describing the specific delta that an error occured on.
+
+Arguments:
+
+ Deltas - The delta which failed
+
+ DBInfo - Describes the database the operation was applied to
+
+ ReplicationStatus - Status of the failed operation
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ NTSTATUS Status;
+
+ UNICODE_STRING AccountName;
+ WCHAR AccountNameBuffer[25];
+ BOOLEAN AccountNameIsAllocated = FALSE;
+ LPWSTR ZeroAccountName = NULL;
+
+ LPWSTR MsgStrings[5];
+ ULONG EventId = 0;
+
+ //
+ // Get the name of the account
+ //
+
+ switch ( Delta->DeltaType ) {
+ case AddOrChangeDomain:
+ EventId = NELOG_NetlogonFailedDomainDelta;
+ AccountName = ((PNETLOGON_DELTA_DOMAIN)(Delta->DeltaUnion.DeltaDomain))->
+ DomainName;
+ break;
+
+ case AddOrChangeGroup:
+ EventId = NELOG_NetlogonFailedGlobalGroupDelta;
+ AccountName = ((PNETLOGON_DELTA_GROUP)(Delta->DeltaUnion.DeltaGroup))->
+ Name;
+ break;
+
+ case AddOrChangeAlias:
+ EventId = NELOG_NetlogonFailedLocalGroupDelta;
+ AccountName = ((PNETLOGON_DELTA_ALIAS)(Delta->DeltaUnion.DeltaAlias))->
+ Name;
+ break;
+
+ case AddOrChangeUser:
+ EventId = NELOG_NetlogonFailedUserDelta;
+ AccountName = ((PNETLOGON_DELTA_USER)(Delta->DeltaUnion.DeltaUser))->
+ UserName;
+ break;
+
+ case ChangeGroupMembership:
+ case ChangeAliasMembership:
+ case DeleteGroup:
+ case DeleteAlias:
+ case DeleteUser:
+ case DeleteGroupByName:
+ case DeleteUserByName:
+
+ switch ( Delta->DeltaType ) {
+ case ChangeGroupMembership:
+ case DeleteGroup:
+ case DeleteGroupByName:
+ EventId = NELOG_NetlogonFailedGlobalGroupDelta; break;
+ case ChangeAliasMembership:
+ case DeleteAlias:
+ EventId = NELOG_NetlogonFailedLocalGroupDelta; break;
+ case DeleteUser:
+ case DeleteUserByName:
+ EventId = NELOG_NetlogonFailedUserDelta; break;
+ }
+
+ //
+ // If all we have is a RID,
+ // convert the RID to a unicode string.
+ //
+ wcscpy( AccountNameBuffer, L"Rid: 0x" );
+ ultow( Delta->DeltaID.Rid, AccountNameBuffer+7, 16 );
+ RtlInitUnicodeString( &AccountName, AccountNameBuffer );
+
+ break;
+
+ case RenameUser:
+ EventId = NELOG_NetlogonFailedUserDelta;
+ AccountName = ((PNETLOGON_DELTA_RENAME_USER)(Delta->DeltaUnion.DeltaRenameUser))->
+ OldName;
+ break;
+
+ case RenameGroup:
+ EventId = NELOG_NetlogonFailedGlobalGroupDelta;
+ AccountName = ((PNETLOGON_DELTA_RENAME_GROUP)(Delta->DeltaUnion.DeltaRenameUser))->
+ OldName;
+ break;
+
+ case RenameAlias:
+ EventId = NELOG_NetlogonFailedLocalGroupDelta;
+ AccountName = ((PNETLOGON_DELTA_RENAME_ALIAS)(Delta->DeltaUnion.DeltaRenameUser))->
+ OldName;
+ break;
+
+ case AddOrChangeLsaPolicy:
+ EventId = NELOG_NetlogonFailedPolicyDelta;
+ RtlInitUnicodeString( &AccountName, L"Policy");
+ break;
+
+ case AddOrChangeLsaTDomain:
+ EventId = NELOG_NetlogonFailedTrustedDomainDelta;
+ AccountName = ((PNETLOGON_DELTA_TRUSTED_DOMAINS)(Delta->DeltaUnion.DeltaTDomains))->
+ DomainName;
+ break;
+
+ case DeleteLsaSecret:
+ case AddOrChangeLsaSecret:
+ EventId = NELOG_NetlogonFailedSecretDelta;
+ RtlInitUnicodeString( &AccountName, Delta->DeltaID.Name);
+ break;
+
+ case AddOrChangeLsaAccount:
+ case DeleteLsaTDomain:
+ case DeleteLsaAccount:
+
+ if ( Delta->DeltaType == DeleteLsaTDomain ) {
+ EventId = NELOG_NetlogonFailedTrustedDomainDelta;
+ } else {
+ EventId = NELOG_NetlogonFailedAccountDelta;
+ }
+
+ //
+ // If all we have is a SID,
+ // convert the SID to a unicode string.
+ //
+ Status = RtlConvertSidToUnicodeString( &AccountName,
+ Delta->DeltaID.Sid,
+ TRUE );
+
+ if ( !NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ AccountNameIsAllocated = TRUE;
+
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL, "NlLogSyncError: Invalid delta type %lx\n", Delta->DeltaType ));
+ return;
+ }
+
+ NlAssert( EventId != 0 );
+
+ //
+ // Convert account name to zero terminated string.
+ //
+
+ ZeroAccountName = NetpMemoryAllocate( AccountName.Length + sizeof(WCHAR) );
+
+ if ( ZeroAccountName == NULL ) {
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( ZeroAccountName, AccountName.Buffer, AccountName.Length );
+ ZeroAccountName[AccountName.Length/sizeof(WCHAR)] = L'\0';
+
+
+
+ //
+ // Write the event log message.
+ //
+
+ MsgStrings[0] = DBInfo->DBName;
+ MsgStrings[1] = ZeroAccountName;
+ MsgStrings[2] = NlGlobalUnicodePrimaryName;
+ MsgStrings[3] = (LPWSTR) ReplicationStatus;
+
+ NlpWriteEventlog (
+ EventId,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &ReplicationStatus,
+ sizeof(ReplicationStatus),
+ MsgStrings,
+ 4 | LAST_MESSAGE_IS_NTSTATUS );
+
+
+ //
+ // Cleanup locals
+ //
+Cleanup:
+ if ( AccountNameIsAllocated ) {
+ RtlFreeUnicodeString( &AccountName );
+ }
+
+ if ( ZeroAccountName != NULL ) {
+ NetpMemoryFree( ZeroAccountName );
+ }
+
+}
+
+#if DBG
+
+VOID
+PrintFullSyncKey(
+ IN ULONG DBIndex,
+ IN PFULL_SYNC_KEY FullSyncKey,
+ IN LPSTR Header
+ )
+/*++
+
+Routine Description:
+
+ Print a full sync key for a particular server
+
+Arguments:
+
+ DBIndex - Database number of the value to query
+
+ FullSyncKey - FullSyncKey structure to print
+
+ Header - string to print before rest of text
+
+Return Value:
+
+ None
+
+--*/
+{
+ NlPrint(( NL_SYNC, "%s " FORMAT_LPWSTR " Full Sync Key:",
+ Header,
+ NlGlobalDBInfoArray[DBIndex].DBName ));
+
+ if ( FullSyncKey->SyncState == NormalState ) {
+ NlPrint(( NL_SYNC, " not in progress\n" ));
+ return;
+ }
+
+ switch ( FullSyncKey->SyncState ) {
+ case NormalState:
+ NlPrint(( NL_SYNC, " NormalState"));
+ break;
+ case DomainState:
+ NlPrint(( NL_SYNC, " DomainState"));
+ break;
+ case UserState:
+ NlPrint(( NL_SYNC, " UserState"));
+ break;
+ case GroupState:
+ NlPrint(( NL_SYNC, " GroupState"));
+ break;
+ case GroupMemberState:
+ NlPrint(( NL_SYNC, " GroupMemberState"));
+ break;
+ case AliasState:
+ NlPrint(( NL_SYNC, " AliasState"));
+ break;
+ case AliasMemberState:
+ NlPrint(( NL_SYNC, " AliasMemberState"));
+ break;
+ default:
+ NlPrint(( NL_SYNC, " Invalid state %ld", FullSyncKey->SyncState ));
+ break;
+ }
+
+ NlPrint(( NL_SYNC, " Continuation Rid: 0x%lx", FullSyncKey->ContinuationRid ));
+ NlPrint(( NL_SYNC, " PDC Serial Number: 0x%lx 0x%lx",
+ FullSyncKey->PdcSerialNumber.HighPart,
+ FullSyncKey->PdcSerialNumber.LowPart ));
+ NlPrint(( NL_SYNC, " PDC Domain Creation Time: 0x%lx 0x%lx\n",
+ FullSyncKey->PdcDomainCreationTime.HighPart,
+ FullSyncKey->PdcDomainCreationTime.LowPart ));
+}
+#else DBG
+#define PrintFullSyncKey( _x, _y, _z )
+#endif DBG
+
+
+
+HKEY
+NlOpenFullSyncKey(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Create/Open the Netlogon\FullSync key in the registry.
+
+Arguments:
+
+ FullSyncKey - Value to write to registry. (NULL says delete entry)
+
+Return Value:
+
+ Return a handle to the key. NULL means the key couldn't be openned.
+
+--*/
+{
+ LONG RegStatus;
+
+ HKEY BaseHandle = NULL;
+ HKEY ParmHandle = NULL;
+ ULONG Disposition;
+
+ //
+ // Open the registry
+ //
+
+ RegStatus = RegConnectRegistryW( NULL,
+ HKEY_LOCAL_MACHINE,
+ &BaseHandle);
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ NlPrint(( NL_CRITICAL,
+ "NlOpenFullSyncKey: Cannot connect to registy %ld.\n",
+ RegStatus ));
+ return NULL;
+ }
+
+
+ //
+ // Open the key for Netlogon\FullSyncKey
+ //
+
+ RegStatus = RegCreateKeyExA(
+ BaseHandle,
+ NL_FULL_SYNC_KEY,
+ 0, //Reserved
+ NULL, // Class
+ REG_OPTION_NON_VOLATILE,
+ KEY_SET_VALUE | KEY_QUERY_VALUE,
+ NULL, // Security descriptor
+ &ParmHandle,
+ &Disposition );
+
+ RegCloseKey( BaseHandle );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ NlPrint(( NL_CRITICAL,
+ "NlOpenFullSyncKey: Cannot create registy key %s %ld.\n",
+ NL_FULL_SYNC_KEY,
+ RegStatus ));
+ return NULL;
+ }
+
+ return ParmHandle;
+}
+
+
+VOID
+NlSetFullSyncKey(
+ ULONG DBIndex,
+ PFULL_SYNC_KEY FullSyncKey
+ )
+/*++
+
+Routine Description:
+
+ Sets the Netlogon\FullSync key to the specified value.
+
+Arguments:
+
+ DBIndex - Database number of the value to query
+
+ FullSyncKey - Value to write to registry. (NULL says delete entry)
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LONG RegStatus;
+ FULL_SYNC_KEY NullFullSyncKey;
+ PFULL_SYNC_KEY LocalFullSyncKey;
+
+ HKEY ParmHandle = NULL;
+
+ //
+ // Open the key for Netlogon\FullSync
+ //
+
+ ParmHandle = NlOpenFullSyncKey( );
+
+ if (ParmHandle == NULL) {
+ goto Cleanup;
+ }
+
+ //
+ // Build the data to write to the registry.
+ //
+
+ if ( FullSyncKey == NULL) {
+ RtlZeroMemory( &NullFullSyncKey, sizeof(NullFullSyncKey));
+ NullFullSyncKey.Version = FULL_SYNC_KEY_VERSION;
+ NullFullSyncKey.SyncState = NormalState;
+ LocalFullSyncKey = &NullFullSyncKey;
+ } else {
+ LocalFullSyncKey = FullSyncKey;
+ }
+
+ //
+ // Set the value in the registry.
+ //
+
+ RegStatus = RegSetValueExW( ParmHandle,
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ 0, // Reserved
+ REG_BINARY,
+ (LPBYTE)LocalFullSyncKey,
+ sizeof(*LocalFullSyncKey));
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ NlPrint(( NL_CRITICAL,
+ "NlSetFullSyncKey: Cannot Set '" NL_FULL_SYNC_KEY "\\" FORMAT_LPWSTR "' %ld.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ RegStatus ));
+ goto Cleanup;
+ }
+
+ PrintFullSyncKey( DBIndex, LocalFullSyncKey, "Setting" );
+
+ //
+ // Be tidy.
+ //
+Cleanup:
+ if ( ParmHandle != NULL ) {
+ RegCloseKey( ParmHandle );
+ }
+ return;
+
+}
+
+
+VOID
+NlQueryFullSyncKey(
+ ULONG DBIndex,
+ PFULL_SYNC_KEY FullSyncKey
+ )
+/*++
+
+Routine Description:
+
+ Queries Netlogon\FullSync key current value.
+
+Arguments:
+
+ DBIndex - Database number of the value to query
+
+ FullSyncKey - Value queried from the registry
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LONG RegStatus;
+ BOOLEAN Failed;
+ DWORD KeyType;
+ DWORD DataSize;
+
+
+ HKEY ParmHandle = NULL;
+
+ //
+ // Open the key for Netlogon\FullSync
+ //
+
+ ParmHandle = NlOpenFullSyncKey( );
+
+ if (ParmHandle == NULL) {
+ Failed = TRUE;
+ goto Cleanup;
+ }
+
+ //
+ // Set the value in the registry.
+ //
+
+ DataSize = sizeof(*FullSyncKey);
+ RegStatus = RegQueryValueExW( ParmHandle,
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ 0, // Reserved
+ &KeyType,
+ (LPBYTE)FullSyncKey,
+ &DataSize );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ NlPrint(( NL_CRITICAL,
+ "NlQueryFullSyncKey: Cannot query '" NL_FULL_SYNC_KEY "\\" FORMAT_LPWSTR "' %ld.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ RegStatus ));
+ Failed = TRUE;
+ goto Cleanup;
+ }
+
+ //
+ // Validate the returned data.
+ //
+
+ if ( KeyType != REG_BINARY ||
+ DataSize != sizeof(*FullSyncKey) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlQueryFullSyncKey: Key size/type wrong'" NL_FULL_SYNC_KEY "\\" FORMAT_LPWSTR "' %ld.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ RegStatus ));
+
+ Failed = TRUE;
+ goto Cleanup;
+ }
+
+ if ( FullSyncKey->Version != FULL_SYNC_KEY_VERSION ) {
+ NlPrint(( NL_CRITICAL,
+ "NlQueryFullSyncKey: Version wrong '" NL_FULL_SYNC_KEY "\\" FORMAT_LPWSTR "' %ld.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ FullSyncKey->Version ));
+
+ Failed = TRUE;
+ goto Cleanup;
+ }
+
+ if ( FullSyncKey->SyncState > SamDoneState ) {
+ NlPrint(( NL_CRITICAL,
+ "NlQueryFullSyncKey: SyncState wrong '" NL_FULL_SYNC_KEY "\\" FORMAT_LPWSTR "' %ld.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ FullSyncKey->SyncState ));
+
+ Failed = TRUE;
+ goto Cleanup;
+ }
+
+ //
+ // Done.
+ //
+
+ Failed = FALSE;
+
+ //
+ // Be tidy.
+ //
+Cleanup:
+
+ //
+ // If we couldn't read the key,
+ // return the default key.
+ //
+
+ if ( Failed ) {
+ RtlZeroMemory( FullSyncKey, sizeof(*FullSyncKey));
+ FullSyncKey->Version = FULL_SYNC_KEY_VERSION;
+ FullSyncKey->SyncState = NormalState;
+ }
+
+ PrintFullSyncKey( DBIndex, FullSyncKey, "Query" );
+
+ if ( ParmHandle != NULL ) {
+ RegCloseKey( ParmHandle );
+ }
+ return;
+
+}
+
+
+NTSTATUS
+NlForceStartupSync(
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Mark the specified database that a full sync is required. The database
+ is marked in memory and on disk to ensure a full sync is completed in
+ the event of a reboot.
+
+Arguments:
+
+ DBInfo - pointer to database info structure.
+
+Return Value:
+
+ NT Status Code
+
+--*/
+{
+ NTSTATUS Status;
+ LARGE_INTEGER LargeZero;
+
+
+ IF_DEBUG( BREAKPOINT ) {
+ NlAssert( FALSE );
+ }
+
+ //
+ // Mark the in-memory structure that a full sync is required.
+ //
+
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ DBInfo->UpdateRqd = TRUE;
+ DBInfo->FullSyncRequired = TRUE;
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ //
+ // Mark the on-disk version in-case we reboot.
+ //
+
+ LargeZero.QuadPart = 0;
+ switch (DBInfo->DBIndex) {
+
+ //
+ // Mark a SAM database.
+ //
+
+ case SAM_DB:
+ case BUILTIN_DB:
+
+ Status = SamISetSerialNumberDomain(
+ DBInfo->DBHandle,
+ &LargeZero,
+ &LargeZero,
+ (BOOLEAN) TRUE );
+
+ break;
+
+ //
+ // Mark a policy database
+ //
+
+ case LSA_DB:
+
+ Status = LsaISetSerialNumberPolicy(
+ DBInfo->DBHandle,
+ &LargeZero,
+ &LargeZero,
+ (BOOLEAN) TRUE );
+ break;
+
+ }
+
+ NlPrint((NL_SYNC,
+ "NlForceStartupSync: Setting " FORMAT_LPWSTR " serial number to Zero\n",
+ DBInfo->DBName ));
+
+ return Status;
+}
+
+
+VOID
+FreeSamSyncTables(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This function frees the SAM enum buffers
+
+Arguments:
+
+ none
+
+Return Value:
+
+ none
+
+--*/
+{
+ if( NlGlobalSamUserRids != NULL ) {
+ MIDL_user_free( NlGlobalSamUserRids );
+ NlGlobalSamUserRids = NULL;
+ }
+ NlGlobalSamUserCount = 0;
+
+ if( NlGlobalSamGroupRids != NULL ) {
+ MIDL_user_free( NlGlobalSamGroupRids );
+ NlGlobalSamGroupRids = NULL;
+ }
+ NlGlobalSamGroupCount = 0;
+
+ if( NlGlobalSamAliasesEnumBuffer != NULL ) {
+ SamIFree_SAMPR_ENUMERATION_BUFFER( NlGlobalSamAliasesEnumBuffer );
+ NlGlobalSamAliasesEnumBuffer = NULL;
+ }
+}
+
+
+VOID
+FreeLsaSyncTables(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This function frees the LSA enum buffers
+
+Arguments:
+
+ none
+
+Return Value:
+
+ none
+
+--*/
+{
+ if( NlGlobalLsaAccountsEnumBuffer.Information != NULL ) {
+
+ LsaIFree_LSAPR_ACCOUNT_ENUM_BUFFER( &NlGlobalLsaAccountsEnumBuffer );
+ NlGlobalLsaAccountsEnumBuffer.Information = NULL;
+ NlGlobalLsaAccountsEnumBuffer.EntriesRead = 0;
+ }
+
+ if( NlGlobalLsaTDomainsEnumBuffer.Information != NULL ) {
+
+ LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( &NlGlobalLsaTDomainsEnumBuffer );
+ NlGlobalLsaTDomainsEnumBuffer.Information = NULL;
+ NlGlobalLsaTDomainsEnumBuffer.EntriesRead = 0;
+ }
+
+ if( NlGlobalLsaSecretsEnumBuffer != NULL ) {
+
+ MIDL_user_free( NlGlobalLsaSecretsEnumBuffer );
+ NlGlobalLsaSecretsEnumBuffer = NULL;
+ NlGlobalLsaSecretCountReturned = 0;
+ }
+}
+
+
+NTSTATUS
+InitSamSyncTables(
+ PDB_INFO DBInfo,
+ SYNC_STATE SyncState,
+ DWORD ContinuationRid
+ )
+/*++
+
+Routine Description:
+
+ This function enumerates the users, group and alias objects from the
+ existing database and leaves the enum buffers in the global
+ pointers.
+
+Arguments:
+
+ DBInfo - pointer to database info structure.
+
+ SyncState - State sync is continuing from
+
+ ContinuationRid - Rid of the last account successfully copied
+
+Return Value:
+
+ NT Status code.
+
+ Note: The enum buffers gotten from SAM are left in the global pointers
+ and they need to be freed up by the clean up function.
+
+--*/
+{
+ NTSTATUS Status;
+
+ SAM_ENUMERATE_HANDLE EnumerationContext;
+ ULONG CountReturned;
+
+ //
+ // sanity checks
+ //
+
+ NlAssert( NlGlobalSamUserRids == NULL );
+ NlAssert( NlGlobalSamGroupRids == NULL );
+ NlAssert( NlGlobalSamAliasesEnumBuffer == NULL );
+
+
+ //
+ // Enumerate users
+ //
+
+ if ( SyncState <= UserState ) {
+ Status = SamIEnumerateAccountRids(
+ DBInfo->DBHandle,
+ SAM_USER_ACCOUNT,
+ (SyncState == UserState) ? ContinuationRid : 0, // Return RIDs greater than this
+ MAX_SAM_PREF_LENGTH,
+ &NlGlobalSamUserCount,
+ &NlGlobalSamUserRids );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlGlobalSamUserRids = NULL;
+ NlGlobalSamUserCount = 0;
+ goto Cleanup;
+ }
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+ }
+
+
+ //
+ // Enumerate groups
+ //
+
+ if ( SyncState <= GroupState ) {
+ Status = SamIEnumerateAccountRids(
+ DBInfo->DBHandle,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ (SyncState == GroupState) ? ContinuationRid : 0, // Return RIDs greater than this
+ MAX_SAM_PREF_LENGTH,
+ &NlGlobalSamGroupCount,
+ &NlGlobalSamGroupRids );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlGlobalSamGroupRids = NULL;
+ NlGlobalSamGroupCount = 0;
+ goto Cleanup;
+ }
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+ }
+
+
+ //
+ // Enumerate Aliases
+ //
+
+ if ( SyncState <= AliasState ) {
+ EnumerationContext = 0;
+ Status = SamrEnumerateAliasesInDomain(
+ DBInfo->DBHandle,
+ &EnumerationContext,
+ &NlGlobalSamAliasesEnumBuffer,
+ MAX_SAM_PREF_LENGTH,
+ &CountReturned );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlGlobalSamAliasesEnumBuffer = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // sanity checks
+ //
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+ NlAssert( CountReturned ==
+ NlGlobalSamAliasesEnumBuffer->EntriesRead );
+ }
+
+ //
+ // Cleanup after ourselves
+ //
+
+Cleanup:
+
+ if( Status != STATUS_SUCCESS ) {
+ FreeSamSyncTables();
+ }
+
+ return Status;
+
+}
+
+
+NTSTATUS
+InitLsaSyncTables(
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ This function enumerates the lsa account, trusted domain and secret
+ objects from the existing database and leaves the enum buffers in
+ the global pointers.
+
+Arguments:
+
+ DBInfo - pointer to database info structure.
+
+Return Value:
+
+ NT Status code.
+
+ Note: This enum buffer got from LSA are left in the global pointers
+ and they need to be freed up by the clean up function.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSA_ENUMERATION_HANDLE EnumerationContext;
+
+ //
+ // sanity checks
+ //
+
+ NlAssert( NlGlobalLsaAccountsEnumBuffer.Information == NULL );
+ NlAssert( NlGlobalLsaTDomainsEnumBuffer.Information == NULL );
+ NlAssert( NlGlobalLsaSecretsEnumBuffer == NULL );
+
+ //
+ // enumerate lsa accounts
+ //
+
+ EnumerationContext = 0;
+ Status = LsarEnumerateAccounts(
+ DBInfo->DBHandle,
+ &EnumerationContext,
+ &NlGlobalLsaAccountsEnumBuffer,
+ MAX_LSA_PREF_LENGTH );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlGlobalLsaAccountsEnumBuffer.Information = NULL;
+ NlGlobalLsaAccountsEnumBuffer.EntriesRead = 0;
+
+ if( Status != STATUS_NO_MORE_ENTRIES ) {
+
+ goto Cleanup;
+ }
+ }
+
+ //
+ // set this flag to indicate that we haven't received any account
+ // record from PDC during full sync.
+ //
+
+ NlGlobalLsaAccountsHack = FALSE;
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+
+ //
+ // enumerate lsa TDomains
+ //
+
+ EnumerationContext = 0;
+ Status = LsarEnumerateTrustedDomains(
+ DBInfo->DBHandle,
+ &EnumerationContext,
+ &NlGlobalLsaTDomainsEnumBuffer,
+ MAX_LSA_PREF_LENGTH );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlGlobalLsaTDomainsEnumBuffer.Information = NULL;
+ NlGlobalLsaTDomainsEnumBuffer.EntriesRead = 0;
+
+ if( Status != STATUS_NO_MORE_ENTRIES ) {
+
+ goto Cleanup;
+ }
+ }
+
+ //
+ // sanity checks
+ //
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+
+ //
+ // Enumerate secrets
+ //
+
+ EnumerationContext = 0;
+ Status = LsaIEnumerateSecrets(
+ DBInfo->DBHandle,
+ &EnumerationContext,
+ &NlGlobalLsaSecretsEnumBuffer,
+ MAX_LSA_PREF_LENGTH,
+ &NlGlobalLsaSecretCountReturned );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlGlobalLsaSecretsEnumBuffer = NULL;
+ NlGlobalLsaSecretCountReturned = 0;
+
+ if( Status != STATUS_NO_MORE_ENTRIES ) {
+
+ goto Cleanup;
+ }
+ }
+
+ //
+ // sanity checks
+ //
+
+ NlAssert( Status != STATUS_MORE_ENTRIES );
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup after ourselves
+ //
+
+Cleanup:
+
+ if( Status != STATUS_SUCCESS ) {
+
+ FreeLsaSyncTables();
+ }
+
+ return Status;
+
+}
+
+
+VOID
+UpdateSamSyncTables(
+ IN LOCAL_SAM_ACCOUNT_TYPE AccountType,
+ IN ULONG RelativeId
+ )
+/*++
+
+Routine Description:
+
+ Zero out the specified relative ID in the enum buffer.
+
+Arguments:
+
+ AccountType - Type of the account object.
+
+ RelativeId - Relative ID to search for.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ ULONG i;
+ ULONG Entries;
+
+ if ( AccountType == AliasAccount ) {
+ PSAMPR_RID_ENUMERATION Entry;
+ Entry = NlGlobalSamAliasesEnumBuffer->Buffer;
+
+ //
+ // If there are no entries to mark,
+ // simply return.
+ //
+
+ if ( Entry == NULL ) {
+ return;
+ }
+
+ //
+ // mark the entry.
+ //
+
+ for (i = 0; i < NlGlobalSamAliasesEnumBuffer->EntriesRead; i++ ) {
+ if ( Entry[i].RelativeId == RelativeId ) {
+ Entry[i].RelativeId = 0;
+ return;
+ }
+ }
+
+ } else {
+
+ PULONG RidArray;
+
+ switch( AccountType ) {
+ case UserAccount:
+ Entries = NlGlobalSamUserCount;
+ RidArray = NlGlobalSamUserRids;
+ break;
+
+ case GroupAccount:
+ Entries = NlGlobalSamGroupCount;
+ RidArray = NlGlobalSamGroupRids;
+ break;
+ }
+
+ //
+ // If there are no entries to mark,
+ // simply return.
+ //
+
+ if ( RidArray == NULL ) {
+ return;
+ }
+
+ //
+ // mark the entry.
+ //
+
+ for (i = 0; i < Entries; i++ ) {
+ if ( RidArray[i] == RelativeId ) {
+ RidArray[i] = 0;
+ return;
+ }
+ }
+
+
+ }
+
+
+ NlPrint((NL_SYNC_MORE, "UpdateSamSyncTables: can't find entry 0x%lx\n",
+ RelativeId ));
+
+}
+
+
+VOID
+UpdateLsaSyncTables(
+ IN LOCAL_LSA_ACCOUNT_TYPE AccountType,
+ IN PVOID Key
+ )
+/*++
+
+Routine Description:
+
+ Free the specified Key in the enum buffer.
+
+Arguments:
+
+ AccountType - Type of the account object.
+
+ Sid - Key to search for, this will either be a pointer to a SID
+ (PSID) or pointer to a secret name (LPWSTR).
+
+Return Value:
+
+ None.
+
+--*/
+{
+ ULONG i;
+ ULONG Entries;
+
+ PLSAPR_ACCOUNT_INFORMATION LsaAccountEntry;
+ PLSAPR_TRUST_INFORMATION LsaTDomainEntry;
+ PLSAPR_UNICODE_STRING LsaSecretEntry;
+
+ switch( AccountType ) {
+
+ case LsaAccount:
+ Entries = NlGlobalLsaAccountsEnumBuffer.EntriesRead;
+ LsaAccountEntry = NlGlobalLsaAccountsEnumBuffer.Information;
+
+ //
+ // received an account record.
+ //
+
+ NlGlobalLsaAccountsHack = TRUE;
+
+ //
+ // mark the entry.
+ //
+
+ for (i = 0; i < Entries; i++, LsaAccountEntry++ ) {
+
+ if ( ( LsaAccountEntry->Sid != NULL ) &&
+ RtlEqualSid( (PSID)LsaAccountEntry->Sid,
+ (PSID)Key )) {
+
+ //
+ // match found, free it up and make the pointer NULL.
+ //
+
+ MIDL_user_free( LsaAccountEntry->Sid );
+ LsaAccountEntry->Sid = NULL;
+
+ return;
+ }
+ }
+
+ break;
+
+ case LsaTDomain:
+ Entries = NlGlobalLsaTDomainsEnumBuffer.EntriesRead;
+ LsaTDomainEntry = NlGlobalLsaTDomainsEnumBuffer.Information;
+
+ for (i = 0; i < Entries; i++, LsaTDomainEntry++ ) {
+
+ if ( ( LsaTDomainEntry->Sid != NULL ) &&
+ RtlEqualSid( (PSID)LsaTDomainEntry->Sid,
+ (PSID)Key )) {
+
+ //
+ // match found, free it up and make the pointer NULL.
+ //
+
+ MIDL_user_free( LsaTDomainEntry->Sid );
+ LsaTDomainEntry->Sid = NULL;
+
+ return;
+ }
+ }
+ break;
+
+ case LsaSecret:
+ Entries = NlGlobalLsaSecretCountReturned;
+ LsaSecretEntry = NlGlobalLsaSecretsEnumBuffer;
+
+ for (i = 0; i < Entries; i++, LsaSecretEntry++ ) {
+
+ if ( ( LsaSecretEntry->Buffer != NULL ) &&
+ !wcsncmp( LsaSecretEntry->Buffer,
+ (LPWSTR)Key,
+ LsaSecretEntry->Length /
+ sizeof(WCHAR) )) {
+
+ //
+ // match found, make the pointer NULL.
+ // since secret enum buffer is a single buffer
+ // consists of serveral secret names, we make the
+ // pointer NULL, but don't free it.
+ //
+
+ LsaSecretEntry->Buffer = NULL;
+
+ return;
+ }
+ }
+ break;
+ }
+
+ NlPrint((NL_SYNC_MORE, "UpdateLsaSyncTables: can't find entry\n"));
+
+}
+
+
+NTSTATUS
+CleanSamSyncTables(
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Delete all users, groups, and aliases that remain in the sync
+ tables. These are users, groups, and aliases that existed in the
+ local database but not in the version on the PDC.
+
+Arguments:
+
+ DBInfo - pointer to database info structure.
+
+Return Value:
+
+ NT Status code.
+
+ Note: The enum buffers got from SAM by the init function are
+ freed in this function and the pointer are reset to NULL.
+
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS RetStatus = STATUS_SUCCESS;
+
+ ULONG i;
+
+ //
+ // Delete all the left over users.
+ //
+
+ for (i = 0; i < NlGlobalSamUserCount; i++ ) {
+
+ if ( NlGlobalSamUserRids[i] != 0 ) {
+
+ Status = NlDeleteSamUser(
+ DBInfo->DBHandle,
+ NlGlobalSamUserRids[i] );
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanSamSyncTables: error deleting user %lx %lX\n",
+ NlGlobalSamUserRids[i],
+ Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+
+ NlPrint((NL_SYNC_MORE,
+ "CleanSamSyncTables: deleting user %lx\n",
+ NlGlobalSamUserRids[i] ));
+ }
+ }
+
+ //
+ // Delete all the left over Groups.
+ //
+
+ for (i = 0; i < NlGlobalSamGroupCount; i++ ) {
+
+ if ( NlGlobalSamGroupRids[i] != 0 ) {
+
+ Status = NlDeleteSamGroup(
+ DBInfo->DBHandle,
+ NlGlobalSamGroupRids[i] );
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanSamSyncTables: error deleting Group %lx %lX\n",
+ NlGlobalSamGroupRids[i],
+ Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+
+ NlPrint((NL_SYNC_MORE,
+ "CleanSamSyncTables: deleting group %lx\n",
+ NlGlobalSamGroupRids[i] ));
+ }
+ }
+
+ //
+ // Delete all the left over Aliases.
+ //
+
+ if ( NlGlobalSamAliasesEnumBuffer != NULL ) {
+ PSAMPR_RID_ENUMERATION Entry;
+
+ Entry = NlGlobalSamAliasesEnumBuffer->Buffer;
+
+ for (i = 0; i < NlGlobalSamAliasesEnumBuffer->EntriesRead; i++, Entry++ ) {
+
+ if ( Entry->RelativeId != 0 ) {
+
+ Status = NlDeleteSamAlias(
+ DBInfo->DBHandle,
+ Entry->RelativeId );
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanSamSyncTables: error deleting Alias %lu %lX\n",
+ Entry->RelativeId,
+ Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+
+ NlPrint((NL_SYNC_MORE,
+ "CleanSamSyncTables: deleting alias %lx\n",
+ Entry->RelativeId ));
+
+ }
+ }
+ }
+
+ //
+ // free up sam enum buffers
+ //
+
+ FreeSamSyncTables();
+
+ return RetStatus;
+}
+
+
+
+NTSTATUS
+CleanLsaSyncTables(
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Delete all Lsa Accounts, Trusted Domains, and Secrets that remain in
+ the sync tables. These are Lsa Accounts, Trusted Domains, and
+ Secrets that existed in the local database but not in the version on
+ the PDC.
+
+Arguments:
+
+ DBInfo - pointer to database info structure.
+
+Return Value:
+
+ NT Status code.
+
+ Note: The enum buffers got from LSA by the init function are
+ freed in this function and the pointer are reset to NULL.
+
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS RetStatus = STATUS_SUCCESS;
+
+ ULONG i;
+ ULONG Entries;
+
+ PLSAPR_ACCOUNT_INFORMATION LsaAccountEntry;
+ PLSAPR_TRUST_INFORMATION LsaTDomainEntry;
+ PLSAPR_UNICODE_STRING LsaSecretEntry;
+
+ LSAPR_HANDLE LsaHandle;
+
+ //
+ // Delete all the left over Lsa accounts.
+ //
+
+ Entries = NlGlobalLsaAccountsEnumBuffer.EntriesRead;
+ LsaAccountEntry = NlGlobalLsaAccountsEnumBuffer.Information;
+
+ //
+ // if no account record received then the PDC must be running
+ // old build that can't enumerate accounts from LSA database. So
+ // don't delete the existing accounts on this database.
+ //
+
+ if( NlGlobalLsaAccountsHack == TRUE ) {
+
+ for (i = 0; i < Entries; i++, LsaAccountEntry++ ) {
+
+ if ( LsaAccountEntry->Sid != NULL ) {
+
+ Status = LsarOpenAccount(
+ DBInfo->DBHandle,
+ LsaAccountEntry->Sid,
+ 0, // No desired access
+ &LsaHandle );
+
+ if ( (!NT_SUCCESS(Status)) ||
+ (!NT_SUCCESS(
+ Status = LsarDelete( LsaHandle ))) ) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanLsaSyncTables: error deleting LsaAccount %lX\n",
+ Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+
+ }
+ }
+ }
+
+ //
+ // Delete all the left over trusted domain accounts.
+ //
+
+ Entries = NlGlobalLsaTDomainsEnumBuffer.EntriesRead;
+ LsaTDomainEntry = NlGlobalLsaTDomainsEnumBuffer.Information;
+
+ for (i = 0; i < Entries; i++, LsaTDomainEntry++ ) {
+
+ if ( LsaTDomainEntry->Sid != NULL ) {
+
+ Status = LsarOpenTrustedDomain(
+ DBInfo->DBHandle,
+ LsaTDomainEntry->Sid,
+ 0, // No desired access
+ &LsaHandle );
+
+ if ( (!NT_SUCCESS(Status)) ||
+ (!NT_SUCCESS(
+ Status = LsarDelete( LsaHandle ))) ) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanLsaSyncTables: error deleting "
+ "TrustedDomain %lx\n",
+ Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+
+ //
+ // The BDC needs to keep its internal trust list up to date.
+ //
+
+ NlUpdateTrustListBySid( LsaTDomainEntry->Sid, NULL );
+
+ }
+ }
+
+ //
+ // Delete all the left over secrets.
+ //
+
+ Entries = NlGlobalLsaSecretCountReturned;
+ LsaSecretEntry = (PLSAPR_UNICODE_STRING)NlGlobalLsaSecretsEnumBuffer;
+
+ for (i = 0; i < Entries; i++, LsaSecretEntry++ ) {
+
+ if ( LsaSecretEntry->Buffer != 0 ) {
+
+ //
+ // ignore local secret objects.
+ //
+
+ if( (LsaSecretEntry->Length / sizeof(WCHAR) >
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) &&
+ (_wcsnicmp( LsaSecretEntry->Buffer,
+ LSA_GLOBAL_SECRET_PREFIX,
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0) ) {
+
+
+ Status = LsarOpenSecret(
+ DBInfo->DBHandle,
+ LsaSecretEntry,
+ 0, // No desired access
+ &LsaHandle );
+
+ if ( (!NT_SUCCESS(Status)) ||
+ (!NT_SUCCESS(
+ Status = LsarDelete( LsaHandle ))) ) {
+
+ NlPrint((NL_CRITICAL,
+ "CleanSyncTables: "
+ "error deleting LsaSecret (%wZ) %lx\n",
+ LsaSecretEntry, Status ));
+
+ RetStatus = Status;
+ continue;
+ }
+ }
+ }
+ }
+
+ //
+ // free up sam enum buffers
+ //
+
+ FreeLsaSyncTables();
+
+ return RetStatus;
+}
+
+
+NTSTATUS
+NlRecoverConflictingAccount(
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ ULONG ConflictingRid,
+ PSESSION_INFO SessionInfo,
+ NTSTATUS Status,
+ BOOLEAN CleanSyncTable,
+ PBOOLEAN ResourceError
+ )
+/*++
+
+Routine Description:
+
+ This procedure recovers the replication from conflicting account. It
+ deletes the conflicting account and create a new account with the
+ given RID.
+
+Arguments:
+
+ Delta: Delta record that is been processed.
+
+ ConflictingRid: Rid of the conflicting account currently on the
+ database.
+
+ SessionInfo: Information shared between PDC and BDC
+
+ Status: Status returned by SamICreateAccountByRid() call.
+
+ CleanSyncTable: if TRUE the Conflicting account is removed from sync
+ table.
+
+ ResourceError: Returns true if this machine is out of resources
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NETLOGON_DELTA_TYPE DeltaType;
+
+ ULONG SaveRID;
+ ULONG DummyRID;
+
+ LOCAL_SAM_ACCOUNT_TYPE AccountType;
+
+ //
+ // if we are trying to a new add user, group or alias
+ // object and if there is an object already exists
+ // then delete the conflicting object and try adding
+ // new object again.
+ //
+
+ DeltaType = Delta->DeltaType;
+
+ if ( ( Status == STATUS_USER_EXISTS ||
+ Status == STATUS_GROUP_EXISTS ||
+ Status == STATUS_ALIAS_EXISTS ) &&
+
+ ( DeltaType == AddOrChangeUser ||
+ DeltaType == AddOrChangeGroup ||
+ DeltaType == AddOrChangeAlias ) ) {
+
+ NlPrint((NL_SYNC,
+ "NlRecoverConflictingAccount: "
+ "conflicting Account: DeltaType (%d), "
+ "Status(%lx), ConflictingRid(%lx)\n",
+ DeltaType, Status, ConflictingRid ));
+
+ SaveRID = Delta->DeltaID.Rid;
+
+ //
+ // Delete conflicting user/group/alias.
+ //
+
+ if ( Status == STATUS_USER_EXISTS ) {
+ Delta->DeltaType = DeleteUser;
+ AccountType = UserAccount;
+
+ } else if ( Status == STATUS_GROUP_EXISTS ) {
+ Delta->DeltaType = DeleteGroup;
+ AccountType = GroupAccount;
+
+ } else {
+ Delta->DeltaType = DeleteAlias;
+ AccountType = AliasAccount;
+ }
+
+ Delta->DeltaID.Rid = ConflictingRid;
+
+ Status = NlUnpackSam( Delta, DBInfo, &DummyRID, SessionInfo );
+
+ Delta->DeltaType = DeltaType;
+ Delta->DeltaID.Rid = SaveRID;
+
+ if ( NT_SUCCESS(Status) ) {
+
+ //
+ // Delete the deleted user/group/alias from the
+ // sync tables.
+ //
+
+ if( CleanSyncTable ) {
+
+ UpdateSamSyncTables( AccountType, ConflictingRid );
+ }
+
+ Delta->DeltaType = DeltaType;
+ Delta->DeltaID.Rid = SaveRID;
+
+ //
+ // Add the group
+ //
+
+ Status = NlUnpackSam( Delta, DBInfo, &DummyRID, SessionInfo );
+
+ }
+
+ }
+
+ //
+ // Log the failure
+ //
+
+ if ( !NT_SUCCESS( Status )) {
+
+ NlPrint((NL_CRITICAL,
+ "Unsuccessful NlUnpackSam: Status (%lx)\n",
+ Status ));
+
+ //
+ // Log which particular account had a problem.
+ //
+
+ NlLogSyncError( Delta, DBInfo, Status );
+
+ }
+
+ //
+ // If we failed for some temporary reason,
+ // stop the sync now to let the system cure itself.
+ //
+
+ *ResourceError = ( Status == STATUS_DISK_FULL ||
+ Status == STATUS_NO_MEMORY ||
+ Status == STATUS_INSUFFICIENT_RESOURCES);
+
+ return Status;
+}
+
+
+
+
+
+ULONG
+NlComputeSyncSleepTime(
+ IN PLARGE_INTEGER ApiStartTime,
+ IN PLARGE_INTEGER ApiFinishTime
+ )
+/*++
+
+Routine Description:
+
+ Compute the amount of time the caller should sleep to ensure we stay
+ within the ReplicationGovernor percentage.
+
+ This routine is called after all processing of the previous delta has
+ been completed on the BDC.
+
+Arguments:
+
+ ApiStartTime -- Time when the previous call to the PDC was made.
+
+ ApiFinishTime -- Time when the previous call to the PDC completed.
+
+Return Value:
+
+ Returns the time to sleep (in milliseconds)
+
+--*/
+{
+ LARGE_INTEGER GoalTimePerLoop;
+ LARGE_INTEGER TimeSpentSoFar;
+ LARGE_INTEGER TimeToSleep;
+ LARGE_INTEGER TimeOnWire;
+
+ //
+ // If the Governor isn't restricting the call rate,
+ // return now indicating no sleep is needed.
+ //
+ if ( NlGlobalGovernorParameter == 100 ) {
+ return 0;
+ }
+
+ //
+ // Since this option will only be used on slow WAN links,
+ // approximate the time spent on the wire as the time it took to complete
+ // the API call to the PDC.
+ //
+
+ TimeOnWire.QuadPart = ApiFinishTime->QuadPart - ApiStartTime->QuadPart;
+ if ( TimeOnWire.QuadPart <= 0 ) {
+ return 0;
+ }
+
+ //
+ // Compute the amount of time we need to spend grand total
+ // between successive calls to the PDC.
+ //
+
+ GoalTimePerLoop.QuadPart = TimeOnWire.QuadPart * 100;
+ GoalTimePerLoop.QuadPart /= NlGlobalGovernorParameter;
+
+ //
+ // Compute the amount of time we actually spent since the
+ // last call to the PDC.
+ //
+
+ (VOID)NtQuerySystemTime( &TimeSpentSoFar );
+ TimeSpentSoFar.QuadPart -= ApiStartTime->QuadPart;
+ if ( TimeSpentSoFar.QuadPart <= 0 ) {
+ return 0;
+ }
+
+ //
+ // Compute the amount of time we need to sleep.
+ //
+
+ TimeToSleep.QuadPart = GoalTimePerLoop.QuadPart - TimeSpentSoFar.QuadPart;
+ if ( TimeToSleep.QuadPart <= 0 ) {
+ return 0;
+ }
+
+ //
+ // Covert from 100-ns to milliseconds
+ //
+
+ TimeToSleep.QuadPart /= 10000;
+
+ if ( TimeToSleep.QuadPart > MAX_SYNC_SLEEP_TIME ) {
+ return MAX_SYNC_SLEEP_TIME;
+ }
+
+ return (DWORD)TimeToSleep.QuadPart;
+}
+
+
+NTSTATUS
+NlSynchronize(
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ To bring this database in sync with the primary. This function will
+ be called if synchronization was specified from command line via
+ /SYNC:Yes or STATUS_SYNCHRONIZATION_REQUIRED was encountered while
+ doing NetAccountDeltas or if we are hopelessly out of sync due to a
+ crash and are in recovery mode.
+
+ If this function failed to complete then the existing SAM database
+ on this machine will be hosed and could not be relied upon. Hence
+ if we fail the caller of this function should reset the primary
+ cookie in the header so that an automatic ReSync is forced as soon
+ as next announcement from the primary
+
+Arguments:
+
+ NONE.
+
+Return Value:
+
+ NT Status Code.
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+
+ ULONG SamSyncContext;
+ SYNC_STATE SyncStateForPdc;
+
+ NTSTATUS SyncStatus;
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL;
+ DWORD DeltaIndex;
+ ULONG PreferredMaximum;
+
+ FULL_SYNC_KEY FullSyncKey;
+
+ LARGE_INTEGER ApiStartTime;
+ LARGE_INTEGER ApiFinishTime;
+ DWORD SyncSleepTime;
+
+ SESSION_INFO SessionInfo;
+
+ ULONG ConflictingRid;
+
+ LPWSTR MsgStrings[3];
+ BOOLEAN FirstTry = TRUE;
+
+ //
+ // Initialization.
+ //
+
+ PreferredMaximum = (SAM_DELTA_BUFFER_SIZE * NlGlobalGovernorParameter) / 100;
+
+ //
+ // Ensure that if we get interrupted in the middle that a newly started
+ // netlogon service will sync.
+ //
+
+ Status = NlForceStartupSync( DBInfo );
+ if ( !NT_SUCCESS(Status) ) {
+ return Status;
+ }
+
+ //
+ // If we're not currently authenticated with the PDC,
+ // do so now.
+ //
+
+FirstTryFailed:
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlSynchronize: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+
+ Status = NlSessionSetup( NlGlobalClientSession );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Grab a copy of the Negotiated Flags
+ //
+
+ SessionInfo.NegotiatedFlags = NlGlobalClientSession->CsNegotiatedFlags;
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlSynchronize: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ //
+ // Determine where the full sync left off.
+ //
+
+ NlQueryFullSyncKey( DBInfo->DBIndex, &FullSyncKey );
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_FULL_SYNC_RESTART ) {
+ SamSyncContext = FullSyncKey.ContinuationRid;
+ SyncStateForPdc = FullSyncKey.SyncState;
+ } else {
+ SamSyncContext = 0;
+ SyncStateForPdc = NormalState;
+ }
+
+ //
+ // build sync tables
+ //
+
+ if ( FirstTry ) {
+ if( DBInfo->DBIndex == LSA_DB ) {
+ Status = InitLsaSyncTables( DBInfo );
+ } else {
+ Status = InitSamSyncTables( DBInfo, SyncStateForPdc, SamSyncContext );
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Loop calling the PDC to get a bunch of deltas
+ //
+
+ SyncSleepTime = 0;
+ for (;;) {
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+
+ //
+ // Wait a while so we don't overburden the secure channel.
+ //
+
+ if ( SyncSleepTime != 0 ) {
+ NlPrint(( NL_SYNC,
+ "NlSynchronize: sleeping %ld for the governor.\n",
+ SyncSleepTime ));
+ (VOID) WaitForSingleObject( NlGlobalReplicatorTerminateEvent, SyncSleepTime );
+ }
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlSynchronize: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ //
+ // Build the Authenticator for this request to the PDC.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlSynchronize: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint((NL_CRITICAL, "NlSynchronize: Client session dropped.\n" ));
+ Status = NlGlobalClientSession->CsConnectionStatus;
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+
+ NlBuildAuthenticator(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &NlGlobalClientSession->CsSessionKey,
+ &OurAuthenticator);
+
+
+ //
+ // copy session key to decrypt sensitive information.
+ // (Copy SessionKey again since we need to grab SessionKey with
+ // the write lock held and call the API to the PDC with the same
+ // write lock..)
+
+ SessionInfo.SessionKey = NlGlobalClientSession->CsSessionKey;
+ SessionInfo.NegotiatedFlags = NlGlobalClientSession->CsNegotiatedFlags;
+
+
+ SyncStatus = NlStartApiClientSession( NlGlobalClientSession, FALSE );
+
+
+ if (NT_SUCCESS(SyncStatus)) {
+ STARTSSIAPITIMER;
+
+ ApiStartTime = NlGlobalClientSession->CsApiTimer.StartTime;
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_FULL_SYNC_RESTART ) {
+
+ SyncStatus = I_NetDatabaseSync2(
+ NlGlobalClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ DBInfo->DBIndex,
+ SyncStateForPdc,
+ &SamSyncContext,
+ &DeltaArray,
+ PreferredMaximum );
+ } else {
+ SyncStatus = I_NetDatabaseSync(
+ NlGlobalClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ DBInfo->DBIndex,
+ &SamSyncContext,
+ &DeltaArray,
+ PreferredMaximum );
+ }
+
+ if ( NlGlobalGovernorParameter != 100 ) {
+ (VOID) NtQuerySystemTime( &ApiFinishTime );
+ }
+ STOPSSIAPITIMER;
+ }
+ (VOID)NlFinishApiClientSession( NlGlobalClientSession, TRUE );
+
+ NlPrint((NL_REPL_TIME,"I_NetDatabaseSync Time:\n"));
+ PRINTSSIAPITIMER;
+
+ //
+ // On an access denied error, force an authentication.
+ //
+ // Returned authenticator may be invalid.
+ //
+
+ if ( (SyncStatus == STATUS_ACCESS_DENIED) ||
+ ( !NlUpdateSeed(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &NlGlobalClientSession->CsSessionKey) ) ) {
+
+ if ( NT_SUCCESS(SyncStatus) ) {
+ Status = STATUS_ACCESS_DENIED;
+ } else {
+ Status = SyncStatus;
+ }
+
+ NlPrint((NL_CRITICAL, "NlSynchronize: authentication failed: %lx\n", Status ));
+
+ NlSetStatusClientSession( NlGlobalClientSession, Status );
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ //
+ // Perhaps the netlogon service on the PDC has just restarted.
+ // Try just once to set up a session to the server again.
+ //
+ if ( FirstTry && SyncStatus == STATUS_ACCESS_DENIED ) {
+ FirstTry = FALSE;
+ goto FirstTryFailed;
+ }
+
+ goto Cleanup;
+ }
+
+ FirstTry = FALSE;
+ SyncStateForPdc = NormalState;
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+
+ //
+ // Finally, error out
+ //
+
+ if ( !NT_SUCCESS( SyncStatus ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSynchronize: "
+ "I_NetDatabaseSync returning: Status (%lx)\n",
+ SyncStatus ));
+
+ Status = SyncStatus;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Loop through the deltas updating the local User and Group list.
+ //
+
+ for ( DeltaIndex = 0;
+ DeltaIndex < DeltaArray->CountReturned;
+ DeltaIndex++ ) {
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlSynchronize: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ //
+ // Unpack the buffer and apply changes to our database
+ //
+
+ Status = NlUnpackSam(
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo,
+ &ConflictingRid,
+ &SessionInfo );
+
+ if ( ! NT_SUCCESS( Status ) ) {
+ BOOLEAN ResourceError;
+
+ Status = NlRecoverConflictingAccount(
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo,
+ ConflictingRid,
+ &SessionInfo,
+ Status,
+ TRUE,
+ &ResourceError );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // If we failed for some temporary reason,
+ // stop the full sync now to let the system cure itself.
+ //
+
+ if ( ResourceError ) {
+ goto Cleanup;
+ }
+
+ //
+ // If the PDC supports redo,
+ // Write this delta to the redo log and otherwise ignore
+ // the failure.
+ //
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO ){
+ NTSTATUS TempStatus;
+
+ TempStatus = NlWriteDeltaToChangeLog(
+ &NlGlobalRedoLogDesc,
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo->DBIndex,
+ NULL );
+
+ //
+ // If we successfully wrote to the redo log,
+ // there's no reason to fail the full sync
+ //
+
+ if ( NT_SUCCESS( TempStatus )) {
+ Status = STATUS_SUCCESS;
+ }
+
+ }
+
+ //
+ // If this is an unexpected failure,
+ // continue processing deltas.
+ //
+ // It is better to continue copying the database as
+ // much as possible than to quit now. The theory is that
+ // we've stumbled upon some circumstance we haven't
+ // anticipated. We'll put this BDC in the best shape
+ // we possibly can.
+ //
+ // Remember this status code until the end.
+ //
+
+ if ( FullSyncKey.CumulativeStatus == STATUS_SUCCESS ) {
+ FullSyncKey.CumulativeStatus = Status;
+ }
+
+ continue;
+ }
+ }
+
+ //
+ // Handle each delta type differently.
+ //
+
+ switch ( DeltaArray->Deltas[DeltaIndex].DeltaType ) {
+
+ //
+ // Capture the Domain header information as it appeared at the
+ // start of the SYNC on the PDC. We use this value to ensure
+ // we don't miss any Deltas.
+ //
+
+ case AddOrChangeDomain:
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ (DeltaArray->Deltas[DeltaIndex]).DeltaUnion.
+ DeltaDomain->DomainModifiedCount,
+ FullSyncKey.PdcSerialNumber );
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ (DeltaArray->Deltas[DeltaIndex]).DeltaUnion.
+ DeltaDomain->DomainCreationTime,
+ FullSyncKey.PdcDomainCreationTime );
+
+ break;
+
+ case AddOrChangeGroup:
+ UpdateSamSyncTables(
+ GroupAccount,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid);
+
+ FullSyncKey.SyncState = GroupState;
+ FullSyncKey.ContinuationRid =
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid;
+ break;
+
+ case AddOrChangeUser:
+ UpdateSamSyncTables(
+ UserAccount,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid);
+
+ FullSyncKey.SyncState = UserState;
+ FullSyncKey.ContinuationRid =
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid;
+ break;
+
+ case ChangeGroupMembership:
+ FullSyncKey.SyncState = GroupMemberState;
+ FullSyncKey.ContinuationRid =
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid;
+ break;
+
+ case AddOrChangeAlias:
+ UpdateSamSyncTables(
+ AliasAccount,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Rid);
+
+ FullSyncKey.SyncState = AliasState;
+ FullSyncKey.ContinuationRid = 0;
+ break;
+
+ case ChangeAliasMembership:
+ FullSyncKey.SyncState = AliasMemberState;
+ FullSyncKey.ContinuationRid = 0;
+ break;
+
+ //
+ // Capture the policy header information as it appeared at
+ // the start of the SYNC on the PDC. We use this value to
+ // ensure we don't miss any Deltas.
+ //
+
+ case AddOrChangeLsaPolicy:
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ (DeltaArray->Deltas[DeltaIndex]).DeltaUnion.
+ DeltaPolicy->ModifiedId,
+ FullSyncKey.PdcSerialNumber );
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ (DeltaArray->Deltas[DeltaIndex]).DeltaUnion.
+ DeltaPolicy->DatabaseCreationTime,
+ FullSyncKey.PdcDomainCreationTime );
+
+ break;
+
+ case AddOrChangeLsaAccount:
+ UpdateLsaSyncTables(
+ LsaAccount,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Sid);
+ break;
+
+ case AddOrChangeLsaTDomain:
+ UpdateLsaSyncTables(
+ LsaTDomain,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Sid);
+ break;
+
+ case AddOrChangeLsaSecret:
+ UpdateLsaSyncTables(
+ LsaSecret,
+ DeltaArray->Deltas[DeltaIndex].DeltaID.Name);
+ break;
+ }
+
+ }
+
+ MIDL_user_free( DeltaArray );
+ DeltaArray = NULL;
+
+ //
+ // If the PDC has given us all of the deltas it has,
+ // we're all done.
+ //
+
+ if ( SyncStatus == STATUS_SUCCESS ) {
+ Status = STATUS_SUCCESS;
+ break;
+ }
+
+ //
+ // Force SAM to disk before saving the sync key.
+ //
+ // This'll ensure that the sync key doesn't indicate SAM is more
+ // recent than it really is.
+ //
+
+ if( DBInfo->DBIndex != LSA_DB ) {
+ LARGE_INTEGER LargeZero;
+
+ LargeZero.QuadPart = 0;
+
+ Status = SamISetSerialNumberDomain(
+ DBInfo->DBHandle,
+ &LargeZero,
+ &LargeZero,
+ (BOOLEAN) FALSE );
+
+ }
+
+
+ //
+ // Remember how far we've gotten in case a reboot happens.
+ //
+
+ NlSetFullSyncKey( DBInfo->DBIndex, &FullSyncKey );
+
+
+ //
+ // Compute the amount of time we need to wait before calling the PDC
+ // again.
+ //
+
+ SyncSleepTime = NlComputeSyncSleepTime( &ApiStartTime,
+ &ApiFinishTime );
+
+ }
+
+
+ //
+ // We've finished the full sync.
+ //
+ // If there were any errors we ignored along the way,
+ // don't clean up.
+ //
+
+ if ( !NT_SUCCESS(FullSyncKey.CumulativeStatus) ) {
+ Status = FullSyncKey.CumulativeStatus;
+
+ //
+ // Mark that the next full sync needs to start from the beginning.
+ //
+ NlSetFullSyncKey( DBInfo->DBIndex, NULL );
+
+ goto Cleanup;
+ }
+
+
+ //
+ // We've successfully replicated all information from the PDC.
+ //
+ // Delete any objects that don't exist in the PDC.
+ //
+
+ if( DBInfo->DBIndex == LSA_DB ) {
+ CleanLsaSyncTables( DBInfo );
+ } else {
+ CleanSamSyncTables( DBInfo );
+ }
+
+
+ //
+ // Set the domain/policy creation time and modified count to their
+ // values on the PDC at the beginning of the Sync.
+ //
+ // Reset the change log before mucking with the serial number in
+ // the change log descriptor.
+ //
+
+ LOCK_CHANGELOG();
+
+ (VOID) NlFixChangeLog( &NlGlobalChangeLogDesc,
+ DBInfo->DBIndex,
+ FullSyncKey.PdcSerialNumber,
+ FALSE ); // Don't copy deleted records to redo log
+ NlGlobalChangeLogDesc.SerialNumber[DBInfo->DBIndex] = FullSyncKey.PdcSerialNumber;
+ DBInfo->CreationTime = FullSyncKey.PdcDomainCreationTime;
+ UNLOCK_CHANGELOG();
+
+
+ NlPrint((NL_SYNC,
+ "NlSynchronize: Setting " FORMAT_LPWSTR " serial number to %lx %lx\n",
+ DBInfo->DBName,
+ FullSyncKey.PdcSerialNumber.HighPart,
+ FullSyncKey.PdcSerialNumber.LowPart ));
+
+ if( DBInfo->DBIndex == LSA_DB ) {
+
+ Status = LsaISetSerialNumberPolicy(
+ DBInfo->DBHandle,
+ &FullSyncKey.PdcSerialNumber,
+ &FullSyncKey.PdcDomainCreationTime,
+ (BOOLEAN) FALSE );
+
+ } else {
+
+ Status = SamISetSerialNumberDomain(
+ DBInfo->DBHandle,
+ &FullSyncKey.PdcSerialNumber,
+ &FullSyncKey.PdcDomainCreationTime,
+ (BOOLEAN) FALSE );
+
+ }
+
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSynchronize: Unable to set serial number: Status (%lx)\n",
+ Status ));
+
+ goto Cleanup;
+ }
+
+ //
+ // Mark that there is no full sync to continue
+ //
+
+ NlSetFullSyncKey( DBInfo->DBIndex, NULL );
+
+ //
+ // Mark that fact permanently in the database.
+ //
+ (VOID) NlResetFirstTimeFullSync( DBInfo->DBIndex );
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ MsgStrings[0] = DBInfo->DBName;
+ MsgStrings[1] = NlGlobalUncPrimaryName;
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ if ( !NlGlobalReplicatorTerminate ) {
+
+ MsgStrings[2] = (LPWSTR) Status;
+ NlpWriteEventlog(
+ NELOG_NetlogonFullSyncFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 | LAST_MESSAGE_IS_NTSTATUS );
+ }
+ } else {
+
+ NlpWriteEventlog(
+ NELOG_NetlogonFullSyncSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 2 );
+ }
+
+ //
+ // Free locally used resources.
+ //
+
+ //
+ // free up sync tables
+ //
+
+ if( DBInfo->DBIndex == LSA_DB ) {
+ FreeLsaSyncTables();
+ } else {
+ FreeSamSyncTables();
+ }
+
+ if ( DeltaArray != NULL ) {
+ MIDL_user_free( DeltaArray );
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSynchronize: returning unsuccessful: Status (%lx)\n",
+ Status ));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlReplicateDeltas(
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Get recent updates from primary and update our private UAS database.
+ Once this function starts it will get all the updates from primary
+ till our database is in sync.
+
+ This function is executed only at machines which may be running
+ NETLOGON service with member/backup role.
+
+ This procedure executes only in the replicator thread.
+
+Arguments:
+
+ ReplParam - Parameters governing the behavior of the replicator thread.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS DeltaStatus;
+ NTSTATUS Status;
+
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL;
+ DWORD DeltaIndex;
+ ULONG PreferredMaximum;
+
+ ULONG ConflictingRid;
+ LARGE_INTEGER LocalSerialNumber;
+ OLD_LARGE_INTEGER OldLocalSerialNumber;
+ LARGE_INTEGER ExpectedSerialNumber;
+
+ LARGE_INTEGER ApiStartTime;
+ LARGE_INTEGER ApiFinishTime;
+ DWORD SyncSleepTime;
+
+ SESSION_INFO SessionInfo;
+
+ DWORD DeltasApplied;
+ BOOLEAN FirstTry = TRUE;
+ BOOLEAN ForceFullSync = FALSE;
+
+ LPWSTR MsgStrings[3];
+
+ //
+ // Initialization.
+ //
+
+ PreferredMaximum = (SAM_DELTA_BUFFER_SIZE * NlGlobalGovernorParameter) / 100;
+
+
+
+ //
+ // If we're not currently authenticated with the PDC,
+ // do so now.
+ //
+
+FirstTryFailed:
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlReplicateDeltas: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ Status = NlSessionSetup( NlGlobalClientSession );
+ if ( !NT_SUCCESS( Status ) ) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+ }
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+
+ //
+ // Loop calling the PDC to get a bunch of deltas
+ //
+
+ DeltasApplied = 0;
+ SyncSleepTime = 0;
+
+ for (;;) {
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+
+ //
+ // Wait a while so we don't overburden the secure channel.
+ //
+
+ if ( SyncSleepTime != 0 ) {
+ NlPrint(( NL_SYNC,
+ "NlReplicateDeltas: sleeping %ld for the governor.\n",
+ SyncSleepTime ));
+ (VOID) WaitForSingleObject( NlGlobalReplicatorTerminateEvent, SyncSleepTime );
+ }
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlReplicateDeltas: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ //
+ // Build the Authenticator for this request to the PDC.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlReplicateDeltas: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint((NL_CRITICAL, "NlReplicateDeltas: Client session dropped.\n" ));
+ Status = NlGlobalClientSession->CsConnectionStatus;
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+
+ NlBuildAuthenticator(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &NlGlobalClientSession->CsSessionKey,
+ &OurAuthenticator );
+
+ LOCK_CHANGELOG();
+ LocalSerialNumber = NlGlobalChangeLogDesc.SerialNumber[DBInfo->DBIndex];
+ UNLOCK_CHANGELOG();
+
+ ExpectedSerialNumber.QuadPart = LocalSerialNumber.QuadPart + 1;
+ NEW_TO_OLD_LARGE_INTEGER( LocalSerialNumber, OldLocalSerialNumber );
+
+ DeltaStatus = NlStartApiClientSession( NlGlobalClientSession, FALSE );
+
+ if ( NT_SUCCESS(DeltaStatus) ) {
+ STARTSSIAPITIMER;
+
+ ApiStartTime = NlGlobalClientSession->CsApiTimer.StartTime;
+
+ DeltaStatus = I_NetDatabaseDeltas(
+ NlGlobalClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ DBInfo->DBIndex,
+ (PNLPR_MODIFIED_COUNT)&OldLocalSerialNumber,
+ &DeltaArray,
+ PreferredMaximum );
+
+ if ( NlGlobalGovernorParameter != 100 ) {
+ (VOID) NtQuerySystemTime( &ApiFinishTime );
+ }
+ STOPSSIAPITIMER;
+ }
+
+ (VOID)NlFinishApiClientSession( NlGlobalClientSession, TRUE );
+
+ OLD_TO_NEW_LARGE_INTEGER( OldLocalSerialNumber, LocalSerialNumber );
+
+ NlPrint((NL_REPL_TIME, "I_NetDatabaseDeltas Time:\n"));
+ PRINTSSIAPITIMER;
+
+
+ //
+ // On an access denied error, force an authentication.
+ //
+ // Returned authenticator may be invalid.
+ //
+ // Notice that all communications errors take this path rather
+ // than the path below which forces a full sync.
+ //
+
+ if ( (DeltaStatus == STATUS_ACCESS_DENIED) ||
+ ( !NlUpdateSeed(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &NlGlobalClientSession->CsSessionKey) ) ) {
+
+
+ if ( NT_SUCCESS(DeltaStatus) ) {
+ Status = STATUS_ACCESS_DENIED;
+ } else {
+ Status = DeltaStatus;
+ }
+
+ NlPrint((NL_CRITICAL, "NlReplicateDeltas: authentication failed.\n" ));
+ NlSetStatusClientSession( NlGlobalClientSession, Status );
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ //
+ // Perhaps the netlogon service on the PDC has just restarted.
+ // Try just once to set up a session to the server again.
+ //
+ if ( FirstTry && DeltaStatus == STATUS_ACCESS_DENIED ) {
+ FirstTry = FALSE;
+ goto FirstTryFailed;
+ }
+ goto Cleanup;
+ }
+
+ //
+ // Copy session key to decrypt sensitive information.
+ //
+
+ SessionInfo.SessionKey = NlGlobalClientSession->CsSessionKey;
+ SessionInfo.NegotiatedFlags = NlGlobalClientSession->CsNegotiatedFlags;
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+ //
+ // Finally, error out
+ //
+
+ if ( !NT_SUCCESS( DeltaStatus ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: "
+ "I_NetDatabaseDeltas returning: Status (%lx)\n",
+ DeltaStatus ));
+
+ //
+ // since we can't handle any other error, call full sync.
+ //
+
+ ForceFullSync = TRUE;
+ Status = DeltaStatus;
+ goto Cleanup;
+ }
+
+ if ( DeltaArray->CountReturned == 0 ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ //
+ // Unpack the buffer and apply changes to appropriate database
+ //
+
+ for ( DeltaIndex=0;
+ DeltaIndex<DeltaArray->CountReturned;
+ DeltaIndex++ ) {
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlReplicateDeltas: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ Status = NlUnpackSam(
+ &(DeltaArray->Deltas)[DeltaIndex] ,
+ DBInfo,
+ &ConflictingRid,
+ &SessionInfo );
+
+ if ( ! NT_SUCCESS( Status ) ) {
+ BOOLEAN ResourceError;
+
+ Status = NlRecoverConflictingAccount(
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo,
+ ConflictingRid,
+ &SessionInfo,
+ Status,
+ FALSE,
+ &ResourceError );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // If we failed for some temporary reason,
+ // stop the full sync now to let the system cure itself.
+ //
+
+ if ( ResourceError ) {
+ goto Cleanup;
+ }
+
+ //
+ // If the PDC supports redo,
+ // Write this delta to the redo log and otherwise ignore
+ // the failure.
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO ){
+ Status = NlWriteDeltaToChangeLog(
+ &NlGlobalRedoLogDesc,
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo->DBIndex,
+ NULL );
+
+ //
+ // If we can't write to the redo log,
+ // remember to get this delta again later.
+ //
+
+ if ( !NT_SUCCESS( Status )) {
+ goto Cleanup;
+ }
+
+ //
+ // If the PDC doesn't support redo,
+ // recover by doing a full sync.
+ //
+
+ } else {
+
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: " FORMAT_LPWSTR
+ ": Force full sync since PDC returned an error we didn't recognize\n",
+ DBInfo->DBName,
+ Status ));
+ ForceFullSync = TRUE;
+ goto Cleanup;
+ }
+
+ }
+ }
+
+ //
+ // Write the delta to the changelog.
+ //
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG){
+ Status = NlWriteDeltaToChangeLog(
+ &NlGlobalChangeLogDesc,
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo->DBIndex,
+ &ExpectedSerialNumber );
+
+ //
+ // Most failures can be ignored.
+ //
+ // However, if the PDC is behind this BDC and we couldn't back out our changes,
+ // we've done the best we could.
+ //
+
+ if ( Status == STATUS_SYNCHRONIZATION_REQUIRED ) {
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: " FORMAT_LPWSTR
+ ": PDC is behind this BDC and our changelog doesn't have the changes in between.\n",
+ DBInfo->DBName,
+ Status ));
+ ForceFullSync = TRUE;
+ goto Cleanup;
+ }
+
+ }
+
+ }
+
+
+ DeltasApplied += DeltaArray->CountReturned;
+ MIDL_user_free( DeltaArray );
+ DeltaArray = NULL;
+
+ //
+ // Set the domain creation time and modified count to their values
+ // on the PDC.
+ //
+
+ LOCK_CHANGELOG();
+ NlGlobalChangeLogDesc.SerialNumber[DBInfo->DBIndex] = LocalSerialNumber;
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG) {
+ (VOID) NlFlushChangeLog( &NlGlobalChangeLogDesc );
+ }
+ UNLOCK_CHANGELOG();
+
+ NlPrint((NL_SYNC,
+ "NlReplicateDeltas: Setting " FORMAT_LPWSTR " serial number to %lx %lx\n",
+ DBInfo->DBName,
+ LocalSerialNumber.HighPart,
+ LocalSerialNumber.LowPart ));
+
+ if( DBInfo->DBIndex == LSA_DB ) {
+
+ Status = LsaISetSerialNumberPolicy(
+ DBInfo->DBHandle,
+ &LocalSerialNumber,
+ &DBInfo->CreationTime,
+ (BOOLEAN) FALSE );
+
+ } else {
+
+ Status = SamISetSerialNumberDomain(
+ DBInfo->DBHandle,
+ &LocalSerialNumber,
+ &DBInfo->CreationTime,
+ (BOOLEAN) FALSE );
+
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: "
+ "Unable to set serial number: Status (%lx)\n",
+ Status ));
+
+ goto Cleanup;
+ }
+
+ //
+ // Sanity check that the PDC returned good serial numbers.
+ //
+
+ if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG) &&
+ ExpectedSerialNumber.QuadPart - 1 != LocalSerialNumber.QuadPart ) {
+
+ ExpectedSerialNumber.QuadPart -= 1;
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: " FORMAT_LPWSTR " PDC serial number info mismatch: PDC says %lx %lx We computed %lx %lx\n",
+ DBInfo->DBName,
+ LocalSerialNumber.HighPart,
+ LocalSerialNumber.LowPart,
+ ExpectedSerialNumber.HighPart,
+ ExpectedSerialNumber.LowPart ));
+
+ //
+ // Above we updated NlGlobalChangeLogDesc.SerialNumber to match LocalSerialNumber.
+ // Therefore, we need to ensure the actual change log entries match that.
+ //
+ // (This will only be caused by a logic error in the way serial numbers are
+ // computed.)
+ //
+
+ LOCK_CHANGELOG();
+ (VOID) NlFixChangeLog( &NlGlobalChangeLogDesc,
+ DBInfo->DBIndex,
+ LocalSerialNumber,
+ FALSE );
+ UNLOCK_CHANGELOG();
+ }
+
+ //
+ // If the PDC has given us all of the deltas it has,
+ // we're all done.
+ //
+
+ if ( DeltaStatus == STATUS_SUCCESS ) {
+ Status = STATUS_SUCCESS;
+ break;
+ }
+
+ //
+ // Compute the amount of time we need to wait before calling the PDC
+ // again.
+ //
+
+ SyncSleepTime = NlComputeSyncSleepTime( &ApiStartTime,
+ &ApiFinishTime );
+
+ }
+
+ //
+ // Mark that we've potentially replicated from a different PDC.
+ //
+ (VOID) NlResetFirstTimeFullSync( DBInfo->DBIndex );
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ MsgStrings[0] = DBInfo->DBName;
+ MsgStrings[1] = NlGlobalUncPrimaryName;
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ if ( !NlGlobalReplicatorTerminate ) {
+
+ MsgStrings[2] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 | LAST_MESSAGE_IS_NTSTATUS );
+ }
+
+ } else {
+
+ if ( DeltasApplied != 0 ) {
+ WCHAR CountBuffer[20]; // random size
+
+ ultow( DeltasApplied, CountBuffer, 10);
+ MsgStrings[2] = CountBuffer;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 3 );
+ }
+
+ }
+
+ //
+ // Clean up any resources we're using.
+ //
+
+ if ( DeltaArray != NULL ) {
+ MIDL_user_free( DeltaArray );
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ if ( ForceFullSync ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+ NlPrint((NL_CRITICAL,
+ "NlReplicateDeltas: returning unsuccessful: Status (%lx)\n",
+ Status ));
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlProcessRedoLog(
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Process the redo log on this BDC for the particular database.
+
+Arguments:
+
+ ChangeLogDesc -- Description of the Changelog buffer being used
+
+Return Value:
+
+ STATUS_SUCCESS - The Service completed successfully.
+
+
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS CumulativeStatus = STATUS_SUCCESS;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+
+ NTSTATUS SyncStatus;
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL;
+ DWORD DeltasApplied;
+ DWORD DeltaIndex;
+ LARGE_INTEGER RunningSerialNumber;
+
+ LARGE_INTEGER ApiStartTime;
+ LARGE_INTEGER ApiFinishTime;
+ DWORD SyncSleepTime;
+
+ SESSION_INFO SessionInfo;
+
+ ULONG ConflictingRid;
+
+ LPWSTR MsgStrings[3];
+ BOOLEAN FirstTry = TRUE;
+ PCHANGELOG_ENTRY ChangeLogEntry = NULL;
+ DWORD ChangeLogEntrySize;
+
+
+ //
+ // Just return if the redo log is empty
+ //
+
+ LOCK_CHANGELOG();
+ if ( !NlGlobalRedoLogDesc.RedoLog ||
+ NlGlobalRedoLogDesc.EntryCount[DBInfo->DBIndex] == 0 ) {
+ UNLOCK_CHANGELOG();
+ return STATUS_SUCCESS;
+ }
+ UNLOCK_CHANGELOG();
+ NlPrint((NL_SYNC, "NlProcessRedoLog: " FORMAT_LPWSTR ": Entered\n", DBInfo->DBName ));
+
+
+ //
+ // If we're not currently authenticated with the PDC,
+ // do so now.
+ //
+
+FirstTryFailed:
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlProcessRedoLog: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+
+ Status = NlSessionSetup( NlGlobalClientSession );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+ }
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+ //
+ // Loop getting changes from the PDC
+ //
+
+ RunningSerialNumber.QuadPart = 0;
+ SyncSleepTime = 0;
+ DeltasApplied = 0;
+
+ for (;;) {
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+
+
+ //
+ // Get the next entry from the redo log.
+ //
+
+ ChangeLogEntry = NlGetNextChangeLogEntry(
+ &NlGlobalRedoLogDesc,
+ RunningSerialNumber,
+ DBInfo->DBIndex,
+ &ChangeLogEntrySize );
+
+ if ( ChangeLogEntry == NULL ) {
+ break;
+ }
+
+ RunningSerialNumber = ChangeLogEntry->SerialNumber;
+
+
+ //
+ // Wait a while so we don't overburden the secure channel.
+ //
+
+ if ( SyncSleepTime != 0 ) {
+ NlPrint(( NL_SYNC,
+ "NlProcessRedoLog: sleeping %ld for the governor.\n",
+ SyncSleepTime ));
+ (VOID) WaitForSingleObject( NlGlobalReplicatorTerminateEvent, SyncSleepTime );
+ }
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlProcessRedoLog: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ //
+ // If this redo log entry is bogus,
+ // don't confuse the PDC into asking us to full sync.
+ //
+ // This list of DeltaType's should be the list of deltas not handled
+ // by NlPackSingleDelta.
+ //
+
+ if ( ChangeLogEntry->DeltaType == DummyChangeLogEntry ||
+ ChangeLogEntry->DeltaType == SerialNumberSkip ) {
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Get the appropriate changes from the PDC.
+ //
+
+ } else {
+
+
+ //
+ // Build the Authenticator for this request to the PDC.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlProcessRedoLog: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint((NL_CRITICAL, "NlProcessRedoLog: Client session dropped.\n" ));
+ Status = NlGlobalClientSession->CsConnectionStatus;
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+
+ NlBuildAuthenticator(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &NlGlobalClientSession->CsSessionKey,
+ &OurAuthenticator);
+
+
+ //
+ // copy session key to decrypt sensitive information.
+ //
+
+ SessionInfo.SessionKey = NlGlobalClientSession->CsSessionKey;
+ SessionInfo.NegotiatedFlags = NlGlobalClientSession->CsNegotiatedFlags;
+
+
+ //
+ // If the PDC doesn't support redo,
+ // force a full sync on this database and clear the redo log.
+ //
+
+ if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO) == 0 ) {
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ //
+ // Force a full sync on this database. That's what we would have
+ // done when we initially added this redo entry.
+ //
+
+ NlPrint(( NL_SYNC,
+ FORMAT_LPWSTR ": Force FULL SYNC because we have a redo log and PDC doesn't support redo.\n",
+ NlGlobalDBInfoArray[DBInfo->DBIndex].DBName ));
+
+ (VOID) NlForceStartupSync( &NlGlobalDBInfoArray[DBInfo->DBIndex] );
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+
+ //
+ // Get the data from the PDC.
+ //
+
+ SyncStatus = NlStartApiClientSession( NlGlobalClientSession, FALSE );
+
+ if (NT_SUCCESS(SyncStatus)) {
+ STARTSSIAPITIMER;
+
+ ApiStartTime = NlGlobalClientSession->CsApiTimer.StartTime;
+
+ SyncStatus = I_NetDatabaseRedo(
+ NlGlobalClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ (LPBYTE) ChangeLogEntry,
+ ChangeLogEntrySize,
+ &DeltaArray );
+
+ if ( NlGlobalGovernorParameter != 100 ) {
+ (VOID) NtQuerySystemTime( &ApiFinishTime );
+ }
+ STOPSSIAPITIMER;
+ }
+ (VOID)NlFinishApiClientSession( NlGlobalClientSession, TRUE );
+
+ NlPrint((NL_REPL_TIME,"I_NetDatabaseRedo Time:\n"));
+ PRINTSSIAPITIMER;
+
+ //
+ // On an access denied error, force an authentication.
+ //
+ // Returned authenticator may be invalid.
+ //
+
+ if ( (SyncStatus == STATUS_ACCESS_DENIED) ||
+ ( !NlUpdateSeed(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &NlGlobalClientSession->CsSessionKey) ) ) {
+
+ if ( NT_SUCCESS(SyncStatus) ) {
+ Status = STATUS_ACCESS_DENIED;
+ } else {
+ Status = SyncStatus;
+ }
+
+ NlPrint((NL_CRITICAL, "NlProcessRedoLog: authentication failed: %lx\n", Status ));
+
+ NlSetStatusClientSession( NlGlobalClientSession, Status );
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ //
+ // Perhaps the netlogon service on the PDC has just restarted.
+ // Try just once to set up a session to the server again.
+ //
+ if ( FirstTry && SyncStatus == STATUS_ACCESS_DENIED ) {
+ FirstTry = FALSE;
+ goto FirstTryFailed;
+ }
+
+ goto Cleanup;
+ }
+
+ FirstTry = FALSE;
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+ //
+ // Finally, error out
+ //
+
+ if ( !NT_SUCCESS( SyncStatus ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlProcessRedoLog: "
+ "I_NetDatabaseRedo returning: Status (%lx)\n",
+ SyncStatus ));
+
+ Status = SyncStatus;
+ goto Cleanup;
+ }
+
+ //
+ // Unpack the buffer and apply changes to appropriate database
+ //
+
+ for ( DeltaIndex=0;
+ DeltaIndex<DeltaArray->CountReturned;
+ DeltaIndex++ ) {
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlProcessRedoLog: Asked to terminate\n" ));
+ Status = STATUS_THREAD_IS_TERMINATING;
+ goto Cleanup;
+ }
+
+ Status = NlUnpackSam(
+ &(DeltaArray->Deltas)[DeltaIndex] ,
+ DBInfo,
+ &ConflictingRid,
+ &SessionInfo );
+
+ if ( ! NT_SUCCESS( Status ) ) {
+ BOOLEAN ResourceError;
+
+ Status = NlRecoverConflictingAccount(
+ &(DeltaArray->Deltas)[DeltaIndex],
+ DBInfo,
+ ConflictingRid,
+ &SessionInfo,
+ Status,
+ FALSE,
+ &ResourceError );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // If we failed for some temporary reason,
+ // stop the full sync now to let the system cure itself.
+ //
+
+ if ( ResourceError ) {
+ goto Cleanup;
+ }
+
+ //
+ // If this is an unexpected failure,
+ // continue processing deltas.
+ //
+ // It is better to continue copying the database as
+ // much as possible than to quit now. The theory is that
+ // we've stumbled upon some circumstance we haven't
+ // anticipated. We'll put this BDC in the best shape
+ // we possibly can.
+ //
+ // Remember this status code until the end.
+ //
+
+ if ( NT_SUCCESS(CumulativeStatus) ) {
+ CumulativeStatus = Status;
+ }
+
+ continue;
+
+ }
+ }
+
+ DeltasApplied ++;
+
+ }
+
+ MIDL_user_free( DeltaArray );
+ DeltaArray = NULL;
+ }
+
+ //
+ // If the operation succeeded,
+ // delete this entry from the redo log.
+ //
+ if ( Status == STATUS_SUCCESS ) {
+
+ NlDeleteChangeLogEntry(
+ &NlGlobalRedoLogDesc,
+ ChangeLogEntry->DBIndex,
+ ChangeLogEntry->SerialNumber );
+ }
+
+ NetpMemoryFree( ChangeLogEntry );
+ ChangeLogEntry = NULL;
+
+
+ //
+ // Compute the amount of time we need to wait before calling the PDC
+ // again.
+ //
+
+ SyncSleepTime = NlComputeSyncSleepTime( &ApiStartTime,
+ &ApiFinishTime );
+
+ }
+
+
+ //
+ // We've finished the redo sync.
+ //
+
+ Status = CumulativeStatus;
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ MsgStrings[0] = DBInfo->DBName;
+ MsgStrings[1] = NlGlobalUncPrimaryName;
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ if ( !NlGlobalReplicatorTerminate ) {
+
+ MsgStrings[2] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 | LAST_MESSAGE_IS_NTSTATUS );
+ }
+
+ } else {
+
+ if ( DeltasApplied != 0 ) {
+ WCHAR CountBuffer[20]; // random size
+
+ ultow( DeltasApplied, CountBuffer, 10);
+ MsgStrings[2] = CountBuffer;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 3 );
+ }
+
+ }
+
+ //
+ // Free locally used resources.
+ //
+
+
+ if( ChangeLogEntry != NULL) {
+ NetpMemoryFree( ChangeLogEntry );
+ }
+
+ if ( DeltaArray != NULL ) {
+ MIDL_user_free( DeltaArray );
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // If we're going to do a full sync, clear the redo log.
+ //
+ // It no longer has value and if we don't clear it now we
+ // may end up forcing another full sync the next time we
+ // try to process the redo log.
+ //
+
+ if ( Status == STATUS_SYNCHRONIZATION_REQUIRED ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlProcessRedoLog: Clearing redo log because full sync is needed.\n" ));
+
+ LOCK_CHANGELOG();
+ RunningSerialNumber.QuadPart = 0;
+ (VOID) NlFixChangeLog( &NlGlobalRedoLogDesc,
+ DBInfo->DBIndex,
+ RunningSerialNumber,
+ FALSE );
+ UNLOCK_CHANGELOG();
+ }
+
+ NlPrint((NL_CRITICAL,
+ "NlProcessRedoLog: returning unsuccessful: Status (%lx)\n",
+ Status ));
+ } else {
+ NlPrint((NL_SYNC, "NlProcessRedoLog: " FORMAT_LPWSTR ": Successful return\n", DBInfo->DBName ));
+ }
+
+ return Status;
+}
+
+
+DWORD
+NlReplicator(
+ IN LPVOID ReplParam
+ )
+/*++
+
+Routine Description:
+
+ This procedure is the main procedure for the replicator thread.
+ This thread is created to contact the PDC and update the local SAM
+ database to match the copy on the PDC.
+
+ Only one copy of this thread will be running at any time.
+
+Arguments:
+
+ ReplParam - Parameters governing the behavior of the replicator thread.
+
+Return Value:
+
+ Exit Status of the thread.
+
+--*/
+{
+ NTSTATUS Status;
+ DWORD i;
+ PDB_INFO DBInfo;
+ BOOLEAN AdminAlert = FALSE;
+ BOOLEAN SyncFailed;
+
+
+
+ //
+ // Sleep a little before contacting the PDC. This sleep prevents all
+ // the BDC and member servers from contacting the PDC at once.
+ //
+
+ NlPrint((NL_SYNC,
+ "NlReplicator: Thread starting Sleep: %ld\n",
+ ((PREPL_PARAM)ReplParam)->RandomSleep ));
+
+ (VOID) WaitForSingleObject( NlGlobalReplicatorTerminateEvent,
+ ((PREPL_PARAM)ReplParam)->RandomSleep );
+
+ //
+ // Mark each database that no sync has yet been done.
+ //
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ for( i = 0; i < NUM_DBS; i++ ) {
+ NlGlobalDBInfoArray[i].SyncDone = FALSE;
+ }
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+
+ //
+ // Loop until we've successfully finished replication.
+ //
+ // The PDC doesn't send periodic pulses to every BDC anymore.
+ // Therefore, the BDC is responsible for ensure it finishes getting
+ // any database changes it knows about.
+ //
+ for (;;) {
+
+ //
+ // If this thread has been asked to leave, do so.
+ //
+
+ if ( NlGlobalReplicatorTerminate ) {
+ NlPrint((NL_SYNC, "NlReplicator: Asked to terminate\n" ));
+ NlGlobalReplicatorIsRunning = FALSE;
+ return (DWORD) STATUS_THREAD_IS_TERMINATING;
+ }
+
+ //
+ // Ensure we have a secure channel to the PDC.
+ // If we don't have a secure channel to the PDC,
+ // we'll exit the thread and wait until the PDC starts before
+ // continuing.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlReplicator: Can't become writer of client session.\n" ));
+ NlGlobalReplicatorIsRunning = FALSE;
+ return (DWORD) STATUS_THREAD_IS_TERMINATING;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ Status = NlSessionSetup( NlGlobalClientSession );
+
+ if ( !NT_SUCCESS(Status)) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ NlPrint((NL_SYNC,
+ "NlReplicator: Replicator thread exitting since PDC is down.\n" ));
+ NlGlobalReplicatorIsRunning = FALSE;
+ return (DWORD) STATUS_THREAD_IS_TERMINATING;
+ }
+
+ }
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+ //
+ // we need to update all databases one after another.
+ //
+
+ SyncFailed = FALSE;
+ for( i = 0; i < NUM_DBS; i++ ) {
+ BOOLEAN FullSyncRequired;
+ BOOLEAN PartialSyncRequired;
+
+
+ //
+ // If this particular database doesn't need to be updated,
+ // skip it.
+ //
+
+ DBInfo = &NlGlobalDBInfoArray[i];
+
+ LOCK_CHANGELOG();
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ if ( !DBInfo->UpdateRqd && NlGlobalRedoLogDesc.EntryCount[i] == 0 ) {
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+ UNLOCK_CHANGELOG();
+ continue;
+ }
+
+ FullSyncRequired = DBInfo->FullSyncRequired;
+ PartialSyncRequired = DBInfo->UpdateRqd;
+
+ DBInfo->UpdateRqd = FALSE;
+ DBInfo->FullSyncRequired = FALSE;
+
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+ UNLOCK_CHANGELOG();
+
+
+ //
+ // If we've switched PDCs and the current PDC is running NT1.0,
+ // force a full sync.
+ //
+ // We wait until now to make this check to ensure that we've set up a
+ // secure channel with the PDC. This prevents a rouge PDC from forcing
+ // us to full sync just by sending us a mailslot message.
+ //
+ // Check the 'SyncDone' flag to ensure we only force this full sync
+ // once. Otherwise, we'll force a full sync here multiple time.
+ // The first time will be legit. The remaining times will be
+ // because a partial sync is needed.
+ //
+
+ if (NlNameCompare( DBInfo->PrimaryName,
+ NlGlobalUnicodePrimaryName,
+ NAMETYPE_COMPUTER) != 0 &&
+ !DBInfo->SyncDone ) {
+
+ //
+ // If this is an NT 1.0 PDC,
+ // Mark this database that it needs a full sync.
+ //
+
+ if ( NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+
+ if ( NlGlobalClientSession->CsState == CS_AUTHENTICATED &&
+ (NlGlobalClientSession->CsNegotiatedFlags &
+ NETLOGON_SUPPORTS_PROMOTION_COUNT) == 0 ){
+
+ FullSyncRequired = TRUE;
+
+ NlPrint((NL_CRITICAL,
+ "NlReplicator: " FORMAT_LPWSTR
+ ": Force FULL SYNC because new PDC is running NT 1.0.\n",
+ DBInfo->DBName ));
+ }
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+ }
+ }
+
+ //
+ // If our caller says we're out of sync with the primary,
+ // do a full sync.
+ //
+ // If we've just finished doing a successfull full sync,
+ // then ignore whoever told us to do another one.
+ //
+
+ if ( FullSyncRequired && !DBInfo->SyncDone ) {
+
+ if( !AdminAlert ) {
+
+ LPWSTR AlertStrings[2];
+
+ //
+ // raise admin alert to inform a fullsync has been called by this
+ // server.
+ //
+
+ AlertStrings[0] = NlGlobalUnicodeComputerName;
+ AlertStrings[1] = NULL;
+
+ RaiseAlert( ALERT_NetlogonFullSync,
+ AlertStrings );
+
+ AdminAlert = TRUE;
+ }
+
+
+ Status = NlSynchronize( DBInfo );
+
+ //
+ // If we're not out of sync with the primary,
+ // just get the deltas from the caller.
+ //
+
+ } else if ( PartialSyncRequired ) {
+ Status = NlReplicateDeltas( DBInfo );
+
+ //
+ // Otherwise, just process the redo log
+ //
+
+ } else {
+
+ Status = NlProcessRedoLog( DBInfo );
+ }
+
+
+
+ //
+ // If the PDC thinks a full Sync is required,
+ // do a full sync now.
+ //
+
+ if (Status == STATUS_SYNCHRONIZATION_REQUIRED) {
+ NlPrint((NL_CRITICAL,
+ "NlReplicator: PDC says " FORMAT_LPWSTR " needs full sync.\n",
+ DBInfo->DBName ));
+
+ if( !AdminAlert ) {
+
+ LPWSTR AlertStrings[2];
+
+ //
+ // raise admin alter to inform a fullsync has been called by this
+ // server.
+ //
+
+ AlertStrings[0] = NlGlobalUnicodeComputerName;
+ AlertStrings[1] = NULL;
+
+ RaiseAlert( ALERT_NetlogonFullSync,
+ AlertStrings );
+
+ AdminAlert = TRUE;
+ }
+
+ FullSyncRequired = TRUE;
+ Status = NlSynchronize( DBInfo );
+ }
+
+ //
+ // If not successful, indicate we need to try again.
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+ SyncFailed = TRUE;
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ DBInfo->UpdateRqd = TRUE;
+ DBInfo->FullSyncRequired = DBInfo->FullSyncRequired ||
+ FullSyncRequired;
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ //
+ // If we've successfully done a full sync,
+ // ignore other requests to do so.
+ //
+
+ } else if (FullSyncRequired ) {
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ DBInfo->SyncDone = TRUE;
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+ }
+
+ }
+
+ //
+ // If we completed all databases,
+ // we're done.
+ //
+ // We have to re-check all the databases since someone may have requested
+ // a sync while we were in the loop above.
+ //
+
+ LOCK_CHANGELOG();
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ for( i = 0; i < NUM_DBS; i++ ) {
+ if ( NlGlobalDBInfoArray[i].UpdateRqd ||
+ NlGlobalRedoLogDesc.EntryCount[i] != 0 ) {
+ break;
+ }
+ }
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+ UNLOCK_CHANGELOG();
+
+ if ( i == NUM_DBS ) {
+ // Don't lock the ReplicatorCritSect within the replicator thread
+ NlGlobalReplicatorIsRunning = FALSE;
+ break;
+ }
+
+
+ //
+ // If the sync failed,
+ // wait a while before bothering the PDC again.
+ //
+
+ if ( SyncFailed ) {
+ ((PREPL_PARAM)ReplParam)->RandomSleep = NlGlobalPulseParameter * 1000;
+ NlPrint((NL_SYNC,
+ "NlReplicator: Sleeping: %ld\n",
+ ((PREPL_PARAM)ReplParam)->RandomSleep ));
+
+ (VOID) WaitForSingleObject( NlGlobalReplicatorTerminateEvent,
+ ((PREPL_PARAM)ReplParam)->RandomSleep );
+
+ }
+
+ }
+
+
+ //
+ // ASSERT( All databases are in sync )
+ //
+ //
+
+ //
+ //
+ // Continue netlogon if we have paused it for doing first
+ // time full sync.
+ //
+
+ NlGlobalFirstTimeFullSync = FALSE;
+
+
+ //
+ // We've done all we can. Exit the thread.
+ //
+
+ NlPrint((NL_SYNC,
+ "NlReplicator: Replicator thread exitting.\n" ));
+ return Status;
+}
+
+
+BOOL
+NlUpdateRequired (
+ IN PDB_CHANGE_INFO DBChangeInfo
+ )
+
+/*++
+
+Routine Description:
+
+ With the information arrived in the mailslot message, this routine
+ determines the Database require update. This routine also sets
+ internal appropriate fields in database structure (
+ NlGlobalDBInfoArray ) so that replication thread will sync the
+ database.
+
+Arguments:
+
+ DBChangeInfo: pointer to database change info structure.
+
+Return Value:
+
+ TRUE : if this database requires update.
+ FALSE : otherwise.
+
+--*/
+
+{
+ PDB_INFO DBInfo;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
+
+ LARGE_INTEGER LocalSerialNumber;
+ LARGE_INTEGER CreationTime;
+
+ if ( DBChangeInfo->DBIndex >= NUM_DBS ) {
+ return FALSE;
+ }
+
+ DBInfo = &NlGlobalDBInfoArray[DBChangeInfo->DBIndex];
+
+ //
+ // Pick up the current serial number of the database
+ //
+
+ LOCK_CHANGELOG();
+ LocalSerialNumber = NlGlobalChangeLogDesc.SerialNumber[DBChangeInfo->DBIndex];
+ CreationTime = DBInfo->CreationTime;
+ UNLOCK_CHANGELOG();
+
+
+ //
+ // We need a full sync if either:
+ // a) the local SAM database is marked as needing a sync.
+ // b) the domain creation times aren't the same.
+ //
+
+ if ( LocalSerialNumber.QuadPart == 0 ||
+ CreationTime.QuadPart == 0 ||
+ CreationTime.QuadPart != DBChangeInfo->NtDateAndTime.QuadPart ) {
+
+ //
+ // Tell the replicator thread that a full sync is needed.
+ //
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ DBInfo->UpdateRqd = TRUE;
+ DBInfo->FullSyncRequired = TRUE;
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ NlPrint((NL_SYNC,
+ "NlUpdateRequired: " FORMAT_LPWSTR " requires full sync\n",
+ NlGlobalDBInfoArray[DBChangeInfo->DBIndex].DBName ));
+
+ NlPrint((NL_SYNC,
+ "\t PDC Serial Number %lx %lx .\n",
+ DBChangeInfo->LargeSerialNumber.HighPart,
+ DBChangeInfo->LargeSerialNumber.LowPart
+ ));
+
+ NlPrint((NL_SYNC,
+ "\t Local Serial Number %lx %lx .\n",
+ LocalSerialNumber.HighPart,
+ LocalSerialNumber.LowPart
+ ));
+
+ NlPrint((NL_SYNC,
+ "\t Local CreationTime %lx %lx .\n",
+ CreationTime.HighPart,
+ CreationTime.LowPart
+ ));
+
+ //
+ // If there are a few number of changes,
+ // just get those few changes.
+ //
+ // If the PDC wants us to call partial sync,
+ // oblige it.
+ //
+ // Do a partial sync even if this BDC is newer than the PDC. The PDC
+ // will give us a better indication of what to do when we call to get the deltas.
+ //
+
+ } else if ( DBChangeInfo->LargeSerialNumber.QuadPart != LocalSerialNumber.QuadPart ) {
+
+ //
+ // Tell the replicator this database needs a partial sync
+ //
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ DBInfo->UpdateRqd = TRUE;
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ NlPrint((NL_SYNC,
+ "NlUpdateRequired: " FORMAT_LPWSTR " requires partial sync\n",
+ NlGlobalDBInfoArray[DBChangeInfo->DBIndex].DBName ));
+
+ NlPrint((NL_SYNC,
+ "\t PDC Serial Number %lx %lx .\n",
+ DBChangeInfo->LargeSerialNumber.HighPart,
+ DBChangeInfo->LargeSerialNumber.LowPart
+ ));
+
+ NlPrint((NL_SYNC,
+ "\t Local Serial Number %lx %lx .\n",
+ LocalSerialNumber.HighPart,
+ LocalSerialNumber.LowPart
+ ));
+
+ } else {
+
+ NlPrint((NL_SYNC,
+ "NlUpdateRequired: " FORMAT_LPWSTR " is in sync\n",
+ NlGlobalDBInfoArray[DBChangeInfo->DBIndex].DBName ));
+ }
+
+ return( DBInfo->UpdateRqd );
+}
+
+
+BOOL
+NlCheckUpdateNotices(
+ IN PNETLOGON_DB_CHANGE UasChange,
+ IN DWORD UasChangeSize
+ )
+/*++
+
+Routine Description:
+
+ Examine the update notice which came from Primary DC with
+ LOGON_UAS_CHANGE message. If there has been an update then get
+ those changes from primary so we stay in sync.
+
+ If replication is already in progress for whatever reason this
+ routine will return immediately causing cureent notice to be
+ ignored. That is OK since replication would ideally be governed by
+ the fact that there are some updates still out there and will run
+ till in sync.
+
+Arguments:
+
+ UasChange -- The UasChange message from the PDC.
+
+ UasChangeSize -- The size (in bytes) of the message.
+
+Return Value:
+
+ TRUE -- iff this message was valid and could be processed.
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // Unmarshalled information from the UasChange message.
+ //
+
+ PCHAR AnsiTemp;
+ LPWSTR AnncPrimary;
+ LPWSTR AnncDomain;
+ DWORD DBCount;
+ DB_CHANGE_INFO DBChangeInfo;
+ DWORD DomainSIDSize;
+
+
+ PCHAR Where;
+ PCHAR WhereDBChangeInfo;
+
+ DWORD StartReplicator = FALSE;
+ DWORD RandomSleep; // Number of millseconds to delay before working
+ DWORD i;
+
+
+ //
+ // Unmarshall the incoming message.
+ //
+
+ Where = UasChange->PrimaryDCName;
+ if ( !NetpLogonGetOemString( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->PrimaryDCName),
+ &AnsiTemp )) {
+ return FALSE;
+ }
+ if ( !NetpLogonGetOemString( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->DomainName),
+ &AnsiTemp )) {
+ return FALSE;
+ }
+
+ if ( !NetpLogonGetUnicodeString( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->UnicodePrimaryDCName),
+ &AnncPrimary )) {
+ return FALSE;
+ }
+ if ( !NetpLogonGetUnicodeString( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->UnicodeDomainName),
+ &AnncDomain )) {
+ return FALSE;
+ }
+
+ //
+ // Ensure message is for this domain.
+ //
+
+ if (NlNameCompare(AnncDomain,
+ NlGlobalUnicodeDomainName,
+ NAMETYPE_DOMAIN) != 0 ) {
+ return FALSE;
+ }
+
+ //
+ // Ignore our own broadcasts.
+ //
+
+ if (NlNameCompare(AnncPrimary,
+ NlGlobalUnicodeComputerName,
+ NAMETYPE_COMPUTER) == 0) {
+
+ NlAssert( NlGlobalRole == RolePrimary );
+ return FALSE;
+ }
+
+
+ //
+ // get DBCount from message
+ //
+
+ if ( !NetpLogonGetBytes( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->DBCount),
+ &DBCount )) {
+ return( FALSE );
+
+ }
+
+ WhereDBChangeInfo = Where;
+
+ //
+ // pass DB change info
+ //
+
+ for( i = 0; i < DBCount; i++ ) {
+
+ //
+ // Get DB_CHANGE_STRUCTURE
+ //
+
+ if( !NetpLogonGetDBInfo( UasChange,
+ UasChangeSize,
+ &Where,
+ &DBChangeInfo ) ) {
+
+ return FALSE;
+
+ }
+
+ }
+
+ //
+ // Check domain SID.
+ //
+ // Read Domain SID Length
+ //
+
+ if ( !NetpLogonGetBytes( UasChange,
+ UasChangeSize,
+ &Where,
+ sizeof(UasChange->DomainSidSize),
+ &DomainSIDSize )) {
+ return( FALSE );
+
+ }
+
+
+ //
+ // get and compare SID
+ //
+
+ if( DomainSIDSize > 0 ) {
+
+ PCHAR DomainSID;
+
+ if ( !NetpLogonGetDomainSID( UasChange,
+ UasChangeSize,
+ &Where,
+ DomainSIDSize,
+ &DomainSID )) {
+ return( FALSE );
+ }
+
+ //
+ // compare domain SIDs
+ //
+
+ if( !RtlEqualSid( NlGlobalPrimaryDomainId, DomainSID ) ) {
+
+ LPWSTR AlertStrings[4];
+
+ //
+ // alert admin.
+ //
+
+ AlertStrings[0] = AnncPrimary;
+ AlertStrings[1] = NlGlobalUnicodeDomainName;
+ AlertStrings[2] = NlGlobalUnicodeComputerName;
+ AlertStrings[3] = NULL;
+
+ RaiseAlert( ALERT_NetLogonMismatchSIDInMsg,
+ AlertStrings );
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonMismatchSIDInMsg,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ AlertStrings,
+ 3 );
+
+
+ return( FALSE );
+ }
+
+ }
+
+ if( NlGlobalRole != RoleBackup ) {
+
+ //
+ // Duplicate PDC found on this domain
+ //
+
+ LPWSTR AlertStrings[4];
+
+ //
+ // alert admin.
+ //
+
+ AlertStrings[0] = AnncPrimary;
+ AlertStrings[1] = NlGlobalUnicodeComputerName;
+ AlertStrings[2] = NlGlobalUnicodeDomainName;
+ AlertStrings[3] = NULL;
+
+ RaiseAlert( ALERT_NetLogonDuplicatePDC,
+ AlertStrings );
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonDuplicatePDC,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ AlertStrings,
+ 3 );
+
+ return( FALSE );
+ }
+
+ //
+ // If we don't currently have a session to a PDC,
+ // Or if this message is from a new PDC (we probably just missed
+ // the LOGON_START_PRIMARY message),
+ // set up a session to the new PDC.
+ //
+
+ Status = NlNewSessionSetup( AnncPrimary );
+ if ( !NT_SUCCESS( Status ) ) {
+ return FALSE;
+ }
+
+
+ //
+ // Update change log info now. However update only those DBs we
+ // support.
+ //
+
+ Where = WhereDBChangeInfo;
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+
+ //
+ // Get DB_CHANGE_STRUCTURE
+ //
+
+ if( !NetpLogonGetDBInfo( UasChange,
+ UasChangeSize,
+ &Where,
+ &DBChangeInfo ) ) {
+
+ return FALSE;
+
+ }
+
+ StartReplicator += NlUpdateRequired( &DBChangeInfo );
+ }
+
+
+ //
+ // Start the replicator thread if it isn't already running.
+ //
+
+ if ( StartReplicator ) {
+
+ //
+ // Generate a pseudo random number in range 0 - random.
+ // Delay our delta/sync request by that much time
+ //
+ // Note that random number generator was seeded at startup time.
+ //
+
+ RandomSleep = SmbGetUlong( &UasChange->Random ) * 1000;
+ RandomSleep = (DWORD) rand() % RandomSleep;
+
+ return( NlStartReplicatorThread( RandomSleep ) );
+
+ }
+
+ return TRUE;
+}
+
+
+BOOL
+IsReplicatorRunning(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Test if the replicator thread is running
+
+ Enter with NlGlobalReplicatorCritSect locked.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ TRUE - The replicator thread is running
+
+ FALSE - The replicator thread is not running.
+
+--*/
+{
+ DWORD WaitStatus;
+
+ //
+ // Determine if the replicator thread is already running.
+ //
+
+ if ( NlGlobalReplicatorThreadHandle != NULL ) {
+
+ //
+ // Time out immediately if the replicator is still running.
+ //
+
+ WaitStatus = WaitForSingleObject( NlGlobalReplicatorThreadHandle, 0 );
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ //
+ // Handle the case that the replicator thread has finished
+ // processing, but is in the process of exitting.
+ //
+
+ if ( !NlGlobalReplicatorIsRunning ) {
+ NlStopReplicator();
+ return FALSE;
+ }
+ return TRUE;
+
+ } else if ( WaitStatus == 0 ) {
+ CloseHandle( NlGlobalReplicatorThreadHandle );
+ NlGlobalReplicatorThreadHandle = NULL;
+ return FALSE;
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "Cannot WaitFor replicator thread: %ld\n",
+ WaitStatus ));
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+VOID
+NlStopReplicator(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Stops the replicator thread if it is running and waits for it to stop.
+
+ Enter with NlGlobalReplicatorCritSect locked.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ //
+ // Ask the replicator to stop running.
+ //
+
+ NlGlobalReplicatorTerminate = TRUE;
+ if ( !SetEvent( NlGlobalReplicatorTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL, "Cannot set replicator termination event: %lu\n",
+ GetLastError() ));
+ }
+
+ //
+ // Determine if the replicator thread is already running.
+ //
+
+ if ( NlGlobalReplicatorThreadHandle != NULL ) {
+
+ //
+ // We've asked the replicator to stop. It should do so soon.
+ // Wait for it to stop.
+ //
+
+ NlWaitForSingleObject( "Wait for replicator to stop",
+ NlGlobalReplicatorThreadHandle );
+
+
+ CloseHandle( NlGlobalReplicatorThreadHandle );
+ NlGlobalReplicatorThreadHandle = NULL;
+
+ }
+
+ if ( !ResetEvent( NlGlobalReplicatorTerminateEvent ) ) {
+ NlPrint((NL_CRITICAL, "Cannot set replicator termination event: %lu\n",
+ GetLastError() ));
+ }
+ NlGlobalReplicatorTerminate = FALSE;
+
+ return;
+}
+
+
+BOOL
+NlStartReplicatorThread(
+ IN DWORD RandomSleep
+ )
+/*++
+
+Routine Description:
+
+ Start the Replication thread if it is not already running.
+
+Arguments:
+
+ RandomSleep - Number of millseconds to delay before working
+
+Return Value:
+ None
+
+--*/
+{
+ DWORD ThreadHandle;
+
+ //
+ // If the replicator thread is already running, do nothing.
+ //
+
+ EnterCriticalSection( &NlGlobalReplicatorCritSect );
+ if ( IsReplicatorRunning() ) {
+ NlPrint((NL_SYNC, "The replicator thread is already running.\n"));
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return TRUE;
+ }
+
+ //
+ // If we're not supposed to ever replicate,
+ // do nothing.
+ //
+
+ if ( NlGlobalGovernorParameter == 0 ) {
+ NlPrint((NL_CRITICAL, "Don't start replicator because Governor is zero.\n"));
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return FALSE;
+ }
+
+ //
+ // Initialize the replication parameters
+ //
+
+ NlGlobalReplicatorTerminate = FALSE;
+
+ NlGlobalReplParam.RandomSleep = RandomSleep;
+
+ NlGlobalReplicatorThreadHandle = CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ NlReplicator,
+ &NlGlobalReplParam,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( NlGlobalReplicatorThreadHandle == NULL ) {
+
+ //
+ // ?? Shouldn't we do something in non-debug case
+ //
+
+ NlPrint((NL_CRITICAL, "Can't create replicator Thread %lu\n",
+ GetLastError() ));
+
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return FALSE;
+ }
+
+
+ NlGlobalReplicatorIsRunning = TRUE;
+
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return TRUE;
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/lsrvutil.c b/private/net/svcdlls/logonsrv/server/lsrvutil.c
new file mode 100644
index 000000000..33ccc0be6
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/lsrvutil.c
@@ -0,0 +1,3055 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ lsrvutil.c
+
+Abstract:
+
+ Utility functions for the netlogon service.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 00-Jun-1989 (PradyM)
+ modified lm10 code for new NETLOGON service
+
+ 00-Feb-1990 (PradyM)
+ bugfixes
+
+ 00-Aug-1990 (t-RichE)
+ added alerts for auth failure due to time slippage
+
+ 11-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+ 09-Apr-1992 JohnRo
+ Prepare for WCHAR.H (_wcsicmp vs _wcscmpi, etc).
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <accessp.h> // NetpAliasMemberToPriv
+#include <alertmsg.h> // Alert message text.
+#include <align.h> // ROUND_UP_COUNT ...
+#include <lmapibuf.h>
+#include <lmerr.h> // System Error Log definitions
+#include <lmserver.h> // server API functions and prototypes
+#include <lmshare.h> // share API functions and prototypes
+#include <lmsvc.h> // SERVICE_UIC codes are defined here
+#include <msgtext.h> // MTXT_* defines
+#include <netcan.h> // NetpwPathCompare()
+#include <replutil.h> // UnpackSamXXX()
+#include <secobj.h> // NetpDomainIdToSid
+#include <ssiapi.h> // I_NetSamDeltas()
+#include <stddef.h> // offsetof
+#include <stdlib.h> // C library functions (rand, etc)
+#include <tstring.h> // IS_PATH_SEPARATOR ...
+
+/*lint -e740 */ /* don't complain about unusual cast */
+
+
+#define MAX_SSI_PWAGE (long) (7L*24L*60L*60L*1000L) // 7 days
+#define MAX_DC_AUTHENTICATION_WAIT (long) (45L*1000L) // 45 seconds
+#define MAX_WKSTA_AUTHENTICATION_WAIT (long) (45L*1000L) // 45 seconds
+
+//
+// We want to prevent too-frequent alerts from
+// being sent in case of Authentication failures.
+//
+
+#define MAX_ALERTS 10 // send one every 10 to 30 mins based on pulse
+
+
+VOID
+RaiseNetlogonAlert(
+ IN DWORD alertNum,
+ IN LPWSTR alertArg1,
+ IN LPWSTR alertArg2,
+ IN OUT DWORD *ptrAlertCount
+ )
+/*++
+
+Routine Description:
+
+ Raise an alert once per MAX_ALERTS occurances
+
+Arguments:
+
+ alertNum -- RaiseAlert() alert number.
+
+ alertArg1 -- RaiseAlert() argument 1.
+
+ alertArg2 -- RaiseAlert() argument 2.
+
+ ptrAlertCount -- Points to the count of occurence of this particular
+ alert. This routine increments it and will set the to that value
+ modulo MAX_ALERTS.
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ LPWSTR AlertStrings[3];
+
+ AlertStrings[0] = alertArg1;
+ AlertStrings[1] = alertArg2;
+ AlertStrings[2] = NULL;
+
+ if (*ptrAlertCount == 0) {
+ RaiseAlert(alertNum, AlertStrings);
+ }
+ (*ptrAlertCount)++;
+ (*ptrAlertCount) %= MAX_ALERTS;
+}
+
+
+
+
+
+BOOL
+NlSetPrimaryName(
+ IN LPWSTR PrimaryName
+ )
+/*++
+
+Routine Description:
+
+ This routine sets the specified PDC name in the appropriate global
+ variables.
+
+Arguments:
+
+ PrimaryName - The servername of the PDC for this domain.
+
+Return Value:
+
+ TRUE - iff the operation was successfull.
+
+--*/
+{
+ LPSTR AnsiPrimaryName;
+ DWORD i;
+
+ //
+ // If the caller wants us to forget the primary name,
+ // just reset the globals.
+ //
+
+ if ( PrimaryName == NULL ) {
+ NlGlobalAnsiPrimaryName[0] = '\0';
+ NlGlobalUncPrimaryName[0] = L'\0';
+ NlGlobalUnicodePrimaryName = NlGlobalUncPrimaryName;
+ return TRUE;
+ }
+
+
+ //
+ // Anytime the PDC changes, force a partial sync on all databases.
+ //
+ // Since the PDC needs to know the serial number of our databases,
+ // this ensures we tell him.
+ //
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ for( i = 0; i < NUM_DBS; i++ ) {
+ NlGlobalDBInfoArray[i].UpdateRqd = TRUE;
+ }
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ //
+ // Copy the primary name to the globals.
+ //
+
+ wcscpy( NlGlobalUncPrimaryName, L"\\\\" );
+ wcsncpy( NlGlobalUncPrimaryName+2,
+ PrimaryName,
+ (sizeof(NlGlobalUncPrimaryName)/sizeof(WCHAR)) - 2);
+ NlGlobalUncPrimaryName[ UNCLEN ] = '\0';
+ NlGlobalUnicodePrimaryName = NlGlobalUncPrimaryName + 2;
+
+ AnsiPrimaryName = NetpLogonUnicodeToOem( NlGlobalUnicodePrimaryName );
+ if ( AnsiPrimaryName == NULL ) {
+ NlGlobalAnsiPrimaryName[0] = '\0';
+ NlGlobalUncPrimaryName[0] = L'\0';
+ NlGlobalUnicodePrimaryName = NlGlobalUncPrimaryName;
+ return FALSE;
+ }
+ lstrcpynA( NlGlobalAnsiPrimaryName,
+ AnsiPrimaryName,
+ sizeof(NlGlobalAnsiPrimaryName) );
+ NlGlobalAnsiPrimaryName[ CNLEN ] = '\0';
+
+ NetpMemoryFree( AnsiPrimaryName );
+
+ return TRUE;
+}
+
+
+
+
+BOOL
+NlResetFirstTimeFullSync(
+ IN DWORD DBIndex
+ )
+/*++
+
+Routine Description:
+
+ If a database is currently marked as needing a first time full sync,
+ reset that requirement.
+
+Arguments:
+
+ DBIndex -- DB Index of the database being changed
+
+Return Value:
+
+ TRUE - iff the operation was successfull.
+
+--*/
+{
+ NTSTATUS Status;
+
+
+ //
+ // If the database is already marked,
+ // Don't bother marking it again.
+ //
+
+ if ( NlNameCompare( NlGlobalDBInfoArray[DBIndex].PrimaryName,
+ NlGlobalUnicodePrimaryName,
+ NAMETYPE_COMPUTER ) == 0 ) {
+ return TRUE;
+ }
+
+
+ //
+ // Handle the LSA specially
+ //
+
+ if ( DBIndex == LSA_DB ) {
+ LSAPR_POLICY_INFORMATION PolicyReplication;
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)&PolicyReplication.PolicyReplicaSourceInfo.ReplicaSource,
+ NlGlobalUnicodePrimaryName );
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)&PolicyReplication.PolicyReplicaSourceInfo.ReplicaAccountName,
+ NULL );
+
+ Status = LsarSetInformationPolicy(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ PolicyReplicaSourceInformation,
+ &PolicyReplication );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlResetFirstTimeFullSync: " FORMAT_LPWSTR
+ ": reset full sync failed 0x%lx " FORMAT_LPWSTR ".\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ Status,
+ NlGlobalUncPrimaryName ));
+
+ return FALSE;
+ }
+
+ //
+ // Handle a SAM database.
+ //
+
+ } else {
+
+ SAMPR_DOMAIN_REPLICATION_INFORMATION DomainReplication;
+
+ RtlInitUnicodeString(
+ (PUNICODE_STRING)&DomainReplication.ReplicaSourceNodeName,
+ NlGlobalUnicodePrimaryName );
+
+ Status = SamrSetInformationDomain(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ DomainReplicationInformation,
+ (PSAMPR_DOMAIN_INFO_BUFFER) &DomainReplication );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlResetFirstTimeFullSync: " FORMAT_LPWSTR
+ ": reset full sync failed 0x%lx " FORMAT_LPWSTR ".\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ Status,
+ NlGlobalUncPrimaryName ));
+
+ return FALSE;
+ }
+ }
+
+ //
+ // Set the in-memory copy to match.
+ //
+
+ wcscpy( NlGlobalDBInfoArray[DBIndex].PrimaryName, NlGlobalUnicodePrimaryName );
+
+ NlPrint((NL_SYNC,
+ "NlResetFirstTimeFullSync: " FORMAT_LPWSTR
+ ": Set ReplicaSource to " FORMAT_LPWSTR ".\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ NlGlobalUncPrimaryName ));
+
+ return TRUE;
+
+}
+
+
+NTSTATUS
+NlOpenSecret(
+ IN PCLIENT_SESSION ClientSession,
+ IN ULONG DesiredAccess,
+ OUT PLSAPR_HANDLE SecretHandle
+ )
+/*++
+
+Routine Description:
+
+
+ Open the Lsa Secret Object containing the password to be used for the
+ specified client session.
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+ On Input, the following fields must be set:
+ CsDomainName
+ CsSecureChannelType
+
+ DesiredAccess - Access required to the secret.
+
+ SecretHandle - Returns a handle to the secret.
+
+Return Value:
+
+ Status of operation.
+
+--*/
+{
+ NTSTATUS Status;
+ WCHAR SecretName[ LSA_GLOBAL_SECRET_PREFIX_LENGTH +
+ SSI_SECRET_PREFIX_LENGTH + DNLEN + 1 ];
+ UNICODE_STRING SecretNameString;
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+
+ //
+ // Determine the name of the secret in LSA secret storage that
+ // defines the password for this account.
+ //
+ // Use:
+ // G$NETLOGON$DomainName for Domain accounts
+ // NETLOGON$MACHINE_ACCOUNT for workstation and server accounts
+ //
+ // Short form:
+ // G$$DomainName for Domain accounts
+ // $MACHINE.ACC for workstation and server accounts
+ //
+
+ switch ( ClientSession->CsSecureChannelType ) {
+ case TrustedDomainSecureChannel:
+ wcscpy( SecretName, LSA_GLOBAL_SECRET_PREFIX );
+ wcscat( SecretName, SSI_SECRET_PREFIX );
+ wcscat( SecretName, ClientSession->CsDomainName.Buffer );
+ break;
+
+ case ServerSecureChannel:
+ case WorkstationSecureChannel:
+ wcscpy( SecretName, SSI_SECRET_PREFIX );
+ wcscat( SecretName, SSI_SECRET_POSTFIX );
+ break;
+
+ default:
+ Status = STATUS_INTERNAL_ERROR;
+ NlPrint((NL_CRITICAL, "NlOpenSecret: Invalid account type\n"));
+ return Status;
+
+ }
+
+ //
+ // Get the Password of the account from LSA secret storage
+ //
+
+ RtlInitUnicodeString( &SecretNameString, SecretName );
+
+ Status = LsarOpenSecret(
+ NlGlobalPolicyHandle,
+ (PLSAPR_UNICODE_STRING)&SecretNameString,
+ DesiredAccess,
+ SecretHandle );
+
+ return Status;
+
+}
+
+
+BOOLEAN
+NlTimeToRediscover(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Determine if it is time to rediscover this Client Session.
+ If a session setup failure happens to a discovered DC,
+ rediscover the DC if the discovery happened a long time ago (more than 5 minutes).
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+
+Return Value:
+
+ TRUE -- iff it is time to re-discover
+
+--*/
+{
+ BOOLEAN ReturnBoolean;
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ ReturnBoolean = NlTimeHasElapsed(
+ ClientSession->CsLastDiscoveryTime,
+ ClientSession->CsSecureChannelType == WorkstationSecureChannel ?
+ MAX_WKSTA_REAUTHENTICATION_WAIT :
+ MAX_DC_REAUTHENTICATION_WAIT );
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ return ReturnBoolean;
+}
+
+
+NTSTATUS
+NlSessionSetup(
+ IN OUT PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Verify that the requestor (this machine) has a valid account at
+ Primary Domain Controller (primary). The authentication
+ is done via an elaborate protocol. This routine will be
+ used only when NETLOGON service starts with role != primary.
+
+ The requestor (i.e. this machine) will generate a challenge
+ and send it to the Primary Domain Controller and will receive
+ a challenge from the primary in response. Now we will compute
+ credentials using primary's challenge and send it across and
+ wait for credentials, computed at primary using our initial
+ challenge, to be returned by PDC. Before computing credentials
+ a sessionkey will be built which uniquely identifies this
+ session and it will be returned to caller for future use.
+
+ If both machines authenticate then they keep the
+ ClientCredential and the session key for future use.
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+ On Input the following fields must be set:
+ CsState
+ CsDomainName
+ CsUncServerName (May be empty string depending on SecureChannelType)
+ CsAccountName
+ CsSecureChannelType
+ The caller must be a writer of the ClientSession.
+
+ On Output, the following fields will be set
+ CsConnectionStatus
+ CsState
+ CsSessionKey
+ CsAuthenticationSeed
+
+Return Value:
+
+ Status of operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ NETLOGON_CREDENTIAL ServerChallenge;
+ NETLOGON_CREDENTIAL ClientChallenge;
+ NETLOGON_CREDENTIAL ComputedServerCredential;
+ NETLOGON_CREDENTIAL ReturnedServerCredential;
+
+ LSAPR_HANDLE SecretHandle = NULL;
+
+ PLSAPR_CR_CIPHER_VALUE CrCurrentPassword = NULL;
+ PLSAPR_CR_CIPHER_VALUE CrOldPassword = NULL;
+ BOOLEAN WeDidDiscovery = FALSE;
+ BOOLEAN ErrorFromDiscoveredServer = FALSE;
+
+ //
+ // Used to indicate whether the current or the old password is being
+ // tried to access the DC.
+ // 0: implies the current password
+ // 1: implies the old password
+ // 2: implies both failed
+ //
+
+ DWORD State;
+
+ //
+ // Ensure we're a writer.
+ //
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ NlAssert( ClientSession->CsFlags & CS_WRITER );
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlSessionSetup: %wZ Try Session setup\n",
+ &ClientSession->CsDomainName ));
+
+
+ //
+ // If we're free to pick the DC which services our request,
+ // do so.
+ //
+ // Apparently there was a problem with the previously chosen DC
+ // so we pick again here. (There is a chance we'll pick the same server.)
+ //
+
+ if ( ClientSession->CsState == CS_IDLE ) {
+
+ WeDidDiscovery = TRUE;
+
+ //
+ // Pick the name of a DC in the domain.
+ //
+
+ Status = NlDiscoverDc(ClientSession, DT_Synchronous ) ;
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot pick trusted DC\n",
+ &ClientSession->CsDomainName ));
+
+ goto Cleanup;
+
+ }
+
+ }
+ NlAssert( ClientSession->CsState != CS_IDLE );
+
+
+ //
+ // Prepare our challenge
+ //
+
+FirstTryFailed:
+ NlComputeChallenge( &ClientChallenge );
+
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ClientChallenge = %lx %lx\n",
+ ((DWORD *)&ClientChallenge)[0],
+ ((DWORD *)&ClientChallenge)[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Get the Password of the account from LSA secret storage
+ //
+
+ Status = NlOpenSecret( ClientSession, SECRET_QUERY_VALUE, &SecretHandle );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot NlOpenSecret 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+
+ //
+ // return more appropriate error.
+ //
+
+ Status = STATUS_NO_TRUST_LSA_SECRET;
+ goto Cleanup;
+ }
+
+ Status = LsarQuerySecret(
+ SecretHandle,
+ &CrCurrentPassword,
+ NULL,
+ &CrOldPassword,
+ NULL );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot LsaQuerySecret 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+
+ //
+ // return more appropriate error.
+ //
+
+ Status = STATUS_NO_TRUST_LSA_SECRET;
+ goto Cleanup;
+ }
+
+ //
+ // Try setting up a secure channel first using the CurrentPassword.
+ // If that fails, try using the OldPassword
+ //
+
+
+ for ( State = 0; ; State++ ) {
+
+ NT_OWF_PASSWORD NtOwfPassword;
+ UNICODE_STRING CurrentPassword;
+ PLSAPR_CR_CIPHER_VALUE PasswordToTry;
+
+
+ //
+ // Use the right password for this iteration
+ //
+
+ if ( State == 0 ) {
+ PasswordToTry = CrCurrentPassword;
+ } else if ( State == 1 ) {
+
+ if ( CrCurrentPassword != NULL &&
+ CrOldPassword != NULL &&
+ CrCurrentPassword->Buffer != NULL &&
+ CrOldPassword->Buffer != NULL &&
+ CrCurrentPassword->Length == CrOldPassword->Length &&
+ RtlEqualMemory( CrCurrentPassword->Buffer,
+ CrOldPassword->Buffer,
+ CrOldPassword->Length ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ new password is bad. Old password is same as new password.\n",
+ &ClientSession->CsDomainName ));
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ PasswordToTry = CrOldPassword;
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ new password is bad, try old one\n",
+ &ClientSession->CsDomainName ));
+ } else {
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+ //
+ // If this particular password isn't present in the LSA,
+ // just ignore it.
+ //
+
+ if ( PasswordToTry == NULL || PasswordToTry->Buffer == NULL ) {
+ continue;
+ }
+
+ CurrentPassword.Length = (USHORT)PasswordToTry->Length;
+ CurrentPassword.MaximumLength = (USHORT)PasswordToTry->MaximumLength;
+ CurrentPassword.Buffer = (LPWSTR)PasswordToTry->Buffer;
+
+
+ //
+ // Get the primary's challenge
+ //
+
+ NlAssert( ClientSession->CsState != CS_IDLE );
+ Status = NlStartApiClientSession( ClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = I_NetServerReqChallenge(ClientSession->CsUncServerName,
+ NlGlobalUnicodeComputerName,
+ &ClientChallenge,
+ &ServerChallenge );
+ }
+
+ if ( !NlFinishApiClientSession( ClientSession, FALSE ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot FinishApiClientSession for I_NetServerReqChallenge 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+ // Failure here indicates that the discovered server is really slow.
+ // Let the "ErrorFromDiscoveredServer" logic do the rediscovery.
+ if ( NT_SUCCESS(Status) ) {
+ // We're dropping the secure channel so
+ // ensure we don't use any successful status from the DC
+ Status = STATUS_NO_LOGON_SERVERS;
+ }
+ ErrorFromDiscoveredServer = TRUE;
+ goto Cleanup;
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot I_NetServerReqChallenge 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+ ErrorFromDiscoveredServer = TRUE;
+ goto Cleanup;
+ }
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerChallenge = %lx %lx\n",
+ ((DWORD *)&ServerChallenge)[0],
+ ((DWORD *)&ServerChallenge)[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Compute the NT OWF password for this user.
+ //
+
+ Status = RtlCalculateNtOwfPassword(
+ &CurrentPassword,
+ &NtOwfPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // return more appropriate error.
+ //
+
+ Status = STATUS_NO_TRUST_LSA_SECRET;
+ goto Cleanup;
+ }
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Password = %lx %lx %lx %lx\n",
+ ((DWORD *) (&NtOwfPassword))[0],
+ ((DWORD *) (&NtOwfPassword))[1],
+ ((DWORD *) (&NtOwfPassword))[2],
+ ((DWORD *) (&NtOwfPassword))[3]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Actually compute the session key given the two challenges and the
+ // password.
+ //
+
+ NlMakeSessionKey(
+ &NtOwfPassword,
+ &ClientChallenge,
+ &ServerChallenge,
+ &ClientSession->CsSessionKey );
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: SessionKey = %lx %lx %lx %lx\n",
+ ((DWORD *) (&ClientSession->CsSessionKey))[0],
+ ((DWORD *) (&ClientSession->CsSessionKey))[1],
+ ((DWORD *) (&ClientSession->CsSessionKey))[2],
+ ((DWORD *) (&ClientSession->CsSessionKey))[3]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Prepare credentials using our challenge.
+ //
+
+ NlComputeCredentials( &ClientChallenge,
+ &ClientSession->CsAuthenticationSeed,
+ &ClientSession->CsSessionKey );
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Authentication Seed = %lx %lx\n",
+ ((DWORD *) (&ClientSession->CsAuthenticationSeed))[0],
+ ((DWORD *) (&ClientSession->CsAuthenticationSeed))[1]));
+#endif // BAD_ALIGNMENT
+
+ //
+ // Send these credentials to primary. The primary will compute
+ // credentials using the challenge supplied by us and compare
+ // with these. If both match then it will compute credentials
+ // using its challenge and return it to us for verification
+ //
+
+ Status = NlStartApiClientSession( ClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ ClientSession->CsNegotiatedFlags = NETLOGON_SUPPORTS_MASK;
+ Status = I_NetServerAuthenticate2( ClientSession->CsUncServerName,
+ ClientSession->CsAccountName,
+ ClientSession->CsSecureChannelType,
+ NlGlobalUnicodeComputerName,
+ &ClientSession->CsAuthenticationSeed,
+ &ReturnedServerCredential,
+ &ClientSession->CsNegotiatedFlags );
+ }
+
+ if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
+ ClientSession->CsNegotiatedFlags = 0;
+ Status = I_NetServerAuthenticate( ClientSession->CsUncServerName,
+ ClientSession->CsAccountName,
+ ClientSession->CsSecureChannelType,
+ NlGlobalUnicodeComputerName,
+ &ClientSession->CsAuthenticationSeed,
+ &ReturnedServerCredential );
+ }
+
+ if ( !NlFinishApiClientSession( ClientSession, FALSE ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot FinishApiClientSession for I_NetServerAuthenticate 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+ // Failure here indicates that the discovered server is really slow.
+ // Let the "ErrorFromDiscoveredServer" logic do the rediscovery.
+ if ( NT_SUCCESS(Status) ) {
+ // We're dropping the secure channel so
+ // ensure we don't use any successful status from the DC
+ Status = STATUS_NO_LOGON_SERVERS;
+ }
+ ErrorFromDiscoveredServer = TRUE;
+ goto Cleanup;
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot I_NetServerAuthenticate 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+ ErrorFromDiscoveredServer = TRUE;
+
+ //
+ // If access is denied, it might be because we weren't able to
+ // authenticate with the new password, try the old password.
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED && State == 0 ) {
+ continue;
+ }
+ goto Cleanup;
+ }
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerCredential GOT = %lx %lx\n",
+ ((DWORD *) (&ReturnedServerCredential))[0],
+ ((DWORD *) (&ReturnedServerCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // The DC returned a server credential to us,
+ // ensure the server credential matches the one we would compute.
+ //
+
+ NlComputeCredentials( &ServerChallenge,
+ &ComputedServerCredential,
+ &ClientSession->CsSessionKey);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerCredential MADE = %lx %lx\n",
+ ((DWORD *) (&ComputedServerCredential))[0],
+ ((DWORD *) (&ComputedServerCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ if (RtlCompareMemory( &ReturnedServerCredential,
+ &ComputedServerCredential,
+ sizeof(ReturnedServerCredential)) !=
+ sizeof(ReturnedServerCredential)) {
+ Status = STATUS_ACCESS_DENIED;
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "Servercredential don't match ours 0x%lx\n",
+ &ClientSession->CsDomainName,
+ Status));
+ goto Cleanup;
+ }
+
+ //
+ // If we've made it this far, we've successfully authenticated
+ // with the DC, drop out of the loop.
+ //
+
+ break;
+ }
+
+ //
+ // If we used the old password to authenticate,
+ // update the DC to the current password ASAP.
+ //
+
+ if ( State == 1 ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ old password succeeded\n",
+ &ClientSession->CsDomainName ));
+ LOCK_TRUST_LIST();
+ ClientSession->CsFlags |= CS_UPDATE_PASSWORD;
+ UNLOCK_TRUST_LIST();
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup
+ //
+
+Cleanup:
+
+ //
+ // Free locally used resources
+ //
+
+ if ( SecretHandle != NULL ) {
+ (VOID) LsarClose( &SecretHandle );
+ SecretHandle == NULL;
+ }
+
+ if ( CrCurrentPassword != NULL ) {
+ (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrCurrentPassword );
+ CrCurrentPassword = NULL;
+ }
+
+ if ( CrOldPassword != NULL ) {
+ (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrOldPassword );
+ CrOldPassword = NULL;
+ }
+
+
+ //
+ // Upon success, save the status and reset counters.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+
+ NlSetStatusClientSession( ClientSession, Status );
+ ClientSession->CsAuthAlertCount = 0;
+ ClientSession->CsTimeoutCount = 0;
+#if DBG
+ if ( ClientSession->CsNegotiatedFlags != NETLOGON_SUPPORTS_MASK ) {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ negotiated %lx flags rather than %lx\n",
+ &ClientSession->CsDomainName,
+ ClientSession->CsNegotiatedFlags,
+ NETLOGON_SUPPORTS_MASK ));
+ }
+#endif // DBG
+
+
+
+ //
+ // write event log and raise alert
+ //
+
+ } else {
+
+ WCHAR PreviouslyDiscoveredServer[UNCLEN+1];
+ LPWSTR MsgStrings[3];
+
+ //
+ // Save the name of the discovered server.
+ //
+
+ if ( *ClientSession->CsUncServerName != L'\0' ) {
+ wcscpy( PreviouslyDiscoveredServer, ClientSession->CsUncServerName );
+ } else {
+ wcscpy( PreviouslyDiscoveredServer, L"<Unknown>" );
+ }
+
+ //
+ // If we didn't do the discovery just now,
+ // and the failure came from the discovered machine,
+ // try the discovery again and redo the session setup.
+ //
+
+ if ( !WeDidDiscovery &&
+ ErrorFromDiscoveredServer &&
+ NlTimeToRediscover( ClientSession) ) {
+
+ NTSTATUS TempStatus;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlSessionSetup: %wZ Retry failed session setup since discovery wasn't recent.\n",
+ &ClientSession->CsDomainName ));
+
+
+ //
+ // Pick the name of a new DC in the domain.
+ //
+
+ NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
+
+ TempStatus = NlDiscoverDc(ClientSession, DT_Synchronous );
+
+ if ( NT_SUCCESS(TempStatus) ) {
+
+ //
+ // Don't bother redoing the session setup if we picked the same DC.
+ //
+
+ if ( NlNameCompare( ClientSession->CsUncServerName+2,
+ PreviouslyDiscoveredServer+2,
+ NAMETYPE_COMPUTER ) != 0 ) {
+ WeDidDiscovery = TRUE;
+ goto FirstTryFailed;
+ } else {
+ NlPrint((NL_SESSION_SETUP,
+ "NlSessionSetup: %wZ Skip retry failed session setup since same DC discovered.\n",
+ &ClientSession->CsDomainName ));
+ }
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "NlSessionSetup: %wZ Session setup: "
+ "cannot re-pick trusted DC\n",
+ &ClientSession->CsDomainName ));
+
+ }
+ }
+
+ switch(Status) {
+
+ case STATUS_NO_TRUST_LSA_SECRET:
+
+ MsgStrings[0] = PreviouslyDiscoveredServer;
+ MsgStrings[1] = ClientSession->CsDomainName.Buffer;
+ MsgStrings[2] = NlGlobalUnicodeComputerName;
+
+ NlpWriteEventlog (NELOG_NetlogonAuthNoTrustLsaSecret,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 );
+ break;
+
+ case STATUS_NO_TRUST_SAM_ACCOUNT:
+
+ MsgStrings[0] = PreviouslyDiscoveredServer;
+ MsgStrings[1] = ClientSession->CsDomainName.Buffer;
+ MsgStrings[2] = NlGlobalUnicodeComputerName;
+
+ NlpWriteEventlog (NELOG_NetlogonAuthNoTrustSamAccount,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 );
+ break;
+
+ case STATUS_ACCESS_DENIED:
+
+ MsgStrings[0] = ClientSession->CsDomainName.Buffer;
+ MsgStrings[1] = PreviouslyDiscoveredServer;
+
+ NlpWriteEventlog (NELOG_NetlogonAuthDCFail,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 );
+ break;
+
+ case STATUS_NO_LOGON_SERVERS:
+ default:
+
+ MsgStrings[0] = ClientSession->CsDomainName.Buffer;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog (NELOG_NetlogonAuthNoDomainController,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+ break;
+ }
+
+
+ MsgStrings[0] = PreviouslyDiscoveredServer;
+
+ RaiseNetlogonAlert( ALERT_NetlogonAuthDCFail,
+ ClientSession->CsDomainName.Buffer,
+ MsgStrings[0],
+ &ClientSession->CsAuthAlertCount);
+
+ //
+ // ?? Is this how to handle failure for all account types.
+ //
+
+ switch(Status) {
+
+ case STATUS_NO_TRUST_LSA_SECRET:
+ case STATUS_NO_TRUST_SAM_ACCOUNT:
+ case STATUS_ACCESS_DENIED:
+
+ NlSetStatusClientSession( ClientSession, Status );
+ break;
+
+ default:
+
+ NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
+ break;
+ }
+ }
+
+
+ //
+ // Mark the time we last tried to authenticate.
+ //
+ // We need to do this after NlSetStatusClientSession which zeros
+ // CsLastAuthenticationTry.
+ //
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ NtQuerySystemTime( &ClientSession->CsLastAuthenticationTry );
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlSessionSetup: %wZ Session setup %s\n",
+ &ClientSession->CsDomainName,
+ (NT_SUCCESS(ClientSession->CsConnectionStatus)) ? "Succeeded" : "Failed" ));
+
+ return Status;
+}
+
+
+BOOLEAN
+NlTimeHasElapsed(
+ IN LARGE_INTEGER StartTime,
+ IN DWORD Timeout
+ )
+/*++
+
+Routine Description:
+
+ Determine if "Timeout" milliseconds has has elapsed since StartTime.
+
+Arguments:
+
+ StartTime - Specifies an absolute time when the event started (100ns units).
+
+ Timeout - Specifies a relative time in milliseconds. 0xFFFFFFFF indicates
+ that the time will never expire.
+
+Return Value:
+
+ TRUE -- iff Timeout milliseconds have elapsed since StartTime.
+
+--*/
+{
+ LARGE_INTEGER TimeNow;
+ LARGE_INTEGER ElapsedTime;
+ LARGE_INTEGER Period;
+
+ //
+ // If the period to too large to handle (i.e., 0xffffffff is forever),
+ // just indicate that the timer has not expired.
+ //
+ // (0x7fffffff is a little over 24 days).
+ //
+
+ if ( Timeout> 0x7fffffff ) {
+ return FALSE;
+ }
+
+ //
+ // Compute the elapsed time since we last authenticated
+ //
+
+ NtQuerySystemTime( &TimeNow );
+ ElapsedTime.QuadPart = TimeNow.QuadPart - StartTime.QuadPart;
+
+ //
+ // Compute Period from milliseconds into 100ns units.
+ //
+
+ Period = RtlEnlargedIntegerMultiply( (LONG) Timeout, 10000 );
+
+
+ //
+ // If the elapsed time is negative (totally bogus) or greater than the
+ // maximum allowed, indicate the session should be reauthenticated.
+ //
+
+ if ( ElapsedTime.QuadPart < 0 || ElapsedTime.QuadPart > Period.QuadPart ) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+BOOLEAN
+NlTimeToReauthenticate(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Determine if it is time to reauthenticate this Client Session.
+ To reduce the number of re-authentication attempts, we try
+ to re-authenticate only on demand and then only at most every 45
+ seconds.
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+
+Return Value:
+
+ TRUE -- iff it is time to re-authenticate
+
+--*/
+{
+ BOOLEAN ReturnBoolean;
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ ReturnBoolean = NlTimeHasElapsed(
+ ClientSession->CsLastAuthenticationTry,
+ ClientSession->CsSecureChannelType == WorkstationSecureChannel ?
+ MAX_WKSTA_AUTHENTICATION_WAIT :
+ MAX_DC_AUTHENTICATION_WAIT );
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ return ReturnBoolean;
+}
+
+
+NTSTATUS
+NlNewSessionSetup(
+ IN LPWSTR primary
+ )
+/*++
+
+Routine Description:
+
+ Set up a session with a "new/different" primary. This routine
+ does what NlSessionSetup does, but also does the following:
+
+ * Coordinate with the replicator thread to ensure we don't remove
+ the current session out from under it.
+
+ * Set the Global indicating the name of the primary.
+
+ * Mark that a "partial sync" is required if this primary is different from
+ the previous primary. (The replicator thread may later decide to
+ do a full sync if the PDC is an NT 1.0 PDC.)
+
+Arguments:
+
+ primary - ptr to name of primary domain controller.
+
+Return Value:
+
+ Status of operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // If we already have a session setup to the primary in question,
+ // don't bother doing it again.
+ //
+
+ if ( NlGlobalClientSession->CsState == CS_AUTHENTICATED &&
+ NlNameCompare( primary,
+ NlGlobalUnicodePrimaryName,
+ NAMETYPE_COMPUTER ) == 0 ) {
+
+ return STATUS_SUCCESS;
+
+ }
+
+
+ //
+ // Ask the replicator to terminate. Wait for it to do so.
+ // Don't allow the replicator to be started until we're done.
+ //
+
+ EnterCriticalSection( &NlGlobalReplicatorCritSect );
+
+ NlStopReplicator();
+
+ //
+ // Become a Writer of the ClientSession.
+ //
+ // Only wait for 10 seconds. This routine is called by the netlogon main
+ // thread. Another thread may have the ClientSession locked and need the
+ // main thread to finish a discovery.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, 10 * 1000 ) ) {
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+
+ NlPrint((NL_CRITICAL,
+ "NlNewSessionSetup: " FORMAT_LPWSTR
+ ": cannot become writer of client session.\n",
+ primary ));
+
+ return STATUS_NO_LOGON_SERVERS;
+ }
+
+ //
+ // Drop the previous session and forget the old primary name.
+ //
+
+ NlSetStatusClientSession( NlGlobalClientSession, STATUS_NO_LOGON_SERVERS );
+
+ //
+ // Remember this new primary name
+ //
+
+ if ( !NlSetPrimaryName( primary ) ) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return STATUS_NO_MEMORY;
+ }
+
+ //
+ // Setup the session.
+ //
+ Status = NlSessionSetup( NlGlobalClientSession );
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return Status;
+
+}
+
+
+NTSTATUS
+NlAuthenticate(
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential,
+ IN ULONG NegotiatedFlags
+ )
+/*++
+
+Routine Description:
+
+ Authenticate the specified user using the specified credentials.
+ If the credentials match, return a ServerCredential to the caller so
+ the caller can be assured that we know the Session Key.
+
+ Previously, and entry must have been placed in the LogonTable for this
+ session. The LogonTable entry must contain the ClientChallenge and the
+ ServerChallenge used to compute the session key.
+
+ If this authentication attempt fails, the LogonTable entry is deleted.
+
+Arguments:
+
+ AccountName - Name of the account to authenticate with.
+
+ SecureChannelType - The type of the account being accessed.
+
+ ComputerName - The name of the workstation from which logon is occurring.
+
+ ClientCredential -- results of a function performed on
+ ClientChallenge using the account's password.
+
+ ServerCredential -- Receives the results of a similar
+ operation performed by the logon server on the ServerChallenge.
+
+ NegotiatedFlags -- Capabilities supported by both client and server.
+
+Return Value:
+
+ Status of operation.
+
+--*/
+{
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession;
+ NETLOGON_CREDENTIAL LocalClientCredential;
+
+ SAMPR_HANDLE UserHandle = NULL;
+ PSAMPR_USER_INFO_BUFFER UserPasswords = NULL;
+
+ NT_OWF_PASSWORD OwfPassword;
+ NETLOGON_CREDENTIAL ServerChallenge;
+
+ WCHAR LocalComputerName[CNLEN+1+1]; // '$' plus trailing '\0'
+
+
+ //
+ // Build the name of the computer trying to connect.
+ // If this is a BDC session from an emulated Cairo domain,
+ // the ComputerName will be the "real" computer name,
+ // and the emulated computer name is a function of the AccountName.
+ // So, always use the AccountName for BDC sessions.
+ //
+
+ if ( SecureChannelType == ServerSecureChannel ) {
+ wcsncpy( LocalComputerName, AccountName, CNLEN+1);
+ LocalComputerName[CNLEN+1] = L'\0';
+
+ // Ditch the trailing '$'
+ LocalComputerName[wcslen(LocalComputerName)-1] = L'\0';
+ } else {
+ wcsncpy( LocalComputerName, ComputerName, CNLEN+1);
+ LocalComputerName[CNLEN] = L'\0';
+ }
+
+ //
+ // we need to retrieve the original challenge supplied by Workstation
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( LocalComputerName );
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: Can't NlFindNamedServerSession " FORMAT_LPWSTR "\n",
+ LocalComputerName ));
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // If the caller claims to be a BDC,
+ // make sure he has a BDC account.
+ //
+ // This shouldn't happen in reality, but other sections of code rely on
+ // the secure channel type matching the SS_BDC bit.
+ //
+
+ if ( IS_BDC_CHANNEL( SecureChannelType) ) {
+ if ((ServerSession->SsFlags & SS_BDC) == 0 ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: BDC connecting on non-BDC channel " FORMAT_LPWSTR "\n",
+ ComputerName ));
+ return STATUS_ACCESS_DENIED;
+ }
+ } else {
+ if ( ServerSession->SsFlags & SS_BDC ) {
+ LPWSTR MsgStrings[4];
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: non-BDC connecting on BDC channel " FORMAT_LPWSTR "\n",
+ ComputerName ));
+ //
+ // This can actually occur if a machine has a BDC account and
+ // then is later converted a DC in another domain. So, give an
+ // explicit message for this problem.
+ //
+ MsgStrings[0] = NlGlobalUnicodeComputerName;
+ MsgStrings[1] = ComputerName;
+ MsgStrings[2] = NlGlobalUnicodeDomainName;
+ MsgStrings[3] = AccountName;
+
+ NlpWriteEventlog (NELOG_NetlogonSessionTypeWrong,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 4 );
+ return STATUS_ACCESS_DENIED;
+ }
+ }
+
+
+ //
+ // Prevent entry from being deleted, but drop the global lock.
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: session locked " FORMAT_LPWSTR "\n",
+ ComputerName ));
+ return STATUS_ACCESS_DENIED;
+ }
+ ServerSession->SsFlags |= SS_LOCKED;
+ ServerSession->SsSecureChannelType = SecureChannelType;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Figure out what transport this connection came in on so we can send out
+ // mailslot messages only on the particular transport.
+ // Don't do it for workstations. We don't send mailslot messages there.
+ //
+
+ if ( ServerSession->SsFlags & SS_BDC ) {
+ //
+ // Don't use 'LocalComputerName'. For Cairo emulated BDCs, that
+ // machine doesn't have a session to us.
+ //
+ ServerSession->SsTransportName = NlTransportLookup( ComputerName );
+ }
+
+ //
+ // Get the encrypted password from SAM.
+ //
+
+ Status = NlSamOpenNamedUser( AccountName, &UserHandle, NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: Cannot NlSamOpenNamedUser " FORMAT_LPWSTR "\n",
+ AccountName ));
+ goto Cleanup;
+ }
+
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserInternal1Information,
+ &UserPasswords );
+
+ if (!NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: Cannot SamrQueryInformationUser " FORMAT_LPWSTR "\n",
+ AccountName ));
+ UserPasswords = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // If the authentication is from an NT client,
+ // use the NT OWF Password,
+ // otherwise, use the LM OWF password.
+ //
+
+ if ( SecureChannelType == UasServerSecureChannel ) {
+ if ( !UserPasswords->Internal1.LmPasswordPresent ) {
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: No LM Password for " FORMAT_LPWSTR "\n",
+ AccountName ));
+
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( &OwfPassword,
+ &UserPasswords->Internal1.EncryptedLmOwfPassword,
+ sizeof(OwfPassword) );
+ } else {
+ if ( UserPasswords->Internal1.NtPasswordPresent ) {
+
+ RtlCopyMemory( &OwfPassword,
+ &UserPasswords->Internal1.EncryptedNtOwfPassword,
+ sizeof(OwfPassword) );
+
+ // Allow for the case the the account has no password at all.
+ } else if ( !UserPasswords->Internal1.LmPasswordPresent ) {
+ UNICODE_STRING TempUnicodeString;
+
+ RtlInitUnicodeString(&TempUnicodeString, NULL);
+ Status = RtlCalculateNtOwfPassword(&TempUnicodeString,
+ &OwfPassword);
+
+ } else {
+
+ NlPrint((NL_CRITICAL,
+ "NlAuthenticate: No NT Password for " FORMAT_LPWSTR "\n",
+ AccountName ));
+
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+ }
+
+
+
+ //
+ // Actually compute the session key given the two challenges and the
+ // password.
+ //
+
+ RtlCopyMemory( &ServerChallenge,
+ &ServerSession->SsSessionKey,
+ sizeof(ServerChallenge) );
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: Password = %lx %lx %lx %lx\n",
+ ((DWORD *) (&OwfPassword))[0],
+ ((DWORD *) (&OwfPassword))[1],
+ ((DWORD *) (&OwfPassword))[2],
+ ((DWORD *) (&OwfPassword))[3]));
+
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: ClientChallenge = %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0],
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1]));
+
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: ServerChallenge = %lx %lx\n",
+ ((DWORD *) (&ServerChallenge))[0],
+ ((DWORD *) (&ServerChallenge))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Actually compute the session key given the two challenges and the
+ // password.
+ //
+
+ NlMakeSessionKey(
+ &OwfPassword,
+ &ServerSession->SsAuthenticationSeed,
+ &ServerChallenge,
+ &ServerSession->SsSessionKey );
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: SessionKey = %lx %lx %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsSessionKey))[0],
+ ((DWORD *) (&ServerSession->SsSessionKey))[1],
+ ((DWORD *) (&ServerSession->SsSessionKey))[2],
+ ((DWORD *) (&ServerSession->SsSessionKey))[3]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Compute ClientCredential to verify the one supplied by ComputerName
+ //
+
+ NlComputeCredentials( &ServerSession->SsAuthenticationSeed,
+ &LocalClientCredential,
+ &ServerSession->SsSessionKey);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: ClientCredential GOT = %lx %lx\n",
+ ((DWORD *) (ClientCredential))[0],
+ ((DWORD *) (ClientCredential))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: ClientCredential MADE = %lx %lx\n",
+ ((DWORD *) (&LocalClientCredential))[0],
+ ((DWORD *) (&LocalClientCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // verify the computed credentials with those supplied
+ //
+
+ if( RtlCompareMemory( ClientCredential,
+ &LocalClientCredential,
+ sizeof(LocalClientCredential)) !=
+ sizeof(LocalClientCredential)) {
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( &ServerSession->SsAuthenticationSeed,
+ &LocalClientCredential,
+ sizeof(LocalClientCredential));
+
+ //
+ // Compute ServerCredential from ServerChallenge to be returned to caller
+ //
+
+ NlComputeCredentials( &ServerChallenge,
+ ServerCredential,
+ &ServerSession->SsSessionKey );
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlAuthenticate: ServerCredential SEND = %lx %lx\n",
+ ((DWORD *) (ServerCredential))[0],
+ ((DWORD *) (ServerCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ //
+ // Allow the entry to disappear.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ if ( NT_SUCCESS( Status ) ) {
+ ServerSession->SsFlags |= SS_AUTHENTICATED;
+ ServerSession->SsNegotiatedFlags = NegotiatedFlags;
+
+ //
+ // If this is a NT BDC,
+ // force it to do a partial sync from us so we can find out
+ // the serial numbers of each of its databases.
+ //
+ // This is especially important for NT 1.0 BDCs which need this
+ // "encouragement" when it reboots. It is probably good on NT 1.0a
+ // BDCs since setting up a secure channel only happens at startup
+ // (in which case it is already going to make the calls) or after
+ // some error condition (in which case the increased paranoia is
+ // is good thing).
+ //
+
+ if ( SecureChannelType == ServerSecureChannel ) {
+ ServerSession->SsFlags |= SS_REPL_MASK;
+ }
+ }
+
+ ServerSession->SsFlags &= ~SS_CHALLENGE;
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlUnlockServerSession( ServerSession );
+
+ //
+ // Delete the ServerSession upon error
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlFreeNamedServerSession( LocalComputerName, FALSE );
+ }
+
+ //
+ // Free locally used resources.
+ //
+
+ if ( UserPasswords != NULL ) {
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserPasswords,
+ UserInternal1Information);
+ }
+
+ if ( UserHandle != NULL ) {
+ SamrCloseHandle( &UserHandle );
+ }
+
+ return Status;
+}
+
+
+
+
+NET_API_STATUS
+NlCreateShare(
+ LPWSTR SharePath,
+ LPWSTR ShareName
+ )
+/*++
+
+Routine Description:
+
+ Share the netlogon scripts directory.
+
+Arguments:
+
+ SharePath - Path that the new share should be point to.
+
+ ShareName - Name of the share.
+
+Return Value:
+
+ TRUE: if successful
+ FALSE: if error (NlExit was called)
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+ SHARE_INFO_502 ShareInfo502;
+
+ WORD AnsiSize;
+ CHAR AnsiRemark[NNLEN+1];
+ TCHAR Remark[NNLEN+1];
+
+ ACE_DATA AceData[] = {
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
+ GENERIC_EXECUTE | GENERIC_READ, &WorldSid}
+ };
+
+
+ //
+ // Build the structure describing the share.
+ //
+
+ ShareInfo502.shi502_path = SharePath;
+ ShareInfo502.shi502_security_descriptor = NULL;
+
+ NlPrint((NL_INIT, "Path to be shared is " FORMAT_LPWSTR "\n",
+ SharePath));
+
+ NetStatus = (NET_API_STATUS) DosGetMessage(
+ NULL, // No insertion strings
+ 0, // No insertion strings
+ AnsiRemark,
+ sizeof(AnsiRemark),
+ MTXT_LOGON_SRV_SHARE_REMARK,
+ MESSAGE_FILENAME,
+ &AnsiSize );
+
+ if ( NetStatus == NERR_Success ) {
+ NetpCopyStrToTStr( Remark, AnsiRemark );
+ ShareInfo502.shi502_remark = Remark;
+ } else {
+ ShareInfo502.shi502_remark = TEXT( "" );
+ }
+
+ ShareInfo502.shi502_netname = ShareName;
+ ShareInfo502.shi502_type = STYPE_DISKTREE;
+ ShareInfo502.shi502_permissions = ACCESS_READ;
+ ShareInfo502.shi502_max_uses = 0xffffffff;
+ ShareInfo502.shi502_passwd = TEXT("");
+
+ //
+ // Set the security descriptor on the share
+ //
+
+ //
+ // Create a security descriptor containing the DACL.
+ //
+
+ Status = NetpCreateSecurityDescriptor(
+ AceData,
+ (sizeof(AceData)/sizeof(AceData[0])),
+ NULL, // Default the owner Sid
+ NULL, // Default the primary group
+ &ShareInfo502.shi502_security_descriptor );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ FORMAT_LPWSTR ": Cannot create security descriptor 0x%lx\n",
+ SharePath, Status ));
+
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ return NetStatus;
+ }
+
+
+ //
+ // Create the share.
+ //
+
+ NetStatus = NetShareAdd(NULL, 502, (LPBYTE) &ShareInfo502, NULL);
+
+ if (NetStatus == NERR_DuplicateShare) {
+
+ PSHARE_INFO_2 ShareInfo2 = NULL;
+
+ NlPrint((NL_INIT, "The netlogon share (" FORMAT_LPWSTR
+ ") already exists. \n", ShareName));
+
+ //
+ // check to see the shared path is same.
+ //
+
+ NetStatus = NetShareGetInfo( NULL,
+ NETLOGON_SCRIPTS_SHARE,
+ 2,
+ (LPBYTE *) &ShareInfo2 );
+
+ if ( NetStatus == NERR_Success ) {
+
+ //
+ // compare path names.
+ //
+ // Note: NlGlobalUnicodeScriptPath is path canonicalized already.
+ //
+ //
+
+ NlPrint((NL_INIT, "The netlogon share current path is "
+ FORMAT_LPWSTR "\n", SharePath));
+
+ if( NetpwPathCompare(
+ NlGlobalUnicodeScriptPath,
+ ShareInfo2->shi2_path, 0, 0 ) != 0 ) {
+
+ //
+ // delete share.
+ //
+
+ NetStatus = NetShareDel( NULL, NETLOGON_SCRIPTS_SHARE, 0);
+
+ if( NetStatus == NERR_Success ) {
+
+ //
+ // Recreate share.
+ //
+
+ NetStatus = NetShareAdd(NULL,
+ 502,
+ (LPBYTE) &ShareInfo502,
+ NULL);
+
+ if( NetStatus == NERR_Success ) {
+
+ NlPrint((NL_INIT, "The netlogon share ("
+ FORMAT_LPWSTR ") is recreated with new path "
+ FORMAT_LPWSTR "\n",
+ ShareName, SharePath ));
+ }
+
+ }
+ }
+ }
+
+ if( ShareInfo2 != NULL ) {
+ NetpMemoryFree( ShareInfo2 );
+ }
+ }
+
+ //
+ // Free the security descriptor
+ //
+
+ NetpMemoryFree( ShareInfo502.shi502_security_descriptor );
+
+
+ if ( NetStatus != NERR_Success ) {
+
+ NlPrint((NL_CRITICAL,
+ "Error %lu attempting to create-share " FORMAT_LPWSTR "\n",
+ NetStatus, ShareName ));
+ return NetStatus;
+
+ }
+
+ return NERR_Success;
+}
+
+
+
+NTSTATUS
+NlSamOpenNamedUser(
+ IN LPWSTR UserName,
+ OUT SAMPR_HANDLE *UserHandle OPTIONAL,
+ OUT PULONG UserId OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Utility routine to open a Sam user given the username.
+
+Arguments:
+
+ UserName - Name of user to open
+
+ UserHandle - Optionally returns a handle to the opened user.
+
+ UserId - Optionally returns the relative ID of the opened user.
+
+Return Value:
+
+--*/
+{
+ NTSTATUS Status;
+
+ SAMPR_ULONG_ARRAY RelativeIdArray;
+ SAMPR_ULONG_ARRAY UseArray;
+ RPC_UNICODE_STRING UserNameString;
+ SAMPR_HANDLE LocalUserHandle = NULL;
+
+ //
+ // Convert the user name to a RelativeId.
+ //
+
+ RtlInitUnicodeString( (PUNICODE_STRING)&UserNameString, UserName );
+ RelativeIdArray.Count = 1;
+ RelativeIdArray.Element = NULL;
+ UseArray.Count = 1;
+ UseArray.Element = NULL;
+
+ Status = SamrLookupNamesInDomain(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 1,
+ &UserNameString,
+ &RelativeIdArray,
+ &UseArray );
+
+ if ( !NT_SUCCESS(Status) ) {
+ RelativeIdArray.Element = NULL;
+ UseArray.Element = NULL;
+ if ( Status == STATUS_NONE_MAPPED ) {
+ Status = STATUS_NO_SUCH_USER;
+ }
+ goto Cleanup;
+ }
+
+ //
+ // we should get back exactly one entry of info back.
+ //
+
+ NlAssert( UseArray.Count == 1 );
+ NlAssert( RelativeIdArray.Count == 1 );
+ NlAssert( UseArray.Element != NULL );
+ NlAssert( RelativeIdArray.Element != NULL );
+
+ if ( UseArray.Element[0] != SidTypeUser ) {
+ Status = STATUS_NO_SUCH_USER;
+ goto Cleanup;
+ }
+
+ //
+ // Open the user
+ //
+
+ if ( UserHandle != NULL ) {
+ Status = SamrOpenUser( NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 0, // No desired access
+ RelativeIdArray.Element[0],
+ &LocalUserHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ LocalUserHandle = NULL;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Free locally used resources.
+ //
+
+Cleanup:
+
+ //
+ // Return information to the caller.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ if ( UserHandle != NULL ) {
+ *UserHandle = LocalUserHandle;
+ LocalUserHandle = NULL;
+ }
+
+ if ( UserId != NULL ) {
+ *UserId = RelativeIdArray.Element[0];
+ }
+
+ }
+
+ //
+ // Close the user handle if we don't need it returned.
+ //
+
+ if ( LocalUserHandle != NULL ) {
+ SamrCloseHandle( &LocalUserHandle );
+ }
+
+ //
+ // Free locally used resources.
+ //
+
+ SamIFree_SAMPR_ULONG_ARRAY( &RelativeIdArray );
+ SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
+
+ return Status;
+
+}
+
+
+
+NTSTATUS
+NlChangePassword(
+ PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Change this machine's password at the primary.
+ Also update password locally if the call succeeded.
+
+ To determine if the password of "machine account"
+ needs to be changed. If the password is older than
+ 7 days then it must be changed asap. We will defer
+ changing the password if we know before hand that
+ primary dc is down since our call will fail anyway.
+
+Arguments:
+
+ ClientSession - Structure describing the session to change the password
+ for. The specified structure must be referenced.
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+
+ LM_OWF_PASSWORD OwfPassword;
+ ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword;
+
+ LSAPR_HANDLE SecretHandle = NULL;
+ PLSAPR_CR_CIPHER_VALUE CrCurrentPassword = NULL;
+ LARGE_INTEGER CurrentPasswordTime;
+ PLSAPR_CR_CIPHER_VALUE CrPreviousPassword = NULL;
+
+ CHAR ClearTextPassword[LM_OWF_PASSWORD_LENGTH];
+ LSAPR_CR_CIPHER_VALUE CrClearTextPasswordString;
+ UNICODE_STRING ClearTextPasswordString;
+
+ BOOL PasswordChangedOnServer = FALSE;
+ BOOL LsaSecretChanged = FALSE;
+ BOOL DefaultPasswordBeingChanged = FALSE;
+
+ //
+ // Initialization
+ //
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+
+ //
+ // If the password change was refused by the DC,
+ // Don't ever try to change the password again (until the next reboot).
+ //
+ // This could have been written to try every MAX_SSI_PWAGE. However,
+ // that gets complex if you take into consideration the CS_UPDATE_PASSWORD
+ // case where the time stamp on the LSA Secret doesn't get changed.
+ //
+
+ LOCK_TRUST_LIST();
+ if ( ClientSession->CsFlags & CS_PASSWORD_REFUSED ) {
+ UNLOCK_TRUST_LIST();
+ return STATUS_SUCCESS;
+ }
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // If the replicator thread is running, do nothing.
+ //
+ // The replicator will be talking to the PDC over the secure channel
+ // and we'd rather not disturb it. In theory we could change the
+ // password out from under the replication (the replicator appropriately
+ // becomes a writer of the ClientSession). However, a replication
+ // is important enough that we'd rather not take a chance that the
+ // recovery logic below might drop the secure channel.
+ //
+ // We only do a spot check here since we don't want to keep the
+ // NlGlobalReplicatorCritSect locked across a session setup. Since
+ // session setup might do a discovery, we'll end up deadlocking when that
+ // discovery then tries to start the replicator thread.
+ //
+
+ if ( NlGlobalRole == RoleBackup) {
+
+ EnterCriticalSection( &NlGlobalReplicatorCritSect );
+
+ if ( IsReplicatorRunning() ) {
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ return STATUS_SUCCESS;
+ }
+
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+ }
+
+
+
+ //
+ // Become a writer of the ClientSession.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NlChangePassword: Can't become writer of client session.\n" ));
+ return STATUS_NO_LOGON_SERVERS;
+ }
+
+
+ //
+ // Determine the time the password was last changed
+ //
+
+ Status = NlOpenSecret(
+ ClientSession,
+ SECRET_QUERY_VALUE | SECRET_SET_VALUE,
+ &SecretHandle );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot LsarOpenSecret %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ goto Cleanup;
+ }
+
+ Status = LsarQuerySecret(
+ SecretHandle,
+ &CrCurrentPassword,
+ &CurrentPasswordTime,
+ &CrPreviousPassword,
+ NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot LsarQuerySecret %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ goto Cleanup;
+ }
+
+
+ //
+ // If the (old or new) password is still the default password
+ // (lower case computer name),
+ // or the password is null (a convenient default for domain trust),
+ // Flag that fact.
+ //
+
+ if ( CrCurrentPassword == NULL ) {
+ CrClearTextPasswordString.Buffer = NULL;
+ CrClearTextPasswordString.Length = 0;
+ CrClearTextPasswordString.MaximumLength = 0;
+ } else {
+ CrClearTextPasswordString = *CrCurrentPassword;
+ }
+
+ ClearTextPasswordString.Buffer = (LPWSTR)CrClearTextPasswordString.Buffer;
+ ClearTextPasswordString.Length = (USHORT)CrClearTextPasswordString.Length;
+ ClearTextPasswordString.MaximumLength = (USHORT)CrClearTextPasswordString.MaximumLength;
+
+ if ( ClearTextPasswordString.Length == 0 ||
+ RtlEqualComputerName( &NlGlobalUnicodeComputerNameString,
+ &ClearTextPasswordString ) ) {
+ DefaultPasswordBeingChanged = TRUE;
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: New LsaSecret is default value.\n",
+ &ClientSession->CsDomainName ));
+
+ } else if ( CrPreviousPassword == NULL || CrPreviousPassword->Length == 0 ) {
+
+ DefaultPasswordBeingChanged = TRUE;
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Old LsaSecret is NULL.\n",
+ &ClientSession->CsDomainName ));
+ } else {
+ UNICODE_STRING PreviousClearTextPasswordString;
+
+ PreviousClearTextPasswordString.Buffer = (LPWSTR)CrPreviousPassword->Buffer;
+ PreviousClearTextPasswordString.Length = (USHORT)CrPreviousPassword->Length;
+ PreviousClearTextPasswordString.MaximumLength = (USHORT)CrPreviousPassword->MaximumLength;
+
+ if ( RtlEqualComputerName( &NlGlobalUnicodeComputerNameString,
+ &PreviousClearTextPasswordString ) ) {
+ DefaultPasswordBeingChanged = TRUE;
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Old LsaSecret is default value.\n",
+ &ClientSession->CsDomainName ));
+ }
+ }
+
+
+ //
+ // If the password has not yet expired,
+ // and the password is not the default,
+ // just return.
+ //
+
+ LOCK_TRUST_LIST();
+ if ( (ClientSession->CsFlags & CS_UPDATE_PASSWORD) == 0 &&
+ !NlTimeHasElapsed( CurrentPasswordTime, MAX_SSI_PWAGE ) &&
+ !DefaultPasswordBeingChanged ) {
+
+ UNLOCK_TRUST_LIST();
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // If the session isn't authenticated,
+ // do so now.
+ //
+ // We're careful to not force this authentication unless the password
+ // needs to be changed.
+ //
+ if ( ClientSession->CsState != CS_AUTHENTICATED ) {
+
+ //
+ // If we've tried to authenticate recently,
+ // don't bother trying again.
+ //
+
+ if ( !NlTimeToReauthenticate( ClientSession ) ) {
+ Status = ClientSession->CsConnectionStatus;
+ goto Cleanup;
+
+ }
+
+ //
+ // Try to set up the session.
+ //
+
+ Status = NlSessionSetup( ClientSession );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+ }
+
+
+ //
+ // Once we change the password in LsaSecret storage,
+ // all future attempts to change the password should use the value
+ // from LsaSecret storage. The secure channel is using the old
+ // value of the password.
+ //
+
+ LOCK_TRUST_LIST();
+ if (ClientSession->CsFlags & CS_UPDATE_PASSWORD) {
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Password already updated in secret\n",
+ &ClientSession->CsDomainName ));
+
+ //
+ // Handle the case where LsaSecret storage has not yet been updated.
+ //
+
+ } else {
+ NETLOGON_CREDENTIAL tmp;
+ PUSHORT p;
+ PUSHORT op;
+ USHORT i;
+
+
+ //
+ // Build a new clear text password using:
+ // 1) the current credentials (Some function of the old password)
+ // 2) the time of day.
+ // 3) a random number.
+ //
+
+ tmp = ClientSession->CsAuthenticationSeed;
+ RtlZeroMemory( ClearTextPassword, sizeof(ClearTextPassword));
+ p = (PUSHORT) &tmp;
+ op = (PUSHORT) ClearTextPassword;
+
+ for (i = 0; i < sizeof(ClearTextPassword)/sizeof(*op); i++) {
+ LARGE_INTEGER TimeNow;
+ srand(*p);
+ NtQuerySystemTime( &TimeNow );
+ *op = rand() + (USHORT)TimeNow.LowPart;
+ // Srvmgr later uses this password as a zero terminated unicode string
+ if ( *op == 0 ) {
+ *op=1;
+ }
+ p++;
+ op++;
+ }
+
+ ClearTextPasswordString.Buffer =
+ (LPWSTR)(CrClearTextPasswordString.Buffer =
+ (PUCHAR)ClearTextPassword);
+
+ ClearTextPasswordString.Length =
+ (USHORT)(CrClearTextPasswordString.Length =
+ sizeof(ClearTextPassword));
+
+ ClearTextPasswordString.MaximumLength =
+ (USHORT)(CrClearTextPasswordString.MaximumLength =
+ CrClearTextPasswordString.Length);
+
+ //
+ // Save this new cleartext password in LsaSecret storage.
+ //
+ // Set the OldValue to the perviously obtained CurrentValue.
+ //
+
+ Status = LsarSetSecret(
+ SecretHandle,
+ &CrClearTextPasswordString,
+ CrCurrentPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot LsarSetSecret %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ UNLOCK_TRUST_LIST();
+ goto Cleanup;
+ }
+
+ //
+ // Flag that we've updated the password in LsaSecret storage.
+ //
+
+ LsaSecretChanged = TRUE;
+ ClientSession->CsFlags |= CS_UPDATE_PASSWORD;
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Flag password changed in LsaSecret\n",
+ &ClientSession->CsDomainName ));
+
+ }
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // Perform the initial encryption.
+ //
+
+ Status = RtlCalculateNtOwfPassword( &ClearTextPasswordString, &OwfPassword);
+
+ if ( !NT_SUCCESS( Status )) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot RtlCalculateNtOwfPassword %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ goto Cleanup;
+ }
+
+ //
+ // Encrypt the password again with the session key.
+ // The PDC will decrypt it on the other side.
+ //
+
+ Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ &OwfPassword,
+ (PNT_OWF_PASSWORD) &ClientSession->CsSessionKey,
+ &SessKeyEncrPassword) ;
+
+ if ( !NT_SUCCESS( Status )) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: "
+ "Cannot RtlEncryptNtOwfPwdWithNtOwfPwd %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ goto Cleanup;
+ }
+
+
+ //
+ // Build the Authenticator for this request to the PDC.
+ //
+
+ NlBuildAuthenticator(
+ &ClientSession->CsAuthenticationSeed,
+ &ClientSession->CsSessionKey,
+ &OurAuthenticator);
+
+
+ //
+ // Change the password on the machine our connection is to.
+ //
+
+ Status = NlStartApiClientSession( ClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = I_NetServerPasswordSet( ClientSession->CsUncServerName,
+ ClientSession->CsAccountName,
+ ClientSession->CsSecureChannelType,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ &SessKeyEncrPassword);
+ }
+
+ // NOTE: This call may drop the secure channel behind our back
+ (VOID) NlFinishApiClientSession( ClientSession, TRUE );
+
+
+ //
+ // Now verify primary's authenticator and update our seed
+ //
+
+ if ( Status != STATUS_ACCESS_DENIED ) {
+ PasswordChangedOnServer = TRUE;
+
+ if (!NlUpdateSeed( &ClientSession->CsAuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &ClientSession->CsSessionKey) ) {
+ if ( NT_SUCCESS(Status) ) {
+ Status = STATUS_ACCESS_DENIED;
+ }
+ }
+ }
+
+ //
+ // If the server refused the change,
+ // put the lsa secret back the way it was.
+ // pretend the change was successful.
+ //
+
+ if ( Status == STATUS_WRONG_PASSWORD ) {
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: PDC refused to change password\n",
+ &ClientSession->CsDomainName ));
+ //
+ // If we changed the LSA secret,
+ // put it back.
+ //
+
+ LOCK_TRUST_LIST();
+ if ( LsaSecretChanged ) {
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: undoing LSA secret change.\n",
+ &ClientSession->CsDomainName ));
+
+ Status = LsarSetSecret(
+ SecretHandle,
+ CrCurrentPassword,
+ CrPreviousPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot undo LsarSetSecret %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ UNLOCK_TRUST_LIST();
+ goto Cleanup;
+ }
+
+ //
+ // Undo what we've done above.
+ //
+ ClientSession->CsFlags &= ~CS_UPDATE_PASSWORD;
+ }
+
+ //
+ // Prevent us from trying too frequently.
+ //
+
+ ClientSession->CsFlags |= CS_PASSWORD_REFUSED;
+ UNLOCK_TRUST_LIST();
+
+ //
+ // Avoid special cleanup below.
+ //
+ PasswordChangedOnServer = FALSE;
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Common exit
+ //
+
+Cleanup:
+
+ if ( PasswordChangedOnServer ) {
+
+ //
+ // On success,
+ // Indicate that the password has now been updated on the
+ // PDC so the old password is no longer in use.
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ LOCK_TRUST_LIST();
+ ClientSession->CsFlags &= ~CS_UPDATE_PASSWORD;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Flag password updated on PDC\n",
+ &ClientSession->CsDomainName ));
+
+ //
+ // If the default password was changed,
+ // avoid leaving the default password around as the old
+ // password. Otherwise, a bogus DC could convince us to use
+ // the bogus DC via the default password.
+ //
+
+ if ( DefaultPasswordBeingChanged ) {
+ NlPrint((NL_SESSION_SETUP,
+ "NlChangePassword: %wZ: Setting LsaSecret old password to same as new password\n",
+ &ClientSession->CsDomainName ));
+
+ Status = LsarSetSecret(
+ SecretHandle,
+ &CrClearTextPasswordString,
+ &CrClearTextPasswordString );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlChangePassword: %wZ: Cannot LsarSetSecret to set old password %lX\n",
+ &ClientSession->CsDomainName,
+ Status));
+ UNLOCK_TRUST_LIST();
+ goto Cleanup;
+ }
+
+ }
+ UNLOCK_TRUST_LIST();
+
+
+
+ //
+ // Notify the Admin that he'll have to manually set this server's
+ // password on both this server and the PDC.
+ //
+
+ } else {
+
+ LPWSTR MsgStrings[2];
+
+ //
+ // Drop the secure channel
+ //
+
+ NlSetStatusClientSession( ClientSession, Status );
+
+ //
+ // write event log
+ //
+
+ MsgStrings[0] = ClientSession->CsAccountName;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog (
+ NELOG_NetlogonPasswdSetFailed,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) & Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+ }
+
+
+ }
+
+
+ //
+ // Clean up locally used resources.
+ //
+
+ if ( SecretHandle != NULL ) {
+ (VOID) LsarClose( &SecretHandle );
+ }
+
+ if ( CrCurrentPassword != NULL ) {
+ (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrCurrentPassword );
+ }
+
+ if ( CrPreviousPassword != NULL ) {
+ (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrPreviousPassword );
+ }
+
+ NlResetWriterClientSession( ClientSession );
+
+ return Status;
+}
+
+
+NTSTATUS
+NlCheckMachineAccount(
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType
+ )
+/*++
+
+Routine Description:
+
+ Check the machine account:
+ Ensure the SecureChannelType is valid,
+ Verify that the account exists,
+ Ensure the group implied by the account type is valid,
+ Ensure the user account is a member of that group,
+ Ensure the user account is the right account type.
+
+Arguments:
+
+ AccountName - The name of the account.
+
+ SecureChannelType - The type of the account.
+
+Return Value:
+
+ STATUS_SUCCESS - the requestor is a member of group
+ Other NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ SAMPR_HANDLE UserHandle = NULL;
+ PSAMPR_USER_INFO_BUFFER UserControl = NULL;
+ DWORD DesiredAccountControl;
+ LPWSTR AccountPostfix;
+
+ LPWSTR GroupName;
+ SAMPR_ULONG_ARRAY RelativeIdArray;
+ SAMPR_ULONG_ARRAY UseArray;
+
+ PSAMPR_GET_GROUPS_BUFFER Groups = NULL;
+
+ RelativeIdArray.Count = 1;
+ RelativeIdArray.Element = NULL;
+ UseArray.Count = 1;
+ UseArray.Element = NULL;
+
+ //
+ // Validate the secure channel type.
+ //
+
+ switch (SecureChannelType) {
+ case WorkstationSecureChannel:
+ GroupName = NULL;
+ DesiredAccountControl = USER_WORKSTATION_TRUST_ACCOUNT;
+ AccountPostfix = SSI_ACCOUNT_NAME_POSTFIX;
+ break;
+
+ case ServerSecureChannel:
+ GroupName = NULL;
+ DesiredAccountControl = USER_SERVER_TRUST_ACCOUNT;
+ AccountPostfix = SSI_ACCOUNT_NAME_POSTFIX;
+ break;
+
+ case UasServerSecureChannel:
+ GroupName = SSI_SERVER_GROUP_W;
+ DesiredAccountControl = USER_NORMAL_ACCOUNT;
+ AccountPostfix = NULL;
+ break;
+
+ case TrustedDomainSecureChannel:
+ GroupName = NULL;
+ DesiredAccountControl = USER_INTERDOMAIN_TRUST_ACCOUNT;
+ AccountPostfix = SSI_ACCOUNT_NAME_POSTFIX;
+ break;
+
+ default:
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Ensure the account name has the correct postfix.
+ //
+
+ if ( AccountPostfix != NULL ) {
+ DWORD Length = wcslen( AccountName );
+
+ if ( Length <= SSI_ACCOUNT_NAME_POSTFIX_LENGTH ) {
+ return STATUS_NO_SUCH_USER;
+ }
+
+ if ( _wcsicmp(&AccountName[Length - SSI_ACCOUNT_NAME_POSTFIX_LENGTH],
+ SSI_ACCOUNT_NAME_POSTFIX) != 0 ) {
+ return STATUS_NO_SUCH_USER;
+ }
+ }
+
+
+
+ //
+ // Open the account.
+ //
+
+ Status = NlSamOpenNamedUser( AccountName, &UserHandle, NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+
+ //
+ // Get the account control information and
+ // Ensure the Account type matches the account type on the account.
+ //
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserControlInformation,
+ &UserControl );
+
+ if (!NT_SUCCESS(Status)) {
+ UserControl = NULL;
+ Status = STATUS_NO_SUCH_USER;
+ goto Cleanup;
+ }
+
+ if ( (UserControl->Control.UserAccountControl & USER_ACCOUNT_TYPE_MASK)
+ != DesiredAccountControl ) {
+ Status = STATUS_NO_SUCH_USER;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Ensure the account is a member of the correct group.
+ //
+
+ if ( GroupName != NULL ) {
+
+ RPC_UNICODE_STRING GroupNameString;
+ ULONG i;
+
+ //
+ // Convert the group name to a RelativeId.
+ //
+
+ RtlInitUnicodeString( (PUNICODE_STRING)&GroupNameString, GroupName );
+
+ Status = SamrLookupNamesInDomain(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 1,
+ &GroupNameString,
+ &RelativeIdArray,
+ &UseArray );
+
+
+ if ( !NT_SUCCESS(Status) ) {
+ RelativeIdArray.Element = NULL;
+ UseArray.Element = NULL;
+ if ( Status == STATUS_NONE_MAPPED ) {
+ Status = STATUS_NO_SUCH_GROUP;
+ }
+ goto Cleanup;
+ }
+
+ if ( UseArray.Element[0] != SidTypeGroup ) {
+ Status = STATUS_NO_SUCH_GROUP;
+ goto Cleanup;
+ }
+
+ //
+ // Open the account and determine the groups it belongs to
+ //
+
+ Status = SamrGetGroupsForUser( UserHandle,
+ &Groups );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Groups = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Walk thru the buffer looking for group SERVERS
+ //
+
+ for ( i=0; i<Groups->MembershipCount; i++ ) {
+ if ( Groups->Groups[i].RelativeId == RelativeIdArray.Element[0] ) {
+ break; // found
+ }
+ }
+
+ //
+ // if this machine not a member of SERVERS quit
+ //
+
+ if (i == Groups->MembershipCount) {
+ Status = STATUS_MEMBER_NOT_IN_GROUP;
+ goto Cleanup;
+ }
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup
+ //
+Cleanup:
+
+ //
+ // Free locally used resources
+ //
+ if ( UserControl != NULL ) {
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserControl, UserControlInformation );
+ }
+
+ if ( Groups != NULL ) {
+ SamIFree_SAMPR_GET_GROUPS_BUFFER( Groups );
+ }
+
+ SamIFree_SAMPR_ULONG_ARRAY( &RelativeIdArray );
+ SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
+
+ if ( UserHandle != NULL ) {
+ SamrCloseHandle( &UserHandle );
+ }
+
+ return Status;
+}
+
+
+
+NTSTATUS
+NlGetUserPriv(
+ IN ULONG GroupCount,
+ IN PGROUP_MEMBERSHIP Groups,
+ IN ULONG UserRelativeId,
+ OUT LPDWORD Priv,
+ OUT LPDWORD AuthFlags
+ )
+
+/*++
+
+Routine Description:
+
+ Determines the Priv and AuthFlags for the specified user.
+
+Arguments:
+
+ GroupCount - Number of groups this user is a member of
+
+ Groups - Array of groups this user is a member of.
+
+ UserRelativeId - Relative ID of the user to query.
+
+ Priv - Returns the Lanman 2.0 Privilege level for the specified user.
+
+ AuthFlags - Returns the Lanman 2.0 Authflags for the specified user.
+
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+
+{
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+
+ ULONG GroupIndex;
+ PSID *UserSids = NULL;
+ ULONG UserSidCount = 0;
+ SAMPR_PSID_ARRAY SamSidArray;
+ SAMPR_ULONG_ARRAY Aliases;
+
+ //
+ // Initialization
+ //
+
+ Aliases.Element = NULL;
+
+ //
+ // Allocate a buffer to point to the SIDs we're interested in
+ // alias membership for.
+ //
+
+ UserSids = (PSID *)
+ NetpMemoryAllocate( (GroupCount+1) * sizeof(PSID) );
+
+ if ( UserSids == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // Add the User's Sid to the Array of Sids.
+ //
+
+ NetStatus = NetpDomainIdToSid( NlGlobalDBInfoArray[SAM_DB].DBId,
+ UserRelativeId,
+ &UserSids[0] );
+
+ if ( NetStatus != NERR_Success ) {
+ Status = NetpApiStatusToNtStatus( NetStatus );
+ goto Cleanup;
+ }
+
+ UserSidCount ++;
+
+
+
+ //
+ // Add each group the user is a member of to the array of Sids.
+ //
+
+ for ( GroupIndex = 0; GroupIndex < GroupCount; GroupIndex ++ ){
+
+ NetStatus = NetpDomainIdToSid( NlGlobalDBInfoArray[SAM_DB].DBId,
+ Groups[GroupIndex].RelativeId,
+ &UserSids[GroupIndex+1] );
+
+ if ( NetStatus != NERR_Success ) {
+ Status = NetpApiStatusToNtStatus( NetStatus );
+ goto Cleanup;
+ }
+
+ UserSidCount ++;
+ }
+
+
+ //
+ // Find out which aliases in the builtin domain this user is a member of.
+ //
+
+ SamSidArray.Count = UserSidCount;
+ SamSidArray.Sids = (PSAMPR_SID_INFORMATION) UserSids;
+ Status = SamrGetAliasMembership( NlGlobalDBInfoArray[BUILTIN_DB].DBHandle,
+ &SamSidArray,
+ &Aliases );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Aliases.Element = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlGetUserPriv: SamGetAliasMembership returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // Convert the alias membership to priv and auth flags
+ //
+
+ NetpAliasMemberToPriv(
+ Aliases.Count,
+ Aliases.Element,
+ Priv,
+ AuthFlags );
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Free Locally used resources.
+ //
+Cleanup:
+ if ( Aliases.Element != NULL ) {
+ SamIFree_SAMPR_ULONG_ARRAY ( &Aliases );
+ }
+
+ if ( UserSids != NULL ) {
+
+ for ( GroupIndex = 0; GroupIndex < UserSidCount; GroupIndex ++ ) {
+ NetpMemoryFree( UserSids[GroupIndex] );
+ }
+
+ NetpMemoryFree( UserSids );
+ }
+
+ return Status;
+}
+/*lint +e740 */ /* don't complain about unusual cast */
diff --git a/private/net/svcdlls/logonsrv/server/mailslot.c b/private/net/svcdlls/logonsrv/server/mailslot.c
new file mode 100644
index 000000000..565f2c370
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/mailslot.c
@@ -0,0 +1,1238 @@
+/*--
+
+
+Copyright (c) 1987-1993 Microsoft Corporation
+
+Module Name:
+
+ mailslot.c
+
+Abstract:
+
+ Routines for doing I/O on the netlogon service's mailslots.
+
+Author:
+
+ 03-Nov-1993 (cliffv)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this module
+#include <ntddbrow.h> // LARGE_INTEGER definition
+#include <align.h> // ALIGN_WCHAR
+#include <lmerr.h> // System Error Log definitions
+#include <lmsvc.h> // SERVICE_UIC codes are defined here
+#include <ntddbrow.h> // Interface to browser driver
+#include <stdlib.h> // max()
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Structure describing one of the primary mailslots the netlogon service
+// will read messages from.
+//
+// This structure is used only by netlogon's main thread and therefore needs
+// no synchronization.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// Define maximum buffer size returned from the browser.
+//
+// Header returned by browser + actual mailslot message size + name of
+// mailslot + name of transport.
+//
+
+#define MAILSLOT_MESSAGE_SIZE \
+ (sizeof(NETLOGON_MAILSLOT)+ \
+ NETLOGON_MAX_MS_SIZE + \
+ (NETLOGON_LM_MAILSLOT_LEN+1) * sizeof(WCHAR) + \
+ (MAXIMUM_FILENAME_LENGTH+1) * sizeof(WCHAR))
+
+typedef struct _NETLOGON_MAILSLOT_DESC {
+
+ HANDLE BrowserHandle; // Handle to the browser device driver
+
+ HANDLE BrowserReadEvent;// Handle to wait on for overlapped I/O
+
+ OVERLAPPED Overlapped; // Governs overlapped I/O
+
+ BOOL ReadPending; // True if a read operation is pending
+
+ BOOL NameAdded; // True if Domain<1B> name has been added
+
+ BOOL AddNameEventLogged;// True if Domain<1B> name add failed at least once
+
+ LPBYTE CurrentMessage; // Pointer to Message1 or Message2 below
+
+ LPBYTE PreviousMessage; // Previous value of CurrentMessage
+
+ //
+ // Buffer containing message from browser
+ //
+ // The buffers are alternated allowing us to compare if an incoming
+ // message is identical to the previous message.
+ //
+ // Leave room so the actual used portion of each buffer is properly aligned.
+ // The NETLOGON_MAILSLOT struct begins with a LARGE_INTEGER which must be
+ // aligned.
+
+ BYTE Message1[ MAILSLOT_MESSAGE_SIZE + ALIGN_WORST ];
+ BYTE Message2[ MAILSLOT_MESSAGE_SIZE + ALIGN_WORST ];
+
+} NETLOGON_MAILSLOT_DESC, *PNETLOGON_MAILSLOT_DESC;
+
+PNETLOGON_MAILSLOT_DESC NlGlobalMailslotDesc;
+
+
+
+
+HANDLE
+NlBrowserCreateEvent(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Creates an event to be used in a DeviceIoControl to the browser.
+
+ ?? Consider caching one or two events to reduce the number of create
+ events
+
+Arguments:
+
+ None
+
+Return Value:
+
+ Handle to an event or NULL if the event couldn't be allocated.
+
+--*/
+{
+ HANDLE EventHandle;
+ //
+ // Create a completion event
+ //
+
+ EventHandle = CreateEvent(
+ NULL, // No security ettibutes
+ TRUE, // Manual reset
+ FALSE, // Initially not signaled
+ NULL); // No Name
+
+ if ( EventHandle == NULL ) {
+ NlPrint((NL_CRITICAL, "Cannot create Browser read event %ld\n", GetLastError() ));
+ }
+
+ return EventHandle;
+}
+
+
+VOID
+NlBrowserCloseEvent(
+ IN HANDLE EventHandle
+ )
+/*++
+
+Routine Description:
+
+ Closes an event used in a DeviceIoControl to the browser.
+
+Arguments:
+
+ EventHandle - Handle of the event to close
+
+Return Value:
+
+ None.
+
+--*/
+{
+ (VOID) CloseHandle( EventHandle );
+}
+
+
+VOID
+NlBrowserClose(
+ VOID
+ );
+
+
+NTSTATUS
+NlBrowserDeviceIoControl(
+ IN DWORD FunctionCode,
+ IN PLMDR_REQUEST_PACKET RequestPacket,
+ IN DWORD RequestPacketSize,
+ IN LPBYTE Buffer,
+ IN DWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Send a DeviceIoControl syncrhonously to the browser.
+
+Arguments:
+
+ FunctionCode - DeviceIoControl function code
+
+ RequestPacket - The request packet to send.
+
+ RequestPacketSize - Size (in bytes) of the request packet.
+
+ Buffer - Additional buffer to pass to the browser
+
+ BufferSize - Size (in bytes) of Buffer
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+ DWORD WinStatus;
+ OVERLAPPED Overlapped;
+ DWORD BytesReturned;
+
+ //
+ // Initialization
+ //
+
+ if ( NlGlobalMailslotDesc == NULL ||
+ NlGlobalMailslotDesc->BrowserHandle == NULL ) {
+ return ERROR_NOT_SUPPORTED;
+ }
+
+ RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION;
+
+ //
+ // Get a completion event
+ //
+
+ Overlapped.hEvent = NlBrowserCreateEvent();
+
+ if ( Overlapped.hEvent == NULL ) {
+ return GetLastError();
+ }
+
+ //
+ // Send the request to the Datagram Receiver device driver.
+ //
+
+ if ( !DeviceIoControl(
+ NlGlobalMailslotDesc->BrowserHandle,
+ FunctionCode,
+ RequestPacket,
+ RequestPacketSize,
+ Buffer,
+ BufferSize,
+ &BytesReturned,
+ &Overlapped )) {
+
+ WinStatus = GetLastError();
+
+ if ( WinStatus == ERROR_IO_PENDING ) {
+ if ( !GetOverlappedResult( NlGlobalMailslotDesc->BrowserHandle,
+ &Overlapped,
+ &BytesReturned,
+ TRUE )) {
+ WinStatus = GetLastError();
+ } else {
+ WinStatus = NO_ERROR;
+ }
+ }
+ } else {
+ WinStatus = NO_ERROR;
+ }
+
+ //
+ // Delete the completion event
+ //
+
+ NlBrowserCloseEvent( Overlapped.hEvent );
+
+
+ if ( WinStatus ) {
+ NlPrint((NL_CRITICAL,"ioctl to Browser returns %ld\n", WinStatus));
+ Status = NetpApiStatusToNtStatus( WinStatus );
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+
+ return Status;
+}
+
+
+
+VOID
+NlBrowserAddName(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Add the Domain<1B> name. The is the name NetGetDcName uses to identify
+ the PDC.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+ BYTE Buffer[sizeof(LMDR_REQUEST_PACKET) +
+ (max(CNLEN, DNLEN) + 1) * sizeof(WCHAR)];
+ PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET) Buffer;
+
+ //
+ // Add the <domain>0x1B name.
+ //
+ // This is the name NetGetDcName uses to identify the PDC.
+ //
+
+ if ( NlGlobalRole == RolePrimary && !NlGlobalMailslotDesc->NameAdded ) {
+
+ RequestPacket->TransportName.Length = 0;
+ RequestPacket->TransportName.Buffer = NULL;
+ RequestPacket->Parameters.AddDelName.Type = PrimaryDomainBrowser;
+ RequestPacket->Parameters.AddDelName.DgReceiverNameLength =
+ NlGlobalUnicodeDomainNameString.Length;
+
+ wcscpy( RequestPacket->Parameters.AddDelName.Name,
+ NlGlobalUnicodeDomainNameString.Buffer );
+
+ Status = NlBrowserDeviceIoControl(
+ IOCTL_LMDR_ADD_NAME,
+ RequestPacket,
+ sizeof(LMDR_REQUEST_PACKET) +
+ NlGlobalUnicodeDomainNameString.Length +
+ sizeof(WCHAR),
+ NULL,
+ 0 );
+
+ if (NT_SUCCESS(Status)) {
+ NlGlobalMailslotDesc->NameAdded = TRUE;
+ } else {
+
+ if ( !NlGlobalMailslotDesc->AddNameEventLogged ) {
+ LPWSTR MsgStrings[2];
+
+ MsgStrings[0] = NlGlobalUnicodeDomainName;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonAddNameFailure,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+
+ NlGlobalMailslotDesc->AddNameEventLogged = TRUE;
+ }
+ NlPrint((NL_CRITICAL,"Can't add the 0x1B name: 0x%lx\n", Status));
+ }
+ }
+
+ return;
+}
+
+
+
+BOOL
+NlBrowserOpen(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine opens the NT LAN Man Datagram Receiver driver and prepares
+ for reading mailslot messages from it.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE -- iff initialization is successful.
+
+ if false, NlExit will already have been called.
+
+--*/
+{
+ NTSTATUS Status;
+ BOOL ReturnValue;
+
+ UNICODE_STRING DeviceName;
+
+ IO_STATUS_BLOCK IoStatusBlock;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+ BYTE Buffer[sizeof(LMDR_REQUEST_PACKET) +
+ (max(CNLEN, DNLEN) + 1) * sizeof(WCHAR)];
+ PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET) Buffer;
+
+
+ //
+ // Allocate the mailslot descriptor for this mailslot
+ //
+
+ NlGlobalMailslotDesc = NetpMemoryAllocate( sizeof(NETLOGON_MAILSLOT_DESC) );
+
+ if ( NlGlobalMailslotDesc == NULL ) {
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+ RtlZeroMemory( NlGlobalMailslotDesc, sizeof(NETLOGON_MAILSLOT_DESC) );
+
+ NlGlobalMailslotDesc->CurrentMessage =
+ ROUND_UP_POINTER( NlGlobalMailslotDesc->Message1, ALIGN_WORST);
+
+
+ //
+ // Open the browser device.
+ //
+ RtlInitUnicodeString(&DeviceName, DD_BROWSER_DEVICE_NAME_U);
+
+ InitializeObjectAttributes(
+ &ObjectAttributes,
+ &DeviceName,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL
+ );
+
+ Status = NtOpenFile(
+ &NlGlobalMailslotDesc->BrowserHandle,
+ SYNCHRONIZE,
+ &ObjectAttributes,
+ &IoStatusBlock,
+ 0,
+ 0
+ );
+
+ if (NT_SUCCESS(Status)) {
+ Status = IoStatusBlock.Status;
+ }
+
+ if (! NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,"NtOpenFile browser driver failed: 0x%lx\n",
+ Status));
+ ReturnValue = FALSE;
+ goto Cleanup;
+ }
+
+ //
+ // Create a completion event
+ //
+
+ NlGlobalMailslotHandle =
+ NlGlobalMailslotDesc->BrowserReadEvent = NlBrowserCreateEvent();
+
+ if ( NlGlobalMailslotDesc->BrowserReadEvent == NULL ) {
+ Status = NetpApiStatusToNtStatus( GetLastError() );
+ ReturnValue = FALSE;
+ goto Cleanup;
+ }
+
+
+ //
+ // Set the maximum number of messages to be queued
+ //
+
+ RequestPacket->TransportName.Length = 0;
+ RequestPacket->TransportName.Buffer = NULL;
+ RequestPacket->Parameters.NetlogonMailslotEnable.MaxMessageCount =
+ NlGlobalMaximumMailslotMessagesParameter;
+
+ Status = NlBrowserDeviceIoControl(
+ IOCTL_LMDR_NETLOGON_MAILSLOT_ENABLE,
+ RequestPacket,
+ sizeof(LMDR_REQUEST_PACKET),
+ NULL,
+ 0 );
+
+ if (! NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,"Can't set browser max message count: 0x%lx\n",
+ Status));
+ ReturnValue = FALSE;
+ goto Cleanup;
+ }
+
+
+ //
+ // Add the Domain<1B> name.
+ //
+
+ NlBrowserAddName();
+
+ ReturnValue = TRUE;
+
+Cleanup:
+ if ( !ReturnValue ) {
+ NlExit( NELOG_NetlogonBrowserDriver, Status, LogErrorAndNtStatus, NULL);
+ NlBrowserClose();
+ }
+
+ return ReturnValue;
+}
+
+
+VOID
+NlBrowserClose(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine cleans up after a NlBrowserInitialize()
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ IO_STATUS_BLOCK IoSb;
+ NTSTATUS Status;
+
+ BYTE Buffer[sizeof(LMDR_REQUEST_PACKET) +
+ (max(CNLEN, DNLEN) + 1) * sizeof(WCHAR)];
+ PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET) Buffer;
+
+ if ( NlGlobalMailslotDesc == NULL) {
+ return;
+ }
+
+
+ //
+ // If we've opened the browser, clean up.
+ //
+
+ if ( NlGlobalMailslotDesc->BrowserHandle != NULL ) {
+
+ //
+ // Tell the browser to stop queueing messages
+ //
+
+ RequestPacket->TransportName.Length = 0;
+ RequestPacket->TransportName.Buffer = NULL;
+ RequestPacket->Parameters.NetlogonMailslotEnable.MaxMessageCount = 0;
+
+ Status = NlBrowserDeviceIoControl(
+ IOCTL_LMDR_NETLOGON_MAILSLOT_ENABLE,
+ RequestPacket,
+ sizeof(LMDR_REQUEST_PACKET),
+ NULL,
+ 0 );
+
+ if (! NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,"Can't reset browser max message count: 0x%lx\n",
+ Status));
+ }
+
+
+ //
+ // Delete the <domain>0x1B name.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+
+ RequestPacket->TransportName.Length = 0;
+ RequestPacket->TransportName.Buffer = NULL;
+ RequestPacket->Parameters.AddDelName.Type = PrimaryDomainBrowser;
+ RequestPacket->Parameters.AddDelName.DgReceiverNameLength =
+ NlGlobalUnicodeDomainNameString.Length;
+
+ wcscpy( RequestPacket->Parameters.AddDelName.Name,
+ NlGlobalUnicodeDomainNameString.Buffer );
+
+ Status = NlBrowserDeviceIoControl(
+ IOCTL_LMDR_DELETE_NAME,
+ RequestPacket,
+ sizeof(LMDR_REQUEST_PACKET) +
+ NlGlobalUnicodeDomainNameString.Length +
+ sizeof(WCHAR),
+ NULL,
+ 0 );
+
+ if (! NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,"Can't remove the 0x1B name: 0x%lx\n",
+ Status));
+ }
+ }
+
+ //
+ // Cancel the I/O operations outstanding on the browser.
+ //
+
+ NtCancelIoFile(NlGlobalMailslotDesc->BrowserHandle, &IoSb);
+
+ //
+ // Close the handle to the browser
+ //
+
+ NtClose(NlGlobalMailslotDesc->BrowserHandle);
+ NlGlobalMailslotDesc->BrowserHandle = NULL;
+ }
+
+ //
+ // Close the global browser read event
+ //
+
+ if ( NlGlobalMailslotDesc->BrowserReadEvent != NULL ) {
+ NlBrowserCloseEvent(NlGlobalMailslotDesc->BrowserReadEvent);
+ }
+ NlGlobalMailslotHandle = NULL;
+
+ //
+ // Free the descriptor describing the browser
+ //
+
+ NetpMemoryFree( NlGlobalMailslotDesc );
+ NlGlobalMailslotDesc = NULL;
+
+}
+
+
+NTSTATUS
+NlBrowserSendDatagram(
+ IN LPSTR OemServerName,
+ IN LPWSTR TransportName,
+ IN LPSTR OemMailslotName,
+ IN PVOID Buffer,
+ IN ULONG BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Send the specified mailslot message to the specified mailslot on the
+ specified server on the specified transport..
+
+Arguments:
+
+ OemServerName -- Name of the server to send to.
+
+ TransportName -- Name of the transport to send on.
+ Use NULL to send on all transports.
+
+ OemMailslotName -- Name of the mailslot to send to.
+
+ Buffer -- Specifies a pointer to the mailslot message to send.
+
+ BufferSize -- Size in bytes of the mailslot message
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ PLMDR_REQUEST_PACKET RequestPacket = NULL;
+
+ UNICODE_STRING ServerNameString;
+ OEM_STRING TempOemString;
+ DWORD OemMailslotNameSize;
+ DWORD TransportNameSize;
+
+ NTSTATUS Status;
+ LPBYTE Where;
+
+ //
+ // If the transport isn't specified,
+ // send on all transports.
+ //
+
+ if ( TransportName == NULL ) {
+ CHAR FullMailslotName[(UNCLEN+2) + NETLOGON_LM_MAILSLOT_LEN];
+
+ //
+ // Build the mailslot name.
+ //
+
+ (VOID) lstrcpyA(FullMailslotName, "\\\\" );
+ (VOID) lstrcatA(FullMailslotName, OemServerName );
+ (VOID) lstrcatA(FullMailslotName, OemMailslotName );
+
+ Status = NlpWriteMailslotA( FullMailslotName,
+ Buffer,
+ BufferSize );
+
+ return Status;
+ }
+
+
+ //
+ // Ensure we've initialized.
+ //
+
+ ServerNameString.Buffer = NULL;
+
+
+ //
+ // Convert the server name to uppercase.
+ //
+
+ RtlInitString( &TempOemString, OemServerName );
+ Status = RtlOemStringToUnicodeString( &ServerNameString,
+ &TempOemString,
+ TRUE ); // Allocate buffer
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlBrowserSendDatagram: Cannot convert server name %08lx\n",
+ Status));
+ goto Cleanup;
+ }
+
+
+ //
+ // Allocate a request packet.
+ //
+
+ OemMailslotNameSize = lstrlenA(OemMailslotName) + 1;
+ TransportNameSize = (wcslen(TransportName) + 1) * sizeof(WCHAR);
+
+ RequestPacket = NetpMemoryAllocate(
+ sizeof(LMDR_REQUEST_PACKET) +
+ TransportNameSize +
+ OemMailslotNameSize +
+ ServerNameString.Length +
+ sizeof(WCHAR)) ; // For alignment
+
+ if (RequestPacket == NULL) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Fill in the Request Packet.
+ //
+
+ RequestPacket->Type = Datagram;
+ RequestPacket->Parameters.SendDatagram.DestinationNameType = ComputerName;
+
+
+ //
+ // Fill in the name of the machine to send the mailslot message to.
+ //
+
+ RequestPacket->Parameters.SendDatagram.NameLength = ServerNameString.Length;
+
+ Where = (LPBYTE) RequestPacket->Parameters.SendDatagram.Name;
+ RtlMoveMemory( Where, ServerNameString.Buffer, ServerNameString.Length );
+ Where += ServerNameString.Length;
+
+
+ //
+ // Fill in the name of the mailslot to send to.
+ //
+
+ RequestPacket->Parameters.SendDatagram.MailslotNameLength =
+ OemMailslotNameSize;
+ strcpy( Where, OemMailslotName);
+ Where += OemMailslotNameSize;
+ Where = ROUND_UP_POINTER( Where, ALIGN_WCHAR );
+
+
+ //
+ // Fill in the TransportName
+ //
+
+ wcscpy( (LPWSTR) Where, TransportName);
+ RtlInitUnicodeString( &RequestPacket->TransportName, (LPWSTR) Where );
+ Where += TransportNameSize;
+
+
+ //
+ // Send the request to the browser.
+ //
+
+ Status = NlBrowserDeviceIoControl(
+ IOCTL_LMDR_WRITE_MAILSLOT,
+ RequestPacket,
+ Where - (LPBYTE)RequestPacket,
+ Buffer,
+ BufferSize );
+
+
+ //
+ // Free locally used resources.
+ //
+Cleanup:
+
+ if ( RequestPacket != NULL ) {
+ NetpMemoryFree( RequestPacket );
+ }
+
+ if ( ServerNameString.Buffer != NULL ) {
+ RtlFreeUnicodeString( &ServerNameString );
+ }
+
+ NlPrint((NL_MAILSLOT_TEXT,
+ "Sent out message to %s%s on transport " FORMAT_LPWSTR ".\n",
+ OemServerName,
+ OemMailslotName,
+ TransportName ));
+
+#if DBG
+ NlpDumpBuffer( NL_MAILSLOT_TEXT, Buffer, BufferSize );
+#endif // DBG
+
+ return Status;
+}
+
+
+
+VOID
+NlMailslotPostRead(
+ IN BOOLEAN IgnoreDuplicatesOfPreviousMessage
+ )
+
+/*++
+
+Routine Description:
+
+ Post a read on the mailslot if one isn't already posted.
+
+Arguments:
+
+ IgnoreDuplicatesOfPreviousMessage - TRUE to indicate that the next
+ message read should be ignored if it is a duplicate of the previous
+ message.
+
+Return Value:
+
+ TRUE -- iff successful.
+
+--*/
+{
+ NET_API_STATUS WinStatus;
+ ULONG LocalBytesRead;
+
+ //
+ // If a read is already pending,
+ // immediately return to caller.
+ //
+
+ if ( NlGlobalMailslotDesc->ReadPending ) {
+ return;
+ }
+
+ //
+ // Decide which buffer to read into.
+ //
+ // Switch back and forth so we always have the current buffer and the
+ // previous buffer.
+ //
+
+ if ( IgnoreDuplicatesOfPreviousMessage ) {
+ NlGlobalMailslotDesc->PreviousMessage = NlGlobalMailslotDesc->CurrentMessage;
+ if ( NlGlobalMailslotDesc->CurrentMessage >= NlGlobalMailslotDesc->Message2 ) {
+ NlGlobalMailslotDesc->CurrentMessage =
+ ROUND_UP_POINTER( NlGlobalMailslotDesc->Message1, ALIGN_WORST);
+ } else {
+ NlGlobalMailslotDesc->CurrentMessage =
+ ROUND_UP_POINTER( NlGlobalMailslotDesc->Message2, ALIGN_WORST);
+ }
+
+ //
+ // If duplicates of the previous message need not be ignored,
+ // indicate so.
+ // Don't bother switching the buffer pointers.
+ //
+
+ } else {
+ NlGlobalMailslotDesc->PreviousMessage = NULL;
+ }
+
+
+ //
+ // Post an overlapped read to the mailslot.
+ //
+
+ RtlZeroMemory( &NlGlobalMailslotDesc->Overlapped,
+ sizeof(NlGlobalMailslotDesc->Overlapped) );
+
+ NlGlobalMailslotDesc->Overlapped.hEvent = NlGlobalMailslotDesc->BrowserReadEvent;
+
+ if ( !DeviceIoControl(
+ NlGlobalMailslotDesc->BrowserHandle,
+ IOCTL_LMDR_NETLOGON_MAILSLOT_READ,
+ NULL,
+ 0,
+ NlGlobalMailslotDesc->CurrentMessage,
+ MAILSLOT_MESSAGE_SIZE,
+ &LocalBytesRead,
+ &NlGlobalMailslotDesc->Overlapped )) {
+
+ WinStatus = GetLastError();
+
+ //
+ // On error, wait a second before returning. This ensures we don't
+ // consume the system in an infinite loop. We don't shutdown netlogon
+ // because the error might be a temporary low memory condition.
+ //
+
+ if( WinStatus != ERROR_IO_PENDING ) {
+ LPWSTR MsgStrings[1];
+
+ NlPrint((NL_CRITICAL,
+ "Error in reading mailslot message from browser"
+ ". WinStatus = %ld\n",
+ WinStatus ));
+
+ MsgStrings[0] = (LPWSTR) WinStatus;
+
+ NlpWriteEventlog( NELOG_NetlogonFailedToReadMailslot,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&WinStatus,
+ sizeof(WinStatus),
+ MsgStrings,
+ 1 | LAST_MESSAGE_IS_NETSTATUS );
+
+ Sleep( 1000 );
+
+ } else {
+ NlGlobalMailslotDesc->ReadPending = TRUE;
+ }
+
+ } else {
+ NlGlobalMailslotDesc->ReadPending = TRUE;
+ }
+
+ return;
+
+}
+
+
+BOOL
+NlMailslotOverlappedResult(
+ OUT LPBYTE *Message,
+ OUT PULONG BytesRead,
+ OUT LPWSTR *Transport,
+ OUT PBOOLEAN IgnoreDuplicatesOfPreviousMessage
+ )
+
+/*++
+
+Routine Description:
+
+ Get the overlapped result of a previous mailslot read.
+
+Arguments:
+
+ Message - Returns a pointer to the buffer containing the message
+
+ BytesRead - Returns the number of bytes read into the buffer
+
+ Transport - Returns a pointer to the name of the transport the message
+ was received on.
+
+ IgnoreDuplicatesOfPreviousMessage - Indicates that duplicates of the
+ previous message are to be ignored.
+
+Return Value:
+
+ TRUE -- iff successful.
+
+--*/
+{
+ NET_API_STATUS WinStatus;
+ ULONG LocalBytesRead;
+ LARGE_INTEGER TimeNow;
+ PNETLOGON_MAILSLOT NetlogonMailslot;
+
+ //
+ // Default to not ignoring duplicate messages.
+ // (Only ignore duplicates if a message has been properly processed.)
+
+ *IgnoreDuplicatesOfPreviousMessage = FALSE;
+
+ //
+ // Always post another read regardless of the success or failure of
+ // GetOverlappedResult.
+ // We don't know the failure mode of GetOverlappedResult, so we don't
+ // know in the failure case if we're discarding a mailslot message.
+ // But we do know that there is no read pending, so make sure we
+ // issue another one.
+ //
+
+ NlGlobalMailslotDesc->ReadPending = FALSE; // no read pending anymore
+
+
+ //
+ // Get the result of the last read
+ //
+
+ if( !GetOverlappedResult( NlGlobalMailslotDesc->BrowserHandle,
+ &NlGlobalMailslotDesc->Overlapped,
+ &LocalBytesRead,
+ TRUE) ) { // wait for the read to complete.
+
+ LPWSTR MsgStrings[1];
+
+ // On error, wait a second before returning. This ensures we don't
+ // consume the system in an infinite loop. We don't shutdown netlogon
+ // because the error might be a temporary low memory condition.
+ //
+
+ WinStatus = GetLastError();
+
+ NlPrint((NL_CRITICAL,
+ "Error in GetOverlappedResult on mailslot read"
+ ". WinStatus = %ld\n",
+ WinStatus ));
+
+ MsgStrings[0] = (LPWSTR) WinStatus;
+
+ NlpWriteEventlog( NELOG_NetlogonFailedToReadMailslot,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&WinStatus,
+ sizeof(WinStatus),
+ MsgStrings,
+ 1 | LAST_MESSAGE_IS_NETSTATUS );
+
+ Sleep( 1000 );
+
+ return FALSE;
+
+ }
+
+ //
+ // On success,
+ // Return the mailslot message to the caller.
+
+
+ NetlogonMailslot = (PNETLOGON_MAILSLOT) NlGlobalMailslotDesc->CurrentMessage;
+
+
+ //
+ // Return pointers into the buffer returned by the browser
+ //
+
+ *Message = &NlGlobalMailslotDesc->CurrentMessage[
+ NetlogonMailslot->MailslotMessageOffset];
+ *BytesRead = NetlogonMailslot->MailslotMessageSize;
+ *Transport = (LPWSTR) &NlGlobalMailslotDesc->CurrentMessage[
+ NetlogonMailslot->TransportNameOffset];
+
+ NlPrint(( NL_MAILSLOT,
+ "Received mailslot opcode 0x%x on transport: " FORMAT_LPWSTR,
+ ((PNETLOGON_LOGON_QUERY)*Message)->Opcode,
+ *Transport ));
+
+ //
+ // Determine if we can discard an ancient or duplicate message
+ //
+ // Only discard messages that are either expensive to process on this
+ // machine or generate excessive traffic to respond to. Don't discard
+ // messages that we've worked hard to get (e.g., discovery responses).
+ //
+
+ switch ( ((PNETLOGON_LOGON_QUERY)*Message)->Opcode) {
+ case LOGON_REQUEST:
+ case LOGON_SAM_LOGON_REQUEST:
+ case LOGON_PRIMARY_QUERY:
+
+ //
+ // If the message is too old,
+ // discard it.
+ //
+ (VOID) NtQuerySystemTime( &TimeNow );
+ if ( NetlogonMailslot->TimeReceived.QuadPart +
+ NlGlobalMailslotMessageTimeout.QuadPart <
+ TimeNow.QuadPart ) {
+ NlPrint((NL_MAILSLOT, " (Discarded as too old.)\n" ));
+ return FALSE;
+ }
+
+ //
+ // If the previous message was recent,
+ // and this message is identical to it,
+ // discard the current message.
+ //
+
+ if ( NlGlobalMailslotDesc->PreviousMessage != NULL ) {
+ PNETLOGON_MAILSLOT PreviousNetlogonMailslot;
+
+ PreviousNetlogonMailslot = (PNETLOGON_MAILSLOT)
+ NlGlobalMailslotDesc->PreviousMessage;
+
+ if ( (PreviousNetlogonMailslot->TimeReceived.QuadPart +
+ NlGlobalMailslotDuplicateTimeout.QuadPart >
+ NetlogonMailslot->TimeReceived.QuadPart) &&
+
+ (PreviousNetlogonMailslot->MailslotMessageSize ==
+ NetlogonMailslot->MailslotMessageSize) &&
+
+ RtlCompareMemory(
+ &NlGlobalMailslotDesc->CurrentMessage[
+ NetlogonMailslot->MailslotMessageOffset],
+ &NlGlobalMailslotDesc->PreviousMessage[
+ PreviousNetlogonMailslot->MailslotMessageOffset],
+ NetlogonMailslot->MailslotMessageSize ) ==
+ NetlogonMailslot->MailslotMessageSize ) {
+
+
+ //
+ // Ensure the next comparison is to the timestamp of the
+ // message we actually responded to.
+ //
+
+ NetlogonMailslot->TimeReceived =
+ PreviousNetlogonMailslot->TimeReceived;
+
+
+ NlPrint((NL_MAILSLOT, " (Discarded as duplicate of previous.)\n" ));
+ *IgnoreDuplicatesOfPreviousMessage = TRUE;
+ return FALSE;
+
+ }
+ }
+ }
+
+ NlPrint(( NL_MAILSLOT, "\n" ));
+
+ NlpDumpBuffer(NL_MAILSLOT_TEXT, *Message, *BytesRead);
+
+ return TRUE;
+
+}
+
+
+
+NTSTATUS
+NlpWriteMailslot(
+ IN LPWSTR MailslotName,
+ IN LPVOID Buffer,
+ IN DWORD BufferSize
+ )
+
+/*++
+
+Routine Description:
+
+ Write a message to a named mailslot
+
+Arguments:
+
+ MailslotName - Unicode name of the mailslot to write to.
+
+ Buffer - Data to write to the mailslot.
+
+ BufferSize - Number of bytes to write to the mailslot.
+
+Return Value:
+
+ NT status code for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+
+ //
+ // Write the mailslot message.
+ //
+
+ NetStatus = NetpLogonWriteMailslot( MailslotName, Buffer, BufferSize );
+ if ( NetStatus != NERR_Success ) {
+ Status = NetpApiStatusToNtStatus( NetStatus );
+ NlPrint((NL_CRITICAL, "NetpLogonWriteMailslot failed %lx\n", Status));
+ return Status;
+ }
+
+ NlPrint((NL_MAILSLOT_TEXT, "Sent out message to " FORMAT_LPWSTR " on all transports.\n",
+ MailslotName));
+
+#if DBG
+ NlpDumpBuffer( NL_MAILSLOT_TEXT, Buffer, BufferSize );
+#endif // DBG
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+NlpWriteMailslotA(
+ IN LPSTR MailslotName,
+ IN LPVOID Buffer,
+ IN DWORD BufferSize
+ )
+
+/*++
+
+Routine Description:
+
+ Write a message to a named mailslot
+
+Arguments:
+
+ MailslotName - ANSI Name of the mailslot to write to.
+
+ Buffer - Data to write to the mailslot.
+
+ BufferSize - Number of bytes to write to the mailslot.
+
+Return Value:
+
+ NT status code for the operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+ LPWSTR UnicodeMailslotName;
+
+ //
+ // Convert mailslot name to unicode and call common routine.
+ //
+
+ UnicodeMailslotName = NetpLogonOemToUnicode( MailslotName );
+ if ( UnicodeMailslotName == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ Status = NlpWriteMailslot( UnicodeMailslotName, Buffer, BufferSize );
+
+ NetpMemoryFree( UnicodeMailslotName );
+
+ return Status;
+}
diff --git a/private/net/svcdlls/logonsrv/server/makefile b/private/net/svcdlls/logonsrv/server/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/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/net/svcdlls/logonsrv/server/makefile.inc b/private/net/svcdlls/logonsrv/server/makefile.inc
new file mode 100644
index 000000000..dfb642706
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/makefile.inc
@@ -0,0 +1 @@
+obj\$(TARGET_DIRECTORY)\nltest.res: nltest.rc
diff --git a/private/net/svcdlls/logonsrv/server/netlogon.c b/private/net/svcdlls/logonsrv/server/netlogon.c
new file mode 100644
index 000000000..291c8bf08
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/netlogon.c
@@ -0,0 +1,4427 @@
+/*--
+
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ netlogon.c
+
+Abstract:
+
+ Entry point and main thread of Netlogon service.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 21-Nov-1990 (madana)
+ added code for update (reverse replication) and lockout support.
+
+ 21-Nov-1990 (madana)
+ server type support.
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+--*/
+
+
+//
+// Common include files.
+//
+
+#define LSRVDATA_ALLOCATE // Allocate data from lsrvdata.h
+#include <logonsrv.h> // Include files common to entire service
+#undef LSRVDATA_ALLOCATE
+
+//
+// Include files specific to this .c file
+//
+
+#include <alertmsg.h> // Alert message text.
+#include <ctype.h> // C library type functions
+#include <iniparm.h> // initial values of global variables
+#include <lmapibuf.h> // NetApiBufferFree
+#include <lmbrowsr.h> // I_BrowserResetNetlogonState
+#include <lmerr.h> // System Error Log definitions
+#include <lmserver.h> // Server API defines and prototypes
+#include <lmwksta.h> // WKSTA API defines and prototypes
+#include <lmsvc.h> // SERVICE_UIC codes are defined here
+#include <nlsecure.h> // NlCreateNetlogonObjects
+#include <ntlsa.h> // Defines policy database
+#include <ntrpcp.h> // Rpcp routines
+#include <replutil.h>
+#include <samisrv.h> // SamIConnect
+#include <srvann.h> // Service announcement
+#include <stddef.h> // offsetof
+#include <stdlib.h> // C library functions: rand()
+#include <string.h> // strnicmp ...
+#include <tstring.h> // IS_PATH_SEPARATOR ...
+#include <secobj.h> // BuiltinDomainSID defined here ..
+
+
+#define INTERROGATE_RESP_DELAY 2000 // may want to tune it
+#define MAX_PRIMARY_TRACK_FAIL 3 // Primary pulse slips
+
+
+
+BOOLEAN
+NetlogonDllInit (
+ IN PVOID DllHandle,
+ IN ULONG Reason,
+ IN PCONTEXT Context OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This is the DLL initialization routine for netlogon.dll.
+
+Arguments:
+
+ Standard.
+
+Return Value:
+
+ TRUE iff initialization succeeded.
+
+--*/
+{
+ NTSTATUS Status;
+ UNREFERENCED_PARAMETER(DllHandle); // avoid compiler warnings
+ UNREFERENCED_PARAMETER(Context); // avoid compiler warnings
+
+
+ //
+ // Handle attaching netlogon.dll to a new process.
+ //
+
+ if (Reason == DLL_PROCESS_ATTACH) {
+
+ if ( !DisableThreadLibraryCalls( DllHandle ) ) {
+ KdPrint(("NETLOGON.DLL: DisableThreadLibraryCalls failed: %ld\n",
+ GetLastError() ));
+ }
+ Status = NlInitChangeLog();
+#if DBG
+ if ( !NT_SUCCESS( Status ) ) {
+ KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
+ Status ));
+ }
+#endif // DBG
+
+ //
+ // Initialize the Critical Section used to serialize access to
+ // variables shared by MSV threads and netlogon threads.
+ //
+
+ InitializeCriticalSection( &NlGlobalMsvCritSect );
+ NlGlobalMsvEnabled = FALSE;
+ NlGlobalMsvThreadCount = 0;
+ NlGlobalMsvTerminateEvent = NULL;
+
+
+ //
+ // Handle detaching netlogon.dll from a process.
+ //
+
+//
+// netlogon.dll never detaches
+//
+#ifdef NETLOGON_PROCESS_DETACH
+
+ } else if (Reason == DLL_PROCESS_DETACH) {
+ Status = NlCloseChangeLog();
+#if DBG
+ if ( !NT_SUCCESS( Status ) ) {
+ KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
+ Status ));
+ }
+#endif // DBG
+
+ //
+ // Delete the Critical Section used to serialize access to
+ // variables shared by MSV threads and netlogon threads.
+ //
+
+ DeleteCriticalSection( &NlGlobalMsvCritSect );
+#endif // NETLOGON_PROCESS_DETACH
+
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ return (BOOLEAN)(NT_SUCCESS(Status));
+
+}
+
+
+
+BOOLEAN
+NlInitDBSerialNumber(
+ IN OUT PLARGE_INTEGER SerialNumber,
+ IN OUT PLARGE_INTEGER CreationTime,
+ IN PUNICODE_STRING ReplicaSource,
+ IN DWORD DBIndex
+ )
+
+/*++
+
+Routine Description:
+
+ Set the SerialNumber and CreationTime in the NlGlobalDBInfoArray data
+ structure.
+
+ On the PDC,
+ Validate that it matches the value found in the change log.
+ Ensure the values are non-zero.
+
+Arguments:
+
+ SerialNumber - Specifies the serial number found in the database.
+ On return, specifies the serial number to write to the database
+
+ CreationTime - Specifies the creation time found in the database.
+ On return, specifies the creation time to write to the database
+
+ ReplicaSource - Specifies the replica source for the datbase.
+
+ DBIndex -- DB Index of the database being initialized
+
+Return Value:
+
+ TRUE -- iff the serial number and creation time need to be written back
+ to the database.
+
+--*/
+
+{
+ BOOLEAN ReturnValue = FALSE;
+
+ //
+ // Save the name of the Replica source.
+ //
+
+ wcsncpy( NlGlobalDBInfoArray[DBIndex].PrimaryName,
+ ReplicaSource->Buffer,
+ ReplicaSource->Length / sizeof(WCHAR) );
+
+ NlGlobalDBInfoArray[DBIndex].PrimaryName[
+ ReplicaSource->Length / sizeof(WCHAR) ] = L'\0';
+
+
+ //
+ // If we're running as the primary,
+ // check to see if we are a newly promoted primary that was in
+ // the middle of a full sync before we were promoted.
+ //
+
+ if (NlGlobalRole == RolePrimary) {
+
+ if ( SerialNumber->QuadPart == 0 || CreationTime->QuadPart == 0 ) {
+
+ NlPrint(( NL_CRITICAL,
+ "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": Pdc has bogus Serial number %lx %lx or Creation Time %lx %lx (reset).\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ SerialNumber->HighPart,
+ SerialNumber->LowPart,
+ CreationTime->HighPart,
+ CreationTime->LowPart ));
+
+ //
+ // This is the primary,
+ // we probably shouldn't be replicating from a partial database,
+ // but at least set the replication information to something
+ // reasonable.
+ //
+
+ (VOID) NtQuerySystemTime( CreationTime );
+ SerialNumber->QuadPart = 1;
+ ReturnValue = TRUE;
+
+ }
+
+ NlGlobalDBInfoArray[DBIndex].UpdateRqd = FALSE;
+
+
+ //
+ // If we aren't the primary flag that an update is required,
+ //
+
+ } else {
+
+ //
+ // If we've never had a full sync on this database,
+ // force one now.
+ //
+
+ if ( ReplicaSource->Length == 0 ) {
+
+ //
+ // Set this flag so that we can pause the netlogon service
+ // when we do the full sync.
+ //
+ NlGlobalFirstTimeFullSync = TRUE;
+
+ NlGlobalDBInfoArray[DBIndex].UpdateRqd = TRUE;
+ NlGlobalDBInfoArray[DBIndex].FullSyncRequired = TRUE;
+
+ NlPrint(( NL_CRITICAL,
+ "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": Force FULL SYNC because first sync after install.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName ));
+
+ } else {
+
+ //
+ // If we were in the middle of a full sync when we stopped,
+ // continue it now.
+ //
+ if ( SerialNumber->QuadPart == 0 || CreationTime->QuadPart == 0 ) {
+
+ NlGlobalDBInfoArray[DBIndex].UpdateRqd = TRUE;
+ NlGlobalDBInfoArray[DBIndex].FullSyncRequired = TRUE;
+
+ NlPrint(( NL_CRITICAL,
+ "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ " is marked as needing FULL SYNC.\n",
+ NlGlobalDBInfoArray[DBIndex].DBName ));
+
+ }
+
+ NlPrint(( NL_SYNC,
+ "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": Last sync done from \\\\" FORMAT_LPWSTR "\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ NlGlobalDBInfoArray[DBIndex].PrimaryName ));
+
+ }
+
+ }
+
+
+
+ //
+ // The global serial number array has already been initialized
+ // from the changelog. If that information is wrong, just reset the
+ // changelog now.
+ //
+
+
+ LOCK_CHANGELOG();
+
+ //
+ // If there was no serial number in the changelog for this database,
+ // set it now.
+ //
+
+ if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart == 0 ) {
+
+ NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": No serial number in change log (set to %lx %lx)\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ SerialNumber->HighPart,
+ SerialNumber->LowPart ));
+
+
+ NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
+
+ //
+ // If the serial number in the changelog is greater than the
+ // serial number in the database, this is caused by the changelog
+ // being flushed to disk and the SAM database not being flushed.
+ //
+ // Cure this problem by deleting the superfluous changelog entries.
+ //
+
+ } else if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart !=
+ SerialNumber->QuadPart ) {
+
+ NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": Changelog serial number different than database: "
+ "ChangeLog = %lx %lx DB = %lx %lx\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ NlGlobalChangeLogDesc.SerialNumber[DBIndex].HighPart,
+ NlGlobalChangeLogDesc.SerialNumber[DBIndex].LowPart,
+ SerialNumber->HighPart,
+ SerialNumber->LowPart ));
+
+ (VOID) NlFixChangeLog( &NlGlobalChangeLogDesc, DBIndex, *SerialNumber, FALSE );
+
+ } else {
+
+ NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
+ ": Serial number is %lx %lx\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ SerialNumber->HighPart,
+ SerialNumber->LowPart ));
+ }
+
+ //
+ // In all cases,
+ // set the globals to match the database.
+ //
+
+ NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
+ NlGlobalDBInfoArray[DBIndex].CreationTime = *CreationTime;
+
+ UNLOCK_CHANGELOG();
+
+
+ return ReturnValue;
+}
+
+
+NTSTATUS
+NlInitLsaDBInfo(
+ DWORD DBIndex
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize NlGlobalDBInfoArray data structure. Some of the LSA
+ database info is already determined in ValidateStartup functions, so
+ those values are used here.
+
+Arguments:
+
+ DBIndex -- DB Index of the database being initialized
+
+Return Value:
+
+ NT status code.
+
+--*/
+
+{
+
+ NTSTATUS Status;
+ PLSAPR_POLICY_INFORMATION PolicyInfo = NULL;
+
+ //
+ // Initialize LSA database info.
+ //
+
+ NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
+ NlGlobalDBInfoArray[DBIndex].DBName = L"LSA";
+ NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_LSA_REPL_NEEDED;
+
+ //
+ // Database ID field contains nothing for LSA database since
+ // there will be only one LSA database on the system.
+ //
+
+ NlGlobalDBInfoArray[DBIndex].DBId = NULL;
+
+ NlGlobalDBInfoArray[DBIndex].DBHandle = NlGlobalPolicyHandle;
+
+ //
+ // Forgo this initialization on a workstation.
+ //
+
+ if ( NlGlobalRole != RoleMemberWorkstation ) {
+ LARGE_INTEGER SerialNumber;
+ LARGE_INTEGER CreationTime;
+
+ //
+ // Get the replica source name
+ //
+
+ Status = LsarQueryInformationPolicy(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ PolicyReplicaSourceInformation,
+ &PolicyInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ PolicyInfo = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Get the LSA Modified information.
+ //
+
+ Status = LsaIGetSerialNumberPolicy(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ &SerialNumber,
+ &CreationTime );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ //
+ // Set the SerialNumber and CreationTime in the globals.
+ //
+
+ if ( NlInitDBSerialNumber(
+ &SerialNumber,
+ &CreationTime,
+ (PUNICODE_STRING)&PolicyInfo->PolicyReplicaSourceInfo.ReplicaSource,
+ DBIndex ) ) {
+
+
+ Status = LsaISetSerialNumberPolicy(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ &SerialNumber,
+ &CreationTime,
+ (BOOLEAN) FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ }
+ }
+
+Cleanup:
+
+ if ( PolicyInfo != NULL ) {
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyReplicaSourceInformation,
+ PolicyInfo );
+ }
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlInitSamDBInfo(
+ DWORD DBIndex,
+ PSID DomainId
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize NlGlobalDBInfoArray data structure. Some of the SAM database
+ info is already determined in ValidateStartup functions, so those
+ values are used here. For BUILTIN database, the database is opened,
+ database handle is obtained and other DB info
+ queried and initialized in this function.
+
+Arguments:
+
+ DBIndex -- DB Index of the database being initialized
+
+ DomainId -- Domain Sid of the database to open/initialize.
+
+Return Value:
+
+ NT status code.
+
+--*/
+
+{
+
+ NTSTATUS Status;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainModified = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainServerRole = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainReplica = NULL;
+
+ BOOLEAN FixRole = FALSE;
+ DOMAIN_SERVER_ROLE DesiredRole;
+
+
+
+ //
+ // Initialize SAM database info.
+ //
+
+ NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
+ if ( DBIndex == SAM_DB ) {
+ NlGlobalDBInfoArray[DBIndex].DBName = L"SAM";
+ NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_ACCOUNT_REPL_NEEDED;
+ } else {
+ NlGlobalDBInfoArray[DBIndex].DBName = L"BUILTIN";
+ NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_BUILTIN_REPL_NEEDED;
+ }
+
+ NlGlobalDBInfoArray[DBIndex].DBId = NetpMemoryAllocate( RtlLengthSid( DomainId ));
+
+ if ( NlGlobalDBInfoArray[DBIndex].DBId == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ RtlCopyMemory( NlGlobalDBInfoArray[DBIndex].DBId,
+ DomainId,
+ RtlLengthSid( DomainId ));
+
+ //
+ // Open the domain.
+ //
+
+ Status = SamrOpenDomain( NlGlobalSamServerHandle,
+ DOMAIN_ALL_ACCESS,
+ NlGlobalDBInfoArray[DBIndex].DBId,
+ &NlGlobalDBInfoArray[DBIndex].DBHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlGlobalDBInfoArray[DBIndex].DBHandle = NULL;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Ensure the role in SAM is compatible with Netlogon's role
+ //
+
+ Status = SamrQueryInformationDomain( NlGlobalDBInfoArray[DBIndex].DBHandle,
+ DomainServerRoleInformation,
+ &DomainServerRole );
+ if ( !NT_SUCCESS(Status) ) {
+ DomainServerRole = NULL;
+ goto Cleanup;
+ }
+
+ switch ( NlGlobalRole ) {
+ case RolePrimary:
+ case RoleMemberWorkstation:
+ if ( DomainServerRole->Role.DomainServerRole != DomainServerRolePrimary ) {
+ FixRole = TRUE;
+ DesiredRole = DomainServerRolePrimary;
+ }
+ break;
+ case RoleBackup:
+ if ( DomainServerRole->Role.DomainServerRole != DomainServerRoleBackup ) {
+ FixRole = TRUE;
+ DesiredRole = DomainServerRoleBackup;
+ }
+ break;
+
+ default:
+ Status = STATUS_INVALID_DOMAIN_ROLE;
+ goto Cleanup;
+ }
+
+ if ( FixRole) {
+ NlPrint(( NL_CRITICAL,
+ "NlInitSamDbInfo: " FORMAT_LPWSTR
+ ": Role is %ld which doesn't match LSA's role. (Fixed)\n",
+ NlGlobalDBInfoArray[DBIndex].DBName,
+ DomainServerRole->Role.DomainServerRole ));
+
+ DomainServerRole->Role.DomainServerRole = DesiredRole;
+
+ Status = SamrSetInformationDomain(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ DomainServerRoleInformation,
+ DomainServerRole );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DomainServerRole = NULL;
+ goto Cleanup;
+ }
+ }
+
+
+
+ //
+ // Forgo this initialization on a workstation.
+ //
+
+ if ( NlGlobalRole != RoleMemberWorkstation ) {
+
+ //
+ // Get the replica source name.
+ //
+
+ Status = SamrQueryInformationDomain(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ DomainReplicationInformation,
+ &DomainReplica );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DomainReplica = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Get the Domain Modified information.
+ //
+
+ Status = SamrQueryInformationDomain(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ DomainModifiedInformation2,
+ &DomainModified );
+
+ if ( !NT_SUCCESS(Status) ) {
+ DomainModified = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Set the SerialNumber and CreationTime in the globals.
+ //
+
+ if ( NlInitDBSerialNumber(
+ &DomainModified->Modified2.DomainModifiedCount,
+ &DomainModified->Modified2.CreationTime,
+ (PUNICODE_STRING)&DomainReplica->Replication.ReplicaSourceNodeName,
+ DBIndex ) ) {
+
+ Status = SamISetSerialNumberDomain(
+ NlGlobalDBInfoArray[DBIndex].DBHandle,
+ &DomainModified->Modified2.DomainModifiedCount,
+ &DomainModified->Modified2.CreationTime,
+ (BOOLEAN) FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+ }
+ }
+
+Cleanup:
+
+ //
+ // Free locally used resources.
+ //
+ if ( DomainModified != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainModified,
+ DomainModifiedInformation2 );
+ }
+
+ if ( DomainServerRole != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainServerRole,
+ DomainServerRoleInformation);
+ }
+
+ if ( DomainReplica != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainReplica,
+ DomainReplicationInformation );
+ }
+
+ return Status;
+
+}
+
+
+BOOL
+NlSetDomainName(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine gets the primary domain name from the LSA and stores
+ that name in global variables.
+
+Arguments:
+
+ NONE.
+
+Return Value:
+
+ TRUE -- Iff the domain name can be saved.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PLSAPR_POLICY_INFORMATION PolicyInfo;
+
+
+ //
+ // Get the Primary Domain Name from the LSA.
+ //
+
+ Status = LsarQueryInformationPolicy(
+ NlGlobalPolicyHandle,
+ PolicyPrimaryDomainInformation,
+ &PolicyInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ if ( PolicyInfo->PolicyPrimaryDomainInfo.Name.Length == 0 ||
+ PolicyInfo->PolicyPrimaryDomainInfo.Name.Length >
+ DNLEN * sizeof(WCHAR) ||
+ PolicyInfo->PolicyPrimaryDomainInfo.Sid == NULL ) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ PolicyInfo );
+
+ NlPrint((NL_CRITICAL, "Primary domain info from LSA invalid.\n"));
+ NlExit( SERVICE_UIC_M_UAS_INVALID_ROLE, 0, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Copy name to the globals.
+ //
+
+ RtlCopyMemory( NlGlobalUnicodeDomainName,
+ PolicyInfo->PolicyPrimaryDomainInfo.Name.Buffer,
+ PolicyInfo->PolicyPrimaryDomainInfo.Name.Length );
+
+ NlGlobalUnicodeDomainName[
+ PolicyInfo->PolicyPrimaryDomainInfo.Name.Length /
+ sizeof(WCHAR)] = L'\0';
+
+ RtlInitUnicodeString( &NlGlobalUnicodeDomainNameString,
+ NlGlobalUnicodeDomainName);
+
+ //
+ // This routine is only called once during initialization so previous
+ // storage need not be freed.
+ //
+
+ NlGlobalAnsiDomainName =
+ NetpLogonUnicodeToOem( NlGlobalUnicodeDomainName);
+
+ if ( NlGlobalAnsiDomainName == NULL ) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ PolicyInfo );
+
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // Save the SID in a global
+ //
+
+ NlGlobalPrimaryDomainId = NetpMemoryAllocate(
+ RtlLengthSid( (PSID)PolicyInfo->PolicyPrimaryDomainInfo.Sid ));
+
+ if ( NlGlobalPrimaryDomainId == NULL ) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ PolicyInfo );
+
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+ RtlCopyMemory( NlGlobalPrimaryDomainId,
+ PolicyInfo->PolicyPrimaryDomainInfo.Sid,
+ RtlLengthSid( PolicyInfo->PolicyPrimaryDomainInfo.Sid ));
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyPrimaryDomainInformation,
+ PolicyInfo );
+
+
+ return TRUE;
+}
+
+
+
+BOOL
+NlInitWorkstation(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Do workstation specific initialization.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE -- iff initialization is successful.
+
+--*/
+{
+ //
+ // Ensure the primary and account domain ID are different.
+ //
+
+ if ( RtlEqualSid( NlGlobalDBInfoArray[SAM_DB].DBId, NlGlobalPrimaryDomainId ) ) {
+
+ LPWSTR AlertStrings[3];
+
+ //
+ // alert admin.
+ //
+
+ AlertStrings[0] = NlGlobalUnicodeComputerName;
+ AlertStrings[1] = NlGlobalUnicodeDomainName;
+ AlertStrings[2] = NULL;
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonSidConflict,
+ EVENTLOG_ERROR_TYPE,
+ NlGlobalPrimaryDomainId,
+ RtlLengthSid( NlGlobalPrimaryDomainId ),
+ AlertStrings,
+ 2 );
+
+ //
+ // This isn't fatal. (Just drop through)
+ //
+ }
+
+
+ //
+ // Set up the Client Session structure to identify the domain and
+ // account used to talk to the DC.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ NlGlobalClientSession = NlAllocateClientSession(
+ &NlGlobalUnicodeDomainNameString,
+ NlGlobalPrimaryDomainId,
+ WorkstationSecureChannel );
+
+ if ( NlGlobalClientSession == NULL ) {
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+
+BOOL
+NlInitDomainController(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Do Domain Controller specific initialization.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE -- iff initialization is successful.
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+ WCHAR ChangeLogFile[PATHLEN+1];
+
+ LPWSTR DCName;
+ DWORD Version;
+
+ BOOL DeferAuth = FALSE;
+
+
+
+ //
+ // Handle if there is no other PDC running in this domain.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ NetStatus = NetpLogonGetDCName( NlGlobalUnicodeComputerName,
+ NlGlobalUnicodeDomainName,
+ 0,
+ &DCName,
+ &Version );
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( NetStatus != NERR_Success) {
+
+ //
+ // If we are the first primary in the domain,
+ // Remember that we are the primary.
+ //
+
+ if (NlGlobalRole == RolePrimary) {
+
+ if ( !NlSetPrimaryName( NlGlobalUnicodeComputerName ) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Handle starting a BDC when there is not current primary in
+ // this domain.
+ //
+
+ } else if ( NlGlobalRole == RoleBackup ) {
+
+ NlpWriteEventlog( SERVICE_UIC_M_NETLOGON_NO_DC,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ NULL,
+ 0 );
+
+ //
+ // Start normally but defer authentication with the
+ // primary until it starts.
+ //
+
+ DeferAuth = TRUE;
+
+ }
+
+ //
+ // There is a primary dc running in this domain
+ //
+
+ } else {
+
+ //
+ // Since there already is a primary in the domain,
+ // we cannot become the primary.
+ //
+
+ if (NlGlobalRole == RolePrimary) {
+
+ //
+ // Don't worry if this is a BDC telling us that we're the PDC.
+ //
+
+ if ( NlNameCompare( NlGlobalUnicodeComputerName,
+ DCName + 2,
+ NAMETYPE_COMPUTER) != 0 ){
+ NlExit(SERVICE_UIC_M_NETLOGON_DC_CFLCT, 0, LogError, NULL);
+ (VOID) NetApiBufferFree( DCName );
+ return FALSE;
+ }
+
+ } else {
+
+ //
+ // If the primary is NOT an NT primary,
+ // refuse to start.
+ //
+ // An NT BDC or member server cannot replicate from a downlevel PDC.
+ //
+
+ if ( Version != LMNT_MESSAGE ) {
+ PSERVER_INFO_101 ServerInfo101 = NULL;
+
+ //
+ // This might just be a LM 2.1A (or newer) BDC responding on
+ // behalf of an NT PDC.
+ //
+ // Ask the PDC if it is NT.
+ //
+
+ NetStatus = NetServerGetInfo( DCName,
+ 101,
+ (LPBYTE *)&ServerInfo101 );
+ if ( NetStatus != NERR_Success ) {
+ NlPrint((NL_CRITICAL,
+ "can't NetServerGetInfo on primary " FORMAT_LPWSTR
+ " %ld.\n",
+ DCName,
+ NetStatus ));
+ (VOID) NetApiBufferFree( DCName );
+ NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, NetStatus, LogError, NULL);
+ return FALSE;
+ }
+
+ if ( (ServerInfo101->sv101_type & SV_TYPE_DOMAIN_CTRL) == 0 ) {
+ NetApiBufferFree( ServerInfo101 );
+ NlPrint((NL_CRITICAL, "PDC " FORMAT_LPWSTR " really isn't a PDC\n",
+ DCName ));
+ (VOID) NetApiBufferFree( DCName );
+ NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, 0, LogError, NULL);
+ return FALSE;
+ }
+
+ if ( (ServerInfo101->sv101_type & SV_TYPE_NT) == 0 ) {
+ NetApiBufferFree( ServerInfo101 );
+ NlPrint((NL_CRITICAL, "PDC " FORMAT_LPWSTR
+ " really isn't an NT PDC\n", DCName ));
+ (VOID) NetApiBufferFree( DCName );
+ NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, 0, LogError, NULL);
+ return FALSE;
+ }
+ NetApiBufferFree( ServerInfo101 );
+ }
+
+ }
+
+ //
+ // Remember this primary name.
+ //
+
+ if ( !NlSetPrimaryName( DCName + 2 ) ) {
+ NlExit(SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
+ (VOID) NetApiBufferFree( DCName );
+ return FALSE;
+ }
+
+ (VOID) NetApiBufferFree( DCName );
+
+ }
+
+
+ //
+ // Open the browser so we can send and receive mailslot messages.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( !NlBrowserOpen() ) {
+ return FALSE;
+ }
+
+
+
+ //
+ // Here ensure that the Secret Password exists.
+ // (If the secret password doesn't exist, we'll never be able
+ // to establish a session to the PDC and netlogon shouldn't be
+ // running).
+ //
+
+ if ( NlGlobalRole == RoleBackup ) {
+
+ LSAPR_HANDLE SecretHandle;
+
+ //
+ // Set up the Client Session structure to identify the domain and
+ // account used to talk to the PDC.
+ //
+
+ NlGlobalClientSession = NlAllocateClientSession(
+ &NlGlobalUnicodeDomainNameString,
+ NlGlobalPrimaryDomainId,
+ ServerSecureChannel );
+
+ if ( NlGlobalClientSession == NULL ) {
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+ Status = NlOpenSecret( NlGlobalClientSession,
+ SECRET_QUERY_VALUE | SECRET_SET_VALUE,
+ &SecretHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_LSA_MACHINE_ACCT, Status, LogError, NULL );
+ return FALSE;
+ }
+
+ (VOID) LsarClose( &SecretHandle );
+
+ }
+
+ //
+ // Check that the server is installed or install pending
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( !NetpIsServiceStarted( SERVICE_SERVER ) ){
+ NlExit( NERR_ServerNotStarted, 0, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // Allocate & initialize the data structs and storage for replication
+ //
+
+
+ Status = NlInitSSI();
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( NELOG_NetlogonSSIInitError, Status, LogErrorAndNtStatus, NULL);
+ return FALSE;
+ }
+
+ //
+ // Create the event the replicator thread waits on to terminate.
+ //
+
+ NlGlobalReplicatorTerminateEvent = CreateEvent(
+ NULL, // No security attributes
+ TRUE, // Must be manually reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if ( NlGlobalReplicatorTerminateEvent == NULL ) {
+ NetStatus = GetLastError();
+ NlPrint((NL_CRITICAL, "Cannot create replicator termination Event %lu\n",
+ NetStatus ));
+ NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
+ return FALSE;
+ }
+
+ //
+ // On a BDC, set up a session to the PDC now.
+ //
+ // If the PDC was previously found,
+ // require now that we successfully establish a session
+ // else
+ // wait till the PDC identifies itself
+ //
+
+ if (NlGlobalRole == RoleBackup ) {
+
+ if ( !DeferAuth ) {
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+ (VOID) NlTimeoutSetWriterClientSession( NlGlobalClientSession, 0xFFFFFFFF );
+ Status = NlSessionSetup( NlGlobalClientSession );
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+ //
+ // Treat it as fatal if the PDC explicitly denies us access.
+ //
+
+ if ( Status == STATUS_NO_TRUST_SAM_ACCOUNT ||
+ Status == STATUS_ACCESS_DENIED ) {
+
+ // NlSessionSetup already logged the error
+ NlExit( NetpNtStatusToApiStatus(Status), 0, DontLogError, NULL);
+ return FALSE;
+ }
+ }
+
+ }
+
+
+ //
+ // Determine the trust list from the LSA.
+ //
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ Status = NlInitTrustList();
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( NELOG_NetlogonFailedToUpdateTrustList, Status, LogErrorAndNtStatus, NULL);
+ return FALSE;
+ }
+
+ //
+ // Create NETLOGON share.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ NetStatus = NlCreateShare( NlGlobalUnicodeScriptPath,
+ NETLOGON_SCRIPTS_SHARE) ;
+
+ if ( NetStatus != NERR_Success ) {
+ LPWSTR MsgStrings[2];
+
+ NlPrint((NL_CRITICAL, "NlCreateShare %lu\n", NetStatus ));
+
+ MsgStrings[0] = NlGlobalUnicodeScriptPath;
+ MsgStrings[1] = (LPWSTR) NetStatus;
+
+ NlpWriteEventlog (NELOG_NetlogonFailedToCreateShare,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &NetStatus,
+ sizeof(NetStatus),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NETSTATUS );
+
+ /* This isn't fatal. Just continue */
+ }
+
+#if DBG
+
+ //
+ // create debug share. Ignore error.
+ //
+
+ if( NlCreateShare(
+ NlGlobalDebugSharePath,
+ DEBUG_SHARE_NAME ) != NERR_Success ) {
+ NlPrint((NL_CRITICAL, "Can't create Debug share (%ws, %ws).\n",
+ NlGlobalDebugSharePath, DEBUG_SHARE_NAME ));
+ }
+
+#endif
+
+ //
+ // If a redo log exists,
+ // open it on a BDC,
+ // delete it on a PDC.
+ //
+
+ wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
+ wcscat( ChangeLogFile, REDO_FILE_POSTFIX );
+
+ if (NlGlobalRole == RoleBackup ) {
+
+ //
+ // Read in the existing redo log file.
+ //
+ // It's OK if the file simply doesn't exist,
+ // we'll create it when we need it.
+ //
+
+ Status = NlOpenChangeLogFile( ChangeLogFile, &NlGlobalRedoLogDesc, FALSE );
+
+ if ( !NT_SUCCESS(Status) && Status != STATUS_NO_SUCH_FILE ) {
+
+ NlpWriteEventlog (
+ NELOG_NetlogonChangeLogCorrupt,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ NULL,
+ 0 );
+
+ NlPrint((NL_CRITICAL, "Deleting broken redo log\n" ));
+
+ (VOID) DeleteFileW( ChangeLogFile );
+
+ }
+
+ } else {
+ (VOID) DeleteFileW( ChangeLogFile );
+ }
+
+ //
+ // Successful initialization.
+ //
+
+ return TRUE;
+}
+
+
+ULONG
+NlServerType(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Determines server type, that is used to set in service table.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ SV_TYPE_DOMAIN_CTRL if role is primary domain controller
+ SV_TYPE_DOMAIN_BAKCTRL if backup
+ 0 if none of the above
+
+
+--*/
+{
+ switch (NlGlobalRole) {
+ case RolePrimary:
+ return SV_TYPE_DOMAIN_CTRL;
+ case RoleBackup:
+ return SV_TYPE_DOMAIN_BAKCTRL;
+ default:
+ return 0;
+ }
+}
+
+
+
+BOOL
+NlInit(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Initialize NETLOGON service related data structs after verfiying that
+ all conditions for startup have been satisfied. Will also create a
+ mailslot to listen to requests from clients and create two shares to
+ allow execution of logon scripts.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE -- iff initialization is successful.
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+
+ LARGE_INTEGER TimeNow;
+ OBJECT_ATTRIBUTES EventAttributes;
+ UNICODE_STRING EventName;
+
+ PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
+
+ PLSAPR_POLICY_INFORMATION PolicyLsaServerRole = NULL;
+ PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL;
+
+ NT_PRODUCT_TYPE NtProductType;
+ DWORD ComputerNameLength;
+
+
+
+ //
+ // Let the ChangeLog routines know that Netlogon is started.
+ //
+
+ NlGlobalChangeLogNetlogonState = NetlogonStarting;
+
+
+ //
+ // seed the pseudo random number generator
+ //
+
+ (VOID) NtQuerySystemTime( &TimeNow );
+ srand( TimeNow.LowPart );
+
+
+ //
+ // Check that the redirector is installed, will exit on error.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( !NetpIsServiceStarted( SERVICE_WORKSTATION ) ){
+ NlExit( NERR_WkstaNotStarted, 0, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Get the local computer name.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ NlGlobalUncUnicodeComputerName[0] = '\\';
+ NlGlobalUncUnicodeComputerName[1] = '\\';
+
+ ComputerNameLength =
+ (sizeof(NlGlobalUncUnicodeComputerName)/sizeof(WCHAR)) - 2;
+
+ if ( !GetComputerNameW( NlGlobalUncUnicodeComputerName+2,
+ &ComputerNameLength ) ) {
+ NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
+ return FALSE;
+ }
+
+ NlGlobalUnicodeComputerName = NlGlobalUncUnicodeComputerName + 2;
+
+ RtlInitUnicodeString( &NlGlobalUnicodeComputerNameString,
+ NlGlobalUnicodeComputerName );
+
+ NlGlobalAnsiComputerName =
+ NetpLogonUnicodeToOem( NlGlobalUnicodeComputerName );
+ if ( NlGlobalAnsiComputerName == NULL ) {
+ NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Open the LSA.
+ //
+
+ Status = LsaIOpenPolicyTrusted( &NlGlobalPolicyHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // Determine whether the Role of the local LSA is primary or backup.
+ //
+
+ Status = LsarQueryInformationPolicy(
+ NlGlobalPolicyHandle,
+ PolicyLsaServerRoleInformation,
+ &PolicyLsaServerRole );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ switch (PolicyLsaServerRole->
+ PolicyServerRoleInfo.LsaServerRole) {
+ case PolicyServerRolePrimary:
+ NlGlobalRole = RolePrimary;
+ break;
+
+ case PolicyServerRoleBackup:
+ NlGlobalRole = RoleBackup;
+ break;
+
+ default:
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyLsaServerRoleInformation,
+ PolicyLsaServerRole );
+
+ NlExit( SERVICE_UIC_M_UAS_INVALID_ROLE, 0, LogError, NULL);
+ return FALSE;
+ }
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyLsaServerRoleInformation,
+ PolicyLsaServerRole );
+
+
+
+ //
+ // If this is Windows-NT running,
+ // change the role to RoleMemberWorkstation.
+ //
+ // All error conditions default to RoleMemberWorkstation.
+ //
+
+ if ( RtlGetNtProductType( &NtProductType ) ) {
+ if ( NtProductType != NtProductLanManNt ) {
+ NlGlobalRole = RoleMemberWorkstation;
+ }
+ } else {
+ NlGlobalRole = RoleMemberWorkstation;
+ }
+
+
+
+ //
+ // Get the Primary Domain name from LSA and save it in globals.
+ //
+
+ if ( !NlSetDomainName() ) {
+ return FALSE;
+ }
+
+ //
+ // If this is a workstation,
+ // get the cached trusted domain list from the registry.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ LPWSTR TrustedDomainList = NULL;
+ BOOL TrustedDomainListKnown;
+
+ //
+ // If NCPA has just joined a domain,
+ // and has pre-determined the trusted domain list for us,
+ // pick up that list.
+ //
+ // When this machine joins a domain,
+ // NCPA caches the trusted domain list where we can find it. That ensures the
+ // trusted domain list is available upon reboot even before we dial via RAS. Winlogon
+ // can therefore get the trusted domain list from us under those circumstances.
+ //
+
+ NetStatus = NlReadRegTrustedDomainList (
+ NlGlobalUnicodeDomainName,
+ TRUE, // Delete this registry key since we no longer need it.
+ &TrustedDomainList,
+ &TrustedDomainListKnown );
+
+
+ if ( NetStatus != NO_ERROR ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, NetStatus, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // If there is a cached list,
+ // Save it back in the registry for future starts.
+ //
+
+ if ( TrustedDomainListKnown ) {
+ NlPrint(( NL_INIT,
+ "Replacing trusted domain list with one for newly joined %ws domain.\n",
+ NlGlobalUnicodeDomainName));
+ NlSaveTrustedDomainList ( TrustedDomainList );
+
+ //
+ // Otherwise, read the current one from the registry.
+ //
+
+ } else {
+ NlPrint(( NL_INIT, "Getting cached trusted domain list from registry.\n" ));
+ NetStatus = NlReadRegTrustedDomainList (
+ NULL,
+ FALSE,
+ &TrustedDomainList,
+ &TrustedDomainListKnown );
+
+ if ( NetStatus != NO_ERROR ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, NetStatus, LogError, NULL);
+ return FALSE;
+ }
+ }
+
+ //
+ // In all cases, set the trusted domain list into globals.
+ //
+
+ (VOID) NlSetTrustedDomainList( TrustedDomainList, TrustedDomainListKnown );
+
+ if ( TrustedDomainList != NULL ) {
+ NetApiBufferFree( TrustedDomainList );
+ }
+
+ }
+
+
+ //
+ // Determine the name of the local Sam Account database as the
+ // user reference it when logging on.
+ //
+ // On a workstation, it is the workstation name.
+ // On a DC, it is the primary domain name.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ RtlInitUnicodeString( &NlGlobalAccountDomainName,
+ NlGlobalUnicodeComputerName );
+ } else {
+ RtlInitUnicodeString( &NlGlobalAccountDomainName,
+ NlGlobalUnicodeDomainName );
+ }
+
+ //
+ // Initialize LSA database info.
+ //
+
+ Status = NlInitLsaDBInfo( LSA_DB );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // Compute the Domain ID of the SAM Account domain.
+ //
+
+ Status = LsarQueryInformationPolicy(
+ NlGlobalPolicyHandle,
+ PolicyAccountDomainInformation,
+ &PolicyAccountDomainInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ) {
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAccountDomainInformation,
+ PolicyAccountDomainInfo );
+
+ NlPrint((NL_CRITICAL, "Account domain info from LSA invalid.\n"));
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
+ return FALSE;
+ }
+
+
+
+
+ //
+ // Wait for SAM to start
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( !NlWaitForSamService( TRUE ) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Open our connection with SAM
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ Status = SamIConnect( NULL, // No server name
+ &NlGlobalSamServerHandle,
+ 0, // Ignore desired access
+ (BOOLEAN) TRUE ); // Indicate we are privileged
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlGlobalSamServerHandle = NULL;
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Open the Sam Account domain.
+ //
+
+ Status = NlInitSamDBInfo(
+ SAM_DB,
+ PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid );
+
+ LsaIFree_LSAPR_POLICY_INFORMATION(
+ PolicyAccountDomainInformation,
+ PolicyAccountDomainInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Create well know SID for netlogon.dll
+ //
+
+ Status = NetpCreateWellKnownSids( NULL );
+
+ if( !NT_SUCCESS( Status ) ) {
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ NlExit( SERVICE_UIC_RESOURCE, NetStatus, LogError, NULL);
+ return FALSE;
+ }
+
+
+ //
+ // Create the security descriptors we'll use for the APIs
+ //
+
+ Status = NlCreateNetlogonObjects();
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( NELOG_NetlogonSystemError, Status, LogErrorAndNtStatus, NULL);
+ return FALSE;
+ }
+
+
+
+ //
+ // Open the SAM Builtin domain.
+ //
+
+ Status = NlInitSamDBInfo( BUILTIN_DB, BuiltinDomainSid );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+
+
+ //
+ // Get our UAS compatibility mode from SAM
+ //
+
+ Status = SamrQueryInformationDomain(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ DomainGeneralInformation,
+ &DomainInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ DomainInfo = NULL;
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ NlGlobalUasCompatibilityMode = DomainInfo->General.UasCompatibilityRequired;
+ IF_DEBUG( CRITICAL ) {
+ if ( !NlGlobalUasCompatibilityMode ) {
+ NlPrint((NL_CRITICAL, "ERROR: UasCompatibility mode is off.\n"));
+ }
+ }
+
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation );
+
+
+
+ //
+ // Create Timer event
+ //
+
+ NlGlobalTimerEvent = CreateEvent(
+ NULL, // No special security
+ FALSE, // Auto Reset
+ FALSE, // No Timers need no attention
+ NULL ); // No name
+
+ if ( NlGlobalTimerEvent == NULL ) {
+ NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
+ return FALSE;
+ }
+
+
+
+
+ //
+ // Do Workstation or Domain Controller specific initialization
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ if ( !NlInitWorkstation() ) {
+ return FALSE;
+ }
+ } else {
+ if ( !NlInitDomainController() ) {
+ return FALSE;
+ }
+ }
+
+ //
+ // Create an event that is signalled when the last MSV thread leaves
+ // a netlogon routine.
+ //
+
+ NlGlobalMsvTerminateEvent = CreateEvent( NULL, // No security attributes
+ TRUE, // Must be manually reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if ( NlGlobalMsvTerminateEvent == NULL ) {
+ NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
+ return FALSE;
+ }
+
+ NlGlobalMsvEnabled = TRUE;
+
+ //
+ // We are now ready to act as a Netlogon service
+ // Enable RPC
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+
+ NlPrint((NL_INIT,"Starting RPC server.\n"));
+
+ //
+ // 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.
+ //
+ NetStatus = RpcpAddInterface ( L"lsass", logon_ServerIfHandle );
+
+ if (NetStatus != NERR_Success) {
+ NlExit( NELOG_NetlogonFailedToAddRpcInterface, NetStatus, LogErrorAndNetStatus, NULL );
+ return FALSE;
+ }
+
+ NlGlobalRpcServerStarted = TRUE;
+
+
+
+ //
+ // Tell the ServiceController what services we provide.
+ //
+
+ if ( !I_ScSetServiceBits( NlGlobalServiceHandle,
+ NlServerType(),
+ TRUE, // Set bits on
+ TRUE, // Force immediate announcement
+ NULL)) { // All transports
+
+ NetStatus = GetLastError();
+
+ NlPrint((NL_CRITICAL,"Couldn't I_ScSetServiceBits %ld 0x%lx.\n",
+ NetStatus, NetStatus ));
+ NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL );
+ return FALSE;
+ }
+
+ //
+ // Tell the browser that the role may have changed
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ NetStatus = I_BrowserResetNetlogonState( NULL );
+
+ if ( NetStatus != NERR_Success ) {
+ NlPrint((NL_INIT,"Couldn't I_BrowserResetNetlogonState %ld 0x%lx.\n",
+ NetStatus, NetStatus ));
+ // This isn't fatal
+ }
+
+
+
+ //
+ // Let the ChangeLog routines know that Netlogon is started.
+ //
+
+ NlGlobalChangeLogNetlogonState = NetlogonStarted;
+
+
+
+
+ //
+ // Set an event telling anyone wanting to call NETLOGON that we're
+ // initialized.
+ //
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+
+ RtlInitUnicodeString( &EventName, L"\\NETLOGON_SERVICE_STARTED");
+ InitializeObjectAttributes( &EventAttributes, &EventName, 0, 0, NULL );
+
+ Status = NtCreateEvent(
+ &NlGlobalStartedEvent,
+ SYNCHRONIZE|EVENT_MODIFY_STATE,
+ &EventAttributes,
+ NotificationEvent,
+ (BOOLEAN) FALSE // The event is initially not signaled
+ );
+
+ if ( !NT_SUCCESS(Status)) {
+
+ //
+ // If the event already exists, a waiting thread beat us to
+ // creating it. Just open it.
+ //
+
+ if( Status == STATUS_OBJECT_NAME_EXISTS ||
+ Status == STATUS_OBJECT_NAME_COLLISION ) {
+
+ Status = NtOpenEvent( &NlGlobalStartedEvent,
+ SYNCHRONIZE|EVENT_MODIFY_STATE,
+ &EventAttributes );
+
+ }
+ if ( !NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ " Failed to open NETLOGON_SERVICE_STARTED event. %lX\n",
+ Status ));
+ NlPrint((NL_CRITICAL,
+ " Failing to initialize SAM Server.\n"));
+ NlExit( SERVICE_UIC_SYSTEM, Status, LogError, NULL);
+ return FALSE;
+ }
+ }
+
+ Status = NtSetEvent( NlGlobalStartedEvent, NULL );
+ if ( !NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,
+ "Failed to set NETLOGON_SERVICE_STARTED event. %lX\n",
+ Status ));
+ NlPrint((NL_CRITICAL, " Failing to initialize SAM Server.\n"));
+
+ NtClose(NlGlobalStartedEvent);
+ NlExit( SERVICE_UIC_SYSTEM, Status, LogError, NULL);
+ return FALSE;
+ }
+
+ //
+ // Don't close the event handle. Closing it would delete the event and
+ // a future waiter would never see it be set.
+ //
+
+
+ //
+ // Announce that we're started
+ //
+
+ if (NlGlobalRole == RolePrimary) {
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ return FALSE;
+ }
+ NlAnnouncePrimaryStart();
+ }
+
+
+ //
+ // we are just about done, this will be final hint
+ //
+
+ if ( !GiveInstallHints( TRUE ) ) {
+ return FALSE;
+ }
+
+
+
+
+ //
+ // If we're not the primary,
+ // sync the SAM database as requested.
+ //
+
+ if ( NlGlobalRole == RoleBackup ) {
+ DWORD i;
+
+ //
+ // Give the BDC a chance to change its password before the replicator
+ // starts.
+ //
+
+ NlChangePassword( NlGlobalClientSession );
+
+ //
+ // Handle each database separately.
+ //
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+
+ //
+ // if /UPDATE:YES has been specified then FORCE replication
+ //
+
+ if ( NlGlobalSynchronizeParameter ) {
+
+ NlPrint(( NL_SYNC,
+ FORMAT_LPWSTR ": Force FULL SYNC because UPDATE was specified.\n",
+ NlGlobalDBInfoArray[i].DBName ));
+
+
+ //
+ // Do a complete full sync (don't restart it).
+ //
+
+ NlSetFullSyncKey( i, NULL );
+
+ Status = NlForceStartupSync( &NlGlobalDBInfoArray[i] );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlExit( SERVICE_UIC_M_DATABASE_ERROR,
+ NetpNtStatusToApiStatus(Status),
+ LogError,
+ NULL);
+ return FALSE;
+ }
+ }
+
+
+ }
+
+ //
+ // See if there is any reason to start replication
+ //
+
+ for ( i = 0; i < NUM_DBS; i++ ) {
+
+ if ( NlGlobalDBInfoArray[i].UpdateRqd ||
+ NlGlobalRedoLogDesc.EntryCount[i] != 0 ) {
+
+ if ( NlGlobalClientSession->CsState != CS_IDLE ) {
+ NlPrint((NL_SYNC, "Starting replicator on startup.\n"));
+ (VOID) NlStartReplicatorThread( 0 );
+ }
+ break;
+ }
+ }
+
+ //
+ // Clear the full sync key on the primary to avoid confusion if we ever
+ // demote to a BDC.
+ //
+ } else if ( NlGlobalRole == RolePrimary ) {
+
+ DWORD i;
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+ NlSetFullSyncKey( i, NULL );
+ (VOID) NlResetFirstTimeFullSync( i );
+ }
+
+ }
+
+
+
+ //
+ // Successful initialization.
+ //
+
+ return TRUE;
+}
+
+
+BOOLEAN
+LogonRequestHandler(
+ IN DWORD Version,
+ IN LPWSTR UnicodeUserName,
+ IN DWORD RequestCount,
+ IN LPSTR OemWorkstationName,
+ IN LPSTR OemMailslotName,
+ IN LPWSTR TransportName,
+ IN ULONG AllowableAccountControlBits
+ )
+
+/*++
+
+Routine Description:
+
+ Respond appropriate to a LM 1.0, LM 2.0 or SAM logon request.
+
+ Requests from LM1.0 clients to be handled differently
+ since response_buffer size has changed due to PATHLEN.
+
+Arguments:
+
+ Version - The version of the input message. This parameter determine
+ the version of the response.
+
+ UnicodeUserName - The name of the user logging on.
+
+ RequestCount - The number of times this user has repeated the logon request.
+
+ OemWorkstationName - The name of the workstation where the user is
+ logging onto.
+
+ OemMailslotName - The name of the mailslot to respond to.
+
+ AllowableAccountControlBits - A mask of allowable SAM account types that
+ are allowed to satisfy this request.
+
+Return Value:
+
+ TRUE if the any duplicates of this message should be ignored.
+
+--*/
+{
+ NTSTATUS Status;
+
+ USHORT Response = 0;
+ PSAMPR_USER_INFO_BUFFER UserControl = NULL;
+
+ NETLOGON_LOGON_RESPONSE2 Response2;
+ NETLOGON_SAM_LOGON_RESPONSE SamResponse;
+ PCHAR Where;
+ BOOLEAN IgnoreDuplicatesOfThisMessage = FALSE;
+
+ SAMPR_HANDLE UserHandle = NULL;
+
+ //
+ // Logons are not processed if the service is paused
+ //
+
+ if ( (NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED) ||
+ ( NlGlobalFirstTimeFullSync == TRUE ) ) {
+
+ if ( Version == LMNT_MESSAGE ) {
+ Response = LOGON_SAM_PAUSE_RESPONSE;
+ } else {
+
+ //
+ // Don't respond to immediately to non-nt clients. They treat
+ // "paused" responses as fatal. That's just not so.
+ // There may be many other DCs that are able to process the logon.
+ //
+ if ( RequestCount >= MAX_LOGONREQ_COUNT &&
+ NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED ) {
+ Response = LOGON_PAUSE_RESPONSE;
+ }
+ }
+
+ goto Cleanup;
+ }
+
+
+ //
+ // If this user does not have an account in SAM,
+ // immediately return a response indicating so.
+ //
+ // All we are trying to do here is ensuring that this guy
+ // has a valid account except that we are not checking the
+ // password
+ //
+ // This is done so that STANDALONE logons for non existent
+ // users can be done in very first try, speeding up the response
+ // to user and reducing processing on DCs/BCs.
+ //
+
+ Status = NlSamOpenNamedUser( UnicodeUserName, &UserHandle, NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ if ( Status == STATUS_NO_SUCH_USER ) {
+
+ if ( Version == LMNT_MESSAGE ) {
+ Response = LOGON_SAM_USER_UNKNOWN;
+ } else if ( Version == LM20_MESSAGE ) {
+ Response = LOGON_USER_UNKNOWN;
+ }
+ }
+
+ goto Cleanup;
+ }
+
+
+ //
+ // Get the account control information.
+ //
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserControlInformation,
+ &UserControl );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // Disallow use of disabled accounts.
+ //
+ // We use this message to determine if a trusted domain has a
+ // particular account. Since the UI recommend disabling an account
+ // rather than deleting it (conservation of rids and all that),
+ // we shouldn't respond that we have the account if we really don't.
+ //
+ // We don't check the disabled bit in the downlevel case. Downlevel
+ // interactive logons are directed at a single particular domain.
+ // It is better here that we indicate we have the account so later
+ // he'll get a better error code indicating that the account is
+ // disabled, rather than allowing him to logon standalone.
+ //
+
+ if ( Version == LMNT_MESSAGE &&
+ (UserControl->Control.UserAccountControl & USER_ACCOUNT_DISABLED) ) {
+ Response = LOGON_SAM_USER_UNKNOWN;
+ goto Cleanup;
+ }
+
+ //
+ // Ensure the Account type matches those valid for logons.
+ //
+ if ( (UserControl->Control.UserAccountControl &
+ USER_ACCOUNT_TYPE_MASK &
+ AllowableAccountControlBits )
+ == 0 ) {
+
+ if ( Version == LMNT_MESSAGE ) {
+ Response = LOGON_SAM_USER_UNKNOWN;
+ } else if ( Version == LM20_MESSAGE ) {
+ Response = LOGON_USER_UNKNOWN;
+ }
+ goto Cleanup;
+ }
+
+ //
+ // For SAM clients, respond immediately.
+ //
+
+ if ( Version == LMNT_MESSAGE ) {
+ Response = LOGON_SAM_LOGON_RESPONSE;
+ goto Cleanup;
+
+ //
+ // For LM 2.0 clients, respond immediately.
+ //
+
+ } else if ( Version == LM20_MESSAGE ) {
+ Response = LOGON_RESPONSE2;
+ goto Cleanup;
+
+ //
+ // For LM 1.0 clients,
+ // don't support the request.
+ //
+
+ } else {
+ Response = LOGON_USER_UNKNOWN;
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ //
+ // Always good to debug
+ //
+
+ NlPrint((NL_LOGON,
+ "%s logon mailslot message for " FORMAT_LPWSTR " from \\\\%s"
+ ". Response 0x%lx\n",
+ Version == LMNT_MESSAGE ? "Sam" : "Uas",
+ UnicodeUserName,
+ OemWorkstationName,
+ Response ));
+
+ //
+ // If we should respond to the caller, do so now.
+ //
+
+ switch (Response) {
+ case LOGON_SAM_PAUSE_RESPONSE:
+ case LOGON_SAM_USER_UNKNOWN:
+ case LOGON_SAM_LOGON_RESPONSE:
+ SamResponse.Opcode = Response;
+
+ Where = (PCHAR) SamResponse.UnicodeLogonServer;
+ NetpLogonPutUnicodeString( NlGlobalUncUnicodeComputerName,
+ sizeof(SamResponse.UnicodeLogonServer),
+ &Where );
+ NetpLogonPutUnicodeString( UnicodeUserName,
+ sizeof(SamResponse.UnicodeUserName),
+ &Where );
+ NetpLogonPutUnicodeString( NlGlobalUnicodeDomainName,
+ sizeof(SamResponse.UnicodeDomainName),
+ &Where );
+ NetpLogonPutNtToken( &Where );
+
+ Status = NlBrowserSendDatagram( OemWorkstationName,
+ TransportName,
+ OemMailslotName,
+ &SamResponse,
+ Where - (PCHAR)&SamResponse );
+
+ if ( NT_SUCCESS(Status) ) {
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ }
+ break;
+
+ case LOGON_RESPONSE2:
+ case LOGON_USER_UNKNOWN:
+ case LOGON_PAUSE_RESPONSE:
+
+ Response2.Opcode = Response;
+
+ Where = Response2.LogonServer;
+ (VOID) lstrcpyA( Where, "\\\\");
+ Where += 2;
+ NetpLogonPutOemString( NlGlobalAnsiComputerName,
+ sizeof(Response2.LogonServer) - 2,
+ &Where );
+ NetpLogonPutLM20Token( &Where );
+
+
+ Status = NlBrowserSendDatagram( OemWorkstationName,
+ TransportName,
+ OemMailslotName,
+ &Response2,
+ Where - (PCHAR)&Response2 );
+
+ if ( NT_SUCCESS(Status) ) {
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ }
+ break;
+
+ default:
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ break;
+ }
+
+ //
+ // Free up any locally used resources.
+ //
+
+ if ( UserControl != NULL ) {
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserControl, UserControlInformation );
+ }
+
+ if ( UserHandle != NULL ) {
+ (VOID) SamrCloseHandle( &UserHandle );
+ }
+
+ return IgnoreDuplicatesOfThisMessage;
+
+}
+
+
+BOOLEAN
+PrimaryQueryHandler(
+ IN DWORD Version,
+ IN LPSTR OemWorkstationName,
+ IN LPSTR OemMailslotName,
+ IN LPWSTR TransportName
+ )
+
+/*++
+
+Routine Description:
+
+ Respond appropriately to a primary query request.
+
+Arguments:
+
+ Version - The version of the input message.
+
+ OemWorkstationName - The name of the workstation where the user is
+ logging onto.
+
+ OemMailslotName - The name of the mailslot to respond to.
+
+ TransportName - The name of the transport to respond on.
+
+Return Value:
+
+ TRUE if the any duplicates of this message should be ignored.
+
+--*/
+{
+ NTSTATUS Status;
+ NETLOGON_PRIMARY Response;
+ PCHAR Where;
+ BOOLEAN IgnoreDuplicatesOfThisMessage = FALSE;
+
+ //
+ // If we're a PDC,
+ // always respond.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ goto Respond;
+ }
+
+
+ //
+ // ASSERT: This machine is a BDC
+ //
+
+ NlAssert( NlGlobalRole == RoleBackup );
+
+
+ //
+ // Always respond to this message if this query is from a downlevel
+ // PDC trying to start up.
+ //
+ //
+ // If this request is from NetGetDCName, ignore the request.
+ // We know the NetGetDCName uses a mailslot name that is
+ // randomly generated and that LM2.0 netlogon uses the
+ // standard netlogon mailslot name to find out if another PDC
+ // is already running.
+ //
+
+ if ( Version != LMNT_MESSAGE &&
+ lstrcmpiA( OemMailslotName, NETLOGON_LM_MAILSLOT_A ) == 0 ) {
+
+ NlPrint((NL_CRITICAL,
+ "PrimaryQueryHandler: Preventing Lanman PDC from starting "
+ "in this NT domain. \\\\%s.\n",
+ OemWorkstationName ));
+ goto Respond;
+ }
+
+ //
+ // If the caller is an NT machine,
+ // don't respond to this request.
+ //
+ // NT 3.1 clients have a sophisticated TCP/IP stack which ensures that the
+ // request reaches the real PDC so we don't need to respond.
+ //
+ // NT 3.5 clients, Chicago clients and newer (8/8/94) WFW clients send
+ // directly to the Domain<1B> address which is registered by the PDC.
+ //
+
+ if ( Version == LMNT_MESSAGE || Version == LMWFW_MESSAGE ) {
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ goto Cleanup;
+ }
+
+
+ //
+ // If this machine is on a WAN,
+ // this primary query message may have reached us but not the PDC.
+ // Therefore, we'll respond to the request if we know who the PDC is.
+ //
+
+ if ( *NlGlobalUnicodePrimaryName == L'\0' ) {
+ NlPrint((NL_MAILSLOT,
+ "PrimaryQueryHandler: This BDC doesn't know who primary is." ));
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ goto Cleanup;
+ }
+
+ //
+ // Ensure we have a session up to the PDC.
+ // This is our evidence that the machine we think is the PDC really
+ // is the PDC.
+ //
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint((NL_MAILSLOT,
+ "PrimaryQueryHandler: This BDC doesn't have a session to the PDC.\n" ));
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Respond to the query
+ //
+Respond:
+
+ NlPrint((NL_MAILSLOT,
+ "%s Primary Query mailslot message from %s. "
+ "Response " FORMAT_LPWSTR "\n",
+ Version == LMNT_MESSAGE ? "Sam" : "Uas",
+ OemWorkstationName,
+ NlGlobalUncPrimaryName ));
+
+ //
+ // Build the response
+ //
+ // If we are the Primary DC, tell the caller our computername.
+ // If we are a backup DC,
+ // tell the downlevel PDC who we think the primary is.
+ //
+
+ Response.Opcode = LOGON_PRIMARY_RESPONSE;
+
+ Where = Response.PrimaryDCName;
+ NetpLogonPutOemString(
+ NlGlobalAnsiPrimaryName,
+ sizeof( Response.PrimaryDCName),
+ &Where );
+
+ //
+ // If this is an NT query,
+ // add the NT specific response.
+ //
+ if ( Version == LMNT_MESSAGE ) {
+ NetpLogonPutUnicodeString(
+ NlGlobalUnicodePrimaryName,
+ sizeof(Response.UnicodePrimaryDCName),
+ &Where );
+
+ NetpLogonPutUnicodeString(
+ NlGlobalUnicodeDomainName,
+ sizeof(Response.UnicodeDomainName),
+ &Where );
+
+ NetpLogonPutNtToken( &Where );
+ }
+
+
+ Status = NlBrowserSendDatagram( OemWorkstationName,
+ TransportName,
+ OemMailslotName,
+ &Response,
+ (DWORD)(Where - (PCHAR)&Response) );
+
+ if ( NT_SUCCESS(Status) ) {
+ IgnoreDuplicatesOfThisMessage = TRUE;
+ }
+
+ //
+ // Free Locally used resources
+ //
+Cleanup:
+
+ return IgnoreDuplicatesOfThisMessage;
+}
+
+BOOL
+TimerExpired(
+ IN PTIMER Timer,
+ IN PLARGE_INTEGER TimeNow,
+ IN OUT LPDWORD Timeout
+ )
+
+/*++
+
+Routine Description:
+
+ Determine whether a timer has expired. If not, adjust the passed in
+ timeout value to take this timer into account.
+
+Arguments:
+
+ Timer - Specifies the timer to check.
+
+ TimeNow - Specifies the current time of day in NT standard time.
+
+ Timeout - Specifies the current amount of time (in milliseconds)
+ that the caller intends to wait for a timer to expire.
+ If this timer has not expired, this value is adjusted to the
+ smaller of the current value and the amount of time remaining
+ on the passed in timer.
+
+Return Value:
+
+ TRUE - if the timer has expired.
+
+--*/
+
+{
+ LARGE_INTEGER Period;
+ LARGE_INTEGER ExpirationTime;
+ LARGE_INTEGER ElapsedTime;
+ LARGE_INTEGER TimeRemaining;
+ LARGE_INTEGER MillisecondsRemaining;
+
+/*lint -e569 */ /* don't complain about 32-bit to 31-bit initialize */
+ LARGE_INTEGER BaseGetTickMagicDivisor = { 0xe219652c, 0xd1b71758 };
+/*lint +e569 */ /* don't complain about 32-bit to 31-bit initialize */
+ CCHAR BaseGetTickMagicShiftCount = 13;
+
+ //
+ // If the period to too large to handle (i.e., 0xffffffff is forever),
+ // just indicate that the timer has not expired.
+ //
+
+ if ( Timer->Period > 0x7fffffff ) {
+ return FALSE;
+ }
+
+ //
+ // If time has gone backwards (someone changed the clock),
+ // just start the timer over again.
+ //
+ // The kernel automatically updates the system time to the CMOS clock
+ // periodically. If we just expired the timer when time went backwards,
+ // we'd risk periodically falsely triggering the timeout.
+ //
+
+ ElapsedTime.QuadPart = TimeNow->QuadPart - Timer->StartTime.QuadPart;
+
+ if ( ElapsedTime.QuadPart < 0 ) {
+ Timer->StartTime = *TimeNow;
+ }
+
+ //
+ // Convert the period from milliseconds to 100ns units.
+ //
+
+ Period = RtlEnlargedIntegerMultiply( (LONG) Timer->Period, 10000 );
+
+ //
+ // Compute the expiration time.
+ //
+
+ ExpirationTime.QuadPart = Timer->StartTime.QuadPart + Period.QuadPart;
+
+ //
+ // Compute the Time remaining on the timer.
+ //
+
+ TimeRemaining.QuadPart = ExpirationTime.QuadPart - TimeNow->QuadPart;
+
+ //
+ // If the timer has expired, tell the caller so.
+ //
+
+ if ( TimeRemaining.QuadPart <= 0 ) {
+ return TRUE;
+ }
+
+
+
+ //
+ // If the timer hasn't expired, compute the number of milliseconds
+ // remaining.
+ //
+
+ MillisecondsRemaining = RtlExtendedMagicDivide(
+ TimeRemaining,
+ BaseGetTickMagicDivisor,
+ BaseGetTickMagicShiftCount );
+
+ NlAssert( MillisecondsRemaining.HighPart == 0 );
+ NlAssert( MillisecondsRemaining.LowPart < 0x7fffffff );
+
+ //
+ // Adjust the running timeout to be the smaller of the current value
+ // and the value computed for this timer.
+ //
+
+ if ( *Timeout > MillisecondsRemaining.LowPart ) {
+ *Timeout = MillisecondsRemaining.LowPart;
+ }
+
+ return FALSE;
+
+}
+
+VOID
+NlScavenger(
+ IN LPVOID ScavengerParam
+)
+/*++
+
+Routine Description:
+
+ This function performs the scavenger operation. This function is
+ called every 15 mins interval. On workstation this function is
+ executed in the main netlogon thread, but on server this function is
+ executed on the scavenger thread, thus making the main thread to
+ process the mailslot messages better.
+
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Return iff the service is to exit or all scavenger operations
+ for this turn are completed.
+
+--*/
+{
+
+
+ //
+ // Change password if neccessary
+ //
+
+ if ( (NlGlobalRole == RoleMemberWorkstation ||
+ NlGlobalRole == RoleBackup) &&
+ !NlGlobalDisablePasswordChangeParameter ) {
+
+ (VOID) NlChangePassword( NlGlobalClientSession );
+ }
+
+
+
+ //
+ // Change the password on each entry in the trust list.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ PLIST_ENTRY ListEntry;
+ PCLIENT_SESSION ClientSession;
+
+ //
+ // Reset all the flags indicating we need to check the password
+ //
+
+ LOCK_TRUST_LIST();
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ ClientSession->CsFlags |= CS_CHECK_PASSWORD;
+ }
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ if ( (ClientSession->CsFlags & CS_CHECK_PASSWORD) == 0 ) {
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+ ClientSession->CsFlags &= ~CS_CHECK_PASSWORD;
+
+ NlRefClientSession( ClientSession );
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // Change the password for this trusted domain.
+ //
+
+ (VOID) NlChangePassword( ClientSession );
+
+ NlUnrefClientSession( ClientSession );
+
+ //
+ // check to see iff we have been asked to leave.
+ //
+
+ if( NlGlobalScavengerTerminate == TRUE ) {
+
+ return;
+ }
+
+ LOCK_TRUST_LIST();
+
+ // Start again at the beginning.
+ ListEntry = NlGlobalTrustList.Flink;
+
+ }
+ UNLOCK_TRUST_LIST();
+
+ }
+
+
+ //
+ // Scavenge through the server session table.
+ //
+
+ if ( NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup ) {
+ NlServerSessionScavenger();
+
+ //
+ // Pick a DC for each non-authenicated entry in the trust list.
+ //
+
+ NlPickTrustedDcForEntireTrustList();
+
+ }
+
+ //
+ // Ensure our Domain<1B> name is registered.
+ //
+
+ NlBrowserAddName();
+
+ UNREFERENCED_PARAMETER( ScavengerParam );
+}
+
+
+BOOL
+IsScavengerRunning(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Test if the scavenger thread is running
+
+ Enter with NlGlobalScavengerCritSect locked.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ TRUE - The scavenger thread is running
+
+ FALSE - The scavenger thread is not running.
+
+--*/
+{
+ DWORD WaitStatus;
+
+ //
+ // Determine if the scavenger thread is already running.
+ //
+
+ if ( NlGlobalScavengerThreadHandle != NULL ) {
+
+ //
+ // Time out immediately if the scavenger is still running.
+ //
+
+ WaitStatus = WaitForSingleObject(
+ NlGlobalScavengerThreadHandle,
+ 0 );
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ return TRUE;
+
+ } else if ( WaitStatus == 0 ) {
+ CloseHandle( NlGlobalScavengerThreadHandle );
+ NlGlobalScavengerThreadHandle = NULL;
+ return FALSE;
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "Cannot WaitFor scavenger thread: %ld\n",
+ WaitStatus ));
+ return TRUE;
+ }
+
+ }
+
+ return FALSE;
+}
+
+
+VOID
+NlStopScavenger(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Stops the scavenger thread if it is running and waits for it to
+ stop.
+
+ Enter with NlGlobalScavengerCritSect locked.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ //
+ // Ask the scavenger to stop running.
+ //
+
+ NlGlobalScavengerTerminate = TRUE;
+
+ //
+ // Determine if the scavenger thread is already running.
+ //
+
+ if ( NlGlobalScavengerThreadHandle != NULL ) {
+
+ //
+ // We've asked the scavenger to stop. It should do so soon.
+ // Wait for it to stop.
+ //
+
+ NlWaitForSingleObject( "Wait for scavenger to stop",
+ NlGlobalScavengerThreadHandle );
+
+
+ CloseHandle( NlGlobalScavengerThreadHandle );
+ NlGlobalScavengerThreadHandle = NULL;
+
+ }
+
+ NlGlobalScavengerTerminate = FALSE;
+
+ return;
+}
+
+
+BOOL
+NlStartScavengerThread(
+ )
+/*++
+
+Routine Description:
+
+ Start the scavenger thread if it is not already running.
+
+Arguments:
+ None
+
+Return Value:
+ None
+
+--*/
+{
+ DWORD ThreadHandle;
+
+ //
+ // If the scavenger thread is already running, do nothing.
+ //
+
+ EnterCriticalSection( &NlGlobalScavengerCritSect );
+ if ( IsScavengerRunning() ) {
+ LeaveCriticalSection( &NlGlobalScavengerCritSect );
+ return FALSE;
+ }
+
+ //
+ // Initialize the scavenger parameters
+ //
+
+ NlGlobalScavengerTerminate = FALSE;
+
+ NlGlobalScavengerThreadHandle = CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE)
+ NlScavenger,
+ NULL,
+ 0, // No special creation flags
+ &ThreadHandle );
+
+ if ( NlGlobalScavengerThreadHandle == NULL ) {
+
+ //
+ // ?? Shouldn't we do something in non-debug case
+ //
+
+ NlPrint((NL_CRITICAL, "Can't create scavenger Thread %lu\n",
+ GetLastError() ));
+
+ LeaveCriticalSection( &NlGlobalScavengerCritSect );
+ return FALSE;
+ }
+
+ LeaveCriticalSection( &NlGlobalScavengerCritSect );
+ return TRUE;
+
+}
+
+
+VOID
+NlMainLoop(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+
+ Waits for a logon request to arrive at the NETLOGON mailslot.
+
+ This routine, also, processes several periodic events. These events
+ are timed by computing a timeout value on the mailslot read which is the
+ time needed before the nearest periodic event needs to be processed.
+ After such a timeout, this routine processes the event.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Return iff the service is to exit.
+
+ mail slot error occurred, eg if someone deleted the NETLOGON
+ mail slot explicitly or if the logon server share has been deleted
+ and cannot be re-shared.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ DWORD WaitStatus;
+
+ DWORD BytesRead;
+ LPBYTE Message;
+ LPWSTR TransportName;
+
+ DWORD ResponseBufferSize;
+ // ?? Get these off the workstation stack
+ BYTE resp_buf[NETLOGON_MAX_MS_SIZE]; // Buffer to build response in
+
+ //
+ // Variables for unmarshalling the message read.
+ //
+
+ DWORD Version;
+ DWORD VersionFlags;
+ PCHAR Where;
+ LPSTR OemWorkstationName;
+ LPSTR AnsiUserName;
+ LPSTR OemMailslotName;
+
+ LPWSTR UnicodeWorkstationName;
+ LPWSTR UnicodeUserName;
+
+ LPSTR AnsiTemp;
+
+ LPWSTR UnicodeTemp;
+ BOOLEAN IgnoreDuplicatesOfThisMessage;
+
+
+
+ //
+ // Variables controlling mailslot read timeout
+ //
+
+ DWORD MainLoopTimeout = 0;
+ LARGE_INTEGER TimeNow;
+
+ TIMER ScavengerTimer;
+ TIMER AnnouncerTimer;
+
+#define NL_WAIT_TERMINATE 0
+#define NL_WAIT_TIMER 1
+#define NL_WAIT_MAILSLOT 2
+#define NL_WAIT_NOTIFY 3
+
+#define NL_WAIT_COUNT 4
+
+ HANDLE WaitHandles[ NL_WAIT_COUNT ];
+ DWORD WaitCount = 0;
+
+
+
+ //
+ // Initialize handles to wait on.
+ //
+
+ WaitHandles[NL_WAIT_TERMINATE] = NlGlobalTerminateEvent;
+ WaitCount++;
+ WaitHandles[NL_WAIT_TIMER] = NlGlobalTimerEvent;
+ WaitCount++;
+
+ if ( NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup ) {
+ WaitHandles[NL_WAIT_MAILSLOT] = NlGlobalMailslotHandle;
+ WaitCount++;
+
+ //
+ // When netlogon is run during retail setup
+ // (in an attempt to replicate the databases to a BDC),
+ // the role is Workstation at the instant netlogon.dll is loaded,
+ // therefore, the ChangeLogEvent won't have been initialized.
+ //
+
+ if ( NlGlobalChangeLogEvent != NULL ) {
+ WaitHandles[NL_WAIT_NOTIFY] = NlGlobalChangeLogEvent;
+ WaitCount++;
+ }
+ }
+
+ NlAssert( WaitCount <= NL_WAIT_COUNT );
+
+
+ //
+ // Set up a secure channel to any DC in the domain.
+ // Don't fail if setup is impossible.
+ //
+ // We wait until now since this is a potentially lengthy operation.
+ // If the user on the workstation is trying to logon immediately after
+ // reboot, we'd rather have him wait in netlogon (where we have more
+ // control) than have him waiting in MSV.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ (VOID) NlTimeoutSetWriterClientSession( NlGlobalClientSession, 0xFFFFFFFF );
+ (VOID) NlSessionSetup( NlGlobalClientSession );
+ NlResetWriterClientSession( NlGlobalClientSession );
+ }
+
+
+
+ //
+ // Force the scavenger to start immediately.
+ //
+ // We want the password on the trust account to change immediately
+ // on the very first boot.
+ //
+
+ (VOID) NtQuerySystemTime( &TimeNow );
+
+ ScavengerTimer.StartTime.QuadPart = 0;
+ ScavengerTimer.Period = NlGlobalScavengeIntervalParameter * 1000L;
+
+
+
+ //
+ // Force the announce to happen immediately.
+ //
+ // We use this initial announcement in case the "Primary Start"
+ // message was lost and this is the first boot of a new PDC.
+ // This ensures that all BDCs receive the name of the new PDC quickly
+ // so they respond correctly to "Primary Query" requests.
+ //
+
+ AnnouncerTimer.StartTime.QuadPart = 0;
+ AnnouncerTimer.Period = NlGlobalPulseParameter * 1000L;
+
+
+
+ //
+ // Ensure we don't immediately time out the discovery timer.
+ //
+
+ NlGlobalDcDiscoveryTimer.StartTime = TimeNow;
+ NlGlobalApiTimer.StartTime = TimeNow;
+
+
+
+ NlPrint((NL_INIT, "Started successfully\n" ));
+
+ //
+ // Loop reading from the Netlogon mailslot
+ //
+
+ IgnoreDuplicatesOfThisMessage = FALSE;
+ for ( ;; ) {
+ DWORD Timeout;
+
+
+
+ //
+ // Issue a mailslot read request if we are domain controller and
+ // there is no outstanding read request pending.
+ //
+
+ if (NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup) {
+ NlMailslotPostRead( IgnoreDuplicatesOfThisMessage );
+ IgnoreDuplicatesOfThisMessage = FALSE;
+ }
+
+
+
+
+ //
+ // Wait for the next interesting event.
+ //
+ // On each iteration of the loop,
+ // we do an "extra" wait with a timeout of 0 to force mailslot
+ // processing to be more important that timeout processing.
+ //
+ // Since we can only compute a non-zero timeout by processing the
+ // timeout events, using a constant 0 allows us to process all
+ // non-timeout events before we compute the next true timeout value.
+ //
+ // This is especially important for handling async discovery.
+ // Our mailslot may be full of responses to discovery queries and
+ // we only have a 5 second timer before we ask for more responses.
+ // We want to avoid asking for additional responses until we finish
+ // processing those we have.
+ //
+
+ if ( MainLoopTimeout != 0 ) {
+ NlPrint((NL_MAILSLOT,
+ "Going to wait on mailslot. (Timeout: %ld)\n",
+ MainLoopTimeout));
+ }
+
+ WaitStatus = WaitForMultipleObjects( WaitCount,
+ WaitHandles,
+ FALSE, // Wait for ANY handle
+ MainLoopTimeout );
+
+ MainLoopTimeout = 0; // Set default timeout
+
+
+ //
+ // If we've been asked to terminate,
+ // do so immediately
+ //
+
+ switch ( WaitStatus ) {
+ case NL_WAIT_TERMINATE: // service termination
+ goto Cleanup;
+
+
+ //
+ // Process timeouts and determine the timeout for the next iteration
+ //
+
+ case WAIT_TIMEOUT: // timeout
+ case NL_WAIT_TIMER: // someone changed a timer
+
+ //
+ // Assume there is no timeout to do.
+ //
+
+ Timeout = (DWORD) -1;
+ (VOID) NtQuerySystemTime( &TimeNow );
+
+
+ //
+ // On the primary, timeout announcements to BDCs
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ if ( TimerExpired( &NlGlobalPendingBdcTimer, &TimeNow, &Timeout )) {
+ NlPrimaryAnnouncementTimeout();
+ NlGlobalPendingBdcTimer.StartTime = TimeNow;
+ continue;
+ }
+ }
+
+
+
+
+
+ //
+ // Check the scavenger timer
+ //
+
+ if ( TimerExpired( &ScavengerTimer, &TimeNow, &Timeout ) ) {
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+
+ //
+ // On workstation run the scavenger on main thread.
+ //
+
+ NlScavenger(NULL);
+
+ } else {
+ //
+ // On server, start scavenger thread if it is not
+ // running already.
+ //
+
+ (VOID)NlStartScavengerThread();
+ }
+
+ ScavengerTimer.StartTime = TimeNow;
+ continue;
+ }
+
+
+
+
+
+ //
+ // Check the DC discovery timer.
+ //
+
+ if ( TimerExpired( &NlGlobalDcDiscoveryTimer, &TimeNow, &Timeout)) {
+
+ NlDcDiscoveryExpired( FALSE );
+
+ //
+ // The above operation might have taken a significant fraction
+ // of DISCOVERY_PERIOD. So, set the timer to the current time
+ // rather than TimeNow to allow time for responses to come in.
+ //
+ (VOID) NtQuerySystemTime( &NlGlobalDcDiscoveryTimer.StartTime );
+ continue;
+ }
+
+
+ //
+ // Check the API timer
+ //
+
+ if ( TimerExpired( &NlGlobalApiTimer, &TimeNow, &Timeout)) {
+
+ NlTimeoutApiClientSession();
+ NlGlobalApiTimer.StartTime = TimeNow;
+ continue;
+ }
+
+
+
+
+ //
+ // If we're the primary,
+ // periodically do announcements
+ //
+
+ if (NlGlobalRole == RolePrimary &&
+ TimerExpired( &AnnouncerTimer, &TimeNow, &Timeout ) ) {
+
+ NlPrimaryAnnouncement( 0 );
+ AnnouncerTimer.StartTime = TimeNow;
+ continue;
+ }
+
+ //
+ // If we've gotten this far,
+ // we know the only thing left to do is to wait for the next event.
+ //
+
+ MainLoopTimeout = Timeout;
+ continue;
+
+
+
+
+ //
+ // Process mailslot messages.
+ //
+
+ case NL_WAIT_MAILSLOT: // mailslot message
+
+ if ( !NlMailslotOverlappedResult( &Message,
+ &BytesRead,
+ &TransportName,
+ &IgnoreDuplicatesOfThisMessage )){
+ // Just continue if there really isn't a message
+ continue;
+ }
+
+ break;
+
+
+
+
+ //
+ // Process interesting changelog events.
+ //
+
+ case NL_WAIT_NOTIFY: // Something interesting Logged to change log
+
+
+
+
+ //
+ // If a "replicate immediately" event has happened,
+ // send a primary announcement.
+ //
+ LOCK_CHANGELOG();
+ if ( NlGlobalChangeLogReplicateImmediately ) {
+
+ NlGlobalChangeLogReplicateImmediately = FALSE;
+ NlGlobalChangeLogLanmanReplicateImmediately = FALSE;
+
+ UNLOCK_CHANGELOG();
+
+ //
+ // Ignore this event on BDCs.
+ //
+ // This event is never set on a BDC. It may have been set
+ // prior to the role change while this machine was a PDC.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ NlPrimaryAnnouncement( ANNOUNCE_IMMEDIATE );
+ }
+ LOCK_CHANGELOG();
+ }
+
+ //
+ // If a "replicate immediately to Lanman" event happened,
+ // send a primary announcement to the lanman BDCs.
+ //
+ if ( NlGlobalChangeLogLanmanReplicateImmediately ) {
+
+ NlGlobalChangeLogLanmanReplicateImmediately = FALSE;
+
+ UNLOCK_CHANGELOG();
+
+ //
+ // Ignore this event on BDCs.
+ //
+ // This event is never set on a BDC. It may have been set
+ // prior to the role change while this machine was a PDC.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ NlLanmanPrimaryAnnouncement();
+ }
+ LOCK_CHANGELOG();
+ }
+
+ //
+ // Process any notifications that need processing
+ //
+
+ while ( !IsListEmpty( &NlGlobalChangeLogNotifications ) ) {
+ PLIST_ENTRY ListEntry;
+ PCHANGELOG_NOTIFICATION Notification;
+
+ ListEntry = RemoveHeadList( &NlGlobalChangeLogNotifications );
+ UNLOCK_CHANGELOG();
+
+ Notification = CONTAINING_RECORD(
+ ListEntry,
+ CHANGELOG_NOTIFICATION,
+ Next );
+
+ switch ( Notification->EntryType ) {
+ case ChangeLogLmServerAdded:
+ // This event happens on a PDC only
+ (VOID) NlAddBdcServerSession( Notification->ObjectRid,
+ NULL,
+ SS_BDC | SS_LM_BDC );
+ break;
+
+ case ChangeLogLmServerDeleted:
+ // This event happens on a PDC only
+ NlFreeLmBdcServerSession( Notification->ObjectRid );
+ break;
+
+ case ChangeLogNtServerAdded:
+ // This event happens on a PDC only
+ (VOID) NlAddBdcServerSession( Notification->ObjectRid,
+ &Notification->ObjectName,
+ SS_BDC );
+ break;
+
+ case ChangeLogWorkstationDeleted:
+ case ChangeLogTrustedDomainDeleted:
+ case ChangeLogNtServerDeleted:
+ // This event happens on both a PDC and BDC
+ NlFreeServerSessionForAccount( &Notification->ObjectName );
+ break;
+
+ case ChangeLogTrustAdded:
+ case ChangeLogTrustDeleted:
+ if ( NlGlobalRole == RolePrimary ) {
+ NlUpdateTrustListBySid( Notification->ObjectSid, NULL );
+ }
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL,
+ "Invalid ChangeLogNotification: %ld %wZ\n",
+ Notification->EntryType,
+ &Notification->ObjectName ));
+
+ }
+
+ NetpMemoryFree( Notification );
+ LOCK_CHANGELOG();
+ }
+
+ UNLOCK_CHANGELOG();
+ continue;
+
+
+ default:
+ NetStatus = GetLastError();
+ NlExit(NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
+ goto Cleanup;
+ }
+
+
+
+
+ //
+ // ASSERT: Message and BytesRead describe a newly read message
+ //
+ //
+ // Got a message. Check for bad length just in case.
+ //
+
+ if (BytesRead < sizeof(unsigned short) ) {
+ NlPrint((NL_CRITICAL,"message size bad %ld\n", BytesRead ));
+ continue; // Need at least an opcode
+ }
+
+ //
+ // Here with a request to process in the Message.
+ //
+
+ Version = NetpLogonGetMessageVersion( Message, &BytesRead, &VersionFlags );
+
+ if (Version == LMUNKNOWNNT_MESSAGE) {
+
+ //
+ // received a non-supported NT message.
+ //
+
+ NlPrint((NL_CRITICAL,
+ "Received a non-supported NT message, Opcode is 0x%x\n",
+ ((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
+
+ continue;
+ }
+
+
+ switch ( ((PNETLOGON_LOGON_QUERY)Message)->Opcode) {
+
+ //
+ // Handle a logon request from a UAS client
+ //
+
+ case LOGON_REQUEST: {
+ USHORT RequestCount;
+
+ //
+ // Unmarshall the incoming message.
+ //
+
+ if ( Version == LMNT_MESSAGE ) {
+ break;
+ }
+
+ Where = ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName;
+ if ( !NetpLogonGetOemString(
+ (PNETLOGON_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName),
+ &OemWorkstationName )) {
+ break;
+ }
+ if ( !NetpLogonGetOemString(
+ (PNETLOGON_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->UserName),
+ &AnsiUserName )) {
+ break;
+ }
+ if ( !NetpLogonGetOemString(
+ (PNETLOGON_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->MailslotName),
+ &OemMailslotName )) {
+ break;
+ }
+
+ // LM 2.x puts request count right before token
+ Where = Message + BytesRead - 2;
+ if ( !NetpLogonGetBytes(
+ (PNETLOGON_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->RequestCount),
+ &RequestCount )) {
+ break;
+ }
+
+ //
+ // Handle the logon request
+ //
+
+ UnicodeUserName = NetpLogonOemToUnicode( AnsiUserName );
+ if ( UnicodeUserName == NULL ) {
+ break;
+ }
+
+ IgnoreDuplicatesOfThisMessage = LogonRequestHandler(
+ Version,
+ UnicodeUserName,
+ RequestCount,
+ OemWorkstationName,
+ OemMailslotName,
+ TransportName,
+ USER_NORMAL_ACCOUNT );
+
+ NetpMemoryFree( UnicodeUserName );
+
+
+ break;
+ }
+
+ //
+ // Handle a logon request from a SAM client
+ //
+
+ case LOGON_SAM_LOGON_REQUEST: {
+ USHORT RequestCount;
+ ULONG AllowableAccountControlBits;
+
+ //
+ // Unmarshall the incoming message.
+ //
+
+
+ if ( Version != LMNT_MESSAGE ) {
+ break;
+ }
+
+ RequestCount = ((PNETLOGON_SAM_LOGON_REQUEST)Message)->RequestCount;
+
+ Where = (PCHAR)
+ (((PNETLOGON_SAM_LOGON_REQUEST)Message)->UnicodeComputerName);
+
+ if ( !NetpLogonGetUnicodeString(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
+ UnicodeComputerName),
+ &UnicodeWorkstationName )) {
+ break;
+ }
+ if ( !NetpLogonGetUnicodeString(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
+ UnicodeUserName),
+ &UnicodeUserName )) {
+ break;
+ }
+ if ( !NetpLogonGetOemString(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
+ MailslotName),
+ &OemMailslotName )) {
+ break;
+ }
+ if ( !NetpLogonGetBytes(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
+ AllowableAccountControlBits),
+ &AllowableAccountControlBits )) {
+ break;
+ }
+
+ //
+ // compare it with primary domain id.
+ //
+ // Don't make the following check mandatory. Chicago is
+ // considering using this message type. Oct 1993.
+ //
+
+
+ if( Where < ((PCHAR)Message + BytesRead ) ) {
+
+ DWORD DomainSidSize;
+
+ //
+ // Read Domain SID Length
+ //
+
+ if ( !NetpLogonGetBytes(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
+ DomainSidSize),
+ &DomainSidSize )) {
+
+ break;
+
+ }
+
+
+ //
+ // get and compare SID
+ //
+
+ if( DomainSidSize > 0 ) {
+
+ PCHAR DomainSid;
+
+ if ( !NetpLogonGetDomainSID(
+ (PNETLOGON_SAM_LOGON_REQUEST)Message,
+ BytesRead,
+ &Where,
+ DomainSidSize,
+ &DomainSid )) {
+
+ break;
+ }
+
+ //
+ // compare domain SIDs
+ //
+
+ if( !RtlEqualSid( NlGlobalPrimaryDomainId, DomainSid ) ) {
+
+ LPWSTR AlertStrings[4];
+
+ //
+ // alert admin.
+ //
+
+ AlertStrings[0] = UnicodeWorkstationName;
+ AlertStrings[1] = NlGlobalUnicodeComputerName;
+ AlertStrings[2] = NlGlobalUnicodeDomainName;
+ AlertStrings[3] = NULL;
+
+ RaiseAlert( ALERT_NetLogonUntrustedClient,
+ AlertStrings );
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonUntrustedClient,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ AlertStrings,
+ 3 );
+
+ break;
+ }
+ }
+ }
+
+ OemWorkstationName =
+ NetpLogonUnicodeToOem( UnicodeWorkstationName );
+
+ if( OemWorkstationName == NULL ) {
+
+ NlPrint((NL_CRITICAL,
+ "Out of memory to send logon response\n"));
+ break;
+ }
+
+ //
+ // Handle the logon request
+ //
+
+ IgnoreDuplicatesOfThisMessage = LogonRequestHandler(
+ Version,
+ UnicodeUserName,
+ RequestCount,
+ OemWorkstationName,
+ OemMailslotName,
+ TransportName,
+ AllowableAccountControlBits );
+
+ NetpMemoryFree( OemWorkstationName );
+
+ break;
+ }
+
+ //
+ // Handle Logon Central query.
+ //
+ // This query could be sent by either LM1.0, LM 2.0 or LM NT Netlogon
+ // services. We ignore LM 2.0 and LM NT queries since they are merely
+ // trying
+ // to find out if there are any LM1.0 netlogon services in the domain.
+ // For LM 1.0 we respond with a LOGON_CENTRAL_RESPONSE to prevent the
+ // starting LM1.0 netlogon service from starting.
+ //
+
+ case LOGON_CENTRAL_QUERY:
+
+ if ( Version != LMUNKNOWN_MESSAGE ) {
+ break;
+ }
+
+ //
+ // Drop on through to LOGON_DISTRIB_QUERY to send the response
+ //
+
+
+ //
+ // Handle a Logon Disrib query
+ //
+ // LM2.0 NETLOGON server never sends this query hence it
+ // must be another LM1.0 NETLOGON server trying to start up
+ // in non-centralized mode. LM2.0 NETLOGON server will respond
+ // with LOGON_CENTRAL_RESPONSE to prevent this.
+ //
+
+ case LOGON_DISTRIB_QUERY:
+
+
+ //
+ // Unmarshall the incoming message.
+ //
+
+ Where = ((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
+ if ( !NetpLogonGetOemString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
+ &OemWorkstationName )) {
+ break;
+ }
+ if ( !NetpLogonGetOemString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
+ &OemMailslotName )) {
+ break;
+ }
+
+ //
+ // Build the response
+ //
+
+ ((PNETLOGON_LOGON_QUERY)resp_buf)->Opcode = LOGON_CENTRAL_RESPONSE;
+ ResponseBufferSize = sizeof( unsigned short); // opcode only
+
+ (VOID) NlBrowserSendDatagram( OemWorkstationName,
+ TransportName,
+ OemMailslotName,
+ resp_buf,
+ ResponseBufferSize );
+
+ break;
+
+
+ //
+ // Handle LOGON_PRIMARY_QUERY
+ //
+ // If we're the PDC, always respond to this message
+ // identifying ourselves.
+ //
+ // Otherwise, only respond to the message if it is from a downlevel
+ // netlogon trying to see if it can start up as a PDC. In that
+ // case, pretend we are a PDC to prevent the downlevel PDC from
+ // starting.
+ //
+ //
+
+ case LOGON_PRIMARY_QUERY:
+
+
+ //
+ // Unmarshall the incoming message.
+ //
+
+
+ Where =((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
+ if ( !NetpLogonGetOemString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
+ &OemWorkstationName )) {
+
+ break;
+ }
+ if ( !NetpLogonGetOemString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
+ &OemMailslotName )) {
+ break;
+ }
+
+ //
+ // Handle the primary query request
+ //
+
+ IgnoreDuplicatesOfThisMessage =
+ PrimaryQueryHandler( Version,
+ OemWorkstationName,
+ OemMailslotName,
+ TransportName );
+
+
+ break;
+
+
+ //
+ // Handle LOGON_FAIL_PRIMARY
+ //
+
+ case LOGON_FAIL_PRIMARY:
+
+ //
+ // If we are the primary,
+ // let everyone know we are really alive.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ // Send a primary start to the LM BDCs
+ NlAnnouncePrimaryStart();
+ // Send a UAS_CHANGE to everyone.
+ NlPrimaryAnnouncement( 0 );
+ break;
+ }
+
+ break;
+
+
+ //
+ // Handle LOGON_UAS_CHANGE
+ //
+
+ case LOGON_UAS_CHANGE:
+
+
+ //
+ // Only accept messages from an NT PDC.
+ //
+
+ if ( Version != LMNT_MESSAGE ) {
+ break;
+ }
+
+ //
+ // Unblock replication thread if neccessary
+ //
+
+ (VOID) NlCheckUpdateNotices(
+ (PNETLOGON_DB_CHANGE)Message,
+ BytesRead );
+
+ break;
+
+
+
+
+
+ //
+ // Handle LOGON_START_PRIMARY
+ //
+ //
+ // We may be here under any of these three cases:
+ // 1) A Primary is coming up for the very first time.
+ // 2) A previous primary went down and the
+ // same or a new primary is starting.
+ //
+
+ case LOGON_START_PRIMARY:
+
+ //
+ // Ignore our own broadcast.
+ //
+
+ if (NlGlobalRole == RolePrimary) {
+ break;
+ }
+
+
+ //
+ // Unmarshall the message.
+ //
+
+ if ( Version != LMNT_MESSAGE ) {
+ break;
+ }
+
+
+ Where =((PNETLOGON_PRIMARY)Message)->PrimaryDCName;
+ if ( !NetpLogonGetOemString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_PRIMARY)Message)->PrimaryDCName ),
+ &AnsiTemp )) {
+ break;
+ }
+
+ if ( !NetpLogonGetUnicodeString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof( ((PNETLOGON_PRIMARY)Message)->UnicodePrimaryDCName),
+ &UnicodeTemp )) {
+ break;
+ }
+
+ //
+ // If the domain name is in the message,
+ // ensure this message is from correct domain.
+ //
+
+ if( Where < ((PCHAR)Message + BytesRead ) ) {
+
+ LPWSTR UnicodeDomainName;
+
+ if ( !NetpLogonGetUnicodeString(
+ Message,
+ BytesRead,
+ &Where,
+ sizeof(((PNETLOGON_PRIMARY)Message)->UnicodeDomainName),
+ &UnicodeDomainName )) {
+
+ NlPrint((NL_CRITICAL,
+ FORMAT_LPWSTR
+ ": Primary Start message had invalid domain name.\n",
+ UnicodeTemp ));
+
+ break;
+ }
+
+ if ( NlNameCompare( UnicodeDomainName,
+ NlGlobalUnicodeDomainName,
+ NAMETYPE_DOMAIN ) != 0 ) {
+
+ NlPrint((NL_CRITICAL,
+ FORMAT_LPWSTR
+ ": Primary Start message from wrong domain "
+ FORMAT_LPWSTR "\n",
+ UnicodeTemp,
+ UnicodeDomainName ));
+
+ break;
+ }
+
+
+ NlPrint((NL_MAILSLOT,
+ FORMAT_LPWSTR
+ ": Primary Start message from correct domain "
+ FORMAT_LPWSTR "\n",
+ UnicodeTemp,
+ UnicodeDomainName ));
+
+ }
+
+ //
+ // Set up a session with the new primary.
+ //
+
+ (VOID) NlNewSessionSetup( UnicodeTemp );
+
+ break;
+
+
+
+ //
+ // Handle DC discovery responses
+ //
+
+ case LOGON_SAM_LOGON_RESPONSE:
+ case LOGON_SAM_USER_UNKNOWN:
+ case LOGON_SAM_PAUSE_RESPONSE:
+
+ //
+ // Only accept messages from an NT PDC.
+ //
+
+ if ( Version != LMNT_MESSAGE ) {
+ break;
+ }
+
+
+ NlDcDiscoveryHandler(
+ (PNETLOGON_SAM_LOGON_RESPONSE)Message,
+ BytesRead,
+ TransportName,
+ Version );
+
+ break;
+
+ //
+ // Messages used for NetLogonEnum support.
+ //
+ // Simply ignore the messages
+ //
+
+ case LOGON_NO_USER:
+ case LOGON_RELOGON_RESPONSE:
+ case LOGON_WKSTINFO_RESPONSE:
+ case LOGON_SAM_WKSTINFO_RESPONSE:
+
+ break;
+
+
+ //
+ // Handle unidentified opcodes
+ //
+
+ default:
+
+ //
+ // Unknown request, continue for re-issue of read.
+ //
+
+ NlPrint((NL_CRITICAL,
+ "Unknown op-code in mailslot message 0x%x\n",
+ ((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
+
+ break;
+ }
+
+ }
+
+Cleanup:
+
+ return;
+}
+
+
+int
+NlNetlogonMain(
+ IN DWORD argc,
+ IN LPWSTR *argv
+ )
+
+/*++
+
+Routine Description:
+
+ Main routine for Netlogon service.
+
+ This routine initializes the netlogon service. This thread becomes
+ the thread that reads logon mailslot messages.
+
+Arguments:
+
+ argc, argv - Command line arguments for the service.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ PDB_INFO DBInfo;
+ DWORD i;
+
+
+
+ //
+ // Initialize all global variable.
+ //
+ // We can't rely on this happening at load time since this address
+ // space is shared by other services.
+ //
+
+ NlGlobalAnsiPrimaryName[0] = '\0';
+ NlGlobalUncPrimaryName[0] = L'\0';
+ NlGlobalUnicodePrimaryName = NlGlobalUncPrimaryName;
+ NlGlobalUasCompatibilityMode = TRUE;
+ // NlGlobalInfiniteTime.HighPart = 0x7FFFFFFF;
+ // NlGlobalInfiniteTime.LowPart = 0xFFFFFFFF;
+ NlGlobalMailslotHandle = NULL;
+ NlGlobalRpcServerStarted = FALSE;
+ NlGlobalAnsiComputerName = NULL;
+ NlGlobalUncUnicodeComputerName[0] = L'\0';
+ NlGlobalUnicodeComputerName = NlGlobalUncUnicodeComputerName;
+ RtlInitUnicodeString( &NlGlobalUnicodeComputerNameString, NULL );
+ NlGlobalAnsiDomainName = NULL;
+ NlGlobalUnicodeDomainName[0] = L'\0';
+ NlGlobalPrimaryDomainId = NULL;
+ NlGlobalSamServerHandle = NULL;
+ NlGlobalPolicyHandle = NULL;
+ NlGlobalRole = RoleMemberWorkstation;
+ NlGlobalUnicodeScriptPath[0] = L'\0';
+ NlGlobalPulseParameter = DEFAULT_PULSE;
+ NlGlobalRandomizeParameter = DEFAULT_RANDOMIZE;
+ NlGlobalSynchronizeParameter = DEFAULT_SYNCHRONIZE;
+ NlGlobalPulseMaximumParameter = DEFAULT_PULSEMAXIMUM;
+ NlGlobalPulseConcurrencyParameter = DEFAULT_PULSECONCURRENCY;
+ NlGlobalPulseTimeout1Parameter = DEFAULT_PULSETIMEOUT1;
+ NlGlobalPulseTimeout2Parameter = DEFAULT_PULSETIMEOUT2;
+ NlGlobalNetlogonSecurityDescriptor = NULL;
+ NlGlobalTooManyGlobalGroups = FALSE;
+
+
+ NlGlobalServerSessionHashTable = NULL;
+ InitializeListHead( &NlGlobalServerSessionTable );
+ InitializeListHead( &NlGlobalBdcServerSessionList );
+ NlGlobalBdcServerSessionCount = 0;
+
+ NlGlobalTransportList = NULL;
+ NlGlobalTransportCount = 0;
+
+ InitializeListHead( &NlGlobalPendingBdcList );
+ NlGlobalPendingBdcCount = 0;
+ NlGlobalPendingBdcTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+
+ InitializeListHead( &NlGlobalTrustList );
+ NlGlobalTrustListLength = 0;
+
+ NlGlobalSSICritSectInit = FALSE;
+ NlGlobalTerminateEvent = NULL;
+ NlGlobalReplicatorTerminateEvent = NULL;
+ NlGlobalStartedEvent = NULL;
+ NlGlobalTimerEvent = NULL;
+
+ NlGlobalServiceHandle = (SERVICE_STATUS_HANDLE) NULL;
+
+ NlGlobalServiceStatus.dwServiceType = SERVICE_WIN32;
+ NlGlobalServiceStatus.dwCurrentState = SERVICE_START_PENDING;
+ NlGlobalServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
+ SERVICE_ACCEPT_PAUSE_CONTINUE;
+ NlGlobalServiceStatus.dwCheckPoint = 0;
+ NlGlobalServiceStatus.dwWaitHint = NETLOGON_INSTALL_WAIT;
+
+ SET_SERVICE_EXITCODE(
+ NO_ERROR,
+ NlGlobalServiceStatus.dwWin32ExitCode,
+ NlGlobalServiceStatus.dwServiceSpecificExitCode
+ );
+
+ NlGlobalClientSession = NULL;
+ NlGlobalTrustedDomainList = NULL;
+ NlGlobalTrustedDomainCount = 0;
+ NlGlobalTrustedDomainListKnown = FALSE;
+ NlGlobalTrustedDomainListTime.QuadPart = 0;
+ NlGlobalDcDiscoveryCount = 0;
+ NlGlobalDcDiscoveryTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+ NlGlobalBindingHandleCount = 0;
+ NlGlobalApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+
+#if DBG
+ NlGlobalTrace = 0;
+ NlGlobalLogFile = INVALID_HANDLE_VALUE;
+ NlGlobalDebugSharePath = NULL;
+#endif // DBG
+
+
+ for( i = 0, DBInfo = &NlGlobalDBInfoArray[0];
+ i < NUM_DBS;
+ i++, DBInfo++ ) {
+
+ RtlZeroMemory( DBInfo, sizeof(*DBInfo) );
+
+ // Force a partial sync on all databases to let the PDC know
+ // what our serial number is.
+ DBInfo->UpdateRqd = TRUE;
+
+ }
+ NlGlobalFirstTimeFullSync = FALSE;
+
+ NlGlobalScavengerThreadHandle = NULL;
+ NlGlobalScavengerTerminate = FALSE;
+
+ InitChangeLogDesc( &NlGlobalRedoLogDesc )
+ NlGlobalRedoLogDesc.RedoLog = TRUE;
+
+
+
+ //
+ // Setup things needed before NlExit can be called
+ //
+
+ NlGlobalTerminate = FALSE;
+
+ NlGlobalTerminateEvent = CreateEvent( NULL, // No security attributes
+ TRUE, // Must be manually reset
+ FALSE, // Initially not signaled
+ NULL ); // No name
+
+ if ( NlGlobalTerminateEvent == NULL ) {
+ NetStatus = GetLastError();
+ NlPrint((NL_CRITICAL, "Cannot create termination Event %lu\n",
+ NetStatus ));
+ return (int) NetStatus;
+ }
+
+
+ //
+ // Initialize trust table crit sect.
+ //
+
+ InitializeCriticalSection( &NlGlobalTrustListCritSect );
+ InitializeCriticalSection( &NlGlobalReplicatorCritSect );
+ InitializeCriticalSection( &NlGlobalDbInfoCritSect );
+ InitializeCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+
+ //
+ // Initialize scavenger thread crit sect.
+ //
+
+ InitializeCriticalSection( &NlGlobalScavengerCritSect );
+
+
+ //
+ // Tell the service controller we've started.
+ //
+ // ?? - Need to set up security descriptor.
+ //
+
+ NlPrint((NL_INIT,"Calling RegisterServiceCtrlHandler\n"));
+
+ NlGlobalServiceHandle =
+ RegisterServiceCtrlHandler( SERVICE_NETLOGON, NlControlHandler);
+
+ if (NlGlobalServiceHandle == (SERVICE_STATUS_HANDLE) NULL) {
+ LPWSTR MsgStrings[1];
+
+ NetStatus = GetLastError();
+
+ NlPrint((NL_CRITICAL, "RegisterServiceCtrlHandler failed %lu\n",
+ NetStatus ));
+
+ MsgStrings[0] = (LPWSTR) NetStatus;
+
+ NlpWriteEventlog (NELOG_NetlogonFailedToRegisterSC,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) &NetStatus,
+ sizeof(NetStatus),
+ MsgStrings,
+ 1 | LAST_MESSAGE_IS_NETSTATUS );
+
+ return (int) NetStatus;
+ }
+
+ if ( !GiveInstallHints( FALSE ) ) {
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Nlparse the command line (.ini) arguments
+ // it will set globals reflecting switch settings
+ //
+
+ if (! Nlparse() ) {
+ goto Cleanup;
+ }
+
+ NlPrint((NL_INIT,"Command line parsed successfully ...\n"));
+
+#ifdef notdef
+#if DBG
+ if ( NlGlobalTrace == 0) {
+ NlGlobalTrace = 0x2284FFFF;
+ }
+#endif // DBG
+#endif // notdef
+
+
+
+ //
+ // Enter the debugger.
+ //
+ // Wait 'til now since we don't want the service controller to time us out.
+ //
+
+
+ IF_DEBUG( BREAKPOINT ) {
+ DbgBreakPoint( );
+ }
+
+
+
+ //
+ // Do startup checks, initialize data structs and do prelim setups
+ //
+
+ if ( !NlInit() ) {
+ goto Cleanup;
+ }
+
+
+
+
+
+ //
+ // Loop till the service is to exit.
+ //
+
+ NlMainLoop();
+
+ //
+ // Common exit point
+ //
+
+Cleanup:
+
+ //
+ // Cleanup and return to our caller.
+ //
+
+ return (int) NlCleanup();
+ UNREFERENCED_PARAMETER( argc );
+ UNREFERENCED_PARAMETER( argv );
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/netlogon.def b/private/net/svcdlls/logonsrv/server/netlogon.def
new file mode 100644
index 000000000..9afd4c24e
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/netlogon.def
@@ -0,0 +1,15 @@
+LIBRARY NETLOGON
+
+DESCRIPTION 'NETLOGON'
+
+EXPORTS
+
+ I_NetNotifyRole
+ I_NetNotifyDelta
+ I_NetNotifyMachineAccount
+ I_NetGetAnyDCName
+ NlNetlogonMain
+ NetrLogonSamLogon
+ NetrLogonSamLogoff
+
+DATA SINGLE SHARED
diff --git a/private/net/svcdlls/logonsrv/server/netlogon.prf b/private/net/svcdlls/logonsrv/server/netlogon.prf
new file mode 100644
index 000000000..b631fe81f
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/netlogon.prf
@@ -0,0 +1,507 @@
+NlComputeChallenge@4
+NlTimeHasElapsed@12
+NlSessionSetup@8
+NlStartApiClientSession@8
+NlComputeCredentials@12
+NlWaitForSingleObject@8
+NlResetWriterClientSession@4
+NlBuildAuthenticator@12
+NlUpdateSeed@12
+NlpDecryptLogonInformation@12
+NlpEncryptLogonInformation@12
+NlpUserValidateHigher@24
+NlpUserValidate@24
+NetrLogonSamLogon@36
+NlFinishApiClientSession@8
+NlRefClientSession@4
+NlUnrefClientSession@4
+NlFindNamedClientSession@4
+NetlogonDllInit@12
+NlpDecryptValidationInformation@16
+TimerExpired@12
+NlOpenSecret@12
+NlTimeToReauthenticate@4
+NlMakeSessionKey@16
+NlSetStatusClientSession@8
+logon_NetrLogonControl2@4
+_fgs__NETLOGON_INFO_2@4
+_fgu__NETLOGON_CONTROL_DATA_INFORMATION@8
+NetrLogonControl2@20
+NlTimeoutOneApiClientSession@4
+NlTimeoutApiClientSession@0
+NetpCreateWellKnownSids@4
+NetpFreeWellKnownSids@0
+NetpAllocateAndInitializeSid@12
+NetpDomainIdToSid@12
+NetpCreateSecurityDescriptor@20
+NetpCreateSecurityObject@24
+NetpDeleteSecurityObject@4
+NetpInitializeAllowedAce@24
+NetpInitializeDeniedAce@24
+NetpInitializeAuditAce@24
+NetpAccessCheckAndAudit@20
+DbgPrint
+DosGetMessage@28
+DosInsMessage@28
+DosPutMessage@12
+MyAllocUnicode@8
+MyAllocUnicodeVector@12
+MyFreeUnicode@4
+MyFreeUnicodeVector@8
+I_BrowserResetNetlogonState@4
+I_NetDatabaseDeltas@32
+I_NetDatabaseSync@32
+I_NetLogonSamLogoff@24
+I_NetLogonSamLogon@36
+I_NetNameCompare@20
+I_NetPathCanonicalize@28
+I_NetServerAuthenticate@24
+I_NetServerAuthenticate2@28
+I_NetServerPasswordSet@28
+I_NetServerReqChallenge@16
+LsaIEnumerateSecrets@20
+LsaIFree_LSAPR_ACCOUNT_ENUM_BUFFER@4
+LsaIFree_LSAPR_CR_CIPHER_VALUE@4
+LsaIFree_LSAPR_POLICY_INFORMATION@8
+LsaIFree_LSAPR_PRIVILEGE_SET@4
+LsaIFree_LSAPR_SR_SECURITY_DESCRIPTOR@4
+LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO@8
+LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER@4
+LsaIFree_LSAPR_UNICODE_STRING@4
+LsaIOpenPolicyTrusted@4
+LsaISetSerialNumberPolicy@16
+LsaISetTimesSecret@12
+LsarAddPrivilegesToAccount@8
+LsarClose@4
+LsarCreateAccount@16
+LsarCreateSecret@16
+LsarCreateTrustedDomain@16
+LsarDelete@4
+LsarEnumerateAccounts@16
+LsarEnumeratePrivilegesAccount@8
+LsarEnumerateTrustedDomains@16
+LsarGetQuotasForAccount@8
+LsarGetSystemAccessAccount@8
+LsarLookupPrivilegeName@12
+LsarLookupPrivilegeValue@12
+LsarOpenAccount@16
+LsarOpenSecret@16
+LsarOpenTrustedDomain@16
+LsarQueryInfoTrustedDomain@12
+LsarQueryInformationPolicy@12
+LsarQuerySecret@20
+LsarQuerySecurityObject@12
+LsarRemovePrivilegesFromAccount@12
+LsarSetInformationPolicy@12
+LsarSetInformationTrustedDomain@12
+LsarSetQuotasForAccount@8
+LsarSetSecret@12
+LsarSetSecurityObject@12
+LsarSetSystemAccessAccount@8
+MIDL_user_allocate@4
+MIDL_user_free@4
+MIDL_user_reallocate@8
+MIDL_user_size@4
+MsvSamLogoff@12
+MsvSamValidate@52
+NetAlertRaiseEx@16
+NetApiBufferAllocate@8
+NetApiBufferFree@4
+NetGetDCName@12
+NetServerGetInfo@12
+NetShareAdd@16
+NetShareDel@12
+NetShareGetInfo@16
+NetUseDel@12
+NetapipBufferAllocate@8
+NetpGetAllowedAce@12
+NetpAccountControlToFlags@8
+NetpDeltaTimeToSeconds@8
+NetpSecondsToDeltaTime@4
+NetpAliasMemberToPriv@16
+NetpGetElapsedSeconds@4
+NetpConvertWorkstationList@4
+NetpAllocStringFromTStr@4
+NetpAllocStrFromStr@4
+NetpAllocStrFromWStr@4
+NetpAllocTStrFromString@8
+NetpAllocWStrFromStr@4
+NetpAllocWStrFromWStr@4
+NetpPackString@12
+NetpCopyStringToBuffer@20
+NetpCopyDataToBuffer@24
+NetpAllocateEnumBuffer@24
+NetpNtStatusToApiStatus@4
+NetpApiStatusToNtStatus@4
+NetpAssertFailed@16
+NetpDbgPrint
+NetpHexDump@8
+NetpCloseConfigData@4
+NetpCopyStringToTStr@8
+NetpCopyWStrToStr@8
+NetpCopyStrToWStr@8
+NetpNCopyStrToWStr@12
+NetpNCopyWStrToStr@12
+NetpGetConfigBool@16
+NetpGetConfigDword@16
+NetpGetWinRegConfigMaxSizes@12
+NetpGetConfigMaxSizes@12
+NetpGetConfigValue@12
+NetpExpandConfigString@12
+NetpInitOemString@8
+NetpIsRemote@16
+NetpIsServiceStarted@4
+NetpIsRemoteServiceStarted@8
+NetpGetPrimaryGroupFromMacField@8
+NetpIsComputerNameValid@4
+NetpIsLocalNameValid@4
+NetpCanonRemoteName@4
+NetpIsGroupNameValid@4
+NetpIsMacPrimaryGroupFieldValid@4
+NetpIsPrintQueueNameValid@4
+NetpIsRemoteNameValid@4
+NetpIsUncComputerNameValid@4
+NetpNamesMatch@8
+NetpIsUserNameValid@4
+I_NetNameCanonicalize@24
+NetpLogonPutOemString@12
+NetpLogonPutUnicodeString@12
+NetpLogonPutDomainSID@12
+NetpLogonPutBytes@12
+NetpLogonPutDBInfo@8
+NetpLogonGetMessageVersion@12
+NetpLogonGetOemString@20
+NetpLogonGetUnicodeString@20
+NetpLogonGetDomainSID@20
+NetpLogonGetBytes@20
+NetpLogonGetDBInfo@16
+NetpLogonOemToUnicode@4
+NetpLogonUnicodeToOem@4
+NetpLogonWriteMailslot@12
+NetpLogonCreateRandomMailslot@8
+NetpLogonGetDCName@16
+NetpMemoryAllocate@4
+NetpMemoryFree@4
+NetpMemoryReallocate@8
+NetpOpenConfigData@16
+NetpOpenConfigDataEx@20
+NetpRotateLogonHoursPhase1@8
+NetpRotateLogonHoursPhase2@12
+NetpRotateLogonHours@12
+NetpWriteEventlog@28
+NetpwPathCompare@16
+NetpwPathCanonicalize@24
+CanonicalizePathName@20
+NetpwPathType@12
+GetToken@16
+NtAccessCheckAndAuditAlarm@44
+NtClose@4
+NtCreateEvent@20
+NtOpenEvent@12
+NtOpenProcessToken@12
+NtOpenThreadToken@16
+NtQueryInformationToken@20
+NtQuerySystemTime@4
+NtResetEvent@8
+NtSetEvent@8
+RtlAddAce@20
+RtlAllocateAndInitializeSid@44
+RtlAssert@16
+RtlCompareMemory@12
+RtlConvertSidToUnicodeString@12
+RtlCopySid@12
+RtlCreateAcl@12
+RtlCreateEnvironment@8
+RtlCreateSecurityDescriptor@8
+RtlDeleteSecurityObject@4
+RtlDestroyEnvironment@4
+RtlDetermineDosPathNameType_U@4
+RtlEqualComputerName@8
+RtlEqualDomainName@8
+RtlEqualSid@8
+RtlEqualUnicodeString@12
+RtlExpandEnvironmentStrings_U@16
+RtlExtendedIntegerMultiply@12
+RtlExtendedMagicDivide@20
+RtlFreeOemString@4
+RtlFreeUnicodeString@4
+RtlGetAce@12
+RtlGetDaclSecurityDescriptor@16
+RtlGetNtProductType@4
+RtlInitAnsiString@8
+RtlInitString@8
+RtlInitUnicodeString@8
+RtlInitializeSid@12
+RtlIntegerToChar@16
+RtlIsDosDeviceName_U@4
+RtlLengthRequiredSid@4
+RtlLengthSid@4
+RtlNewSecurityObject@24
+RtlNtStatusToDosError@4
+RtlOemStringToUnicodeString@12
+RtlQueryInformationAcl@16
+RtlQueryTimeZoneInformation@4
+RtlSetDaclSecurityDescriptor@16
+RtlSetEnvironmentVariable@12
+RtlSetGroupSecurityDescriptor@12
+RtlSetOwnerSecurityDescriptor@12
+RtlSetSaclSecurityDescriptor@16
+RtlSubAuthorityCountSid@4
+RtlSubAuthoritySid@8
+RtlSystemTimeToLocalTime@8
+RtlTimeToSecondsSince1970@8
+RtlTimeToSecondsSince1980@8
+RtlUnicodeStringToOemString@12
+_except_handler2
+_itoa
+_wcsicmp
+_wcsnicmp
+_wcsupr
+fprintf
+iswctype
+memmove
+rand
+sprintf
+srand
+strncmp
+strncpy
+time
+towupper
+vsprintf
+wcscat
+wcschr
+wcscmp
+wcscpy
+wcscspn
+wcslen
+wcsncmp
+wcsncpy
+wcsspn
+wtol@4
+logon_NetrLogonSamLogon@4
+_fgs__STRING@4
+_fgs__UNICODE_STRING@4
+_fgs__NETLOGON_LOGON_IDENTITY_INFO@4
+_fgs__NETLOGON_NETWORK_INFO@4
+_fgu__NETLOGON_LEVEL@8
+_fgu__NETLOGON_VALIDATION@8
+NlpWriteMailslot@12
+NlPrimaryAnnouncement@4
+NlLanmanPrimaryAnnouncement@0
+NlChangePassword@4
+NlScavenger@4
+IsScavengerRunning@0
+NlStartScavengerThread@0
+NlServerSessionScavenger@0
+NlPickTrustedDcForEntireTrustList@0
+GiveInstallHints@4
+NlMoveToNextChangeLogBlock@8
+NlMoveToNextChangeLogEntry@8
+ValidateThisEntry@16
+ValidateList@8
+NlDcDiscoveryMachine@24
+NlWaitForSamService@4
+NlSamOpenNamedUser@12
+NlGetHashVal@8
+NlFindNamedServerSession@4
+NlAnnouncePrimaryStart@0
+NlWriteChangeLogBytes@16
+NlAllocChangeLogBlock@12
+NlWriteChangeLogEntry@20
+I_NetNotifyDelta@40
+I_NetNotifyRole@4
+NlInitChangeLogBuffer@0
+InitChangeLogHeadAndTail@8
+NlInitChangeLog@0
+NlGetGroupEntry@8
+NlAddGroupEntry@8
+NlAddGlobalGroupsToList@8
+NlInitSpecialGroupList@0
+NlIsServersGroupEmpty@4
+NlChangeLogWorker@4
+NlStartChangeLogWorkerThread@0
+IsChangeLogWorkerRunning@0
+logon_NetrServerReqChallenge@4
+logon_NetrServerAuthenticate@4
+NlSetPrimaryName@4
+NlCheckMachineAccount@8
+NlInitDBSerialNumber@20
+NlServerType@0
+NlInitLsaDBInfo@4
+NlInitSamDBInfo@8
+NlSetDomainName@0
+NlDcDiscoveryHandler@16
+NlInitDomainController@0
+NlInit@0
+NlMainLoop@0
+NlNetlogonMain@8
+NlCreateShare@8
+NlAuthenticate@24
+NlUnlockServerSession@4
+NlpAtoX@4
+NlCreateNetlogonObjects@0
+Nlparse@0
+NlInitSSI@0
+NlVerifyWorkstation@4
+NetrServerReqChallenge@16
+NetrServerAuthenticate@24
+NetrServerAuthenticate2@28
+NlInsertServerSession@20
+NlAllocateClientSession@12
+NlInitTrustList@0
+NlUpdateTrustListBySid@8
+NlDiscoverDc@8
+NlCreateChangeLogFile@4
+NlResetChangeLog@8
+NlSendChangeLogNotification@16
+NlCloseChangeLogFile@4
+NlFixChangeLog@20
+I_NetNotifyMachineAccount@20
+NlFindChangeLogEntry@20
+NlFindNextChangeLogEntry@12
+NlGetNextUniqueChangeLogEntry@20
+NlRecoverChangeLog@4
+IsSpecialLocalGroup@4
+NlSimulateUserDelta@4
+NlAddWorkerQueueEntry@8
+NlProcessQueueEntry@4
+NlStopChangeLogWorker@0
+NlCleanup@0
+NlExit@16
+NlControlHandler@4
+RaiseAlert@8
+logon_NetrAccountDeltas@4
+logon_NetrGetAnyDCName@4
+logon_NetrGetDCName@4
+logon_NetrServerPasswordSet@4
+logon_NetrLogonUasLogoff@4
+logon_NetrDatabaseSync@4
+logon_NetrAccountSync@4
+logon_NetrLogonSamLogoff@4
+logon_NetrLogonControl@4
+logon_NetrLogonUasLogon@4
+logon_NetrDatabaseDeltas@4
+_fgs__NETLOGON_INTERACTIVE_INFO@4
+_fgs__NETLOGON_SERVICE_INFO@4
+_fgs__NETLOGON_VALIDATION_SAM_INFO@4
+_fgs__NETLOGON_VALIDATION_UAS_INFO@4
+_fgs__NLPR_SID_INFORMATION@4
+_fgs__NLPR_SID_ARRAY@4
+_fgs__NLPR_CR_CIPHER_VALUE@4
+_fgs__NLPR_LOGON_HOURS@4
+_fgs__NLPR_USER_PRIVATE_INFO@4
+_fgs__NETLOGON_DELTA_USER@4
+_fgs__NETLOGON_DELTA_GROUP@4
+_fgs__NETLOGON_DELTA_GROUP_MEMBER@4
+_fgs__NETLOGON_DELTA_ALIAS@4
+_fgs__NETLOGON_DELTA_ALIAS_MEMBER@4
+_fgs__NETLOGON_DELTA_DOMAIN@4
+_fgs__NETLOGON_DELTA_RENAME@4
+_fgs__NETLOGON_DELTA_POLICY@4
+_fgs__NETLOGON_DELTA_TRUSTED_DOMAINS@4
+_fgs__NETLOGON_DELTA_ACCOUNTS@4
+_fgs__NETLOGON_DELTA_SECRET@4
+_fgu__NETLOGON_DELTA_UNION@8
+_fgu__NETLOGON_DELTA_ID_UNION@8
+_fgs__NETLOGON_DELTA_ENUM@4
+_fgs__NETLOGON_DELTA_ENUM_ARRAY@4
+_fgu__NETLOGON_CONTROL_QUERY_INFORMATION@8
+NetrLogonUasLogon@16
+NetrLogonUasLogoff@16
+NlpEncryptValidationInformation@16
+NlpUserLogoffHigher@12
+NlpUserValidateOnPdc@20
+NetrLogonSamLogoff@24
+NetrGetDCName@12
+RaiseNetlogonAlert@16
+NlNewSessionSetup@4
+NlGetUserPriv@20
+NlInitWorkstation@0
+LogonRequestHandler@24
+PrimaryQueryHandler@12
+NlStopScavenger@0
+NlStringToLpwstr@4
+NlStringToLpstr@4
+NlpWriteMailslotA@12
+NlpWriteEventlog@24
+SpecialGroupOp@8
+NlPackUasHeader@16
+NlPackVarLenField@28
+NlPackUasUser@20
+NlPackUasGroup@16
+NlPackUasBuiltinGroup@12
+NlPackUasGroupMember@12
+NlPackUasUserGroupMember@12
+NlPackUasDomain@8
+NlPackUasDelete@20
+NlCopyUnicodeString@8
+NlCopyData@12
+NlFreeDBDelta@4
+NlFreeDBDeltaArray@8
+NlPackSamUser@20
+NlEncryptSensitiveData@8
+NlPackSamGroup@16
+NlPackSamGroupMember@16
+NlPackSamAlias@16
+NlPackSamAliasMember@16
+NlPackSamDomain@12
+NlUnpackSamUser@16
+NlDecryptSensitiveData@12
+NlDeleteSamUser@8
+NlDeleteSamGroup@8
+NlDeleteSamAlias@8
+NlUnpackSamGroup@12
+NlUnpackSamGroupMember@12
+NlUnpackSamAlias@12
+NlUnpackSamAliasMember@12
+NlUnpackSamDomain@8
+NlUnpackSam@16
+NetrServerPasswordSet@28
+NetrDatabaseDeltas@32
+NlSyncSamDatabase@24
+NlSyncLsaDatabase@20
+NetrDatabaseSync@32
+NetrAccountDeltas@48
+NetrAccountSync@48
+NetrLogonControl@16
+NetrGetAnyDCName@12
+I_NetGetAnyDCName@8
+NlCheckAuthenticator@12
+NlEnsureClientIsNamedUser@4
+NlFreeServerSession@4
+NlFreeNamedServerSession@8
+NlFreeServerSessionForAccount@4
+NlFreeClientSession@4
+NlReadSamLogonResponse@16
+NlDcDiscoveryExpired@4
+NlCaptureServerClientSession@8
+NlPickDomainWithAccount@8
+NlPackLsaPolicy@12
+NlPackLsaTDomain@16
+NlPackLsaAccount@20
+NlPackLsaSecret@20
+NlUnpackLsaPolicy@8
+NlUnpackLsaTDomain@12
+NlUnpackLsaAccount@12
+NlUnpackLsaSecret@16
+NlDeleteLsaTDomain@8
+NlDeleteLsaAccount@8
+NlDeleteLsaSecret@8
+NlForceStartupSync@4
+FreeSamSyncTables@0
+FreeLsaSyncTables@0
+InitSamSyncTables@4
+InitLsaSyncTables@4
+UpdateSamSyncTables@8
+UpdateLsaSyncTables@8
+CleanSamSyncTables@4
+CleanLsaSyncTables@4
+NlRecoverConflictingAccount@24
+NlSynchronize@4
+NlReplicateDeltas@4
+NlReplicator@4
+NlUpdateRequired@4
+NlCheckUpdateNotices@8
+IsReplicatorRunning@0
+NlStartReplicatorThread@4
+NlStopReplicator@0
diff --git a/private/net/svcdlls/logonsrv/server/netlogon.rc b/private/net/svcdlls/logonsrv/server/netlogon.rc
new file mode 100644
index 000000000..b0b79e383
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/netlogon.rc
@@ -0,0 +1,12 @@
+#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DLL
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "Net Logon Services DLL"
+#define VER_INTERNALNAME_STR "NetLogon.DLL"
+#define VER_ORIGINALFILENAME_STR "NetLogon.DLL"
+
+#include "common.ver"
+
diff --git a/private/net/svcdlls/logonsrv/server/nldebug.h b/private/net/svcdlls/logonsrv/server/nldebug.h
new file mode 100644
index 000000000..95e99bbfd
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nldebug.h
@@ -0,0 +1,180 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ nldebug.h
+
+Abstract:
+
+ Netlogon service debug support
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Revision History:
+
+ 21-May-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+ 09-Apr-1992 JohnRo
+ Prepare for WCHAR.H (_wcsicmp vs _wcscmpi, etc).
+
+--*/
+
+//
+// changelg.c will #include this file with DEBUG_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef DEBUG_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+
+////////////////////////////////////////////////////////////////////////
+//
+// Debug Definititions
+//
+////////////////////////////////////////////////////////////////////////
+
+#define NL_INIT 0x00000001 // Initialization
+#define NL_MISC 0x00000002 // Misc debug
+#define NL_LOGON 0x00000004 // Logon processing
+#define NL_SYNC 0x00000008 // Synchronization and replication
+#define NL_MAILSLOT 0x00000010 // Mailslot messages
+#define NL_PULSE 0x00000020 // Pulse processing
+#define NL_CRITICAL 0x00000100 // Only real important errors
+#define NL_SESSION_SETUP 0x00000200 // Trusted Domain maintenance
+#define NL_PACK 0x00000800 // Pack/Unpack of sync messages
+#define NL_SERVER_SESS 0x00001000 // Server session maintenance
+#define NL_CHANGELOG 0x00002000 // Change Log references
+
+//
+// Very verbose bits
+//
+
+#define NL_PULSE_MORE 0x00040000 // Verbose pulse processing
+#define NL_SESSION_MORE 0x00080000 // Verbose session management
+#define NL_REPL_TIME 0x00100000 // replication timing output
+#define NL_REPL_OBJ_TIME 0x00200000 // replication objects get/set timing output
+#define NL_ENCRYPT 0x00400000 // debug encrypt and decrypt acorss net
+#define NL_SYNC_MORE 0x00800000 // additional replication dbgprint
+#define NL_PACK_VERBOSE 0x01000000 // Verbose Pack/Unpack
+#define NL_MAILSLOT_TEXT 0x02000000 // Verbose Mailslot messages
+#define NL_CHALLENGE_RES 0x04000000 // challenge response debug
+#define NL_NETLIB 0x08000000 // Netlogon portion of Netlib
+
+//
+// Control bits.
+//
+
+#ifdef DONT_REQUIRE_ACCOUNT
+#define NL_DONT_REQUIRE_ACCOUNT 0x00020000 // Don't require account on DC discovery
+#endif DONT_REQUIRE_ACCOUNT
+
+#define NL_INHIBIT_CANCEL 0x10000000 // Don't cancel API calls
+#define NL_TIMESTAMP 0x20000000 // TimeStamp each output line
+#define NL_ONECHANGE_REPL 0x40000000 // Only replicate one change per call
+#define NL_BREAKPOINT 0x80000000 // Enter debugger on startup
+
+
+
+#if DBG || defined(NLTEST_IMAGE)
+
+EXTERN DWORD NlGlobalTrace;
+
+//
+// Debug share path.
+//
+
+EXTERN LPWSTR NlGlobalDebugSharePath;
+
+#define IF_DEBUG(Function) \
+ if (NlGlobalTrace & NL_ ## Function)
+
+#define NlPrint(_x_) NlPrintRoutine _x_
+
+VOID
+NlAssertFailed(
+ IN PVOID FailedAssertion,
+ IN PVOID FileName,
+ IN ULONG LineNumber,
+ IN PCHAR Message OPTIONAL
+ );
+
+#define NlAssert(Predicate) \
+ { \
+ if (!(Predicate)) \
+ NlAssertFailed( #Predicate, __FILE__, __LINE__, NULL ); \
+ }
+
+
+#define DEBUG_DIR L"\\debug"
+#define DEBUG_FILE L"\\netlogon.log"
+#define DEBUG_BAK_FILE L"\\netlogon.bak"
+
+#define DEBUG_SHARE_NAME L"DEBUG"
+
+VOID
+NlPrintRoutine(
+ IN DWORD DebugFlag,
+ IN LPSTR FORMATSTRING, // PRINTF()-STYLE FORMAT STRING.
+ ... // OTHER ARGUMENTS ARE POSSIBLE.
+ );
+
+VOID
+NlpDumpHexData(
+ IN DWORD DebugFlag,
+ IN LPDWORD Buffer,
+ IN DWORD BufferSize
+ );
+
+VOID
+NlpDumpSid(
+ IN DWORD DebugFlag,
+ IN PSID Sid
+ );
+
+VOID
+NlpDumpBuffer(
+ IN DWORD DebugFlag,
+ IN PVOID Buffer,
+ IN DWORD BufferSize
+ );
+
+VOID
+NlOpenDebugFile(
+ IN BOOL ReopenFlag
+ );
+
+//
+// Debug log file
+//
+
+EXTERN HANDLE NlGlobalLogFile;
+#define DEFAULT_MAXIMUM_LOGFILE_SIZE 20000000
+EXTERN DWORD NlGlobalLogFileMaxSize;
+
+//
+// To serialize access to log file.
+//
+
+EXTERN CRITICAL_SECTION NlGlobalLogFileCritSect;
+
+#else
+
+#define IF_DEBUG(Function) if (FALSE)
+
+// Nondebug version.
+#define NlpDumpHexData /* no output; ignore arguments */
+#define NlpDumpBuffer
+#define NlpDumpSid
+#define NlPrint(_x_)
+#define NlAssert(Predicate) /* no output; ignore arguments */
+
+#endif // DBG
+
+#undef EXTERN
diff --git a/private/net/svcdlls/logonsrv/server/nlp.c b/private/net/svcdlls/logonsrv/server/nlp.c
new file mode 100644
index 000000000..59947fb36
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nlp.c
@@ -0,0 +1,1246 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ nlp.c
+
+Abstract:
+
+ Private Netlogon service utility routines.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 7-Jun-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 08-May-1992 JohnRo
+ Use net config helpers for NetLogon.
+ Use <prefix.h> equates.
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <lmerr.h> // NERR_*
+#include <winerror.h> // NO_ERROR
+#include <prefix.h> // PREFIX_ equates.
+#include <stdlib.h> // C library functions: rand()
+#include <stdarg.h> // va_list, etc.
+#include <stdio.h> // vsprintf().
+#include <tstr.h> // TCHAR_ equates.
+#include <align.h> // alignment macros defined
+
+
+LPWSTR
+NlStringToLpwstr(
+ IN PUNICODE_STRING String
+ )
+
+/*++
+
+Routine Description:
+
+ Convert a Unicode String to an LPWSTR.
+
+Arguments:
+
+ String - Unicode String to copy
+
+Return Value:
+
+ LPWSTR in a NetpMemoryAllocate'd buffer.
+ NULL is returned if there is no memory.
+
+--*/
+
+{
+ LPWSTR Buffer;
+
+ Buffer = NetpMemoryAllocate( String->Length + sizeof(WCHAR) );
+
+ if ( Buffer != NULL ) {
+ RtlCopyMemory( Buffer, String->Buffer, String->Length );
+ Buffer[ String->Length / sizeof(WCHAR) ] = L'\0';
+ }
+
+ return Buffer;
+}
+
+
+LPSTR
+NlStringToLpstr(
+ IN PUNICODE_STRING String
+ )
+
+/*++
+
+Routine Description:
+
+ Convert a Unicode String to an LPSTR.
+
+Arguments:
+
+ String - Unicode String to copy
+
+Return Value:
+
+ LPWSTR in a NetpMemoryAllocate'd buffer.
+ NULL is returned if there is no memory.
+
+--*/
+
+{
+ NTSTATUS Status;
+ STRING AnsiString;
+
+ AnsiString.MaximumLength = (USHORT) RtlUnicodeStringToOemSize( String );
+
+ AnsiString.Buffer = NetpMemoryAllocate( AnsiString.MaximumLength );
+
+ if ( AnsiString.Buffer != NULL ) {
+ Status = RtlUnicodeStringToOemString( &AnsiString,
+ String,
+ (BOOLEAN) FALSE );
+ if ( !NT_SUCCESS( Status ) ) {
+ NetpMemoryFree( AnsiString.Buffer );
+ return NULL;
+ }
+ }
+
+ return AnsiString.Buffer;
+}
+
+
+VOID
+NlpPutString(
+ IN PUNICODE_STRING OutString,
+ IN PUNICODE_STRING InString,
+ IN PUCHAR *Where
+ )
+
+/*++
+
+Routine Description:
+
+ This routine copies the InString string to the memory pointed to by
+ the Where parameter, and fixes the OutString string to point to that
+ new copy.
+
+Parameters:
+
+ OutString - A pointer to a destination NT string
+
+ InString - A pointer to an NT string to be copied
+
+ Where - A pointer to space to put the actual string for the
+ OutString. The pointer is adjusted to point to the first byte
+ following the copied string.
+
+Return Values:
+
+ None.
+
+--*/
+
+{
+ ASSERT( OutString != NULL );
+ ASSERT( InString != NULL );
+ ASSERT( Where != NULL && *Where != NULL);
+ ASSERT( *Where == ROUND_UP_POINTER( *Where, sizeof(WCHAR) ) );
+#ifdef notdef
+ KdPrint(("NlpPutString: %ld %Z\n", InString->Length, InString ));
+ KdPrint((" InString: %lx %lx OutString: %lx Where: %lx\n", InString,
+ InString->Buffer, OutString, *Where ));
+#endif
+
+ if ( InString->Length > 0 ) {
+
+ OutString->Buffer = (PWCH) *Where;
+ OutString->MaximumLength = (USHORT)(InString->Length + sizeof(WCHAR));
+
+ RtlCopyUnicodeString( OutString, InString );
+
+ *Where += InString->Length;
+// *((WCHAR *)(*Where)) = L'\0';
+ *(*Where) = '\0';
+ *(*Where + 1) = '\0';
+ *Where += 2;
+
+ } else {
+ RtlInitUnicodeString(OutString, NULL);
+ }
+#ifdef notdef
+ KdPrint((" OutString: %ld %lx\n", OutString->Length, OutString->Buffer));
+#endif
+
+ return;
+}
+
+
+VOID
+NlpWriteEventlog (
+ IN DWORD EventID,
+ IN DWORD EventType,
+ IN LPBYTE RawDataBuffer OPTIONAL,
+ IN DWORD RawDataSize,
+ IN LPWSTR *StringArray,
+ IN DWORD StringCount
+ )
+/*++
+
+Routine Description:
+
+ Stub routine for calling Event Log.
+
+Arguments:
+
+ EventID - event log ID.
+
+ EventType - Type of event.
+
+ RawDataBuffer - Data to be logged with the error.
+
+ numbyte - Size in bytes of "RawDataBuffer"
+
+ StringArray - array of null-terminated strings.
+
+ StringCount - number of zero terminated strings in "StringArray". The following
+ flags can be OR'd in to the count:
+
+ LAST_MESSAGE_IS_NTSTATUS 0x80000000
+ LAST_MESSAGE_IS_NETSTATUS 0x40000000
+
+Return Value:
+
+ None.
+
+--*/
+{
+ DWORD ErrorCode;
+ WCHAR ErrorNumberBuffer[25];
+
+ //
+ // If an NT status code was passed in,
+ // convert it to a net status code.
+ //
+
+ if ( StringCount & LAST_MESSAGE_IS_NTSTATUS ) {
+ StringCount &= ~LAST_MESSAGE_IS_NTSTATUS;
+
+ //
+ // Do the "better" error mapping when eventviewer ParameterMessageFile
+ // can be a list of files. Then, add netmsg.dll to the list.
+ //
+ // StringArray[StringCount-1] = (LPWSTR) NetpNtStatusToApiStatus( (NTSTATUS) StringArray[StringCount-1] );
+ if ( (NTSTATUS)StringArray[StringCount-1] == STATUS_SYNCHRONIZATION_REQUIRED ) {
+ StringArray[StringCount-1] = (LPWSTR) NERR_SyncRequired;
+ } else {
+ StringArray[StringCount-1] = (LPWSTR) RtlNtStatusToDosError( (NTSTATUS) StringArray[StringCount-1] );
+ }
+
+ StringCount |= LAST_MESSAGE_IS_NETSTATUS;
+ }
+
+ //
+ // If a net/windows status code was passed in,
+ // convert to the the %%N format the eventviewer knows.
+ //
+
+ if ( StringCount & LAST_MESSAGE_IS_NETSTATUS ) {
+ StringCount &= ~LAST_MESSAGE_IS_NETSTATUS;
+
+ wcscpy( ErrorNumberBuffer, L"%%" );
+ ultow( (ULONG) StringArray[StringCount-1], ErrorNumberBuffer+2, 10 );
+ StringArray[StringCount-1] = ErrorNumberBuffer;
+
+ }
+
+
+ //
+ // Dump the event to the debug file.
+ //
+
+#if DBG
+ IF_DEBUG( MISC ) {
+ DWORD i;
+ NlPrint((NL_MISC, "Eventlog: %ld (%ld) ",
+ EventID,
+ EventType ));
+
+ for (i = 0; i < StringCount; i++ ) {
+ NlPrint((NL_MISC, "\"" FORMAT_LPWSTR "\" ", StringArray[i] ));
+ }
+
+ if( RawDataSize ) {
+ if ( RawDataSize < 16 && COUNT_IS_ALIGNED( RawDataSize, sizeof(DWORD)) ) {
+ NlpDumpHexData( NL_MISC,
+ (LPDWORD) RawDataBuffer,
+ RawDataSize/sizeof(DWORD) );
+ } else {
+ NlPrint((NL_MISC, "\n" ));
+ NlpDumpBuffer( NL_MISC, RawDataBuffer, RawDataSize );
+ }
+ } else {
+ NlPrint((NL_MISC, "\n" ));
+ }
+
+ }
+#endif // DBG
+
+ //
+ // write event
+ //
+
+ ErrorCode = NetpWriteEventlog(
+ SERVICE_NETLOGON,
+ EventID,
+ EventType,
+ StringCount,
+ StringArray,
+ RawDataSize,
+ RawDataBuffer);
+
+
+ IF_DEBUG( MISC ) {
+ if( ErrorCode != NO_ERROR ) {
+ NlPrint((NL_CRITICAL,
+ "Error writing this event in the eventlog, Status = %ld\n",
+ ErrorCode ));
+ }
+ }
+
+ return;
+}
+
+#if DBG
+
+VOID
+NlpDumpBuffer(
+ IN DWORD DebugFlag,
+ PVOID Buffer,
+ DWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Dumps the buffer content on to the debugger output.
+
+Arguments:
+
+ DebugFlag: Debug flag to pass on to NlPrintRoutine
+
+ Buffer: buffer pointer.
+
+ BufferSize: size of the buffer.
+
+Return Value:
+
+ none
+
+--*/
+{
+#define NUM_CHARS 16
+
+ DWORD i, limit;
+ CHAR TextBuffer[NUM_CHARS + 1];
+ LPBYTE BufferPtr = Buffer;
+
+ //
+ // If we aren't debugging this functionality, just return.
+ //
+ if ( (NlGlobalTrace & DebugFlag) == 0 ) {
+ return;
+ }
+
+ NlPrint((0,"------------------------------------\n"));
+
+ //
+ // Hex dump of the bytes
+ //
+ limit = ((BufferSize - 1) / NUM_CHARS + 1) * NUM_CHARS;
+
+ for (i = 0; i < limit; i++) {
+
+ if (i < BufferSize) {
+
+ NlPrint((0,"%02x ", BufferPtr[i]));
+
+ if (BufferPtr[i] < 31 ) {
+ TextBuffer[i % NUM_CHARS] = '.';
+ } else if (BufferPtr[i] == '\0') {
+ TextBuffer[i % NUM_CHARS] = ' ';
+ } else {
+ TextBuffer[i % NUM_CHARS] = (CHAR) BufferPtr[i];
+ }
+
+ } else {
+
+ NlPrint((0," "));
+ TextBuffer[i % NUM_CHARS] = ' ';
+
+ }
+
+ if ((i + 1) % NUM_CHARS == 0) {
+ TextBuffer[NUM_CHARS] = 0;
+ NlPrint((0," %s\n", TextBuffer));
+ }
+
+ }
+
+ NlPrint((0,"------------------------------------\n"));
+}
+
+
+VOID
+NlpDumpHexData(
+ IN DWORD DebugFlag,
+ LPDWORD Buffer,
+ DWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Dumps the hex data on to the debugger output.
+
+Arguments:
+
+ DebugFlag: Debug flag to pass on to NlPrintRoutine
+
+ Buffer: buffer pointer to hex data.
+
+ BufferSize: size of the buffer.
+
+Return Value:
+
+ none
+
+--*/
+{
+ DWORD i;
+
+ //
+ // If we aren't debugging this functionality, just return.
+ //
+ if ( (NlGlobalTrace & DebugFlag) == 0 ) {
+ return;
+ }
+
+#ifndef MIPS
+
+ // data alignment problem on mips ??
+
+ if( !POINTER_IS_ALIGNED( Buffer, ALIGN_DWORD) ) {
+ return;
+ }
+
+#endif //MIPS
+
+ for(i = 0; i < BufferSize; i++) {
+
+ if( i != 0 && i % 4 == 0 ) {
+ NlPrint((0,"\n"));
+ }
+
+ NlPrint((0,"%08lx ", Buffer[i]));
+ }
+
+ NlPrint((0,"\n"));
+
+}
+
+
+VOID
+NlpDumpSid(
+ IN DWORD DebugFlag,
+ IN PSID Sid OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Dumps a SID to the debugger output
+
+Arguments:
+
+ DebugFlag - Debug flag to pass on to NlPrintRoutine
+
+ Sid - SID to output
+
+Return Value:
+
+ none
+
+--*/
+{
+
+
+ //
+ // If we aren't debugging this functionality, just return.
+ //
+ if ( (NlGlobalTrace & DebugFlag) == 0 ) {
+ return;
+ }
+
+ //
+ // Output the SID
+ //
+
+ if ( Sid == NULL ) {
+ NlPrint((0, "(null)\n"));
+ } else {
+ UNICODE_STRING SidString;
+ NTSTATUS Status;
+
+ Status = RtlConvertSidToUnicodeString( &SidString, Sid, TRUE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((0, "Invalid 0x%lX\n", Status ));
+ } else {
+ NlPrint((0, "%wZ\n", &SidString ));
+ RtlFreeUnicodeString( &SidString );
+ }
+ }
+
+}
+#endif // DBG
+
+
+DWORD
+NlpAtoX(
+ IN LPWSTR String
+ )
+/*++
+
+Routine Description:
+
+ Converts hexadecimal string to DWORD integer.
+
+ Accepts the following form of hex string
+
+ 0[x-X][0-9, a-f, A-F]*
+
+Arguments:
+
+ String: hexadecimal string.
+
+Return Value:
+
+ Decimal value of the hex string.
+
+--*/
+
+{
+ DWORD Value = 0;
+
+ if( String == NULL )
+ return 0;
+
+ if( *String != TEXT('0') )
+ return 0;
+
+ String++;
+
+ if( *String == TCHAR_EOS )
+ return 0;
+
+ if( ( *String != TEXT('x') ) && ( *String != TEXT('X') ) )
+ return 0;
+
+ String++;
+
+ while(*String != TCHAR_EOS ) {
+
+ if( (*String >= TEXT('0')) && (*String <= TEXT('9')) ) {
+ Value = Value * 16 + ( *String - '0');
+ } else if( (*String >= TEXT('a')) && (*String <= TEXT('f')) ) {
+ Value = Value * 16 + ( 10 + *String - TEXT('a'));
+ } else if( (*String >= TEXT('A')) && (*String <= TEXT('F')) ) {
+ Value = Value * 16 + ( 10 + *String - TEXT('A'));
+ } else {
+ break;
+ }
+ String++;
+ }
+
+ return Value;
+}
+
+
+VOID
+NlWaitForSingleObject(
+ IN LPSTR WaitReason,
+ IN HANDLE WaitHandle
+ )
+/*++
+
+Routine Description:
+
+ Waits an infinite amount of time for the specified handle.
+
+Arguments:
+
+ WaitReason - Text describing what we're waiting on
+
+ WaitHandle - Handle to wait on
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ DWORD WaitStatus;
+
+ //
+ // Loop waiting.
+ //
+
+ for (;;) {
+ WaitStatus = WaitForSingleObject( WaitHandle,
+ 2*60*1000 ); // Two minutes
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ NlPrint((NL_CRITICAL,
+ "WaitForSingleObject 2-minute timeout (Rewaiting): %s\n",
+ WaitReason ));
+ continue;
+
+ } else if ( WaitStatus == 0 ) {
+ break;
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "WaitForSingleObject error: %ld %s\n",
+ WaitStatus,
+ WaitReason ));
+ UNREFERENCED_PARAMETER(WaitReason);
+ break;
+ }
+ }
+
+}
+
+
+BOOLEAN
+NlWaitForSamService(
+ BOOLEAN NetlogonServiceCalling
+ )
+/*++
+
+Routine Description:
+
+ This procedure waits for the SAM service to start and to complete
+ all its initialization.
+
+Arguments:
+
+ NetlogonServiceCalling:
+ TRUE if this is the netlogon service proper calling
+ FALSE if this is the changelog worker thread calling
+
+Return Value:
+
+ TRUE : if the SAM service is successfully starts.
+
+ FALSE : if the SAM service can't start.
+
+--*/
+{
+ NTSTATUS Status;
+ DWORD WaitStatus;
+ UNICODE_STRING EventName;
+ HANDLE EventHandle;
+ OBJECT_ATTRIBUTES EventAttributes;
+
+ //
+ // open SAM event
+ //
+
+ RtlInitUnicodeString( &EventName, L"\\SAM_SERVICE_STARTED");
+ InitializeObjectAttributes( &EventAttributes, &EventName, 0, 0, NULL );
+
+ Status = NtOpenEvent( &EventHandle,
+ SYNCHRONIZE|EVENT_MODIFY_STATE,
+ &EventAttributes );
+
+ if ( !NT_SUCCESS(Status)) {
+
+ if( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
+
+ //
+ // SAM hasn't created this event yet, let us create it now.
+ // SAM opens this event to set it.
+ //
+
+ Status = NtCreateEvent(
+ &EventHandle,
+ SYNCHRONIZE|EVENT_MODIFY_STATE,
+ &EventAttributes,
+ NotificationEvent,
+ FALSE // The event is initially not signaled
+ );
+
+ if( Status == STATUS_OBJECT_NAME_EXISTS ||
+ Status == STATUS_OBJECT_NAME_COLLISION ) {
+
+ //
+ // second change, if the SAM created the event before we
+ // do.
+ //
+
+ Status = NtOpenEvent( &EventHandle,
+ SYNCHRONIZE|EVENT_MODIFY_STATE,
+ &EventAttributes );
+
+ }
+ }
+
+ if ( !NT_SUCCESS(Status)) {
+
+ //
+ // could not make the event handle
+ //
+
+ NlPrint((NL_CRITICAL,
+ "NlWaitForSamService couldn't make the event handle : "
+ "%lx\n", Status));
+
+ return( FALSE );
+ }
+ }
+
+ //
+ // Loop waiting.
+ //
+
+ for (;;) {
+ WaitStatus = WaitForSingleObject( EventHandle,
+ 5*1000 ); // 5 Seconds
+
+ if ( WaitStatus == WAIT_TIMEOUT ) {
+ if ( NetlogonServiceCalling ) {
+ NlPrint((NL_CRITICAL,
+ "NlWaitForSamService 5-second timeout (Rewaiting)\n" ));
+ if (!GiveInstallHints( FALSE )) {
+ (VOID) NtClose( EventHandle );
+ return FALSE;
+ }
+ }
+ continue;
+
+ } else if ( WaitStatus == 0 ) {
+ break;
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "NlWaitForSamService: error %ld\n",
+ WaitStatus ));
+ (VOID) NtClose( EventHandle );
+ return FALSE;
+ }
+ }
+
+ (VOID) NtClose( EventHandle );
+ return TRUE;
+
+}
+
+
+
+
+#if DBG
+
+
+VOID
+NlOpenDebugFile(
+ IN BOOL ReopenFlag
+ )
+/*++
+
+Routine Description:
+
+ Opens or re-opens the debug file
+
+Arguments:
+
+ ReopenFlag - TRUE to indicate the debug file is to be closed, renamed,
+ and recreated.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ WCHAR LogFileName[500];
+ WCHAR BakFileName[500];
+ DWORD FileAttributes;
+ DWORD PathLength;
+ DWORD WinError;
+
+ //
+ // Close the handle to the debug file, if it is currently open
+ //
+
+ EnterCriticalSection( &NlGlobalLogFileCritSect );
+ if ( NlGlobalLogFile != INVALID_HANDLE_VALUE ) {
+ CloseHandle( NlGlobalLogFile );
+ NlGlobalLogFile = INVALID_HANDLE_VALUE;
+ }
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+
+ //
+ // make debug directory path first, if it is not made before.
+ //
+ if( NlGlobalDebugSharePath == NULL ) {
+
+ if ( !GetWindowsDirectoryW(
+ LogFileName,
+ sizeof(LogFileName)/sizeof(WCHAR) ) ) {
+ NlPrint((NL_CRITICAL, "Window Directory Path can't be "
+ "retrieved, %lu.\n", GetLastError() ));
+ return;
+ }
+
+ //
+ // check debug path length.
+ //
+
+ PathLength = wcslen(LogFileName) * sizeof(WCHAR) +
+ sizeof(DEBUG_DIR) + sizeof(WCHAR);
+
+ if( (PathLength + sizeof(DEBUG_FILE) > sizeof(LogFileName) ) ||
+ (PathLength + sizeof(DEBUG_BAK_FILE) > sizeof(BakFileName) ) ) {
+
+ NlPrint((NL_CRITICAL, "Debug directory path (%ws) length is too long.\n",
+ LogFileName));
+ goto ErrorReturn;
+ }
+
+ wcscat(LogFileName, DEBUG_DIR);
+
+ //
+ // copy debug directory name to global var.
+ //
+
+ NlGlobalDebugSharePath =
+ NetpMemoryAllocate( (wcslen(LogFileName) + 1) * sizeof(WCHAR) );
+
+ if( NlGlobalDebugSharePath == NULL ) {
+ NlPrint((NL_CRITICAL, "Can't allocated memory for debug share "
+ "(%ws).\n", LogFileName));
+ goto ErrorReturn;
+ }
+
+ wcscpy(NlGlobalDebugSharePath, LogFileName);
+ }
+ else {
+ wcscpy(LogFileName, NlGlobalDebugSharePath);
+ }
+
+ //
+ // Check this path exists.
+ //
+
+ FileAttributes = GetFileAttributesW( LogFileName );
+
+ if( FileAttributes == 0xFFFFFFFF ) {
+
+ WinError = GetLastError();
+ if( WinError == ERROR_FILE_NOT_FOUND ) {
+
+ //
+ // Create debug directory.
+ //
+
+ if( !CreateDirectoryW( LogFileName, NULL) ) {
+ NlPrint((NL_CRITICAL, "Can't create Debug directory (%ws), "
+ "%lu.\n", LogFileName, GetLastError() ));
+ goto ErrorReturn;
+ }
+
+ }
+ else {
+ NlPrint((NL_CRITICAL, "Can't Get File attributes(%ws), "
+ "%lu.\n", LogFileName, WinError ));
+ goto ErrorReturn;
+ }
+ }
+ else {
+
+ //
+ // if this is not a directory.
+ //
+
+ if(!(FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+
+ NlPrint((NL_CRITICAL, "Debug directory path (%ws) exists "
+ "as file.\n", LogFileName));
+ goto ErrorReturn;
+ }
+ }
+
+ //
+ // Create the name of the old and new log file names
+ //
+
+ (VOID) wcscpy( BakFileName, LogFileName );
+ (VOID) wcscat( LogFileName, L"\\Netlogon.log" );
+ (VOID) wcscat( BakFileName, L"\\Netlogon.bak" );
+
+
+ //
+ // If this is a re-open,
+ // delete the backup file,
+ // rename the current file to the backup file.
+ //
+
+ if ( ReopenFlag ) {
+
+ if ( !DeleteFile( BakFileName ) ) {
+ WinError = GetLastError();
+ if ( WinError != ERROR_FILE_NOT_FOUND ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot delete " FORMAT_LPWSTR "(%ld)\n",
+ BakFileName,
+ WinError ));
+ NlPrint((NL_CRITICAL, " Try to re-open the file.\n"));
+ ReopenFlag = FALSE; // Don't truncate the file
+ }
+ }
+ }
+
+ if ( ReopenFlag ) {
+ if ( !MoveFile( LogFileName, BakFileName ) ) {
+ NlPrint((NL_CRITICAL,
+ "Cannot rename " FORMAT_LPWSTR " to " FORMAT_LPWSTR
+ " (%ld)\n",
+ LogFileName,
+ BakFileName,
+ GetLastError() ));
+ NlPrint((NL_CRITICAL,
+ " Try to re-open the file.\n"));
+ ReopenFlag = FALSE; // Don't truncate the file
+ }
+ }
+
+ //
+ // Open the file.
+ //
+
+ EnterCriticalSection( &NlGlobalLogFileCritSect );
+ NlGlobalLogFile = CreateFileW( LogFileName,
+ GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ ReopenFlag ? CREATE_ALWAYS : OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL );
+
+
+ if ( NlGlobalLogFile == INVALID_HANDLE_VALUE ) {
+ NlPrint((NL_CRITICAL, "cannot open " FORMAT_LPWSTR ",\n",
+ LogFileName ));
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+ goto ErrorReturn;
+ } else {
+ // Position the log file at the end
+ (VOID) SetFilePointer( NlGlobalLogFile,
+ 0,
+ NULL,
+ FILE_END );
+ }
+
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+ return;
+
+ErrorReturn:
+ NlPrint((NL_CRITICAL,
+ " Debug output will be written to debug terminal.\n"));
+ return;
+}
+
+#define MAX_PRINTF_LEN 1024 // Arbitrary.
+
+VOID
+NlPrintRoutine(
+ IN DWORD DebugFlag,
+ IN LPSTR Format,
+ ...
+ )
+
+{
+ va_list arglist;
+ char OutputBuffer[MAX_PRINTF_LEN];
+ ULONG length;
+ DWORD BytesWritten;
+ static BeginningOfLine = TRUE;
+ static LineCount = 0;
+ static TruncateLogFileInProgress = FALSE;
+
+ //
+ // If we aren't debugging this functionality, just return.
+ //
+ if ( DebugFlag != 0 && (NlGlobalTrace & DebugFlag) == 0 ) {
+ return;
+ }
+
+ //
+ // vsprintf isn't multithreaded + we don't want to intermingle output
+ // from different threads.
+ //
+
+ EnterCriticalSection( &NlGlobalLogFileCritSect );
+ length = 0;
+
+ //
+ // Handle the beginning of a new line.
+ //
+ //
+
+ if ( BeginningOfLine ) {
+
+ //
+ // If the log file is getting huge,
+ // truncate it.
+ //
+
+ if ( NlGlobalLogFile != INVALID_HANDLE_VALUE &&
+ !TruncateLogFileInProgress ) {
+
+ //
+ // Only check every 50 lines,
+ //
+
+ LineCount++;
+ if ( LineCount >= 50 ) {
+ DWORD FileSize;
+ LineCount = 0;
+
+ //
+ // Is the log file too big?
+ //
+
+ FileSize = GetFileSize( NlGlobalLogFile, NULL );
+ if ( FileSize == 0xFFFFFFFF ) {
+ (void) DbgPrint( "[NETLOGON] Cannot GetFileSize %ld\n",
+ GetLastError );
+ } else if ( FileSize > NlGlobalLogFileMaxSize ) {
+ TruncateLogFileInProgress = TRUE;
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+ NlOpenDebugFile( TRUE );
+ NlPrint(( NL_MISC,
+ "Logfile truncated because it was larger than %ld bytes\n",
+ NlGlobalLogFileMaxSize ));
+ EnterCriticalSection( &NlGlobalLogFileCritSect );
+ TruncateLogFileInProgress = FALSE;
+ }
+
+ }
+ }
+
+ //
+ // If we're writing to the debug terminal,
+ // indicate this is a Netlogon message.
+ //
+
+ if ( NlGlobalLogFile == INVALID_HANDLE_VALUE ) {
+ length += (ULONG) sprintf( &OutputBuffer[length], "[NETLOGON] " );
+ }
+
+ //
+ // Put the timestamp at the begining of the line.
+ //
+ IF_DEBUG( TIMESTAMP ) {
+ SYSTEMTIME SystemTime;
+ GetLocalTime( &SystemTime );
+ length += (ULONG) sprintf( &OutputBuffer[length],
+ "%02u/%02u %02u:%02u:%02u ",
+ SystemTime.wMonth,
+ SystemTime.wDay,
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond );
+ }
+
+ //
+ // Indicate the type of message on the line
+ //
+ {
+ char *Text;
+
+ switch (DebugFlag) {
+ case NL_INIT:
+ Text = "INIT"; break;
+ case NL_MISC:
+ Text = "MISC"; break;
+ case NL_LOGON:
+ Text = "LOGON"; break;
+ case NL_SYNC:
+ case NL_PACK:
+ case NL_PACK_VERBOSE:
+ case NL_REPL_TIME:
+ case NL_REPL_OBJ_TIME:
+ case NL_SYNC_MORE:
+ Text = "SYNC"; break;
+ case NL_ENCRYPT:
+ Text = "ENCRYPT"; break;
+ case NL_MAILSLOT:
+ case NL_MAILSLOT_TEXT:
+ case NL_NETLIB:
+ Text = "MAILSLOT"; break;
+ case NL_CRITICAL:
+ Text = "CRITICAL"; break;
+ case NL_SESSION_SETUP:
+ case NL_SESSION_MORE:
+ case NL_CHALLENGE_RES:
+ case NL_INHIBIT_CANCEL:
+ case NL_SERVER_SESS:
+ Text = "SESSION"; break;
+ case NL_CHANGELOG:
+ Text = "CHANGELOG"; break;
+ case NL_PULSE:
+ case NL_PULSE_MORE:
+ Text = "PULSE"; break;
+
+ case NL_TIMESTAMP:
+ case NL_BREAKPOINT:
+ default:
+ Text = "UNKNOWN"; break;
+
+ case 0:
+ Text = NULL;
+ }
+ if ( Text != NULL ) {
+ length += (ULONG) sprintf( &OutputBuffer[length], "[%s] ", Text );
+ }
+ }
+ }
+
+ //
+ // Put a the information requested by the caller onto the line
+ //
+
+ va_start(arglist, Format);
+
+ length += (ULONG) vsprintf(&OutputBuffer[length], Format, arglist);
+ BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );
+ if ( BeginningOfLine ) {
+ OutputBuffer[length-1] = '\r';
+ OutputBuffer[length] = '\n';
+ OutputBuffer[length+1] = '\0';
+ length++;
+ }
+
+ va_end(arglist);
+
+ NlAssert(length <= MAX_PRINTF_LEN);
+
+
+ //
+ // If the log file isn't open,
+ // just output to the debug terminal
+ //
+
+ if ( NlGlobalLogFile == INVALID_HANDLE_VALUE ) {
+ (void) DbgPrint( (PCH) OutputBuffer);
+
+ //
+ // Write the debug info to the log file.
+ //
+
+ } else {
+ if ( !WriteFile( NlGlobalLogFile,
+ OutputBuffer,
+ length,
+ &BytesWritten,
+ NULL ) ) {
+ (void) DbgPrint( (PCH) OutputBuffer);
+ }
+
+ }
+
+ LeaveCriticalSection( &NlGlobalLogFileCritSect );
+
+} // NlPrint
+
+//
+// Have my own version of RtlAssert so debug versions of netlogon really assert on
+// free builds.
+//
+VOID
+NlAssertFailed(
+ IN PVOID FailedAssertion,
+ IN PVOID FileName,
+ IN ULONG LineNumber,
+ IN PCHAR Message OPTIONAL
+ )
+{
+ char Response[ 2 ];
+
+ while (TRUE) {
+ NlPrint(( NL_CRITICAL, "Assertion failed: %s%s (Source File: %s, line %ld)\n",
+ Message ? Message : "",
+ FailedAssertion,
+ FileName,
+ LineNumber
+ ));
+
+ DbgPrint( "\n*** Assertion failed: %s%s\n*** Source File: %s, line %ld\n\n",
+ Message ? Message : "",
+ FailedAssertion,
+ FileName,
+ LineNumber
+ );
+
+ DbgPrompt( "Break, Ignore, Terminate Process or Terminate Thread (bipt)? ",
+ Response,
+ sizeof( Response )
+ );
+ switch (Response[0]) {
+ case 'B':
+ case 'b':
+ DbgBreakPoint();
+ break;
+
+ case 'I':
+ case 'i':
+ return;
+
+ case 'P':
+ case 'p':
+ NtTerminateProcess( NtCurrentProcess(), STATUS_UNSUCCESSFUL );
+ break;
+
+ case 'T':
+ case 't':
+ NtTerminateThread( NtCurrentThread(), STATUS_UNSUCCESSFUL );
+ break;
+ }
+ }
+
+ DbgBreakPoint();
+ NtTerminateProcess( NtCurrentProcess(), STATUS_UNSUCCESSFUL );
+}
+
+#endif // DBG
diff --git a/private/net/svcdlls/logonsrv/server/nlp.h b/private/net/svcdlls/logonsrv/server/nlp.h
new file mode 100644
index 000000000..24fedd23a
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nlp.h
@@ -0,0 +1,95 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ nlp.h
+
+Abstract:
+
+ Private Netlogon service utility routines.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 7-Jun-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//
+// Special flags to NlpWriteEventlog
+//
+
+#define LAST_MESSAGE_IS_NTSTATUS 0x80000000
+#define LAST_MESSAGE_IS_NETSTATUS 0x40000000
+
+//
+// Procedure forwards from nlp.c
+//
+
+NTSTATUS
+NlpWriteMailslot(
+ IN LPWSTR MailslotName,
+ IN LPVOID Buffer,
+ IN DWORD BufferSize
+ );
+
+NTSTATUS
+NlpWriteMailslotA(
+ IN LPSTR MailslotName,
+ IN LPVOID Buffer,
+ IN DWORD BufferSize
+ );
+
+LPWSTR
+NlStringToLpwstr(
+ IN PUNICODE_STRING String
+ );
+
+LPSTR
+NlStringToLpstr(
+ IN PUNICODE_STRING String
+ );
+
+VOID
+NlpWriteEventlog (
+ IN DWORD EventID,
+ IN DWORD EventType,
+ IN LPBYTE buffer OPTIONAL,
+ IN DWORD numbytes,
+ IN LPWSTR *msgbuf,
+ IN DWORD strcount
+ );
+
+
+DWORD
+NlpAtoX(
+ IN LPWSTR String
+ );
+
+VOID
+NlWaitForSingleObject(
+ IN LPSTR WaitReason,
+ IN HANDLE WaitHandle
+ );
+
+BOOLEAN
+NlWaitForSamService(
+ BOOLEAN NetlogonServiceCalling
+ );
+
+VOID
+NlpPutString(
+ IN PUNICODE_STRING OutString,
+ IN PUNICODE_STRING InString,
+ IN PUCHAR *Where
+ );
+
diff --git a/private/net/svcdlls/logonsrv/server/nlsecure.c b/private/net/svcdlls/logonsrv/server/nlsecure.c
new file mode 100644
index 000000000..271a3082b
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nlsecure.c
@@ -0,0 +1,104 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ nlsecure.c
+
+Abstract:
+
+ This module contains the Netlogon service support routines
+ which create security objects and enforce security _access checking.
+
+Author:
+
+ Cliff Van Dyke (CliffV) 22-Aug-1991
+
+Revision History:
+
+--*/
+
+#include <nt.h>
+
+#include <windef.h>
+
+#include <lmcons.h>
+#include <secobj.h>
+
+#include <logonsrv.h>
+
+#define NLSECURE_ALLOCATE // Force globals to be allocated
+#include "nlsecure.h"
+
+
+NTSTATUS
+NlCreateNetlogonObjects(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This function creates the workstation user-mode objects which are
+ represented by security descriptors.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // Order matters! These ACEs are inserted into the DACL in the
+ // following order. Security access is granted or denied based on
+ // the order of the ACEs in the DACL.
+ //
+ //
+ // Members of Group SECURITY_LOCAL aren't allowed to do a UAS logon
+ // to force it to be done remotely.
+ //
+
+ ACE_DATA AceData[] = {
+
+ {ACCESS_DENIED_ACE_TYPE, 0, 0,
+ NETLOGON_UAS_LOGON_ACCESS |
+ NETLOGON_UAS_LOGOFF_ACCESS,
+ &LocalSid},
+
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
+ GENERIC_ALL, &AliasAdminsSid},
+
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
+ NETLOGON_CONTROL_ACCESS, &AliasAccountOpsSid},
+
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
+ NETLOGON_CONTROL_ACCESS, &AliasSystemOpsSid},
+
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
+ NETLOGON_UAS_LOGON_ACCESS |
+ NETLOGON_UAS_LOGOFF_ACCESS |
+ NETLOGON_QUERY_ACCESS, &WorldSid}
+ };
+
+ //
+ // Actually create the security descriptor.
+ //
+
+ Status = NetpCreateSecurityObject(
+ AceData,
+ sizeof(AceData)/sizeof(AceData[0]),
+ LocalSystemSid,
+ LocalSystemSid,
+ &NlGlobalNetlogonInfoMapping,
+ &NlGlobalNetlogonSecurityDescriptor );
+
+ return Status;
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/nlsecure.h b/private/net/svcdlls/logonsrv/server/nlsecure.h
new file mode 100644
index 000000000..e9a880006
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nlsecure.h
@@ -0,0 +1,88 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ nlsecure.h
+
+Abstract:
+
+ Private header file to be included by Netlogon service modules that
+ need to enforce security.
+
+Author:
+
+ Cliff Van Dyke (CliffV) 22-Aug-1991
+
+Revision History:
+
+--*/
+
+#ifndef _NLSECURE_INCLUDED_
+#define _NLSECURE_INCLUDED_
+
+//
+// nlsecure.c will #include this file with NLSECURE_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef NLSECURE_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+//-------------------------------------------------------------------//
+// //
+// Object specific access masks //
+// //
+//-------------------------------------------------------------------//
+
+//
+// ConfigurationInfo specific access masks
+//
+#define NETLOGON_UAS_LOGON_ACCESS 0x0001
+#define NETLOGON_UAS_LOGOFF_ACCESS 0x0002
+#define NETLOGON_CONTROL_ACCESS 0x0004
+#define NETLOGON_QUERY_ACCESS 0x0008
+
+#define NETLOGON_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \
+ NETLOGON_UAS_LOGON_ACCESS | \
+ NETLOGON_UAS_LOGOFF_ACCESS | \
+ NETLOGON_CONTROL_ACCESS | \
+ NETLOGON_QUERY_ACCESS )
+
+
+//
+// Object type names for audit alarm tracking
+//
+#define NETLOGON_SERVICE_OBJECT TEXT("NetlogonService")
+
+//
+// Security descriptors of Netlogon Service objects to control user accesses.
+//
+
+EXTERN PSECURITY_DESCRIPTOR NlGlobalNetlogonSecurityDescriptor;
+
+//
+// Generic mapping for each Netlogon Service object object
+//
+
+EXTERN GENERIC_MAPPING NlGlobalNetlogonInfoMapping
+#ifdef NLSECURE_ALLOCATE
+ = {
+ STANDARD_RIGHTS_READ, // Generic read
+ STANDARD_RIGHTS_WRITE, // Generic write
+ STANDARD_RIGHTS_EXECUTE, // Generic execute
+ NETLOGON_ALL_ACCESS // Generic all
+ }
+#endif // NLSECURE_ALLOCATE
+ ;
+
+
+NTSTATUS
+NlCreateNetlogonObjects(
+ VOID
+ );
+
+#endif // ifndef _NLSECURE_INCLUDED_
diff --git a/private/net/svcdlls/logonsrv/server/nltest.c b/private/net/svcdlls/logonsrv/server/nltest.c
new file mode 100644
index 000000000..12d26b528
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nltest.c
@@ -0,0 +1,3664 @@
+/*--
+
+Copyright (c) 1987-1993 Microsoft Corporation
+
+Module Name:
+
+ nltest.c
+
+Abstract:
+
+ Test program for the Netlogon service.
+
+Author:
+
+ 13-Apr-1992 (cliffv)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ Madana - added various options.
+
+--*/
+
+
+//
+// Common include files.
+//
+
+#define NLTEST_IMAGE
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+#include <align.h>
+#include <stdio.h>
+#include <string.h>
+#include <strarray.h>
+#include <tstring.h>
+#include <lmerr.h>
+#include <lmapibuf.h>
+#include <ssiapi.h>
+#include <winreg.h>
+#include "..\..\..\..\newsam\server\samsrvp.h"
+#include <wtypes.h>
+#include <ntstatus.dbg>
+#include <winerror.dbg>
+
+
+VOID
+ListDeltas(
+ LPWSTR DeltaFileName,
+ BOOLEAN ListRedoFile
+ );
+
+DWORD NlGlobalTrace =0xFFFFFFFF;
+
+typedef struct _MAILSLOT_INFO {
+ CHAR Name[DNLEN+NETLOGON_NT_MAILSLOT_LEN+3];
+ HANDLE ResponseHandle;
+ BOOL State;
+ NETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
+ OVERLAPPED OverLapped;
+ BOOL ReadPending;
+} MAIL_INFO, PMAIL_INFO;
+
+MAIL_INFO GlobalMailInfo[64];
+DWORD GlobalIterationCount = 0;
+LPWSTR GlobalAccountName;
+HANDLE GlobalPostEvent;
+CRITICAL_SECTION GlobalPrintCritSect;
+
+
+VOID
+DumpBuffer(
+ PVOID Buffer,
+ DWORD BufferSize
+ )
+/*++
+Routine Description:
+
+ Dumps the buffer content on to the debugger output.
+
+Arguments:
+
+ Buffer: buffer pointer.
+
+ BufferSize: size of the buffer.
+
+Return Value:
+
+ none
+
+--*/
+{
+ DWORD j;
+ PULONG LongBuffer;
+ ULONG LongLength;
+
+ LongBuffer = Buffer;
+ LongLength = min( BufferSize, 24 )/4;
+
+ for(j = 0; j < LongLength; j++) {
+ printf("%08lx ", LongBuffer[j]);
+ }
+
+ if ( BufferSize != LongLength*4 ) {
+ printf( "..." );
+ }
+
+ printf("\n");
+
+}
+
+
+VOID
+NlpDumpSid(
+ IN DWORD DebugFlag,
+ IN PSID Sid OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Dumps a SID to the debugger output
+
+Arguments:
+
+ DebugFlag - Debug flag to pass on to NlPrintRoutine
+
+ Sid - SID to output
+
+Return Value:
+
+ none
+
+--*/
+{
+
+ //
+ // Output the SID
+ //
+
+ if ( Sid == NULL ) {
+ NlPrint((0, "(null)\n"));
+ } else {
+ UNICODE_STRING SidString;
+ NTSTATUS Status;
+
+ Status = RtlConvertSidToUnicodeString( &SidString, Sid, TRUE );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((0, "Invalid 0x%lX\n", Status ));
+ } else {
+ NlPrint((0, "%wZ\n", &SidString ));
+ RtlFreeUnicodeString( &SidString );
+ }
+ }
+
+ UNREFERENCED_PARAMETER( DebugFlag );
+}
+
+VOID
+NlpDumpHexData(
+ IN DWORD DebugFlag,
+ LPDWORD Buffer,
+ DWORD BufferSize
+ )
+/*++
+Routine Description:
+
+ Dumps the buffer content on to the debugger output.
+
+Arguments:
+
+ Buffer: buffer pointer.
+
+ BufferSize: size of the buffer.
+
+Return Value:
+
+ none
+
+--*/
+{
+ DumpBuffer( Buffer, BufferSize );
+ UNREFERENCED_PARAMETER( DebugFlag );
+}
+
+
+VOID
+PrintTime(
+ LPSTR Comment,
+ LARGE_INTEGER ConvertTime
+ )
+/*++
+
+Routine Description:
+
+ Print the specified time
+
+Arguments:
+
+ Comment - Comment to print in front of the time
+
+ Time - GMT time to print (Nothing is printed if this is zero)
+
+Return Value:
+
+ None
+
+--*/
+{
+ //
+ // If we've been asked to convert an NT GMT time to ascii,
+ // Do so
+ //
+
+ if ( ConvertTime.QuadPart != 0 ) {
+ LARGE_INTEGER LocalTime;
+ TIME_FIELDS TimeFields;
+ NTSTATUS Status;
+
+ printf( "%s", Comment );
+
+ Status = RtlSystemTimeToLocalTime( &ConvertTime, &LocalTime );
+ if ( !NT_SUCCESS( Status )) {
+ printf( "Can't convert time from GMT to Local time\n" );
+ LocalTime = ConvertTime;
+ }
+
+ RtlTimeToTimeFields( &LocalTime, &TimeFields );
+
+ printf( "%8.8lx %8.8lx = %ld/%ld/%ld %ld:%2.2ld:%2.2ld\n",
+ ConvertTime.LowPart,
+ ConvertTime.HighPart,
+ TimeFields.Month,
+ TimeFields.Day,
+ TimeFields.Year,
+ TimeFields.Hour,
+ TimeFields.Minute,
+ TimeFields.Second );
+ }
+}
+
+LPSTR
+FindSymbolicNameForStatus(
+ DWORD Id
+ )
+{
+ ULONG i;
+
+ i = 0;
+ if (Id == 0) {
+ return "STATUS_SUCCESS";
+ }
+
+ if (Id & 0xC0000000) {
+ while (ntstatusSymbolicNames[ i ].SymbolicName) {
+ if (ntstatusSymbolicNames[ i ].MessageId == (NTSTATUS)Id) {
+ return ntstatusSymbolicNames[ i ].SymbolicName;
+ } else {
+ i += 1;
+ }
+ }
+ }
+
+ while (winerrorSymbolicNames[ i ].SymbolicName) {
+ if (winerrorSymbolicNames[ i ].MessageId == Id) {
+ return winerrorSymbolicNames[ i ].SymbolicName;
+ } else {
+ i += 1;
+ }
+ }
+
+#ifdef notdef
+ while (neteventSymbolicNames[ i ].SymbolicName) {
+ if (neteventSymbolicNames[ i ].MessageId == Id) {
+ return neteventSymbolicNames[ i ].SymbolicName
+ } else {
+ i += 1;
+ }
+ }
+#endif // notdef
+
+ return NULL;
+}
+
+
+VOID
+PrintStatus(
+ NET_API_STATUS NetStatus
+ )
+/*++
+
+Routine Description:
+
+ Print a net status code.
+
+Arguments:
+
+ NetStatus - The net status code to print.
+
+Return Value:
+
+ None
+
+--*/
+{
+ printf( "Status = %lu 0x%lx", NetStatus, NetStatus );
+
+ switch (NetStatus) {
+ case NERR_Success:
+ printf( " NERR_Success" );
+ break;
+
+ case NERR_DCNotFound:
+ printf( " NERR_DCNotFound" );
+ break;
+
+ case NERR_UserNotFound:
+ printf( " NERR_UserNotFound" );
+ break;
+
+ case NERR_NetNotStarted:
+ printf( " NERR_NetNotStarted" );
+ break;
+
+ case NERR_WkstaNotStarted:
+ printf( " NERR_WkstaNotStarted" );
+ break;
+
+ case NERR_ServerNotStarted:
+ printf( " NERR_ServerNotStarted" );
+ break;
+
+ case NERR_BrowserNotStarted:
+ printf( " NERR_BrowserNotStarted" );
+ break;
+
+ case NERR_ServiceNotInstalled:
+ printf( " NERR_ServiceNotInstalled" );
+ break;
+
+ case NERR_BadTransactConfig:
+ printf( " NERR_BadTransactConfig" );
+ break;
+
+ default:
+ printf( " %s", FindSymbolicNameForStatus( NetStatus ) );
+ break;
+
+ }
+
+ printf( "\n" );
+}
+
+VOID
+NlAssertFailed(
+ IN PVOID FailedAssertion,
+ IN PVOID FileName,
+ IN ULONG LineNumber,
+ IN PCHAR Message OPTIONAL
+ )
+{
+ printf( "\n*** Assertion failed: %s%s\n*** Source File: %s, line %ld\n\n",
+ Message ? Message : "",
+ FailedAssertion,
+ FileName,
+ LineNumber
+ );
+
+}
+
+
+VOID
+WhoWillLogMeOnResponse(
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads the responses that are received for the query
+ messages sent from the main thread.
+
+Arguments:
+
+ none
+
+Return Value:
+
+ None
+
+--*/
+{
+ DWORD i;
+ DWORD WaitCount;
+ DWORD IndexArray[64];
+ HANDLE HandleArray[64];
+
+ LPWSTR LocalDomainName;
+ LPWSTR LocalServerName;
+ LPWSTR LocalUserName;
+ PCHAR Where;
+ DWORD Version;
+ DWORD VersionFlags;
+ SYSTEMTIME SystemTime;
+
+ NETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
+ DWORD SamLogonResponseSize;
+ DWORD WaitStatus;
+ NET_API_STATUS NetStatus;
+ BOOL AllReceived;
+
+ for(;;) {
+
+ //
+ // make wait array.
+ //
+
+ WaitCount = 0;
+
+ AllReceived = TRUE;
+
+ for (i = 0; i < GlobalIterationCount; i++ ) {
+
+ //
+ if( GlobalMailInfo[i].State == TRUE ) {
+
+ //
+ // if a response is received.
+ //
+
+ continue;
+ }
+
+ AllReceived = FALSE;
+
+ //
+ // post a read.
+ //
+
+ if( GlobalMailInfo[i].ReadPending == FALSE ) {
+
+ if ( !ReadFile( GlobalMailInfo[i].ResponseHandle,
+ (PCHAR)&GlobalMailInfo[i].SamLogonResponse,
+ sizeof(NETLOGON_SAM_LOGON_RESPONSE),
+ &SamLogonResponseSize,
+ &GlobalMailInfo[i].OverLapped )) { // Overlapped I/O
+
+ NetStatus = GetLastError();
+
+ if( NetStatus != ERROR_IO_PENDING ) {
+
+ printf( "Cannot read mailslot (%s) : %ld\n",
+ GlobalMailInfo[i].Name,
+ NetStatus);
+ goto Cleanup;
+ }
+ }
+
+ GlobalMailInfo[i].ReadPending = TRUE;
+
+ }
+
+ HandleArray[WaitCount] = GlobalMailInfo[i].ResponseHandle;
+ IndexArray[WaitCount] = i;
+
+ WaitCount++;
+ }
+
+ if( (WaitCount == 0) ) {
+
+ if( AllReceived ) {
+
+ //
+ // we received responses for all messages, so we are
+ // done.
+ //
+
+ goto Cleanup;
+ }
+ else {
+
+ // wait for an query posted
+ //
+
+ WaitStatus = WaitForSingleObject( GlobalPostEvent, (DWORD) -1 );
+
+ if( WaitStatus != 0 ) {
+ printf("Can't successfully wait post event %ld\n",
+ WaitStatus );
+
+ goto Cleanup;
+ }
+
+ continue;
+ }
+ }
+
+ //
+ // wait for response.
+ //
+
+ WaitStatus = WaitForMultipleObjects(
+ WaitCount,
+ HandleArray,
+ FALSE, // Wait for ANY handle
+ 15000 ); // 3 * 5 Secs
+
+ if( WaitStatus == WAIT_TIMEOUT ) {
+
+ // we are done.
+
+ break;
+ }
+
+ if( (WaitStatus < 0) || (WaitStatus >= WaitCount ) ) {
+
+ printf("Invalid WaitStatus returned %ld\n", WaitStatus );
+ goto Cleanup;
+ }
+
+ //
+ // get index
+ //
+
+ i = IndexArray[WaitStatus];
+
+
+ //
+ // read response
+ //
+
+ if( !GetOverlappedResult(
+ GlobalMailInfo[i].ResponseHandle,
+ &GlobalMailInfo[i].OverLapped,
+ &SamLogonResponseSize,
+ TRUE) ) { // wait for the read complete.
+
+ printf("can't read overlapped response %ld",GetLastError() );
+ goto Cleanup;
+
+ }
+
+ SamLogonResponse = GlobalMailInfo[i].SamLogonResponse;
+
+ //
+ // indicate that we received a response.
+ //
+
+ GlobalMailInfo[i].State = TRUE;
+ GlobalMailInfo[i].ReadPending = FALSE;
+
+
+ GetLocalTime( &SystemTime );
+
+ EnterCriticalSection( &GlobalPrintCritSect );
+
+ printf( "[%02u:%02u:%02u] ",
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond );
+
+ printf( "Response %ld: ", i);
+
+ //
+ // Ensure the version is expected.
+ //
+
+ Version = NetpLogonGetMessageVersion( &SamLogonResponse,
+ &SamLogonResponseSize,
+ &VersionFlags );
+
+ if ( Version != LMNT_MESSAGE ) {
+ printf("Response version not valid.\n");
+ goto Continue;
+ }
+
+ //
+ // Pick up the name of the server that responded.
+ //
+
+ Where = (PCHAR) &SamLogonResponse.UnicodeLogonServer;
+ if ( !NetpLogonGetUnicodeString(
+ (PCHAR)&SamLogonResponse,
+ SamLogonResponseSize,
+ &Where,
+ sizeof(SamLogonResponse.UnicodeLogonServer),
+ &LocalServerName ) ) {
+
+ printf("Response server name not formatted right\n");
+ goto Continue;
+ }
+
+ //
+ // Pick up the name of the account the response is for.
+ //
+
+ if ( !NetpLogonGetUnicodeString(
+ (PCHAR)&SamLogonResponse,
+ SamLogonResponseSize,
+ &Where,
+ sizeof(SamLogonResponse.UnicodeUserName ),
+ &LocalUserName ) ) {
+
+ printf("Response User name not formatted right\n");
+ goto Continue;
+ }
+
+ //
+ // Pick up the name of the domain the response is for.
+ //
+
+ if ( !NetpLogonGetUnicodeString(
+ (PCHAR)&SamLogonResponse,
+ SamLogonResponseSize,
+ &Where,
+ sizeof(SamLogonResponse.UnicodeUserName ),
+ &LocalDomainName ) ) {
+
+ printf("Response Domain name not formatted right\n");
+ goto Continue;
+ }
+
+ //
+ // If the response is for the correct account,
+ // break out of the loop.
+ //
+
+ if ( NlNameCompare(
+ GlobalAccountName,
+ LocalUserName,
+ NAMETYPE_USER)!=0){
+
+ printf("Response not for correct User name "
+ FORMAT_LPWSTR " s.b. " FORMAT_LPWSTR "\n",
+ LocalUserName, GlobalAccountName );
+ goto Continue;
+ }
+
+
+
+ printf( "S:" FORMAT_LPWSTR " D:" FORMAT_LPWSTR
+ " A:" FORMAT_LPWSTR,
+ LocalServerName,
+ LocalDomainName,
+ LocalUserName );
+
+ //
+ // If the DC recognizes our account,
+ // we've successfully found the DC.
+ //
+
+ switch (SamLogonResponse.Opcode) {
+ case LOGON_SAM_LOGON_RESPONSE:
+
+ printf( " (Act found)\n" );
+ break;
+
+ case LOGON_SAM_USER_UNKNOWN:
+
+ printf( " (Act not found)\n" );
+ break;
+
+ case LOGON_PAUSE_RESPONSE:
+
+ printf( " (netlogon paused)\n" );
+ break;
+
+ default:
+ printf( " (Unknown opcode: %lx)\n", SamLogonResponse.Opcode );
+ break;
+ }
+
+Continue:
+
+ LeaveCriticalSection( &GlobalPrintCritSect );
+ }
+
+Cleanup:
+
+ //
+ // print non-responsed mailslots.
+ //
+
+ for( i = 0; i < GlobalIterationCount; i++ ) {
+
+ if( GlobalMailInfo[i].State == FALSE ) {
+
+ printf("Didn't receive a response for mail "
+ "message %ld (%s)\n", i, GlobalMailInfo[i].Name );
+ }
+ }
+
+ return;
+}
+
+
+
+VOID
+WhoWillLogMeOn(
+ IN LPWSTR DomainName,
+ IN LPWSTR AccountName,
+ IN DWORD IterationCount
+ )
+
+/*++
+
+Routine Description:
+
+ Determine which DC will log the specified account on
+
+Arguments:
+
+ DomainName - name of the "doamin" to send the message to
+
+ AccountName - Name of our user account to find.
+
+ IterationCount - Number of consecutive messages to send.
+
+Return Value:
+
+ None
+
+--*/
+{
+
+ NET_API_STATUS NetStatus;
+ ULONG AllowableAccountControlBits = USER_ACCOUNT_TYPE_MASK;
+
+ WCHAR NetlogonMailslotName[DNLEN+NETLOGON_NT_MAILSLOT_LEN+4];
+ NETLOGON_SAM_LOGON_REQUEST SamLogonRequest;
+ PCHAR Where;
+ PCHAR WhereResponseMailslot;
+
+ HANDLE *ResponseMailslotHandle = NULL;
+
+ WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH+1];
+ DWORD ComputerNameLength = MAX_COMPUTERNAME_LENGTH+1;
+
+ DWORD i;
+ DWORD j;
+ SYSTEMTIME SystemTime;
+
+ HANDLE ResponseThreadHandle;
+ DWORD ThreadId;
+ DWORD WaitStatus;
+ DWORD SamLogonResponseSize;
+
+ //
+ // support only 64 iterations
+ //
+
+ if( IterationCount > 64 ) {
+
+ printf("Interations set to 64, maximum supported\n");
+ IterationCount = 64;
+ }
+
+ GlobalIterationCount = IterationCount;
+ GlobalAccountName = AccountName;
+
+ InitializeCriticalSection( &GlobalPrintCritSect );
+
+ //
+ // Get out computer name
+ //
+
+ if (!GetComputerName( ComputerName, &ComputerNameLength ) ) {
+ printf( "Can't GetComputerName\n" );
+ return;
+ }
+
+ //
+ // create mailslots
+ //
+
+ for (i = 0; i < IterationCount; i++ ) {
+
+ //
+ // Create a mailslot for the DC's to respond to.
+ //
+
+ if (NetStatus = NetpLogonCreateRandomMailslot(
+ GlobalMailInfo[i].Name,
+ &GlobalMailInfo[i].ResponseHandle)){
+
+ printf( "Cannot create temp mailslot %ld\n", NetStatus );
+ goto Cleanup;
+ }
+
+ if ( !SetMailslotInfo( GlobalMailInfo[i].ResponseHandle,
+ (DWORD) MAILSLOT_WAIT_FOREVER ) ) {
+ printf( "Cannot set mailslot info %ld\n", GetLastError() );
+ goto Cleanup;
+ }
+
+ (void) memset( &GlobalMailInfo[i].OverLapped, '\0',
+ sizeof(OVERLAPPED) );
+
+ GlobalMailInfo[i].State = FALSE;
+ GlobalMailInfo[i].ReadPending = FALSE;
+ }
+
+
+ //
+ // create post event
+ //
+
+ GlobalPostEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+
+ if( GlobalPostEvent == NULL ) {
+
+ printf("can't create post event %ld \n", GetLastError() );
+ goto Cleanup;
+ }
+
+ InitializeCriticalSection( &GlobalPrintCritSect );
+
+ //
+ // start response thread.
+ //
+
+ ResponseThreadHandle =
+ CreateThread(
+ NULL, // No security attributes
+ THREAD_STACKSIZE,
+ (LPTHREAD_START_ROUTINE)
+ WhoWillLogMeOnResponse,
+ NULL,
+ 0, // No special creation flags
+ &ThreadId );
+
+ if ( ResponseThreadHandle == NULL ) {
+
+ printf("can't create response thread %ld\n", GetLastError() );
+ goto Cleanup;
+ }
+
+ //
+ // Build the query message.
+ //
+
+ SamLogonRequest.Opcode = LOGON_SAM_LOGON_REQUEST;
+ SamLogonRequest.RequestCount = 0;
+
+ Where = (PCHAR) &SamLogonRequest.UnicodeComputerName;
+
+ NetpLogonPutUnicodeString(
+ ComputerName,
+ sizeof(SamLogonRequest.UnicodeComputerName),
+ &Where );
+
+ NetpLogonPutUnicodeString(
+ AccountName,
+ sizeof(SamLogonRequest.UnicodeUserName),
+ &Where );
+
+ WhereResponseMailslot = Where;
+
+ wcscpy( NetlogonMailslotName, L"\\\\" );
+ wcscat( NetlogonMailslotName, DomainName );
+ // wcscat( NetlogonMailslotName, L"*" ); // Don't add for computer name
+ wcscat( NetlogonMailslotName, NETLOGON_NT_MAILSLOT_W);
+
+ //
+ // Send atmost 3 messages/mailslot
+ //
+
+ for( j = 0; j < 3; j++ ) {
+
+ //
+ // Repeat the message multiple times to load the servers
+ //
+
+ for (i = 0; i < IterationCount; i++ ) {
+
+ if( GlobalMailInfo[i].State == TRUE ) {
+
+ //
+ // if a response is received.
+ //
+
+ continue;
+ }
+
+ Where = WhereResponseMailslot;
+
+ NetpLogonPutOemString(
+ GlobalMailInfo[i].Name,
+ sizeof(SamLogonRequest.MailslotName),
+ &Where );
+
+ NetpLogonPutBytes(
+ &AllowableAccountControlBits,
+ sizeof(SamLogonRequest.AllowableAccountControlBits),
+ &Where );
+
+ NetpLogonPutNtToken( &Where );
+
+ //
+ // Send the message to a DC for the domain.
+ //
+
+ NetStatus = NetpLogonWriteMailslot(
+ NetlogonMailslotName,
+ (PCHAR)&SamLogonRequest,
+ Where - (PCHAR)(&SamLogonRequest) );
+
+ if ( NetStatus != NERR_Success ) {
+ printf( "Cannot write netlogon mailslot: %ld\n", NetStatus);
+ goto Cleanup;
+ }
+
+ GetLocalTime( &SystemTime );
+
+ EnterCriticalSection( &GlobalPrintCritSect );
+
+ printf( "[%02u:%02u:%02u] ",
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond );
+
+ printf( "Mail message %ld sent successfully (%s) \n",
+ i, GlobalMailInfo[i].Name );
+
+ LeaveCriticalSection( &GlobalPrintCritSect );
+
+ if( !SetEvent( GlobalPostEvent ) ) {
+ printf("Can't set post event %ld \n", GetLastError() );
+ goto Cleanup;
+ }
+
+
+ }
+
+ //
+ // wait 5 secs to see response thread received all responses.
+ //
+
+ WaitStatus = WaitForSingleObject( ResponseThreadHandle, 5000 );
+ // 15 secs. TIMEOUT
+
+ if( WaitStatus != WAIT_TIMEOUT ) {
+
+ if( WaitStatus != 0 ) {
+ printf("can't do WaitForSingleObject %ld\n", WaitStatus);
+ }
+
+ goto Cleanup;
+ }
+ }
+
+
+Cleanup:
+
+ //
+ // Wait for the response thread to complete.
+ //
+
+ if( ResponseThreadHandle != NULL ) {
+
+ WaitStatus = WaitForSingleObject( ResponseThreadHandle, 15000 );
+ // 15 secs. TIMEOUT
+
+ if( WaitStatus ) {
+
+ if( WaitStatus == WAIT_TIMEOUT ) {
+ printf("Can't stop response thread (TIMEOUT) \n");
+ } else {
+ printf("Can't stop response thread %ld \n", WaitStatus);
+ }
+ }
+
+ }
+
+
+ for (i = 0; i < IterationCount; i++ ) {
+
+ if( GlobalMailInfo[i].ResponseHandle != NULL ) {
+ CloseHandle( GlobalMailInfo[i].ResponseHandle);
+ }
+ }
+
+ if( GlobalPostEvent != NULL ) {
+ CloseHandle( GlobalPostEvent );
+ }
+
+ DeleteCriticalSection( &GlobalPrintCritSect );
+
+ return;
+}
+
+#define MAX_PRINTF_LEN 1024 // Arbitrary.
+
+VOID
+NlPrintRoutine(
+ IN DWORD DebugFlag,
+ IN LPSTR Format,
+ ...
+ )
+{
+ va_list arglist;
+ char OutputBuffer[MAX_PRINTF_LEN];
+
+ //
+ // Put a the information requested by the caller onto the line
+ //
+
+ va_start(arglist, Format);
+ (VOID) vsprintf(OutputBuffer, Format, arglist);
+ va_end(arglist);
+
+ printf( "%s", OutputBuffer );
+ return;
+ UNREFERENCED_PARAMETER( DebugFlag );
+}
+
+NTSTATUS
+SimulateFullSync(
+ LPWSTR PdcName,
+ LPWSTR MachineName
+ )
+/*++
+
+Routine Description:
+
+ This function simulate a full sync replication by calling
+ NetDatabaseSync API and simply ignoring successfully returned data.
+
+Arguments:
+
+ PdcName - Name of the PDC from where the database replicated.
+
+ MachineName - Name of the machine account used to authenticate.
+
+Return Value:
+
+ Network Status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ NETLOGON_CREDENTIAL ServerChallenge;
+ NETLOGON_CREDENTIAL ClientChallenge;
+ NETLOGON_CREDENTIAL ComputedServerCredential;
+ NETLOGON_CREDENTIAL ReturnedServerCredential;
+
+ NETLOGON_CREDENTIAL AuthenticationSeed;
+ NETLOGON_SESSION_KEY SessionKey;
+
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR ReturnAuthenticator;
+
+ UNICODE_STRING Password;
+ NT_OWF_PASSWORD NtOwfPassword;
+
+ ULONG SamSyncContext = 0;
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL;
+
+ DWORD DatabaseIndex;
+ DWORD i;
+
+ WCHAR AccountName[SSI_ACCOUNT_NAME_LENGTH+1];
+
+ //
+ // Prepare our challenge
+ //
+
+ NlComputeChallenge( &ClientChallenge );
+
+ printf("ClientChallenge = %lx %lx\n",
+ ((DWORD*)&ClientChallenge)[0],
+ ((DWORD *)&ClientChallenge)[1]);
+
+ //
+ // Get the primary's challenge
+ //
+
+ Status = I_NetServerReqChallenge(PdcName,
+ MachineName,
+ &ClientChallenge,
+ &ServerChallenge );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ fprintf( stderr,
+ "I_NetServerReqChallenge to " FORMAT_LPWSTR
+ " returned 0x%lx\n",
+ PdcName,
+ Status );
+ return(Status);
+ }
+
+
+ printf("ServerChallenge = %lx %lx\n",
+ ((DWORD *)&ServerChallenge)[0],
+ ((DWORD *)&ServerChallenge)[1]);
+
+ Password.Length =
+ Password.MaximumLength = wcslen(MachineName) * sizeof(WCHAR);
+ Password.Buffer = MachineName;
+
+ //
+ // Compute the NT OWF password for this user.
+ //
+
+ Status = RtlCalculateNtOwfPassword( &Password, &NtOwfPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ fprintf(stderr, "Can't compute OWF password 0x%lx \n", Status );
+ return(Status);
+ }
+
+
+ printf("Password = %lx %lx %lx %lx\n",
+ ((DWORD *) (&NtOwfPassword))[0],
+ ((DWORD *) (&NtOwfPassword))[1],
+ ((DWORD *) (&NtOwfPassword))[2],
+ ((DWORD *) (&NtOwfPassword))[3]);
+
+
+ //
+ // Actually compute the session key given the two challenges and the
+ // password.
+ //
+
+ NlMakeSessionKey(
+ &NtOwfPassword,
+ &ClientChallenge,
+ &ServerChallenge,
+ &SessionKey );
+
+ printf("SessionKey = %lx %lx %lx %lx\n",
+ ((DWORD *) (&SessionKey))[0],
+ ((DWORD *) (&SessionKey))[1],
+ ((DWORD *) (&SessionKey))[2],
+ ((DWORD *) (&SessionKey))[3]);
+
+ //
+ // Prepare credentials using our challenge.
+ //
+
+ NlComputeCredentials( &ClientChallenge,
+ &AuthenticationSeed,
+ &SessionKey );
+
+ printf("ClientCredential = %lx %lx\n",
+ ((DWORD *) (&AuthenticationSeed))[0],
+ ((DWORD *) (&AuthenticationSeed))[1]);
+
+ //
+ // Send these credentials to primary. The primary will compute
+ // credentials using the challenge supplied by us and compare
+ // with these. If both match then it will compute credentials
+ // using its challenge and return it to us for verification
+ //
+
+ wcscpy( AccountName, MachineName );
+ wcscat( AccountName, SSI_ACCOUNT_NAME_POSTFIX);
+
+ Status = I_NetServerAuthenticate( PdcName,
+ AccountName,
+ ServerSecureChannel,
+ MachineName,
+ &AuthenticationSeed,
+ &ReturnedServerCredential );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ fprintf(stderr,
+ "I_NetServerAuthenticate to " FORMAT_LPWSTR " 0x%lx\n",
+ &PdcName,
+ Status );
+
+ return(Status);
+
+ }
+
+
+ printf("ServerCredential GOT = %lx %lx\n",
+ ((DWORD *) (&ReturnedServerCredential))[0],
+ ((DWORD *) (&ReturnedServerCredential))[1]);
+
+
+ //
+ // The DC returned a server credential to us,
+ // ensure the server credential matches the one we would compute.
+ //
+
+ NlComputeCredentials( &ServerChallenge,
+ &ComputedServerCredential,
+ &SessionKey);
+
+
+ printf("ServerCredential MADE =%lx %lx\n",
+ ((DWORD *) (&ComputedServerCredential))[0],
+ ((DWORD *) (&ComputedServerCredential))[1]);
+
+
+ if (RtlCompareMemory( &ReturnedServerCredential,
+ &ComputedServerCredential,
+ sizeof(ReturnedServerCredential)) !=
+ sizeof(ReturnedServerCredential)) {
+
+ fprintf( stderr, "Access Denied \n");
+ return( STATUS_ACCESS_DENIED );
+ }
+
+
+ printf("Session Setup to " FORMAT_LPWSTR " completed successfully \n",
+ PdcName);
+
+ //
+ // retrive database info
+ //
+
+ for( DatabaseIndex = 0 ; DatabaseIndex < 3; DatabaseIndex++) {
+
+ SamSyncContext = 0;
+
+ for( i = 0; ; i++) {
+
+ NlBuildAuthenticator(
+ &AuthenticationSeed,
+ &SessionKey,
+ &OurAuthenticator);
+
+ Status = I_NetDatabaseSync(
+ PdcName,
+ MachineName,
+ &OurAuthenticator,
+ &ReturnAuthenticator,
+ DatabaseIndex,
+ &SamSyncContext,
+ &DeltaArray,
+ 128 * 1024 ); // 128K
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ fprintf( stderr,
+ "I_NetDatabaseSync to " FORMAT_LPWSTR " failed 0x%lx\n",
+ PdcName,
+ Status );
+
+ return(Status);
+ }
+
+ if ( ( !NlUpdateSeed(
+ &AuthenticationSeed,
+ &ReturnAuthenticator.Credential,
+ &SessionKey) ) ) {
+
+ fprintf(stderr, "NlUpdateSeed failed \n" );
+ return( STATUS_ACCESS_DENIED );
+ }
+
+ printf( "Received %ld Buffer data \n", i);
+ //
+ // ignore return data
+ //
+
+ MIDL_user_free( DeltaArray );
+
+ if ( Status == STATUS_SUCCESS ) {
+
+ break;
+ }
+
+ }
+
+ printf("FullSync replication of database %ld completed "
+ "successfully \n", DatabaseIndex );
+
+ }
+
+}
+
+
+LONG
+ForceRegOpenSubkey(
+ HKEY ParentHandle,
+ LPSTR KeyName,
+ LPSTR Subkey,
+ REGSAM DesiredAccess,
+ PHKEY ReturnHandle
+ )
+
+/*++
+
+Routine Description:
+
+ Open the specified key one subkey at a time defeating access denied by
+ setting the DACL to allow us access. This kludge is needed since the
+ security tree is shipped not allowing Administrators access.
+
+Arguments:
+
+ ParentHandle - Currently open handle
+
+ KeyName - Entire key name (for error messages only)
+
+ Subkey - Direct subkey of ParentHandle
+
+ DesiredAccess - Desired access to the new key
+
+ ReturnHandle - Returns an open handle to the newly opened key.
+
+
+Return Value:
+
+ Return TRUE for success.
+
+--*/
+
+{
+ LONG RegStatus;
+ LONG SavedStatus;
+ HKEY TempHandle = NULL;
+ BOOLEAN DaclChanged = FALSE;
+
+ SECURITY_INFORMATION SecurityInformation = DACL_SECURITY_INFORMATION;
+ DWORD OldSecurityDescriptorSize;
+ CHAR OldSecurityDescriptor[1024];
+ CHAR NewSecurityDescriptor[1024];
+
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ PSID AdminSid = NULL;
+ BOOL DaclPresent;
+ BOOL DaclDefaulted;
+ PACL Dacl;
+ ACL_SIZE_INFORMATION AclSizeInfo;
+ ACCESS_ALLOWED_ACE *Ace;
+ DWORD i;
+
+
+ //
+ // Open the sub-key
+ //
+
+ SavedStatus = RegOpenKeyExA(
+ ParentHandle,
+ Subkey,
+ 0, //Reserved
+ DesiredAccess,
+ ReturnHandle );
+
+ if ( SavedStatus != ERROR_ACCESS_DENIED ) {
+ return SavedStatus;
+ }
+
+ //
+ // If access is denied,
+ // try changing the DACL to give us access
+ //
+
+ // printf( "Cannot RegOpenKey %s subkey %s ", KeyName, Subkey );
+ // PrintStatus( SavedStatus );
+
+ //
+ // Open again asking to change the DACL
+ //
+
+ RegStatus = RegOpenKeyExA(
+ ParentHandle,
+ Subkey,
+ 0, //Reserved
+ WRITE_DAC | READ_CONTROL,
+ &TempHandle );
+
+ if ( RegStatus != ERROR_SUCCESS) {
+ printf( "Cannot RegOpenKey to change DACL %s subkey %s ", KeyName, Subkey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Get the current DACL so we can restore it.
+ //
+
+ OldSecurityDescriptorSize = sizeof(OldSecurityDescriptor);
+ RegStatus = RegGetKeySecurity(
+ TempHandle,
+ SecurityInformation,
+ (PSECURITY_DESCRIPTOR) OldSecurityDescriptor,
+ &OldSecurityDescriptorSize );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot RegGetKeySecurity for %s subkey %s ", KeyName, Subkey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Build the Administrators SID
+ //
+ if ( !AllocateAndInitializeSid( &NtAuthority,
+ 2, // two subauthorities
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ &AdminSid ) ) {
+ printf( "Cannot AllocateAndInitializeSid " );
+ PrintStatus( GetLastError() );
+ goto Cleanup;
+ }
+
+ //
+ // Change the DACL to allow all access
+ //
+
+ RtlCopyMemory( NewSecurityDescriptor,
+ OldSecurityDescriptor,
+ OldSecurityDescriptorSize );
+
+ if ( !GetSecurityDescriptorDacl(
+ (PSECURITY_DESCRIPTOR)NewSecurityDescriptor,
+ &DaclPresent,
+ &Dacl,
+ &DaclDefaulted )) {
+ printf( "Cannot GetSecurityDescriptorDacl for %s subkey %s ", KeyName, Subkey );
+ PrintStatus( GetLastError() );
+ goto Cleanup;
+ }
+
+ if ( !DaclPresent ) {
+ printf( "Cannot GetSecurityDescriptorDacl " );
+ printf( "Cannot DaclNotPresent for %s subkey %s ", KeyName, Subkey );
+ goto Cleanup;
+ }
+
+ if ( !GetAclInformation(
+ Dacl,
+ &AclSizeInfo,
+ sizeof(AclSizeInfo),
+ AclSizeInformation )) {
+ printf( "Cannot GetAclInformation for %s subkey %s ", KeyName, Subkey );
+ PrintStatus( GetLastError() );
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Search for an administrators ACE and give it "DesiredAccess"
+ //
+
+ for ( i=0; i<AclSizeInfo.AceCount ; i++ ) {
+
+ if ( !GetAce( Dacl, i, (LPVOID *) &Ace ) ) {
+ printf( "Cannot GetAce %ld for %s subkey %s ", i, KeyName, Subkey );
+ PrintStatus( GetLastError() );
+ goto Cleanup;
+ }
+
+ if ( Ace->Header.AceType != ACCESS_ALLOWED_ACE_TYPE ) {
+ continue;
+ }
+
+ if ( !EqualSid( AdminSid, (PSID)&Ace->SidStart ) ) {
+ continue;
+ }
+
+ Ace->Mask |= DesiredAccess;
+ break;
+
+ }
+
+ if ( i >= AclSizeInfo.AceCount ) {
+ printf( "No Administrators Ace for %s subkey %s\n", KeyName, Subkey );
+ goto Cleanup;
+ }
+
+ //
+ // Actually set the new DACL on the key
+ //
+
+ RegStatus = RegSetKeySecurity(
+ TempHandle,
+ SecurityInformation,
+ (PSECURITY_DESCRIPTOR)NewSecurityDescriptor );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot RegSetKeySecurity for %s subkey %s ", KeyName, Subkey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ DaclChanged = TRUE;
+
+
+ //
+ // Open the sub-key again with the desired access
+ //
+
+ SavedStatus = RegOpenKeyExA(
+ ParentHandle,
+ Subkey,
+ 0, //Reserved
+ DesiredAccess,
+ ReturnHandle );
+
+ if ( SavedStatus != ERROR_SUCCESS ) {
+ printf( "Cannot RegOpenKeyEx following DACL change for %s subkey %s ", KeyName, Subkey );
+ PrintStatus( SavedStatus );
+ goto Cleanup;
+ }
+
+
+Cleanup:
+ if ( TempHandle != NULL ) {
+ //
+ // Restore DACL to original value.
+ //
+
+ if ( DaclChanged ) {
+
+ RegStatus = RegSetKeySecurity(
+ TempHandle,
+ SecurityInformation,
+ (PSECURITY_DESCRIPTOR)OldSecurityDescriptor );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot RegSetKeySecurity to restore %s subkey %s ", KeyName, Subkey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+ }
+ (VOID) RegCloseKey( TempHandle );
+ }
+
+ if ( AdminSid != NULL ) {
+ (VOID) FreeSid( AdminSid );
+ }
+
+ return SavedStatus;
+
+}
+
+
+
+LONG
+ForceRegOpenKey(
+ HKEY BaseHandle,
+ LPSTR KeyName,
+ REGSAM DesiredAccess,
+ PHKEY ReturnHandle
+ )
+
+/*++
+
+Routine Description:
+
+ Open the specified key one subkey at a time defeating access denied by
+ setting the DACL to allow us access. This kludge is needed since the
+ security tree is shipped not allowing Administrators access.
+
+Arguments:
+
+ BaseHandle - Currently open handle
+
+ KeyName - Registry key to open relative to BaseHandle.
+
+ DesiredAccess - Desired access to the new key
+
+ ReturnHandle - Returns an open handle to the newly opened key.
+
+
+Return Value:
+
+ Return TRUE for success.
+
+--*/
+
+{
+ LONG RegStatus;
+ PCHAR StartOfSubkey;
+ PCHAR EndOfSubkey;
+ CHAR Subkey[512];
+ HKEY ParentHandle;
+
+ ASSERT( KeyName[0] != '\0' );
+
+
+ //
+ // Loop opening the next subkey.
+ //
+
+ EndOfSubkey = KeyName;
+ ParentHandle = BaseHandle;
+
+ for (;;) {
+
+
+ //
+ // Compute the name of the next subkey.
+ //
+
+ StartOfSubkey = EndOfSubkey;
+
+ for ( ;; ) {
+
+ if ( *EndOfSubkey == '\0' || *EndOfSubkey == '\\' ) {
+ strncpy( Subkey, StartOfSubkey, EndOfSubkey-StartOfSubkey );
+ Subkey[EndOfSubkey-StartOfSubkey] = '\0';
+ if ( *EndOfSubkey == '\\' ) {
+ EndOfSubkey ++;
+ }
+ break;
+ }
+ EndOfSubkey ++;
+ }
+
+
+ //
+ // Open the sub-key
+ //
+
+ RegStatus = ForceRegOpenSubkey(
+ ParentHandle,
+ KeyName,
+ Subkey,
+ DesiredAccess,
+ ReturnHandle );
+
+
+ //
+ // Close the parent handle and return any error condition.
+ //
+
+ if ( ParentHandle != BaseHandle ) {
+ (VOID) RegCloseKey( ParentHandle );
+ }
+
+ if( RegStatus != ERROR_SUCCESS ) {
+ *ReturnHandle = NULL;
+ return RegStatus;
+ }
+
+
+ //
+ // If this is the entire key name,
+ // we're done.
+ //
+
+ if ( *EndOfSubkey == '\0' ) {
+ return ERROR_SUCCESS;
+ }
+
+ ParentHandle = *ReturnHandle;
+
+ }
+
+}
+
+
+struct {
+ LPSTR Name;
+ enum {
+ UnicodeStringType,
+ HexDataType,
+ LmPasswordType,
+ NtPasswordType
+ } Type;
+} UserVariableDataTypes[] = {
+ { "SecurityDescriptor" , HexDataType },
+ { "AccountName" , UnicodeStringType },
+ { "FullName" , UnicodeStringType },
+ { "AdminComment" , UnicodeStringType },
+ { "UserComment" , UnicodeStringType },
+ { "Parameters" , UnicodeStringType },
+ { "HomeDirectory" , UnicodeStringType },
+ { "HomeDirectoryDrive" , UnicodeStringType },
+ { "ScriptPath" , UnicodeStringType },
+ { "ProfilePath" , UnicodeStringType },
+ { "Workstations" , UnicodeStringType },
+ { "LogonHours" , HexDataType },
+ { "Groups" , HexDataType },
+ { "LmOwfPassword" , LmPasswordType },
+ { "NtOwfPassword" , NtPasswordType },
+ { "NtPasswordHistory" , HexDataType },
+ { "LmPasswordHistory" , HexDataType }
+};
+
+
+VOID
+PrintUserInfo(
+ IN LPWSTR ServerName,
+ IN LPSTR UserName
+ )
+/*++
+
+Routine Description:
+
+ Print a user's description from the SAM database
+
+Arguments:
+
+ ServerName - Name of server to query
+
+ UserName - Name of user to query
+
+Return Value:
+
+ None
+
+--*/
+{
+ NTSTATUS Status;
+ LONG RegStatus;
+ ULONG i;
+
+ HKEY BaseHandle = NULL;
+ HKEY UserHandle = NULL;
+ HKEY RidHandle = NULL;
+
+ CHAR UserKey[200];
+ CHAR RidKey[200];
+ LONG Rid;
+ CHAR AnsiRid[20];
+
+ CHAR FixedData[1000];
+ ULONG FixedDataSize;
+ SAMP_V1_0A_FIXED_LENGTH_USER FixedUser1_0A;
+ PSAMP_V1_0A_FIXED_LENGTH_USER f;
+ PSAMP_V1_0_FIXED_LENGTH_USER f1_0;
+ BOOLEAN IsVersion1_0;
+
+ CHAR VariableData[32768];
+ ULONG VariableDataSize;
+ PSAMP_VARIABLE_LENGTH_ATTRIBUTE v;
+
+ LM_OWF_PASSWORD LmOwfPassword;
+ NT_OWF_PASSWORD NtOwfPassword;
+
+ //
+ // Open the registry
+ //
+
+ RegStatus = RegConnectRegistryW( ServerName,
+ HKEY_LOCAL_MACHINE,
+ &BaseHandle);
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot connect to registy on " FORMAT_LPWSTR " ", ServerName );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+
+ //
+ // Open the key for this user name.
+ //
+
+ strcpy( UserKey, "SAM\\SAM\\Domains\\Account\\Users\\Names\\" );
+ strcat( UserKey, UserName );
+
+ RegStatus = ForceRegOpenKey( BaseHandle,
+ UserKey,
+ KEY_READ|KEY_QUERY_VALUE,
+ &UserHandle );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot open %s ", UserKey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Get the RID of the user
+ //
+
+ RegStatus = RegQueryValueExW( UserHandle,
+ NULL, // No name
+ NULL, // Reserved
+ &Rid, // Really the type
+ NULL, // Data not needed
+ NULL); // Data not needed
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot Query %s ", UserKey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ printf( "User: %s\nRid: 0x%lx\n",
+ UserName,
+ Rid );
+
+
+ //
+ // Open the key for this user rid.
+ //
+
+ sprintf( AnsiRid, "%8.8lx", Rid );
+ strcpy( RidKey, "SAM\\SAM\\Domains\\Account\\Users\\" );
+ strcat( RidKey, AnsiRid );
+
+ RegStatus = ForceRegOpenKey( BaseHandle,
+ RidKey,
+ KEY_READ|KEY_QUERY_VALUE,
+ &RidHandle );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot open %s ", RidKey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+
+ //
+ // Get the Fixed Values associated with this RID
+ //
+
+ FixedDataSize = sizeof(FixedData);
+ RegStatus = RegQueryValueExA( RidHandle,
+ "F", // Fixed value
+ NULL, // Reserved
+ NULL, // Type Not Needed
+ FixedData,
+ &FixedDataSize );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot Query %s ", RidKey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // If the fixed length data is NT 1.0,
+ // convert it to NT 1.0A format.
+ //
+
+ if ( IsVersion1_0 = (FixedDataSize == sizeof(*f1_0)) ) {
+ f1_0 = (PSAMP_V1_0_FIXED_LENGTH_USER) &FixedData;
+ FixedUser1_0A.LastLogon = f1_0->LastLogon;
+ FixedUser1_0A.LastLogoff = f1_0->LastLogoff;
+ FixedUser1_0A.PasswordLastSet = f1_0->PasswordLastSet;
+ FixedUser1_0A.AccountExpires = f1_0->AccountExpires;
+ FixedUser1_0A.UserId = f1_0->UserId;
+ FixedUser1_0A.PrimaryGroupId = f1_0->PrimaryGroupId;
+ FixedUser1_0A.UserAccountControl = f1_0->UserAccountControl;
+ FixedUser1_0A.CountryCode = f1_0->CountryCode;
+ FixedUser1_0A.BadPasswordCount = f1_0->BadPasswordCount;
+ FixedUser1_0A.LogonCount = f1_0->LogonCount;
+ FixedUser1_0A.AdminCount = f1_0->AdminCount;
+ RtlCopyMemory( FixedData, &FixedUser1_0A, sizeof(FixedUser1_0A) );
+ }
+
+ //
+ // Print the fixed length data.
+ //
+
+ f = (PSAMP_V1_0A_FIXED_LENGTH_USER) &FixedData;
+
+ if ( !IsVersion1_0) {
+ printf( "Version: 0x%lx\n", f->Revision );
+ }
+
+ PrintTime( "LastLogon: ", f->LastLogon );
+ PrintTime( "LastLogoff: ", f->LastLogoff );
+ PrintTime( "PasswordLastSet: ", f->PasswordLastSet );
+ PrintTime( "AccountExpires: ", f->AccountExpires );
+ if ( !IsVersion1_0) {
+ PrintTime( "LastBadPasswordTime: ", f->LastBadPasswordTime );
+ }
+
+ printf( "PrimaryGroupId: 0x%lx\n", f->PrimaryGroupId );
+ printf( "UserAccountControl: 0x%lx\n", f->UserAccountControl );
+
+ printf( "CountryCode: 0x%lx\n", f->CountryCode );
+ printf( "CodePage: 0x%lx\n", f->CodePage );
+ printf( "BadPasswordCount: 0x%lx\n", f->BadPasswordCount );
+ printf( "LogonCount: 0x%lx\n", f->LogonCount );
+ printf( "AdminCount: 0x%lx\n", f->AdminCount );
+
+
+ //
+ // Get the Variable Values associated with this RID
+ //
+
+ VariableDataSize = sizeof(VariableData);
+ RegStatus = RegQueryValueExA( RidHandle,
+ "V", // Variable value
+ NULL, // Reserved
+ NULL, // Type Not Needed
+ VariableData,
+ &VariableDataSize );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot Query %s \n", RidKey );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Loop printing all the attributes.
+ //
+
+ v = (PSAMP_VARIABLE_LENGTH_ATTRIBUTE) VariableData;
+
+ for ( i=0;
+ i<sizeof(UserVariableDataTypes)/sizeof(UserVariableDataTypes[0]);
+ i++ ) {
+
+ UNICODE_STRING UnicodeString;
+
+ //
+ // Make the offset relative to the beginning of the queried value.
+ //
+
+ v[i].Offset += SAMP_USER_VARIABLE_ATTRIBUTES *
+ sizeof(SAMP_VARIABLE_LENGTH_ATTRIBUTE);
+
+
+
+ //
+ // Ensure the data item descriptor is in the registry.
+ //
+
+ if ( ((PCHAR)&v[i]) > ((PCHAR)v)+VariableDataSize ) {
+ printf( "Variable data desc %ld not in variable value.\n", i );
+ goto Cleanup;
+ }
+
+ if ( v[i].Offset > (LONG) VariableDataSize ||
+ v[i].Offset + v[i].Length > VariableDataSize ) {
+ printf( "Variable data item %ld not in variable value.\n", i );
+ printf( "Offset: %ld Length: %ld Size: %ld\n",
+ v[i].Offset,
+ v[i].Length,
+ VariableDataSize );
+ goto Cleanup;
+
+ }
+
+ //
+ // Don't print null data.
+ //
+
+ if ( v[i].Length == 0 ) {
+ continue;
+ }
+
+ //
+ // Print the various types of data.
+ //
+
+ switch ( UserVariableDataTypes[i].Type ) {
+ case UnicodeStringType:
+
+ UnicodeString.Buffer = (PUSHORT)(((PCHAR)v)+v[i].Offset);
+ UnicodeString.Length = (USHORT)v[i].Length;
+ printf( "%s: %wZ\n", UserVariableDataTypes[i].Name, &UnicodeString);
+ break;
+
+ case LmPasswordType:
+ Status = RtlDecryptLmOwfPwdWithIndex(
+ (PENCRYPTED_LM_OWF_PASSWORD)(((PCHAR)v)+v[i].Offset),
+ &Rid,
+ &LmOwfPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ printf( "Cannot decrypt LM password: " );
+ PrintStatus( Status );
+ goto Cleanup;
+ }
+
+ printf( "%s: ", UserVariableDataTypes[i].Name);
+ DumpBuffer( &LmOwfPassword, sizeof(LmOwfPassword ));
+ break;
+
+ case NtPasswordType:
+ Status = RtlDecryptNtOwfPwdWithIndex(
+ (PENCRYPTED_NT_OWF_PASSWORD)(((PCHAR)v)+v[i].Offset),
+ &Rid,
+ &NtOwfPassword );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ printf( "Cannot decrypt NT password: " );
+ PrintStatus( Status );
+ goto Cleanup;
+ }
+
+ printf( "%s: ", UserVariableDataTypes[i].Name);
+ DumpBuffer( &NtOwfPassword, sizeof(NtOwfPassword ));
+ break;
+
+
+ case HexDataType:
+
+ printf( "%s: ", UserVariableDataTypes[i].Name);
+ DumpBuffer( (((PCHAR)v)+v[i].Offset), v[i].Length );
+ break;
+ }
+ }
+
+
+ //
+ // Be tidy.
+ //
+Cleanup:
+ if ( UserHandle != NULL ) {
+ RegCloseKey( UserHandle );
+ }
+ if ( RidHandle != NULL ) {
+ RegCloseKey( RidHandle );
+ }
+ if ( BaseHandle != NULL ) {
+ RegCloseKey( BaseHandle );
+ }
+ return;
+
+}
+
+
+VOID
+SetDbflagInRegistry(
+ LPWSTR ServerName,
+ ULONG DbFlagValue
+ )
+/*++
+
+Routine Description:
+
+ Set the value DbFlagValue in the Netlogon service portion of the registry.
+
+Arguments:
+
+ ServerName - Name of the server to update
+
+ DbFlagValue - Value to set dbflag to.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LONG RegStatus;
+ UCHAR AnsiDbFlag[20];
+ DWORD AnsiDbFlagLength;
+
+ HKEY BaseHandle = NULL;
+ HKEY ParmHandle = NULL;
+ LPSTR KeyName;
+#define NL_PARAM_KEY "SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters"
+
+ //
+ // Open the registry
+ //
+
+ RegStatus = RegConnectRegistryW( ServerName,
+ HKEY_LOCAL_MACHINE,
+ &BaseHandle);
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot connect to registy on " FORMAT_LPWSTR " ", ServerName );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+
+ //
+ // Open the key for Netlogon\parameters.
+ //
+
+ KeyName = NL_PARAM_KEY;
+ RegStatus = ForceRegOpenKey(
+ BaseHandle,
+ KeyName,
+ KEY_SET_VALUE,
+ &ParmHandle );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot open " NL_PARAM_KEY );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ //
+ // Set the DbFlag value into the registry.
+ //
+
+ AnsiDbFlagLength = sprintf( AnsiDbFlag, "0x%8.8lx", DbFlagValue );
+
+ RegStatus = RegSetValueExA( ParmHandle,
+ "DbFlag",
+ 0, // Reserved
+ REG_SZ,
+ AnsiDbFlag,
+ AnsiDbFlagLength + 1 );
+
+ if ( RegStatus != ERROR_SUCCESS ) {
+ printf( "Cannot Set %s:", KeyName );
+ PrintStatus( RegStatus );
+ goto Cleanup;
+ }
+
+ printf( "%s set to %s\n", KeyName, AnsiDbFlag );
+
+ //
+ // Be tidy.
+ //
+Cleanup:
+ if ( ParmHandle != NULL ) {
+ RegCloseKey( ParmHandle );
+ }
+ if ( BaseHandle != NULL ) {
+ RegCloseKey( BaseHandle );
+ }
+ return;
+
+}
+
+
+NET_API_STATUS
+UaspGetDomainId(
+ IN LPWSTR ServerName OPTIONAL,
+ OUT PSAM_HANDLE SamServerHandle OPTIONAL,
+ OUT PPOLICY_ACCOUNT_DOMAIN_INFO * AccountDomainInfo
+ )
+
+/*++
+
+Routine Description:
+
+ Return a domain ID of the account domain of a server.
+
+Arguments:
+
+ ServerName - A pointer to a string containing the name of the
+ Domain Controller (DC) to query. A NULL pointer
+ or string specifies the local machine.
+
+ SamServerHandle - Returns the SAM connection handle if the caller wants it.
+
+ DomainId - Receives a pointer to the domain ID.
+ Caller must deallocate buffer using NetpMemoryFree.
+
+Return Value:
+
+ Error code for the operation.
+
+--*/
+
+{
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+
+ SAM_HANDLE LocalSamHandle = NULL;
+
+ ACCESS_MASK LSADesiredAccess;
+ LSA_HANDLE LSAPolicyHandle = NULL;
+ OBJECT_ATTRIBUTES LSAObjectAttributes;
+
+ UNICODE_STRING ServerNameString;
+
+
+ //
+ // Connect to the SAM server
+ //
+
+ RtlInitUnicodeString( &ServerNameString, ServerName );
+
+ Status = SamConnect(
+ &ServerNameString,
+ &LocalSamHandle,
+ SAM_SERVER_LOOKUP_DOMAIN,
+ NULL);
+
+ if ( !NT_SUCCESS(Status)) {
+ printf( "UaspGetDomainId: Cannot connect to Sam %lX\n",Status );
+ LocalSamHandle = NULL;
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+
+ //
+ // Open LSA to read account domain info.
+ //
+
+ //
+ // set desired access mask.
+ //
+
+ LSADesiredAccess = POLICY_VIEW_LOCAL_INFORMATION;
+
+ InitializeObjectAttributes( &LSAObjectAttributes,
+ NULL, // Name
+ 0, // Attributes
+ NULL, // Root
+ NULL ); // Security Descriptor
+
+ Status = LsaOpenPolicy( &ServerNameString,
+ &LSAObjectAttributes,
+ LSADesiredAccess,
+ &LSAPolicyHandle );
+
+ if( !NT_SUCCESS(Status) ) {
+
+ printf( "UaspGetDomainId: Cannot open LSA Policy %lX\n", Status );
+
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+
+ //
+ // now read account domain info from LSA.
+ //
+
+ Status = LsaQueryInformationPolicy(
+ LSAPolicyHandle,
+ PolicyAccountDomainInformation,
+ (PVOID *) AccountDomainInfo );
+
+ if( !NT_SUCCESS(Status) ) {
+
+ printf( "UaspGetDomainId: "
+ "Cannot read LSA %lX\n", Status );
+
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+
+ //
+ // Return the SAM connection handle to the caller if he wants it.
+ // Otherwise, disconnect from SAM.
+ //
+
+ if ( ARGUMENT_PRESENT( SamServerHandle ) ) {
+ *SamServerHandle = LocalSamHandle;
+ LocalSamHandle = NULL;
+ }
+
+ NetStatus = NERR_Success;
+
+
+ //
+ // Cleanup locally used resources
+ //
+Cleanup:
+ if ( LocalSamHandle != NULL ) {
+ (VOID) SamCloseHandle( LocalSamHandle );
+ }
+
+ if( LSAPolicyHandle != NULL ) {
+ LsaClose( LSAPolicyHandle );
+ }
+
+ return NetStatus;
+
+} // UaspGetDomainId
+
+
+NET_API_STATUS
+UaspOpenDomain(
+ IN LPWSTR ServerName OPTIONAL,
+ IN ULONG DesiredAccess,
+ OUT PSAM_HANDLE DomainHandle,
+ OUT PSID *DomainId OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ Return a SAM Connection handle and a domain handle given the server
+ name and the access desired to the domain.
+
+ Only a single thread in a process can open a domain at a time.
+ Subsequent threads block in this routine. This exclusive access allows
+ a single SAM connection handle to be cached. The routine
+ UaspCloseDomain closes the domain and allows other threads to proceed.
+
+Arguments:
+
+ ServerName - A pointer to a string containing the name of the remote
+ server containing the SAM database. A NULL pointer
+ or string specifies the local machine.
+
+ DesiredAccess - Supplies the access mask indicating which access types
+ are desired to the domain. This routine always requests DOMAIN_LOOKUP
+ access in addition to those specified.
+
+ DomainHandle - Receives the Domain handle to be used on future calls
+ to the SAM server.
+
+ DomainId - Recieves a pointer to the Sid of the domain. This domain ID
+ must be freed using NetpMemoryFree.
+
+Return Value:
+
+ Error code for the operation. NULL means initialization was successful.
+
+--*/
+
+{
+
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+ PSID LocalDomainId;
+ HANDLE SamServerHandle;
+ PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo;
+
+ //
+ // Give everyone DOMAIN_LOOKUP access.
+ //
+
+ DesiredAccess |= DOMAIN_LOOKUP;
+
+ if ( ServerName == NULL ) {
+ ServerName = L"";
+ }
+
+ if ( *ServerName != L'\0' &&
+ (ServerName[0] != L'\\' || ServerName[1] != L'\\') ) {
+ return NERR_InvalidComputer;
+ }
+
+ //
+ // Connect to the SAM server and
+ // Determine the Domain Id of the account domain for this server.
+ //
+
+ NetStatus = UaspGetDomainId( ServerName,
+ &SamServerHandle,
+ &AccountDomainInfo );
+
+ if ( NetStatus != NERR_Success ) {
+ printf( "UaspUpdateCache: Cannot UaspGetDomainId %ld\n",
+ NetStatus );
+ return ( NetStatus );
+ }
+
+
+ //
+ // Choose the domain ID for the right SAM domain.
+ //
+
+ LocalDomainId = AccountDomainInfo->DomainSid;
+
+ //
+ // At this point the domain ID of the account domain of the server is
+ // cached. Open the domain.
+ //
+
+ Status = SamOpenDomain( SamServerHandle,
+ DesiredAccess,
+ LocalDomainId,
+ DomainHandle );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ printf( "UaspOpenDomain: Cannot SamOpenDomain %lX\n", Status );
+ *DomainHandle = NULL;
+ return NetpNtStatusToApiStatus( Status );
+ }
+
+ //
+ // Return the DomainId to the caller in an allocated buffer
+ //
+
+ if (ARGUMENT_PRESENT( DomainId ) ) {
+ ULONG SidSize;
+ SidSize = RtlLengthSid( LocalDomainId );
+
+ *DomainId = NetpMemoryAllocate( SidSize );
+
+ if ( *DomainId == NULL ) {
+ (VOID) SamCloseHandle( *DomainHandle );
+ *DomainHandle = NULL;
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ if ( !NT_SUCCESS( RtlCopySid( SidSize, *DomainId, LocalDomainId) ) ) {
+ (VOID) SamCloseHandle( *DomainHandle );
+ *DomainHandle = NULL;
+ NetpMemoryFree( *DomainId );
+ *DomainId = NULL;
+ return NERR_InternalError;
+ }
+
+ }
+
+ return NERR_Success;
+
+} // UaspOpenDomain
+
+VOID
+SetLockout(
+ IN LPWSTR ServerName,
+ IN ULONG LockoutThreshold,
+ IN ULONG LockoutDuration,
+ IN ULONG LockoutWindow
+ )
+/*++
+
+Routine Description:
+
+ Set the lockout parameter on a domain.
+
+Arguments:
+
+Return Value:
+
+ Exit status
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+ DOMAIN_LOCKOUT_INFORMATION LockoutInfo;
+ HANDLE DomainHandle;
+
+
+ NetStatus = UaspOpenDomain(
+ ServerName,
+ DOMAIN_WRITE_PASSWORD_PARAMS,
+ &DomainHandle,
+ NULL );
+
+ if ( NetStatus != NERR_Success ) {
+ printf( "UaspOpenDomain failed %ld\n", NetStatus );
+ return;
+ }
+
+ //
+ // Fill in the info level structure
+ //
+
+ LockoutInfo.LockoutThreshold = (USHORT) LockoutThreshold;
+ // Convert times from seconds to 100ns
+ LockoutInfo.LockoutDuration =
+ RtlEnlargedIntegerMultiply( LockoutDuration, -10000000 );
+ LockoutInfo.LockoutObservationWindow =
+ RtlEnlargedIntegerMultiply( LockoutWindow, -10000000 );
+
+ //
+ // Set the information in SAM
+ //
+
+ Status = SamSetInformationDomain( DomainHandle,
+ DomainLockoutInformation,
+ &LockoutInfo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ printf( "Can't SamSetInformationDomain 0x%lx\n", Status );
+ }
+
+
+}
+
+int _CRTAPI1
+main(
+ IN int argc,
+ IN char ** argv
+ )
+/*++
+
+Routine Description:
+
+ Drive the Netlogon service by calling I_NetLogonControl2.
+
+Arguments:
+
+ argc - the number of command-line arguments.
+
+ argv - an array of pointers to the arguments.
+
+Return Value:
+
+ Exit status
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+
+ LPSTR argument;
+ int i;
+ DWORD FunctionCode = 0;
+ LPSTR AnsiServerName = NULL;
+ CHAR AnsiUncServerName[UNCLEN+1];
+ LPSTR AnsiDomainName = NULL;
+ LPSTR AnsiTrustedDomainName = NULL;
+ LPWSTR TrustedDomainName = NULL;
+ LPSTR AnsiUserName = NULL;
+ LPSTR AnsiPassword = NULL;
+ LPSTR AnsiSimMachineName = NULL;
+ LPSTR AnsiDeltaFileName = NULL;
+ LPSTR ShutdownReason = NULL;
+ LPWSTR ServerName = NULL;
+ LPWSTR UserName = NULL;
+ PNETLOGON_INFO_1 NetlogonInfo1 = NULL;
+ ULONG Rid = 0;
+ DWORD Level = 1;
+ DWORD ShutdownSeconds;
+ LPBYTE InputDataPtr = NULL;
+
+ DWORD DbFlagValue;
+
+ LARGE_INTEGER ConvertTime;
+ ULONG IterationCount;
+
+ NT_OWF_PASSWORD NtOwfPassword;
+ BOOLEAN NtPasswordPresent = FALSE;
+ LM_OWF_PASSWORD LmOwfPassword;
+ BOOLEAN LmPasswordPresent = FALSE;
+ BOOLEAN GetPdcName = FALSE;
+ BOOLEAN GetTrustedDcName = FALSE;
+ BOOLEAN GetDcList = FALSE;
+ BOOLEAN WhoWill = FALSE;
+ BOOLEAN QuerySync = FALSE;
+ BOOLEAN SimFullSync = FALSE;
+ BOOLEAN QueryUser = FALSE;
+ BOOLEAN ListDeltasFlag = FALSE;
+ BOOLEAN ListRedoFlag = FALSE;
+ BOOLEAN ResetSecureChannelsFlag = FALSE;
+ BOOLEAN ShutdownAbort = FALSE;
+ BOOLEAN TrustedDomainsFlag = FALSE;
+
+ BOOLEAN DoLockout = FALSE;
+ ULONG LockoutThreshold;
+ ULONG LockoutDuration;
+ ULONG LockoutWindow;
+
+
+#define QUERY_PARAM "/QUERY"
+#define REPL_PARAM "/REPL"
+#define SYNC_PARAM "/SYNC"
+#define PDC_REPL_PARAM "/PDC_REPL"
+#define SERVER_PARAM "/SERVER:"
+#define PWD_PARAM "/PWD:"
+#define RID_PARAM "/RID:"
+#define USER_PARAM "/USER:"
+#define BP_PARAM "/BP"
+#define DBFLAG_PARAM "/DBFLAG:"
+#define DCLIST_PARAM "/DCLIST:"
+#define DCNAME_PARAM "/DCNAME:"
+#define DCTRUST_PARAM "/DCTRUST:"
+#define TRUNCATE_LOG_PARAM "/TRUNC"
+#define BACKUP_CHANGE_LOG_PARAM "/BKP_CHK"
+#define TIME_PARAM "/TIME:"
+#define WHOWILL_PARAM "/WHOWILL:"
+#define BDC_QUERY_PARAM "/BDC_QUERY:"
+#define LOGON_QUERY_PARAM "/LOGON_QUERY"
+#define SIM_SYNC_PARAM "/SIM_SYNC:"
+#define LIST_DELTAS_PARAM "/LIST_DELTAS:"
+#define LIST_REDO_PARAM "/LIST_REDO:"
+#define SC_RESET_PARAM "/SC_RESET:"
+#define SC_QUERY_PARAM "/SC_QUERY:"
+#define SHUTDOWN_PARAM "/SHUTDOWN:"
+#define SHUTDOWN_ABORT_PARAM "/SHUTDOWN_ABORT"
+#define LOCKOUT_PARAM "/LOCKOUT:"
+#define TRANSPORT_PARAM "/TRANSPORT_NOTIFY"
+#define FINDUSER_PARAM "/FINDUSER:"
+#define TRUSTED_DOMAINS_PARAM "/TRUSTED_DOMAINS"
+
+ //
+ // Set the netlib debug flag.
+ //
+ extern DWORD NetlibpTrace;
+ NetlibpTrace |= 0x8000; // NETLIB_DEBUG_LOGON
+
+ ConvertTime.QuadPart = 0;
+
+
+ //
+ // Loop through the arguments handle each in turn
+ //
+
+ for ( i=1; i<argc; i++ ) {
+
+ argument = argv[i];
+
+
+ //
+ // Handle /QUERY
+ //
+
+ if ( _stricmp( argument, QUERY_PARAM ) == 0 ) {
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_QUERY;
+
+
+ //
+ // Handle /SC_QUERY
+ //
+
+ } else if ( _strnicmp( argument,
+ SC_QUERY_PARAM,
+ sizeof(SC_QUERY_PARAM) - 1 ) == 0 ) {
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_TC_QUERY;
+
+ AnsiTrustedDomainName = &argument[sizeof(SC_QUERY_PARAM)-1];
+
+ TrustedDomainName = NetpAllocWStrFromStr( AnsiTrustedDomainName );
+
+ if ( TrustedDomainName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ Level = 2;
+ InputDataPtr = (LPBYTE)TrustedDomainName;
+
+
+ //
+ // Handle /FINDUSER
+ //
+
+ } else if ( _strnicmp( argument,
+ FINDUSER_PARAM,
+ sizeof(FINDUSER_PARAM) - 1 ) == 0 ) {
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_FIND_USER;
+
+ AnsiUserName = &argument[sizeof(FINDUSER_PARAM)-1];
+
+ TrustedDomainName = NetpAllocWStrFromStr( AnsiUserName );
+
+ if ( TrustedDomainName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ Level = 4;
+ InputDataPtr = (LPBYTE)TrustedDomainName;
+
+ //
+ // Handle /REPL
+ //
+
+ } else if (_stricmp(argument, REPL_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_REPLICATE;
+
+
+ //
+ // Handle /SYNC
+ //
+
+ } else if (_stricmp(argument, SYNC_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_SYNCHRONIZE;
+
+
+ //
+ // Handle /SC_RESET
+ //
+
+ } else if (_strnicmp(argument,
+ SC_RESET_PARAM,
+ sizeof(SC_RESET_PARAM) - 1 ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_REDISCOVER;
+ AnsiTrustedDomainName = &argument[sizeof(SC_RESET_PARAM)-1];
+
+ TrustedDomainName = NetpAllocWStrFromStr( AnsiTrustedDomainName );
+
+ if ( TrustedDomainName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ Level = 2;
+ InputDataPtr = (LPBYTE)TrustedDomainName;
+
+ //
+ // Handle /QUERY
+ //
+
+ } else if ( _stricmp( argument, TRANSPORT_PARAM ) == 0 ) {
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_TRANSPORT_NOTIFY;
+
+
+ //
+ // Handle /PDC_REPL
+ //
+
+ } else if (_stricmp(argument, PDC_REPL_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_PDC_REPLICATE;
+
+
+ //
+ // Handle /BP
+ //
+
+ } else if (_stricmp(argument, BP_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_BREAKPOINT;
+
+ //
+ // Handle /TRUNCATE_LOG
+ //
+
+ } else if (_stricmp(argument, TRUNCATE_LOG_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_TRUNCATE_LOG;
+
+
+ //
+ // Handle /BKP_CHK
+ //
+
+ } else if (_stricmp(argument, BACKUP_CHANGE_LOG_PARAM ) == 0 ){
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_BACKUP_CHANGE_LOG;
+
+
+ //
+ // Handle /DBFLAG:dbflag
+ //
+
+ } else if (_strnicmp(argument,
+ DBFLAG_PARAM,
+ sizeof(DBFLAG_PARAM)-1 ) == 0 ){
+ char *end;
+
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_SET_DBFLAG;
+
+ DbFlagValue = strtoul( &argument[sizeof(DBFLAG_PARAM)-1], &end, 16 );
+ InputDataPtr = (LPBYTE)DbFlagValue;
+
+ //
+ // Handle /Time:LSL MSL
+ //
+
+ } else if (_strnicmp(argument,
+ TIME_PARAM,
+ sizeof(TIME_PARAM)-1 ) == 0 ){
+ char *end;
+
+ if ( ConvertTime.QuadPart != 0 ) {
+ goto Usage;
+ }
+
+ ConvertTime.LowPart = strtoul( &argument[sizeof(TIME_PARAM)-1], &end, 16 );
+ i++;
+ argument = argv[i];
+
+ ConvertTime.HighPart = strtoul( argument, &end, 16 );
+
+
+ //
+ // Handle /WHOWILL:Domain User [IterationCount]
+ //
+
+ } else if (_strnicmp(argument,
+ WHOWILL_PARAM,
+ sizeof(WHOWILL_PARAM)-1 ) == 0 ){
+ char *end;
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(WHOWILL_PARAM)-1];
+
+ i++;
+ argument = argv[i];
+ AnsiUserName = argument;
+
+ if ( i+1 < argc ) {
+ i++;
+ argument = argv[i];
+
+ IterationCount = strtoul( argument, &end, 16 );
+ } else {
+ IterationCount = 1;
+ }
+
+ WhoWill = TRUE;
+
+
+ //
+ // Handle /LOCKOUT:Threshold Duration Window
+ //
+
+ } else if (_strnicmp(argument,
+ LOCKOUT_PARAM,
+ sizeof(LOCKOUT_PARAM)-1 ) == 0 ){
+
+ char *end;
+
+ LockoutThreshold = strtoul( &argument[sizeof(LOCKOUT_PARAM)-1], &end, 10 );
+ i++;
+ argument = argv[i];
+
+ LockoutDuration = strtoul( argument, &end, 10 );
+ i++;
+ argument = argv[i];
+
+ LockoutWindow = strtoul( argument, &end, 10 );
+ DoLockout = TRUE;
+
+
+ //
+ // Handle /BDC_QUERY:Domain
+ //
+
+ } else if (_strnicmp(argument,
+ BDC_QUERY_PARAM,
+ sizeof(BDC_QUERY_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(BDC_QUERY_PARAM)-1];
+ QuerySync = TRUE;
+
+ //
+ // Handle /LOGON_QUERY
+ //
+
+ } else if ( _stricmp( argument, LOGON_QUERY_PARAM ) == 0 ) {
+ if ( FunctionCode != 0 ) {
+ goto Usage;
+ }
+
+ FunctionCode = NETLOGON_CONTROL_QUERY;
+ Level = 3;
+
+ //
+ // Handle full sync simulation
+ //
+
+ } else if (_strnicmp(argument,
+ SIM_SYNC_PARAM,
+ sizeof(SIM_SYNC_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(SIM_SYNC_PARAM)-1];
+
+ i++;
+
+ if( i >= argc ) {
+ goto Usage;
+ }
+
+ argument = argv[i];
+ AnsiSimMachineName = argument;
+
+ SimFullSync = TRUE;
+
+ //
+ // handle delta listing
+ //
+
+ } else if (_strnicmp(argument,
+ LIST_DELTAS_PARAM,
+ sizeof(LIST_DELTAS_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDeltaFileName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDeltaFileName = &argument[sizeof(LIST_DELTAS_PARAM)-1];
+
+ ListDeltasFlag = TRUE;
+
+ //
+ // handle redo listing
+ //
+
+ } else if (_strnicmp(argument,
+ LIST_REDO_PARAM,
+ sizeof(LIST_REDO_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDeltaFileName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDeltaFileName = &argument[sizeof(LIST_REDO_PARAM)-1];
+
+ ListRedoFlag = TRUE;
+
+ //
+ // Handle /DCLIST
+ //
+
+ } else if (_strnicmp(argument,
+ DCLIST_PARAM,
+ sizeof(DCLIST_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(DCLIST_PARAM)-1];
+ GetDcList = TRUE;
+
+ //
+ // Handle /DCNAME
+ //
+
+ } else if (_strnicmp(argument,
+ DCNAME_PARAM,
+ sizeof(DCNAME_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(DCNAME_PARAM)-1];
+ GetPdcName = TRUE;
+
+ //
+ // Handle /DCTRUST
+ //
+
+ } else if (_strnicmp(argument,
+ DCTRUST_PARAM,
+ sizeof(DCTRUST_PARAM)-1 ) == 0 ){
+
+ if ( AnsiDomainName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiDomainName = &argument[sizeof(DCTRUST_PARAM)-1];
+ GetTrustedDcName = TRUE;
+
+
+ //
+ // Handle /SERVER:servername
+ //
+
+ } else if (_strnicmp(argument, SERVER_PARAM, sizeof(SERVER_PARAM)-1 ) == 0 ){
+ if ( AnsiServerName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiServerName = &argument[sizeof(SERVER_PARAM)-1];
+
+
+ //
+ // Handle /PWD:password
+ //
+
+ } else if (_strnicmp(argument, PWD_PARAM, sizeof(PWD_PARAM)-1 ) == 0 ){
+ if ( AnsiPassword != NULL ) {
+ goto Usage;
+ }
+
+ AnsiPassword = &argument[sizeof(PWD_PARAM)-1];
+
+
+ //
+ // Handle /USER:username
+ //
+
+ } else if (_strnicmp(argument, USER_PARAM, sizeof(USER_PARAM)-1 ) == 0 ){
+ if ( AnsiUserName != NULL ) {
+ goto Usage;
+ }
+
+ AnsiUserName = &argument[sizeof(USER_PARAM)-1];
+ QueryUser = TRUE;
+
+
+ //
+ // Handle /RID:relative_id
+ //
+
+ } else if (_strnicmp(argument, RID_PARAM, sizeof(RID_PARAM)-1 ) == 0 ){
+ char *end;
+
+ if ( Rid != 0 ) {
+ goto Usage;
+ }
+
+ Rid = strtol( &argument[sizeof(RID_PARAM)-1], &end, 16 );
+
+ //
+ // Handle /SHUTDOWN:Reason seconds
+ //
+
+ } else if (_strnicmp(argument,
+ SHUTDOWN_PARAM,
+ sizeof(SHUTDOWN_PARAM)-1 ) == 0 ){
+
+ if ( ShutdownReason != NULL ) {
+ goto Usage;
+ }
+
+ ShutdownReason = &argument[sizeof(SHUTDOWN_PARAM)-1];
+
+ if ( i+1 < argc ) {
+ char *end;
+ i++;
+ argument = argv[i];
+ if ( !ISDIGIT(argument[0]) ) {
+ fprintf(stderr, "Second argument to " SHUTDOWN_PARAM " must be a number.\n\n");
+ goto Usage;
+ }
+ ShutdownSeconds = strtoul( argument, &end, 10 );
+ } else {
+ ShutdownSeconds = 60;
+ }
+
+
+ //
+ // Handle /SHUTDOWN_ABORT
+ //
+
+ } else if (_stricmp(argument, SHUTDOWN_ABORT_PARAM ) == 0 ){
+
+ ShutdownAbort = TRUE;
+
+ //
+ // Handle /TRUSTED_DOMAINS
+ //
+
+ } else if (_stricmp(argument, TRUSTED_DOMAINS_PARAM ) == 0 ){
+
+ TrustedDomainsFlag = TRUE;
+
+
+ //
+ // Handle all other parameters
+ //
+
+ } else {
+Usage:
+ fprintf( stderr, "Usage: nltest [/OPTIONS]\n\n" );
+
+ fprintf(
+ stderr,
+ "\n"
+ " " SERVER_PARAM "<ServerName> - Specify <ServerName>\n"
+ "\n"
+ " " QUERY_PARAM " - Query <ServerName> netlogon service\n"
+ " " REPL_PARAM " - Force replication on <ServerName> BDC\n"
+ " " SYNC_PARAM " - Force SYNC on <ServerName> BDC\n"
+ " " PDC_REPL_PARAM " - Force UAS change message from <ServerName> PDC\n"
+ "\n"
+ " " SC_QUERY_PARAM "<DomainName> - Query secure channel for <Domain> on <ServerName>\n"
+ " " SC_RESET_PARAM "<DomainName> - Reset secure channel for <Domain> on <ServerName>\n"
+ " " DCLIST_PARAM "<DomainName> - Get list of DC's for <DomainName>\n"
+ " " DCNAME_PARAM "<DomainName> - Get the PDC name for <DomainName>\n"
+ " " DCTRUST_PARAM "<DomainName> - Get name of DC is used for trust of <DomainName>\n"
+ " " WHOWILL_PARAM "<Domain>* <User> [<Iteration>] - See if <Domain> will log on <User>\n"
+ " " FINDUSER_PARAM "<User> - See which trusted <Domain> will log on <User>\n"
+ " " TRANSPORT_PARAM " - Notify of netlogon of new transport\n"
+ "\n"
+ " " BP_PARAM " - Force a BreakPoint in Netlogon on <ServerName>\n"
+ " " DBFLAG_PARAM "<HexFlags> - New debug flag\n"
+ " " TRUNCATE_LOG_PARAM " - Truncate log file (rename to *.bak)\n"
+ "\n"
+ " " PWD_PARAM "<CleartextPassword> - Specify Password to encrypt\n"
+ " " RID_PARAM "<HexRid> - RID to encrypt Password with\n"
+ " " USER_PARAM "<UserName> - Query User info on <ServerName>\n"
+ "\n"
+ " " TIME_PARAM "<Hex LSL> <Hex MSL> - Convert NT GMT time to ascii\n"
+ " " LOCKOUT_PARAM "<Thresh> <Duration> <Window> - set lockout parameters on a domain\n"
+ " " LOGON_QUERY_PARAM " - Query number of cumulative logon attempts\n"
+ " " TRUSTED_DOMAINS_PARAM " - Query names of domains trusted by workstation\n"
+ "\n"
+ " " BDC_QUERY_PARAM "<DomainName> - Query replication status of BDCs for <DomainName>\n"
+ " " SIM_SYNC_PARAM "<DomainName> <MachineName> - Simulate full sync replication\n"
+ "\n"
+ " " BACKUP_CHANGE_LOG_PARAM " - Backup Change log file (copy to netlogon.bkp)\n"
+ " " LIST_DELTAS_PARAM "<FileName> - display the content of given change log file \n"
+ " " LIST_REDO_PARAM "<FileName> - display the content of given redo log file \n"
+ "\n"
+ " " SHUTDOWN_PARAM "<Reason> [<Seconds>] - Shutdown <ServerName> for <Reason>\n"
+ " " SHUTDOWN_ABORT_PARAM " - Abort a system shutdown\n"
+ "\n" );
+ return(1);
+ }
+ }
+
+
+ //
+ // Convert the server name to unicode.
+ //
+
+ if ( AnsiServerName != NULL ) {
+ if ( AnsiServerName[0] == '\\' && AnsiServerName[1] == '\\' ) {
+ ServerName = NetpAllocWStrFromStr( AnsiServerName );
+ } else {
+ AnsiUncServerName[0] = '\\';
+ AnsiUncServerName[1] = '\\';
+ strcpy(AnsiUncServerName+2, AnsiServerName);
+ ServerName = NetpAllocWStrFromStr( AnsiUncServerName );
+ AnsiServerName = AnsiUncServerName;
+ }
+ }
+
+ //
+ // Convert the user name to unicode.
+ //
+
+ if ( AnsiUserName != NULL ) {
+
+ UserName = NetpAllocWStrFromStr( AnsiUserName );
+
+ if ( UserName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+ }
+
+
+ //
+ // If we've been asked to contact the Netlogon service,
+ // Do so
+ //
+
+ if ( FunctionCode != 0 ) {
+
+
+ //
+ // The dbflag should be set in the registry as well as in netlogon
+ // proper.
+ //
+
+ if ( FunctionCode == NETLOGON_CONTROL_SET_DBFLAG ) {
+ SetDbflagInRegistry( ServerName, DbFlagValue );
+ }
+
+ NetStatus = I_NetLogonControl2( ServerName,
+ FunctionCode,
+ Level,
+ (LPBYTE) &InputDataPtr,
+ (LPBYTE *)&NetlogonInfo1 );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "I_NetLogonControl failed: " );
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ if( (Level == 1) || (Level == 2) ) {
+
+ //
+ // Print level 1 information
+ //
+
+ printf( "Flags: %lx", NetlogonInfo1->netlog1_flags );
+
+ if ( NetlogonInfo1->netlog1_flags & NETLOGON_REPLICATION_IN_PROGRESS ) {
+
+ if ( NetlogonInfo1->netlog1_flags & NETLOGON_FULL_SYNC_REPLICATION ) {
+ printf( " FULL_SYNC " );
+ }
+ else {
+ printf( " PARTIAL_SYNC " );
+ }
+
+ printf( " REPLICATION_IN_PROGRESS" );
+ }
+ else if ( NetlogonInfo1->netlog1_flags & NETLOGON_REPLICATION_NEEDED ) {
+
+ if ( NetlogonInfo1->netlog1_flags & NETLOGON_FULL_SYNC_REPLICATION ) {
+ printf( " FULL_SYNC " );
+ }
+ else {
+ printf( " PARTIAL_SYNC " );
+ }
+
+ printf( " REPLICATION_NEEDED" );
+ }
+ if ( NetlogonInfo1->netlog1_flags & NETLOGON_REDO_NEEDED) {
+ printf( " REDO_NEEDED" );
+ }
+ printf( "\n" );
+
+ printf( "Connection ");
+ PrintStatus( NetlogonInfo1->netlog1_pdc_connection_status );
+ }
+
+ if( Level == 2 ) {
+
+ //
+ // Print level 2 only information
+ //
+
+ PNETLOGON_INFO_2 NetlogonInfo2;
+
+ NetlogonInfo2 = (PNETLOGON_INFO_2)NetlogonInfo1;
+
+ printf("Trusted DC Name %ws \n",
+ NetlogonInfo2->netlog2_trusted_dc_name );
+ printf("Trusted DC Connection Status ");
+ PrintStatus( NetlogonInfo2->netlog2_tc_connection_status );
+ }
+ if ( Level == 3 ) {
+ printf( "Number of attempted logons: %ld\n",
+ ((PNETLOGON_INFO_3)NetlogonInfo1)->netlog3_logon_attempts );
+ }
+ if( Level == 4 ) {
+
+ PNETLOGON_INFO_4 NetlogonInfo4;
+
+ NetlogonInfo4 = (PNETLOGON_INFO_4)NetlogonInfo1;
+
+ printf("Domain Name: %ws\n",
+ NetlogonInfo4->netlog4_trusted_domain_name );
+ printf("Trusted DC Name %ws \n",
+ NetlogonInfo4->netlog4_trusted_dc_name );
+ }
+
+ }
+
+ //
+ // If we've been asked to debug password encryption,
+ // do so.
+ //
+
+ if ( AnsiPassword != NULL ) {
+ LPWSTR Password = NULL;
+ UNICODE_STRING UnicodePasswordString;
+ STRING AnsiPasswordString;
+ CHAR LmPasswordBuffer[LM20_PWLEN + 1];
+
+ Password = NetpAllocWStrFromStr( AnsiPassword );
+ RtlInitUnicodeString( &UnicodePasswordString, Password );
+
+
+ //
+ // Compute the NT One-Way-Function of the password
+ //
+
+ Status = RtlCalculateNtOwfPassword( &UnicodePasswordString,
+ &NtOwfPassword );
+ if ( !NT_SUCCESS(Status) ) {
+ fprintf( stderr, "RtlCalculateNtOwfPassword failed: 0x%lx", Status);
+ return(1);
+ }
+
+ printf( "NT OWF Password for: %s ", AnsiPassword );
+ DumpBuffer( &NtOwfPassword, sizeof( NtOwfPassword ));
+ printf("\n");
+ NtPasswordPresent = TRUE;
+
+
+
+ //
+ // Compute the Ansi version to the Cleartext 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.
+ //
+
+ AnsiPasswordString.Buffer = LmPasswordBuffer;
+ AnsiPasswordString.MaximumLength = sizeof(LmPasswordBuffer);
+
+ RtlZeroMemory( LmPasswordBuffer, sizeof(LmPasswordBuffer) );
+
+ Status = RtlUpcaseUnicodeStringToOemString(
+ &AnsiPasswordString,
+ &UnicodePasswordString,
+ FALSE );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ RtlZeroMemory( LmPasswordBuffer, sizeof(LmPasswordBuffer) );
+ Status = STATUS_SUCCESS;
+
+ printf( "LM OWF Password for: %s\n", AnsiPassword );
+ printf( " ----- Password doesn't translate from unicode ----\n");
+ LmPasswordPresent = FALSE;
+
+ } else {
+
+ Status = RtlCalculateLmOwfPassword(
+ LmPasswordBuffer,
+ &LmOwfPassword);
+ printf( "LM OWF Password for: %s ", AnsiPassword );
+ DumpBuffer( &LmOwfPassword, sizeof( LmOwfPassword ));
+ printf("\n");
+ LmPasswordPresent = TRUE;
+ }
+
+ }
+
+ //
+ // If we've been given a Rid,
+ // use it to further encrypt the password
+ //
+
+ if ( Rid != 0 ) {
+ ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
+ ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
+
+ if ( NtPasswordPresent ) {
+
+ Status = RtlEncryptNtOwfPwdWithIndex(
+ &NtOwfPassword,
+ &Rid,
+ &EncryptedNtOwfPassword
+ );
+
+ printf( "NT OWF Password encrypted by: 0x%lx ", Rid );
+ if ( NT_SUCCESS( Status ) ) {
+ DumpBuffer( &EncryptedNtOwfPassword,sizeof(EncryptedNtOwfPassword));
+ printf("\n");
+ } else {
+ printf( "RtlEncryptNtOwfPwdWithIndex returns 0x%lx\n", Status );
+ }
+ }
+
+ if ( LmPasswordPresent ) {
+
+ Status = RtlEncryptLmOwfPwdWithIndex(
+ &LmOwfPassword,
+ &Rid,
+ &EncryptedLmOwfPassword
+ );
+
+ printf( "LM OWF Password encrypted by: 0x%lx ", Rid );
+ if ( NT_SUCCESS( Status ) ) {
+ DumpBuffer( &EncryptedLmOwfPassword,sizeof(EncryptedLmOwfPassword));
+ printf("\n");
+ } else {
+ printf( "RtlEncryptNtOwfPwdWithIndex returns 0x%lx\n", Status );
+ }
+ }
+ }
+
+ //
+ // If we've been asked to query a user,
+ // do so.
+ //
+
+ if ( QueryUser ) {
+ PrintUserInfo( ServerName, AnsiUserName );
+ }
+
+ //
+ // If we've been asked to get the list of domain controllers,
+ // Do so
+ //
+
+ if ( AnsiDomainName != NULL ) {
+ LPWSTR DomainName;
+
+ DomainName = NetpAllocWStrFromStr( AnsiDomainName );
+
+ if ( DomainName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ if ( GetPdcName ) {
+ LPWSTR PdcName;
+
+ NetStatus = NetGetDCName(
+ ServerName,
+ DomainName,
+ (LPBYTE *)&PdcName );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "NetGetDCName failed: " );
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ printf( "PDC for Domain " FORMAT_LPWSTR " is " FORMAT_LPWSTR "\n",
+ DomainName, PdcName );
+
+ } else if ( GetDcList ) {
+ DWORD DCCount;
+ PUNICODE_STRING DCNames;
+ DWORD i;
+
+ NetStatus = I_NetGetDCList(
+ ServerName,
+ DomainName,
+ &DCCount,
+ &DCNames );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "I_NetGetDCList failed: ");
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ printf( "List of DCs in Domain " FORMAT_LPWSTR "\n", DomainName );
+ for (i=0; i<DCCount; i++ ) {
+ if ( DCNames[i].Length > 0 ) {
+ printf(" %wZ", &DCNames[i] );
+ } else {
+ printf(" NULL");
+ }
+ if ( i==0 ) {
+ printf( " (PDC)");
+ }
+ printf("\n");
+ }
+
+ } else if ( GetTrustedDcName ) {
+ LPWSTR TrustedDcName;
+
+ NetStatus = NetGetAnyDCName(
+ ServerName,
+ DomainName,
+ (LPBYTE *)&TrustedDcName );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "NetGetAnyDCName failed: ");
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ printf( "Trusted DC for Domain " FORMAT_LPWSTR " is " FORMAT_LPWSTR "\n",
+ DomainName, TrustedDcName );
+
+ } else if ( WhoWill ) {
+
+ WhoWillLogMeOn( DomainName, UserName, IterationCount );
+
+ } else if( QuerySync ) {
+
+ DWORD DCCount;
+ PUNICODE_STRING DCNames;
+ DWORD i;
+ PNETLOGON_INFO_1 SyncNetlogonInfo1 = NULL;
+ LPWSTR SyncServerName = NULL;
+
+ NetStatus = I_NetGetDCList(
+ ServerName,
+ DomainName,
+ &DCCount,
+ &DCNames );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "I_NetGetDCList failed: ");
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ for (i=1; i<DCCount; i++ ) {
+
+ if ( DCNames[i].Length > 0 ) {
+ SyncServerName = DCNames[i].Buffer;
+ } else {
+ SyncServerName = NULL;
+ }
+
+ NetStatus = I_NetLogonControl(
+ SyncServerName,
+ NETLOGON_CONTROL_QUERY,
+ 1,
+ (LPBYTE *)&SyncNetlogonInfo1 );
+
+ if ( NetStatus != NERR_Success ) {
+ printf( "Server : " FORMAT_LPWSTR "\n", SyncServerName );
+ printf( "\tI_NetLogonControl failed: ");
+ PrintStatus( NetStatus );
+ }
+ else {
+
+ printf( "Server : " FORMAT_LPWSTR "\n", SyncServerName );
+
+ printf( "\tSyncState : " );
+
+ if ( SyncNetlogonInfo1->netlog1_flags == 0 ) {
+ printf( " IN_SYNC \n" );
+ }
+ else if ( SyncNetlogonInfo1->netlog1_flags & NETLOGON_REPLICATION_IN_PROGRESS ) {
+ printf( " REPLICATION_IN_PROGRESS \n" );
+ }
+ else if ( SyncNetlogonInfo1->netlog1_flags & NETLOGON_REPLICATION_NEEDED ) {
+ printf( " REPLICATION_NEEDED \n" );
+ } else {
+ printf( " UNKNOWN \n" );
+ }
+
+ printf( "\tConnectionState : ");
+ PrintStatus( SyncNetlogonInfo1->netlog1_pdc_connection_status );
+
+ NetApiBufferFree( SyncNetlogonInfo1 );
+ }
+ }
+ } else if( SimFullSync ) {
+
+ LPWSTR MachineName;
+ LPWSTR PdcName;
+
+ MachineName = NetpAllocWStrFromStr( AnsiSimMachineName );
+
+ if ( MachineName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ NetStatus = NetGetDCName(
+ ServerName,
+ DomainName,
+ (LPBYTE *)&PdcName );
+
+ if ( NetStatus != NERR_Success ) {
+ fprintf( stderr, "NetGetDCName failed: " );
+ PrintStatus( NetStatus );
+ return(1);
+ }
+
+ Status = SimulateFullSync( PdcName, MachineName );
+
+ if ( !NT_SUCCESS( Status )) {
+ return(1);
+ }
+ }
+ }
+
+ //
+ // if we are asked to display the change log file. do so.
+ //
+
+ if( ListDeltasFlag || ListRedoFlag ) {
+
+ LPWSTR DeltaFileName;
+
+ DeltaFileName = NetpAllocWStrFromStr( AnsiDeltaFileName );
+
+ if ( DeltaFileName == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return(1);
+ }
+
+ ListDeltas( DeltaFileName, ListRedoFlag );
+ }
+
+
+ //
+ // Handle shutting down a system.
+ //
+
+ if ( ShutdownReason != NULL ) {
+ if ( !InitiateSystemShutdownA( AnsiServerName,
+ ShutdownReason,
+ ShutdownSeconds,
+ FALSE, // Don't lose unsaved changes
+ TRUE ) ) { // Reboot when done
+ fprintf( stderr, "InitiateSystemShutdown failed: ");
+ PrintStatus( GetLastError() );
+ return 1;
+ }
+ }
+
+ if ( ShutdownAbort ) {
+ if ( !AbortSystemShutdownA( AnsiServerName ) ) {
+ fprintf( stderr, "AbortSystemShutdown failed: ");
+ PrintStatus( GetLastError() );
+ return 1;
+ }
+ }
+
+ //
+ // Print the list of domains trusted by a workstation.
+ //
+ if ( TrustedDomainsFlag ) {
+ ULONG CurrentIndex;
+ ULONG EntryCount;
+ LPWSTR CurrentEntry;
+ LPWSTR TrustedDomainList;
+
+ Status = NetEnumerateTrustedDomains( ServerName, &TrustedDomainList );
+
+ if ( !NT_SUCCESS(Status) ) {
+ fprintf( stderr, "NetEnumerateTrustedDOmains failed: ");
+ PrintStatus( Status );
+ return 1;
+ }
+
+ EntryCount = NetpTStrArrayEntryCount( TrustedDomainList );
+
+ printf( "Trusted domain list:\n" );
+ CurrentEntry = TrustedDomainList;
+
+ for ( CurrentIndex=0; CurrentIndex<EntryCount; CurrentIndex++ ) {
+
+ printf( " %ws\n", CurrentEntry );
+
+ CurrentEntry += wcslen(CurrentEntry) + 1;
+
+ }
+
+ NetApiBufferFree( TrustedDomainList );
+ }
+
+ //
+ // Handle setting lockout parameters on a domain.
+ //
+
+ if ( DoLockout ) {
+ SetLockout( ServerName, LockoutThreshold, LockoutDuration, LockoutWindow );
+ }
+
+
+ //
+ // If we've been asked to convert an NT GMT time to ascii,
+ // Do so
+ //
+
+ PrintTime( "", ConvertTime );
+
+ printf("The command completed successfully\n");
+ return 0;
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/nltest.prf b/private/net/svcdlls/logonsrv/server/nltest.prf
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nltest.prf
@@ -0,0 +1 @@
+
diff --git a/private/net/svcdlls/logonsrv/server/nltest.rc b/private/net/svcdlls/logonsrv/server/nltest.rc
new file mode 100644
index 000000000..a09177583
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nltest.rc
@@ -0,0 +1,12 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_APP
+#define VER_FILESUBTYPE VFT2_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "Microsoft\256 Logon Server Test Utility"
+
+#define VER_INTERNALNAME_STR "nltest.exe"
+#define VER_ORIGINALFILENAME_STR "nltest.exe"
+
+#include <common.ver>
+
diff --git a/private/net/svcdlls/logonsrv/server/nltest1.c b/private/net/svcdlls/logonsrv/server/nltest1.c
new file mode 100644
index 000000000..e9a09b164
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/nltest1.c
@@ -0,0 +1,522 @@
+/*--
+
+Copyright (c) 1993 Microsoft Corporation
+
+Module Name:
+
+ nltest.c
+
+Abstract:
+
+ Test program for the Netlogon service.
+
+Author:
+
+ 21-Apr-1993 (madana)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+
+//
+// Common include files.
+//
+
+#define NLTEST_IMAGE
+#include <logonsrv.h> // Include files common to entire service
+#include <stdio.h>
+#include <string.h>
+#include <align.h>
+
+//
+// delta entry in the list
+//
+
+typedef struct _DELTA_ENTRY {
+ LIST_ENTRY Next;
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ DWORD Order;
+} DELTA_ENTRY, *PDELTA_ENTRY;
+
+
+LIST_ENTRY GlobalDeltaLists[NUM_DBS + 1];
+ // list of deltas, include VOID DB also.
+
+//
+// Externals needed by chutil.obj
+
+CRITICAL_SECTION NlGlobalChangeLogCritSect;
+LARGE_INTEGER NlGlobalChangeLogPromotionIncrement = DOMAIN_PROMOTION_INCREMENT;
+LARGE_INTEGER PromotionMask = DOMAIN_PROMOTION_MASK;
+LONG NlGlobalChangeLogPromotionMask;
+
+//
+// Stub routine needed by chutil.obj
+//
+
+VOID
+NlpWriteEventlog (
+ IN DWORD EventID,
+ IN DWORD EventType,
+ IN LPBYTE RawDataBuffer OPTIONAL,
+ IN DWORD RawDataSize,
+ IN LPWSTR *StringArray,
+ IN DWORD StringCount
+ )
+{
+ return;
+ UNREFERENCED_PARAMETER( EventID );
+ UNREFERENCED_PARAMETER( EventType );
+ UNREFERENCED_PARAMETER( RawDataBuffer );
+ UNREFERENCED_PARAMETER( RawDataSize );
+ UNREFERENCED_PARAMETER( StringArray );
+ UNREFERENCED_PARAMETER( StringCount );
+}
+
+
+VOID
+MakeDeltaLists(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This routine make list of deltas of individual databases.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ none.
+
+--*/
+{
+
+ PCHANGELOG_ENTRY ChangeLogEntry;
+ DWORD j;
+ DWORD Order = 1;
+
+ //
+ // initialize list enties.
+ //
+
+ for( j = 0; j < NUM_DBS + 1; j++ ) {
+ InitializeListHead(&GlobalDeltaLists[j]);
+ }
+
+ //
+ // The cache is valid if it is empty.
+ //
+
+ if ( ChangeLogIsEmpty( &NlGlobalChangeLogDesc) ) {
+ return;
+ }
+
+ ChangeLogEntry = (PCHANGELOG_ENTRY)(NlGlobalChangeLogDesc.Head+1);
+ do {
+
+ PDELTA_ENTRY NewDelta;
+
+ //
+ // make delta entry to insert in the list
+ //
+
+ NewDelta = (PDELTA_ENTRY)NetpMemoryAllocate( sizeof(DELTA_ENTRY) );
+
+ if ( NewDelta == NULL ) {
+ fprintf( stderr, "Not enough memory\n" );
+ return;
+ }
+
+ NewDelta->ChangeLogEntry = ChangeLogEntry;
+ NewDelta->Order = Order++;
+
+ //
+ // add this entry to appropriate list.
+ //
+
+ InsertTailList( &GlobalDeltaLists[ChangeLogEntry->DBIndex],
+ &NewDelta->Next );
+
+
+ } while ( ( ChangeLogEntry =
+ NlMoveToNextChangeLogEntry(&NlGlobalChangeLogDesc, ChangeLogEntry) ) != NULL );
+
+ return;
+
+}
+
+#if !DBG
+// This routine is defined in chutil.obj for the debug version
+
+VOID
+PrintChangeLogEntry(
+ PCHANGELOG_ENTRY ChangeLogEntry
+ )
+/*++
+
+Routine Description:
+
+ This routine print the content of the given changelog entry.
+
+Arguments:
+
+ ChangeLogEntry -- pointer to the change log entry to print
+
+Return Value:
+
+ none.
+
+--*/
+{
+ LPSTR DeltaName;
+
+ switch ( ChangeLogEntry->DeltaType ) {
+ case AddOrChangeDomain:
+ DeltaName = "AddOrChangeDomain";
+ break;
+ case AddOrChangeGroup:
+ DeltaName = "AddOrChangeGroup";
+ break;
+ case DeleteGroupByName:
+ case DeleteGroup:
+ DeltaName = "DeleteGroup";
+ break;
+ case RenameGroup:
+ DeltaName = "RenameGroup";
+ break;
+ case AddOrChangeUser:
+ DeltaName = "AddOrChangeUser";
+ break;
+ case DeleteUserByName:
+ case DeleteUser:
+ DeltaName = "DeleteUser";
+ break;
+ case RenameUser:
+ DeltaName = "RenameUser";
+ break;
+ case ChangeGroupMembership:
+ DeltaName = "ChangeGroupMembership";
+ break;
+ case AddOrChangeAlias:
+ DeltaName = "AddOrChangeAlias";
+ break;
+ case DeleteAlias:
+ DeltaName = "DeleteAlias";
+ break;
+ case RenameAlias:
+ DeltaName = "RenameAlias";
+ break;
+ case ChangeAliasMembership:
+ DeltaName = "ChangeAliasMembership";
+ break;
+ case AddOrChangeLsaPolicy:
+ DeltaName = "AddOrChangeLsaPolicy";
+ break;
+ case AddOrChangeLsaTDomain:
+ DeltaName = "AddOrChangeLsaTDomain";
+ break;
+ case DeleteLsaTDomain:
+ DeltaName = "DeleteLsaTDomain";
+ break;
+ case AddOrChangeLsaAccount:
+ DeltaName = "AddOrChangeLsaAccount";
+ break;
+ case DeleteLsaAccount:
+ DeltaName = "DeleteLsaAccount";
+ break;
+ case AddOrChangeLsaSecret:
+ DeltaName = "AddOrChangeLsaSecret";
+ break;
+ case DeleteLsaSecret:
+ DeltaName = "DeleteLsaSecret";
+ break;
+ case SerialNumberSkip:
+ DeltaName = "SerialNumberSkip";
+ break;
+ case DummyChangeLogEntry:
+ DeltaName = "DummyChangeLogEntry";
+ break;
+
+ default:
+ DeltaName ="(Unknown)";
+ break;
+ }
+
+ NlPrint((NL_CHANGELOG,
+ "DeltaType %s (%ld) SerialNumber: %lx %lx",
+ DeltaName,
+ ChangeLogEntry->DeltaType,
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+
+ if ( ChangeLogEntry->ObjectRid != 0 ) {
+ NlPrint((NL_CHANGELOG," Rid: 0x%lx", ChangeLogEntry->ObjectRid ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_REPLICATE_IMMEDIATELY ) {
+ NlPrint((NL_CHANGELOG," Immediately" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_PDC_PROMOTION ) {
+ NlPrint((NL_CHANGELOG," Promotion" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_PASSWORD_CHANGE ) {
+ NlPrint((NL_CHANGELOG," PasswordChanged" ));
+ }
+ if ( ChangeLogEntry->Flags & CHANGELOG_DOMAINUSERS_CHANGED ) {
+ NlPrint((NL_CHANGELOG," DomainUsersChanged" ));
+ }
+
+
+ if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+ NlPrint(( NL_CHANGELOG, " Name: '" FORMAT_LPWSTR "'",
+ (LPWSTR)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY))));
+ }
+
+ if( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ) {
+ NlPrint((NL_CHANGELOG," Sid: "));
+ NlpDumpSid( NL_CHANGELOG,
+ (PSID)((PBYTE)(ChangeLogEntry)+ sizeof(CHANGELOG_ENTRY)) );
+ } else {
+ NlPrint((NL_CHANGELOG,"\n" ));
+ }
+}
+#endif // DBG
+
+
+VOID
+PrintDelta(
+ PDELTA_ENTRY Delta
+ )
+/*++
+
+Routine Description:
+
+ This routine print the content of the given delta.
+
+Arguments:
+
+ Delta: pointer to a delta entry to be printed.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ printf( "Order: %ld ", Delta->Order );
+ PrintChangeLogEntry( Delta->ChangeLogEntry );
+}
+
+
+VOID
+PrintDeltaLists(
+ )
+/*++
+
+Routine Description:
+
+ This routine prints deltas of individual databases and validates the
+ sequence.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ none.
+
+--*/
+{
+
+ DWORD j;
+ LARGE_INTEGER RunningChangeLogSerialNumber[NUM_DBS+1];
+
+ for( j = 0; j < NUM_DBS + 1; j++ ) {
+ RunningChangeLogSerialNumber[j].QuadPart = 0;
+ }
+
+ //
+ // for each database.
+ //
+ for( j = 0; j < NUM_DBS + 1; j++ ) {
+
+ if( j == SAM_DB ) {
+ printf("Deltas of SAM DATABASE \n\n" );
+ } else if( j == BUILTIN_DB ) {
+ printf("Deltas of BUILTIN DATABASE \n\n" );
+ } else if( j == LSA_DB ) {
+ printf("Deltas of LSA DATABASE \n\n" );
+ } else if( j == VOID_DB ) {
+ printf("VOID Deltas \n\n" );
+ }
+
+ while( !IsListEmpty( &GlobalDeltaLists[j] ) ) {
+
+ PDELTA_ENTRY NextDelta;
+ PCHANGELOG_ENTRY ChangeLogEntry;
+
+ NextDelta = (PDELTA_ENTRY)
+ RemoveHeadList( &GlobalDeltaLists[j] );
+
+ ChangeLogEntry = NextDelta->ChangeLogEntry;
+
+ //
+ // validate this delta.
+ //
+
+ if ( RunningChangeLogSerialNumber[j].QuadPart == 0 ) {
+
+ //
+ // first entry for this database
+ //
+ // Increment to next expected serial number
+ //
+
+ RunningChangeLogSerialNumber[j].QuadPart =
+ ChangeLogEntry->SerialNumber.QuadPart + 1;
+
+
+ //
+ // Otherwise ensure the serial number is the value expected.
+ //
+
+ } else {
+
+
+ //
+ // If the order is wrong,
+ // just report the problem.
+ //
+
+ if ( !IsSerialNumberEqual(
+ &NlGlobalChangeLogDesc,
+ ChangeLogEntry,
+ &RunningChangeLogSerialNumber[j] ) ) {
+
+ printf("*** THIS ENTRY IS OUT OF SEQUENCE *** \n");
+
+ }
+
+ RunningChangeLogSerialNumber[j].QuadPart =
+ ChangeLogEntry->SerialNumber.QuadPart + 1;
+ }
+
+
+
+ //
+ // print delta
+ //
+
+ PrintDelta( NextDelta );
+
+ //
+ // free this entry.
+ //
+
+ NetpMemoryFree( NextDelta );
+
+ }
+
+ printf("-----------------------------------------------\n");
+ }
+
+}
+
+VOID
+ListDeltas(
+ LPWSTR DeltaFileName,
+ BOOLEAN ListRedoFile
+ )
+/*++
+
+Routine Description:
+
+ This function prints out the content of the change log file in
+ readable format. Also it also checks the consistency of the change
+ log. If not, it will point out the inconsistency.
+
+Arguments:
+
+ DeltaFileName - name of the change log file.
+
+ ListRedoFile - True if this is a redo log and not a change log
+
+Return Value:
+
+ none.
+
+--*/
+{
+ NTSTATUS Status;
+
+ // Needed by routines in chutil.obj
+ InitializeCriticalSection( &NlGlobalChangeLogCritSect );
+ NlGlobalChangeLogPromotionMask = PromotionMask.HighPart;
+ InitChangeLogDesc( &NlGlobalChangeLogDesc );
+
+ NlGlobalChangeLogDesc.RedoLog = ListRedoFile;
+
+ //
+ // Read in the existing changelog file.
+ //
+
+ Status = NlOpenChangeLogFile( DeltaFileName, &NlGlobalChangeLogDesc, TRUE );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ fprintf( stderr, "Couldn't NlOpenChangeLogFile'" FORMAT_LPWSTR
+ "': 0x%lx \n",
+ DeltaFileName,
+ Status );
+
+ goto Cleanup;
+ }
+
+ //
+ // Write to this file if conversion needed.
+ //
+ if ( NlGlobalChangeLogDesc.Version3 ) {
+ printf( "Converting version 3 changelog to version 4 -- writing netlv40.chg\n");
+ wcscpy( NlGlobalChangeLogFilePrefix, L"netlv40" );
+ }
+
+ //
+ // Convert the changelog file to the right size/version.
+ //
+
+ Status = NlResizeChangeLogFile( &NlGlobalChangeLogDesc, NlGlobalChangeLogDesc.BufferSize );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ fprintf( stderr, "Couldn't NlOpenChangeLogFile'" FORMAT_LPWSTR
+ "': 0x%lx \n",
+ DeltaFileName,
+ Status );
+
+ goto Cleanup;
+ }
+
+ //
+ // print change log signature
+
+ printf( "FILE SIGNATURE : %s \n\n", NlGlobalChangeLogDesc.Buffer );
+
+ MakeDeltaLists();
+
+ PrintDeltaLists();
+
+Cleanup:
+
+ return;
+}
diff --git a/private/net/svcdlls/logonsrv/server/oldstub.c b/private/net/svcdlls/logonsrv/server/oldstub.c
new file mode 100644
index 000000000..eb47dde27
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/oldstub.c
@@ -0,0 +1,457 @@
+/*++
+
+Copyright (c) 1994 Microsoft Corporation
+
+Module Name:
+
+ oldstub.c
+
+Abstract:
+
+ This file contains functions generated by midl v1.0. These
+ functions 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 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 "logon_s.h"
+
+/* routine that frees graph for struct _UNICODE_STRING */
+void _fgs__UNICODE_STRING (UNICODE_STRING * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _NLPR_SID_ARRAY */
+void _fgs__NLPR_SID_ARRAY (NLPR_SID_ARRAY * _source)
+ {
+ if (_source->Sids !=0)
+ {
+ MIDL_user_free((void *)(_source->Sids));
+ }
+ }
+
+/* routine that frees graph for struct _NLPR_CR_CIPHER_VALUE */
+void _fgs__NLPR_CR_CIPHER_VALUE (NLPR_CR_CIPHER_VALUE * _source)
+ {
+ if (_source->Buffer !=0)
+ {
+ MIDL_user_free((void *)(_source->Buffer));
+ }
+ }
+
+/* routine that frees graph for struct _NLPR_LOGON_HOURS */
+void _fgs__NLPR_LOGON_HOURS (NLPR_LOGON_HOURS * _source)
+ {
+ if (_source->LogonHours !=0)
+ {
+ MIDL_user_free((void *)(_source->LogonHours));
+ }
+ }
+
+/* routine that frees graph for struct _NLPR_USER_PRIVATE_INFO */
+void _fgs__NLPR_USER_PRIVATE_INFO (NLPR_USER_PRIVATE_INFO * _source)
+ {
+ if (_source->Data !=0)
+ {
+ MIDL_user_free((void *)(_source->Data));
+ }
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_USER */
+void _fgs__NETLOGON_DELTA_USER (NETLOGON_DELTA_USER * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->UserName);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->FullName);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->HomeDirectory);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->HomeDirectoryDrive);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->ScriptPath);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->AdminComment);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->WorkStations);
+ _fgs__NLPR_LOGON_HOURS ((NLPR_LOGON_HOURS *)&_source->LogonHours);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->UserComment);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->Parameters);
+ _fgs__NLPR_USER_PRIVATE_INFO ((NLPR_USER_PRIVATE_INFO *)&_source->PrivateData);
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_GROUP */
+void _fgs__NETLOGON_DELTA_GROUP (NETLOGON_DELTA_GROUP * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->Name);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->AdminComment);
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_GROUP_MEMBER */
+void _fgs__NETLOGON_DELTA_GROUP_MEMBER (NETLOGON_DELTA_GROUP_MEMBER * _source)
+ {
+ if (_source->MemberIds !=0)
+ {
+ MIDL_user_free((void *)(_source->MemberIds));
+ }
+ if (_source->Attributes !=0)
+ {
+ MIDL_user_free((void *)(_source->Attributes));
+ }
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_ALIAS */
+void _fgs__NETLOGON_DELTA_ALIAS (NETLOGON_DELTA_ALIAS * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->Name);
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_ALIAS_MEMBER */
+void _fgs__NETLOGON_DELTA_ALIAS_MEMBER (NETLOGON_DELTA_ALIAS_MEMBER * _source)
+ {
+ _fgs__NLPR_SID_ARRAY ((NLPR_SID_ARRAY *)&_source->Members);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_DOMAIN */
+void _fgs__NETLOGON_DELTA_DOMAIN (NETLOGON_DELTA_DOMAIN * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DomainName);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->OemInformation);
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_RENAME */
+void _fgs__NETLOGON_DELTA_RENAME (NETLOGON_RENAME_GROUP * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->OldName);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->NewName);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_POLICY */
+void _fgs__NETLOGON_DELTA_POLICY (NETLOGON_DELTA_POLICY * _source)
+ {
+ if (_source->EventAuditingOptions !=0)
+ {
+ MIDL_user_free((void *)(_source->EventAuditingOptions));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->PrimaryDomainName);
+ if (_source->PrimaryDomainSid !=0)
+ {
+ MIDL_user_free((void *)(_source->PrimaryDomainSid));
+ }
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_TRUSTED_DOMAINS */
+void _fgs__NETLOGON_DELTA_TRUSTED_DOMAINS (NETLOGON_DELTA_TRUSTED_DOMAINS * _source)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DomainName);
+ if (_source->ControllerNames !=0)
+ {
+ {
+ unsigned long _sym15;
+ for (_sym15 = 0; _sym15 < (unsigned long )(0 + _source->NumControllerEntries); _sym15++)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->ControllerNames[_sym15]);
+ }
+ }
+ MIDL_user_free((void *)(_source->ControllerNames));
+ }
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_ACCOUNTS */
+void _fgs__NETLOGON_DELTA_ACCOUNTS (NETLOGON_DELTA_ACCOUNTS * _source)
+ {
+ if (_source->PrivilegeAttributes !=0)
+ {
+ MIDL_user_free((void *)(_source->PrivilegeAttributes));
+ }
+ if (_source->PrivilegeNames !=0)
+ {
+ {
+ unsigned long _sym21;
+ for (_sym21 = 0; _sym21 < (unsigned long )(0 + _source->PrivilegeEntries); _sym21++)
+ {
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->PrivilegeNames[_sym21]);
+ }
+ }
+ MIDL_user_free((void *)(_source->PrivilegeNames));
+ }
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_SECRET */
+void _fgs__NETLOGON_DELTA_SECRET (NETLOGON_DELTA_SECRET * _source)
+ {
+ _fgs__NLPR_CR_CIPHER_VALUE ((NLPR_CR_CIPHER_VALUE *)&_source->CurrentValue);
+ _fgs__NLPR_CR_CIPHER_VALUE ((NLPR_CR_CIPHER_VALUE *)&_source->OldValue);
+ if (_source->SecurityDescriptor !=0)
+ {
+ MIDL_user_free((void *)(_source->SecurityDescriptor));
+ }
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+// Written by CliffV since MIDL no longer generates these.
+/* routine that frees graph for struct _NETLOGON_DELTA_DELETE */
+void _fgs__NETLOGON_DELTA_DELETE (NETLOGON_DELTA_DELETE_USER * _source)
+ {
+ MIDL_user_free((void *)(_source->AccountName));
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString1);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString2);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString3);
+ _fgs__UNICODE_STRING ((UNICODE_STRING *)&_source->DummyString4);
+ }
+
+
+/* routine that frees graph for union _NETLOGON_DELTA_UNION */
+void _fgu__NETLOGON_DELTA_UNION (NETLOGON_DELTA_UNION * _source, NETLOGON_DELTA_TYPE _branch)
+ {
+ switch (_branch)
+ {
+ case AddOrChangeDomain :
+ {
+ if (_source->DeltaDomain !=0)
+ {
+ _fgs__NETLOGON_DELTA_DOMAIN ((NETLOGON_DELTA_DOMAIN *)_source->DeltaDomain);
+ MIDL_user_free((void *)(_source->DeltaDomain));
+ }
+ break;
+ }
+ case AddOrChangeGroup :
+ {
+ if (_source->DeltaGroup !=0)
+ {
+ _fgs__NETLOGON_DELTA_GROUP ((NETLOGON_DELTA_GROUP *)_source->DeltaGroup);
+ MIDL_user_free((void *)(_source->DeltaGroup));
+ }
+ break;
+ }
+ case RenameGroup :
+ {
+ if (_source->DeltaRenameGroup !=0)
+ {
+ _fgs__NETLOGON_DELTA_RENAME ((NETLOGON_RENAME_GROUP *)_source->DeltaRenameGroup);
+ MIDL_user_free((void *)(_source->DeltaRenameGroup));
+ }
+ break;
+ }
+ case AddOrChangeUser :
+ {
+ if (_source->DeltaUser !=0)
+ {
+ _fgs__NETLOGON_DELTA_USER ((NETLOGON_DELTA_USER *)_source->DeltaUser);
+ MIDL_user_free((void *)(_source->DeltaUser));
+ }
+ break;
+ }
+ case RenameUser :
+ {
+ if (_source->DeltaRenameUser !=0)
+ {
+ _fgs__NETLOGON_DELTA_RENAME ((NETLOGON_RENAME_GROUP *)_source->DeltaRenameUser);
+ MIDL_user_free((void *)(_source->DeltaRenameUser));
+ }
+ break;
+ }
+ case ChangeGroupMembership :
+ {
+ if (_source->DeltaGroupMember !=0)
+ {
+ _fgs__NETLOGON_DELTA_GROUP_MEMBER ((NETLOGON_DELTA_GROUP_MEMBER *)_source->DeltaGroupMember);
+ MIDL_user_free((void *)(_source->DeltaGroupMember));
+ }
+ break;
+ }
+ case AddOrChangeAlias :
+ {
+ if (_source->DeltaAlias !=0)
+ {
+ _fgs__NETLOGON_DELTA_ALIAS ((NETLOGON_DELTA_ALIAS *)_source->DeltaAlias);
+ MIDL_user_free((void *)(_source->DeltaAlias));
+ }
+ break;
+ }
+ case RenameAlias :
+ {
+ if (_source->DeltaRenameAlias !=0)
+ {
+ _fgs__NETLOGON_DELTA_RENAME ((NETLOGON_RENAME_GROUP *)_source->DeltaRenameAlias);
+ MIDL_user_free((void *)(_source->DeltaRenameAlias));
+ }
+ break;
+ }
+ case ChangeAliasMembership :
+ {
+ if (_source->DeltaAliasMember !=0)
+ {
+ _fgs__NETLOGON_DELTA_ALIAS_MEMBER ((NETLOGON_DELTA_ALIAS_MEMBER *)_source->DeltaAliasMember);
+ MIDL_user_free((void *)(_source->DeltaAliasMember));
+ }
+ break;
+ }
+ case AddOrChangeLsaPolicy :
+ {
+ if (_source->DeltaPolicy !=0)
+ {
+ _fgs__NETLOGON_DELTA_POLICY ((NETLOGON_DELTA_POLICY *)_source->DeltaPolicy);
+ MIDL_user_free((void *)(_source->DeltaPolicy));
+ }
+ break;
+ }
+ case AddOrChangeLsaTDomain :
+ {
+ if (_source->DeltaTDomains !=0)
+ {
+ _fgs__NETLOGON_DELTA_TRUSTED_DOMAINS ((NETLOGON_DELTA_TRUSTED_DOMAINS *)_source->DeltaTDomains);
+ MIDL_user_free((void *)(_source->DeltaTDomains));
+ }
+ break;
+ }
+ case AddOrChangeLsaAccount :
+ {
+ if (_source->DeltaAccounts !=0)
+ {
+ _fgs__NETLOGON_DELTA_ACCOUNTS ((NETLOGON_DELTA_ACCOUNTS *)_source->DeltaAccounts);
+ MIDL_user_free((void *)(_source->DeltaAccounts));
+ }
+ break;
+ }
+ case AddOrChangeLsaSecret :
+ {
+ if (_source->DeltaSecret !=0)
+ {
+ _fgs__NETLOGON_DELTA_SECRET ((NETLOGON_DELTA_SECRET *)_source->DeltaSecret);
+ MIDL_user_free((void *)(_source->DeltaSecret));
+ }
+ break;
+ }
+ case DeleteUserByName:
+ case DeleteGroupByName:
+ if (_source->DeltaDeleteUser !=0) {
+ _fgs__NETLOGON_DELTA_DELETE ((NETLOGON_DELTA_DELETE_USER *)_source->DeltaDeleteUser);
+ MIDL_user_free((void *)(_source->DeltaDeleteUser));
+ }
+ break;
+ case SerialNumberSkip:
+ if (_source->DeltaSerialNumberSkip !=0) {
+ MIDL_user_free((void *)(_source->DeltaSerialNumberSkip));
+ }
+ break;
+ default :
+ {
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for union _NETLOGON_DELTA_ID_UNION */
+void _fgu__NETLOGON_DELTA_ID_UNION (NETLOGON_DELTA_ID_UNION * _source, NETLOGON_DELTA_TYPE _branch)
+ {
+ switch (_branch)
+ {
+ case AddOrChangeLsaPolicy :
+ case AddOrChangeLsaTDomain :
+ case DeleteLsaTDomain :
+ case AddOrChangeLsaAccount :
+ case DeleteLsaAccount :
+ {
+ if (_source->Sid !=0)
+ {
+ MIDL_user_free((void *)(_source->Sid));
+ }
+ break;
+ }
+ case AddOrChangeLsaSecret :
+ case DeleteLsaSecret :
+ {
+ if (_source->Name !=0)
+ {
+ MIDL_user_free((void *)(_source->Name));
+ }
+ break;
+ }
+ default :
+ {
+ break;
+ }
+ }
+ }
+
+/* routine that frees graph for struct _NETLOGON_DELTA_ENUM */
+void _fgs__NETLOGON_DELTA_ENUM (NETLOGON_DELTA_ENUM * _source)
+ {
+ _fgu__NETLOGON_DELTA_ID_UNION ((NETLOGON_DELTA_ID_UNION *)&_source->DeltaID, _source->DeltaType);
+ _fgu__NETLOGON_DELTA_UNION ((NETLOGON_DELTA_UNION *)&_source->DeltaUnion, _source->DeltaType);
+ }
+
diff --git a/private/net/svcdlls/logonsrv/server/parse.c b/private/net/svcdlls/logonsrv/server/parse.c
new file mode 100644
index 000000000..0a0f453b1
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/parse.c
@@ -0,0 +1,464 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ parse.c
+
+Abstract:
+
+ Routine to parse the command line.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 01-Aug-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+ 09-May-1992 JohnRo
+ Enable use of win32 registry.
+ Use net config helpers for NetLogon.
+ Fixed UNICODE bug handling debug file name.
+ Use <prefix.h> equates.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+#include <config.h> // net config helpers.
+#include <configp.h> // USE_WIN32_CONFIG (if defined), etc.
+#include <confname.h> // SECTION_ equates, NETLOGON_KEYWORD_ equates.
+#include <lmapibuf.h> // NetApiBufferFree().
+#include <lmerr.h> // NERR_ equates.
+#include <lmsname.h> // SERVICE_NETLOGON.
+#include <lmsvc.h> // SERVICE_UIC codes are defined here
+#include <prefix.h> // PREFIX_ equates.
+#include <tstring.h> // NetpNCopy{type}To{type}.
+
+//
+// Include files specific to this .c file
+//
+
+#include <iniparm.h> // DEFAULT_, MIN_, and MAX_ equates.
+#include <stdlib.h> // C library functions (rand, etc)
+#include <string.h> // strnicmp
+#include <tstring.h> // NetpCopy...
+
+
+NET_API_STATUS
+NlParseOne(
+ IN LPNET_CONFIG_HANDLE SectionHandle,
+ IN LPWSTR Keyword,
+ IN ULONG DefaultValue,
+ IN ULONG MinimumValue,
+ IN ULONG MaximumValue,
+ OUT PULONG Value
+ )
+/*++
+
+Routine Description:
+
+ Get a single numeric parameter from the netlogon section of the registry.
+
+Arguments:
+
+ SectionHandle - Handle into the registry.
+
+ Keyword - Name of the value to read.
+
+ DefaultValue - Default value if parameter doesn't exist.
+
+ MinimumValue - Minumin valid value.
+
+ MaximumValue - Maximum valid value.
+
+ Value - Returns the value parsed.
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ LPWSTR ValueT = NULL;
+
+ //
+ // Determine if the value is specified in the registry at all.
+ //
+
+ NetStatus = NetpGetConfigValue (
+ SectionHandle,
+ Keyword,
+ &ValueT );
+
+ if( ValueT != NULL ) {
+ NetApiBufferFree( ValueT );
+ ValueT = NULL;
+ }
+
+ //
+ // If the value wasn't specified,
+ // use the default.
+ //
+
+ if ( NetStatus == NERR_CfgParamNotFound ) {
+ *Value = DefaultValue;
+
+ //
+ // If the value was specifed,
+ // get it from the registry.
+ //
+
+ } else {
+
+ NetStatus = NetpGetConfigDword (
+ SectionHandle,
+ Keyword, // keyword wanted
+ DefaultValue,
+ Value );
+
+ if (NetStatus == NO_ERROR) {
+ if ( *Value > MaximumValue || *Value < MinimumValue ) {
+ LPWSTR MsgStrings[1];
+
+ MsgStrings[0] = Keyword;
+
+ NlpWriteEventlog( SERVICE_UIC_BADPARMVAL,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)Value,
+ sizeof(*Value),
+ MsgStrings,
+ 1 );
+
+ if ( *Value > MaximumValue ) {
+ *Value = MaximumValue;
+ } else if ( *Value < MinimumValue ) {
+ *Value = MinimumValue;
+ }
+ }
+
+ } else {
+
+ return NetStatus;
+
+ }
+ }
+
+ return NERR_Success;
+}
+
+
+//
+// Table of numeric parameters to parse.
+//
+
+struct {
+ LPWSTR Keyword;
+ ULONG DefaultValue;
+ ULONG MinimumValue;
+ ULONG MaximumValue;
+ PULONG Value;
+} ParseTable[] =
+{
+{ NETLOGON_KEYWORD_PULSE, DEFAULT_PULSE, MIN_PULSE, MAX_PULSE, &NlGlobalPulseParameter },
+{ NETLOGON_KEYWORD_RANDOMIZE, DEFAULT_RANDOMIZE, MIN_RANDOMIZE, MAX_RANDOMIZE, &NlGlobalRandomizeParameter },
+{ NETLOGON_KEYWORD_PULSEMAXIMUM, DEFAULT_PULSEMAXIMUM, MIN_PULSEMAXIMUM, MAX_PULSEMAXIMUM, &NlGlobalPulseMaximumParameter },
+{ NETLOGON_KEYWORD_PULSECONCURRENCY, DEFAULT_PULSECONCURRENCY, MIN_PULSECONCURRENCY, MAX_PULSECONCURRENCY, &NlGlobalPulseConcurrencyParameter },
+{ NETLOGON_KEYWORD_PULSETIMEOUT1, DEFAULT_PULSETIMEOUT1, MIN_PULSETIMEOUT1, MAX_PULSETIMEOUT1, &NlGlobalPulseTimeout1Parameter },
+{ NETLOGON_KEYWORD_PULSETIMEOUT2, DEFAULT_PULSETIMEOUT2, MIN_PULSETIMEOUT2, MAX_PULSETIMEOUT2, &NlGlobalPulseTimeout2Parameter },
+{ NETLOGON_KEYWORD_GOVERNOR, DEFAULT_GOVERNOR, MIN_GOVERNOR, MAX_GOVERNOR, &NlGlobalGovernorParameter },
+{ NETLOGON_KEYWORD_MAXIMUMMAILSLOTMESSAGES, DEFAULT_MAXIMUMMAILSLOTMESSAGES, MIN_MAXIMUMMAILSLOTMESSAGES, MAX_MAXIMUMMAILSLOTMESSAGES, &NlGlobalMaximumMailslotMessagesParameter },
+{ NETLOGON_KEYWORD_MAILSLOTMESSAGETIMEOUT, DEFAULT_MAILSLOTMESSAGETIMEOUT, MIN_MAILSLOTMESSAGETIMEOUT, MAX_MAILSLOTMESSAGETIMEOUT, &NlGlobalMailslotMessageTimeoutParameter },
+{ NETLOGON_KEYWORD_MAILSLOTDUPLICATETIMEOUT,DEFAULT_MAILSLOTDUPLICATETIMEOUT,MIN_MAILSLOTDUPLICATETIMEOUT,MAX_MAILSLOTDUPLICATETIMEOUT,&NlGlobalMailslotDuplicateTimeoutParameter },
+{ NETLOGON_KEYWORD_EXPECTEDDIALUPDELAY, DEFAULT_EXPECTEDDIALUPDELAY, MIN_EXPECTEDDIALUPDELAY, MAX_EXPECTEDDIALUPDELAY, &NlGlobalExpectedDialupDelayParameter },
+{ NETLOGON_KEYWORD_SCAVENGEINTERVAL, DEFAULT_SCAVENGEINTERVAL, MIN_SCAVENGEINTERVAL, MAX_SCAVENGEINTERVAL, &NlGlobalScavengeIntervalParameter },
+#if DBG
+{ NETLOGON_KEYWORD_DBFLAG, 0, 0, 0xFFFFFFFF, &NlGlobalTrace },
+{ NETLOGON_KEYWORD_MAXIMUMLOGFILESIZE, DEFAULT_MAXIMUM_LOGFILE_SIZE, 0, 0xFFFFFFFF, &NlGlobalLogFileMaxSize },
+#endif // DBG
+};
+
+//
+// Table of boolean to parse.
+//
+
+struct {
+ LPWSTR Keyword;
+ BOOL DefaultValue;
+ PBOOL Value;
+} BoolParseTable[] =
+{
+{ NETLOGON_KEYWORD_UPDATE, DEFAULT_SYNCHRONIZE, &NlGlobalSynchronizeParameter },
+{ NETLOGON_KEYWORD_DISABLEPASSWORDCHANGE, DEFAULT_DISABLE_PASSWORD_CHANGE, &NlGlobalDisablePasswordChangeParameter },
+{ NETLOGON_KEYWORD_REFUSEPASSWORDCHANGE, DEFAULT_REFUSE_PASSWORD_CHANGE, &NlGlobalRefusePasswordChangeParameter },
+};
+
+
+BOOL
+Nlparse(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Get parameters from registry.
+
+ All of the parameters are described in iniparm.h.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ TRUE -- iff the parse was successful.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ LPWSTR ValueT = NULL;
+ LPWSTR Keyword = NULL;
+ ULONG i;
+
+
+ //
+ // Variables for scanning the configuration data.
+ //
+
+ LPNET_CONFIG_HANDLE SectionHandle = NULL;
+
+
+ //
+ // Open the NetLogon configuration section.
+ //
+
+ NetStatus = NetpOpenConfigData(
+ &SectionHandle,
+ NULL, // no server name.
+#if defined(USE_WIN32_CONFIG)
+ SERVICE_NETLOGON,
+#else
+ SECT_NT_NETLOGON, // section name
+#endif
+ TRUE ); // we only want readonly access
+
+ if ( NetStatus != NO_ERROR ) {
+ SectionHandle = NULL;
+ NlExit(SERVICE_UIC_BADPARMVAL, NetStatus, LogError, NULL );
+ goto Cleanup;
+ }
+
+ //
+ // Loop parsing all the numeric parameters.
+ //
+
+ for ( i=0; i<sizeof(ParseTable)/sizeof(ParseTable[0]); i++ ) {
+
+ NetStatus = NlParseOne(
+ SectionHandle,
+ ParseTable[i].Keyword,
+ ParseTable[i].DefaultValue,
+ ParseTable[i].MinimumValue,
+ ParseTable[i].MaximumValue,
+ ParseTable[i].Value );
+
+ if ( NetStatus != NERR_Success ) {
+ Keyword = ParseTable[i].Keyword;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Loop parsing all the boolean parameters.
+ //
+
+ for ( i=0; i<sizeof(BoolParseTable)/sizeof(BoolParseTable[0]); i++ ) {
+
+ NetStatus = NetpGetConfigBool (
+ SectionHandle,
+ BoolParseTable[i].Keyword,
+ BoolParseTable[i].DefaultValue,
+ BoolParseTable[i].Value );
+
+ if (NetStatus != NO_ERROR) {
+ Keyword = BoolParseTable[i].Keyword;
+ goto Cleanup;
+ }
+
+ }
+
+
+ //
+ // Get the "SCRIPTS" configured parameter
+ //
+
+ NetStatus = NetpGetConfigValue (
+ SectionHandle,
+ NETLOGON_KEYWORD_SCRIPTS, // key wanted
+ &ValueT ); // Must be freed by NetApiBufferFree().
+
+ //
+ // Handle the default
+ //
+ if (NetStatus == NERR_CfgParamNotFound) {
+ ValueT = NetpAllocWStrFromWStr( DEFAULT_SCRIPTS );
+ if ( ValueT == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ } else {
+ NetStatus = NO_ERROR;
+ }
+ }
+
+ if (NetStatus == NO_ERROR) {
+
+ TCHAR OutPathname[PATHLEN+1];
+ ULONG type;
+ NlAssert( ValueT != NULL );
+
+ //
+ // Convert the /Scripts: parameter or configured script path to a full
+ // pathname.
+ //
+
+ NetStatus = I_NetPathCanonicalize( NULL,
+ ValueT,
+ OutPathname,
+ sizeof(OutPathname),
+ NULL,
+ &type,
+ 0L );
+ if (NetStatus != NERR_Success ) {
+ Keyword = NETLOGON_KEYWORD_SCRIPTS;
+ goto Cleanup;
+ }
+
+ if (type == ITYPE_PATH_ABSD) {
+ NetpCopyTStrToWStr(NlGlobalUnicodeScriptPath, OutPathname);
+ } else if (type == ITYPE_PATH_RELND) {
+ if ( !GetWindowsDirectoryW(
+ NlGlobalUnicodeScriptPath,
+ sizeof(NlGlobalUnicodeScriptPath)/sizeof(WCHAR) ) ) {
+ NetStatus = GetLastError();
+ Keyword = NETLOGON_KEYWORD_SCRIPTS;
+ goto Cleanup;
+ }
+ wcscat( NlGlobalUnicodeScriptPath, L"\\" );
+ wcscat( NlGlobalUnicodeScriptPath, OutPathname );
+ } else {
+ Keyword = NETLOGON_KEYWORD_SCRIPTS;
+ NetStatus = NERR_BadComponent;
+ goto Cleanup;
+ }
+
+ (VOID) NetApiBufferFree( ValueT );
+ ValueT = NULL;
+ } else {
+ Keyword = NETLOGON_KEYWORD_SCRIPTS;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Convert parameters to a more convenient form.
+ //
+
+ // Convert to from seconds to 100ns
+ NlGlobalPulseMaximum =
+ RtlEnlargedIntegerMultiply( NlGlobalPulseMaximumParameter,
+ 10000000 );
+
+ // Convert to from seconds to 100ns
+ NlGlobalPulseTimeout1 =
+ RtlEnlargedIntegerMultiply( NlGlobalPulseTimeout1Parameter,
+ 10000000 );
+
+ // Convert to from seconds to 100ns
+ NlGlobalPulseTimeout2 =
+ RtlEnlargedIntegerMultiply( NlGlobalPulseTimeout2Parameter,
+ 10000000 );
+
+ // Convert to from seconds to 100ns
+ NlGlobalMailslotMessageTimeout =
+ RtlEnlargedIntegerMultiply( NlGlobalMailslotMessageTimeoutParameter,
+ 10000000 );
+
+ // Convert to from seconds to 100ns
+ NlGlobalMailslotDuplicateTimeout =
+ RtlEnlargedIntegerMultiply( NlGlobalMailslotDuplicateTimeoutParameter,
+ 10000000 );
+
+
+ NlGlobalShortApiCallPeriod =
+ SHORT_API_CALL_PERIOD + NlGlobalExpectedDialupDelayParameter * 1000;
+
+#if DBG
+ //
+ // Open the debug file
+ //
+
+ NlOpenDebugFile( FALSE );
+
+
+
+ NlPrint((NL_INIT, "Following are the effective values after parsing\n"));
+
+ NlPrint((NL_INIT," ScriptsParameter = " FORMAT_LPWSTR "\n",
+ NlGlobalUnicodeScriptPath));
+
+ for ( i=0; i<sizeof(ParseTable)/sizeof(ParseTable[0]); i++ ) {
+ NlPrint((NL_INIT," " FORMAT_LPWSTR " = %lu (0x%lx)\n",
+ ParseTable[i].Keyword,
+ *ParseTable[i].Value,
+ *ParseTable[i].Value ));
+ }
+
+ for ( i=0; i<sizeof(BoolParseTable)/sizeof(BoolParseTable[0]); i++ ) {
+
+ NlPrint((NL_INIT," " FORMAT_LPWSTR " = %s\n",
+ BoolParseTable[i].Keyword,
+ *BoolParseTable[i].Value ? "TRUE":"FALSE" ));
+ }
+
+ IF_DEBUG( NETLIB ) {
+ extern DWORD NetlibpTrace;
+ NetlibpTrace |= 0x8000; // NETLIB_DEBUG_LOGON
+ }
+#endif // DBG
+
+
+ NetStatus = NERR_Success;
+
+
+ //
+ // Free any locally used resources
+ //
+Cleanup:
+ if ( NetStatus != NERR_Success ) {
+ NlExit(SERVICE_UIC_BADPARMVAL, NetStatus, LogError, Keyword );
+ }
+
+ if ( ValueT != NULL) {
+ (VOID) NetApiBufferFree( ValueT );
+ }
+ if ( SectionHandle != NULL ) {
+ (VOID) NetpCloseConfigData( SectionHandle );
+ }
+
+ return (NetStatus == NERR_Success);
+}
diff --git a/private/net/svcdlls/logonsrv/server/repluas.c b/private/net/svcdlls/logonsrv/server/repluas.c
new file mode 100644
index 000000000..fbb272c61
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/repluas.c
@@ -0,0 +1,2452 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ repluas.c
+
+Abstract:
+
+ Low level functions for SSI UAS Replication apis. These functions
+ are used only for downlevel supports.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 23-Aug-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+ Madana
+ Fixed several problems.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <accessp.h> // Routines shared with NetUser Apis
+#include <lmerr.h> // NERR_*
+#include <replutil.h> // Local procedure forwards
+#include <ntrpcp.h> // MIDL_user_free
+#include <secobj.h> // NetpDomainIdToSid
+#include <ssidelta.h>
+#include <stddef.h> // offsetof
+#include <loghours.h>
+
+//
+// Macro for setting a Unalligned Ushort.
+//
+
+#ifdef i386
+#define PutUnalignedUshort(DestAddress, Value) *(DestAddress) = (USHORT)(Value)
+#else
+#define PutUnalignedUshort(DestAddress,Value) { \
+ ( (PUCHAR)(DestAddress) )[0] = BYTE_0(Value); \
+ ( (PUCHAR)(DestAddress) )[1] = BYTE_1(Value); \
+ }
+#endif
+
+BOOLEAN
+SpecialGroupOp(
+ IN PUNICODE_STRING NameString,
+ IN OUT PDWORD Groups
+ )
+{
+
+ if( _wcsnicmp( NameString->Buffer,
+ UAS_BUILTIN_ADMINS_GROUP_NAME,
+ NameString->Length) == 0) {
+
+ if( Groups != NULL ) {
+ *Groups |= UAS_BUILTIN_ADMINS_GROUP;
+ }
+
+ return(TRUE);
+ }
+ else if( _wcsnicmp( NameString->Buffer,
+ UAS_BUILTIN_USERS_GROUP_NAME,
+ NameString->Length) == 0) {
+
+ if( Groups != NULL ) {
+ *Groups |= UAS_BUILTIN_USERS_GROUP;
+ }
+
+ return(TRUE);
+ }
+ else if( _wcsnicmp( NameString->Buffer,
+ UAS_BUILTIN_GUESTS_GROUP_NAME,
+ NameString->Length) == 0) {
+
+ if( Groups != NULL ) {
+ *Groups |= UAS_BUILTIN_GUESTS_GROUP;
+ }
+
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+
+NTSTATUS
+NlPackUasHeader(
+ IN BYTE Opcode,
+ IN DWORD InitialSize,
+ OUT PUSHORT *RecordSize,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor
+ )
+/*++
+
+Routine Description:
+
+ Place a header at the front of the record returned from I_NetAccountDeltas
+ or I_NetAccountSync.
+
+ A three-byte header is reserved as follows:
+ _____________________________________________
+ | length_word | opcode_byte | packed_struct |
+ ---------------------------------------------
+
+ The BufferDescriptor is updated to point to where the caller should
+ place the structure describing the delta.
+
+Arguments:
+
+ Opcode - Specified an OPCODE describing the data that will follow this
+ header. Use one the of the DELTA_ defines.
+
+ InitialSize - Specifies the size of the fixed-length portion of the
+ Uas record.
+
+ RecordSize - Returns a Pointer to where the total record size is to
+ be placed. Warning, the returned value is NOT properly aligned
+ an should only be set using PutUnalignedUshort.
+
+ BufferDescriptor - Describes the buffer to place the header into.
+ The descriptor is updated to reflect that the header has been
+ placed into the buffer.
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The header does not fit into the buffer.
+
+--*/
+{
+ NlPrint((NL_PACK_VERBOSE,"NlPackUasHeader: Opcode: %ld InitialSize: 0x%lx\n",
+ Opcode,
+ InitialSize ));
+ NlPrint((NL_PACK_VERBOSE," Buffer: FixedDataEnd:%lx EndOfVar:%lx\n",
+ BufferDescriptor->FixedDataEnd,
+ BufferDescriptor->EndOfVariableData));
+
+ //
+ // Ensure the header fits in the return buffer.
+ //
+
+ if ( (LONG)(BufferDescriptor->EndOfVariableData -
+ BufferDescriptor->FixedDataEnd) <
+ (LONG)(NETLOGON_DELTA_HEADER_SIZE + InitialSize) ) {
+ NlPrint((NL_PACK," Header doesn't fit into buffer\n" ));
+
+ return STATUS_MORE_ENTRIES;
+ }
+
+ //
+ // Return a pointer to the RecordSize field of the header and initially
+ // set the RecordSize field to be the initial size.
+ //
+ // Return a pointer to where the record size should be stored
+ // and put the opcode in the header.
+ //
+
+ *RecordSize = (PUSHORT) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += sizeof (unsigned short);
+
+ PutUnalignedUshort( *RecordSize, InitialSize + NETLOGON_DELTA_HEADER_SIZE );
+
+
+ //
+ // Set the Opcode into the header.
+ //
+
+ *(BufferDescriptor->FixedDataEnd) = Opcode;
+ BufferDescriptor->FixedDataEnd++;
+
+ return STATUS_SUCCESS;
+}
+
+
+
+
+NTSTATUS
+NlPackVarLenField(
+ IN RPC_UNICODE_STRING *UnicodeString,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ OUT PUSHORT StringOffset,
+ IN OUT USHORT *RunningOffset,
+ IN DWORD MaxStringLength,
+ IN BOOL TruncateSilently,
+ IN DWORD RelativeId
+ )
+/*++
+
+Routine Description:
+
+ Pack the specified UnicodeString into the specified buffer as an Ansi
+ zero terminated string.
+
+Arguments:
+
+ UnicodeString - Specifies the unicode String to pack into the buffer.
+
+ BufferDescriptor - Describes the buffer to pack the string into.
+ Specifically, FixedDataEnd describes where the corresponding
+ ANSI string will be placed. EndOfVariableData points to one
+ byte beyond the space available for the string. FixedDataEnd is
+ adjusted to reflect the copied data.
+
+ StringOffset - Receives the offset of the string from the beggining of
+ the base structure. If UnicodeString points to a NULL string, 0 is
+ returned as the offset; otherwise, this will either be the value
+ of RunningOffset as passed into this routine.
+
+ WARNING: This pointer need not be properly aligned.
+
+ RunningOffset - Specifies the current offset from the base structure.
+ This value is incremented by the size of the copied ANSI string
+ including the zero byte. The value returned is suitable for passing
+ to the next NlPackVarLenField call.
+
+ MaxStringLength - The maximum length in bytes (not including the zero byte)
+ of the resultant ANSI string.
+
+ TruncateSilently - Specifies the action to take if the ANSI string is
+ truncated to MaxStringLength. If TRUE, the string is silently
+ truncated. If FALSE, the string is truncated an a
+ STATUS_INVALID_PARAMETER error is return.
+
+ RelativeId -- If non-zero, specifies that unicode string is an account
+ name. RelativeId is the RelativeId of that account. Account names
+ are always converted to upper case for downlevel compatibility.
+ If an account name cannot be converted or truncated, a fictitious
+ account name, "RID########", will used.
+
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_INVALID_PARAMETER - The specified UnicodeString is longer than
+ its UAS equivalent. Or Unicode character could not be mapped to
+ ANSI.
+
+ STATUS_MORE_ENTRIES - The String does not fit in the specified buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+
+ OEM_STRING AnsiString;
+ BOOLEAN AnsiStringAllocated = FALSE;
+
+ CHAR RidNameBufferAnsi[13]; // "RID" + ######## + '$' + '\0'
+
+ //
+ // Be Verbose
+ //
+
+ IF_DEBUG( PACK_VERBOSE ) {
+ if ( UnicodeString->Length != 0 ) {
+ NlPrint((NL_PACK_VERBOSE,
+ "NlPackVarLenField: String:%wZ (0x%lx) Maximum: 0x%lx\n",
+ UnicodeString,
+ UnicodeString->Length/sizeof(WCHAR),
+ UnicodeString->MaximumLength/sizeof(WCHAR) ));
+ NlPrint((NL_PACK_VERBOSE,
+ " Buffer: FixedDataEnd:%lx EndOfVar:%lx\n",
+ BufferDescriptor->FixedDataEnd,
+ BufferDescriptor->EndOfVariableData));
+ NlPrint((NL_PACK_VERBOSE,
+ " RunningOffset:0x%lx TruncateSilently: %ld\n",
+ *RunningOffset,
+ TruncateSilently ));
+ }
+ }
+
+
+
+ //
+ // Convert the string to OEM (upper casing Account Names)
+ //
+
+ if ( RelativeId != 0 ) {
+
+ Status = RtlUpcaseUnicodeStringToOemString(
+ &AnsiString,
+ (PUNICODE_STRING) UnicodeString,
+ (BOOLEAN) TRUE );
+ } else {
+
+ Status = RtlUnicodeStringToOemString(
+ &AnsiString,
+ (PUNICODE_STRING) UnicodeString,
+ (BOOLEAN) TRUE );
+
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+ if ( Status != STATUS_UNMAPPABLE_CHARACTER ) {
+ goto Cleanup;
+ }
+
+ NlPrint((NL_CRITICAL,
+ " String contains unmappable character (truncated)\n" ));
+ RtlInitString( &AnsiString, "" );
+
+ if ( TruncateSilently ) {
+ Status = STATUS_SUCCESS;
+ } else {
+ Status = STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ AnsiStringAllocated = TRUE;
+ }
+
+
+ //
+ // Validate the length of the Ansi String.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ if ( AnsiString.Length > (USHORT)MaxStringLength ) {
+ AnsiString.Length = (USHORT)MaxStringLength;
+ AnsiString.Buffer[(USHORT)MaxStringLength] = '\0';
+
+ NlPrint((NL_PACK_VERBOSE," String too long (truncated) '%Z'\n",
+ &AnsiString ));
+
+ if ( TruncateSilently ) {
+ Status = STATUS_SUCCESS;
+ } else {
+ Status = STATUS_INVALID_PARAMETER;
+ }
+ }
+ }
+
+
+ //
+ // Handle an invalid string.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ //
+ // If this name is an account name,
+ // convert the account name to RID#########.
+ //
+ // Non-account names have already been truncated appropriately.
+ //
+
+ if ( RelativeId != 0 ) {
+ NTSTATUS LocalStatus;
+
+ if ( AnsiStringAllocated ) {
+ RtlFreeOemString( &AnsiString );
+ AnsiStringAllocated = FALSE;
+ }
+
+ AnsiString.Length = 12;
+ AnsiString.MaximumLength = 13;
+ AnsiString.Buffer = RidNameBufferAnsi;
+ lstrcpyA( RidNameBufferAnsi, "RID" );
+ LocalStatus =
+ RtlIntegerToChar( RelativeId, 16, (-8), &RidNameBufferAnsi[3]);
+ NlAssert( NT_SUCCESS(LocalStatus) );
+ RidNameBufferAnsi[11] = '$'; // Obfuscate the name
+ RidNameBufferAnsi[12] = '\0'; // Null Terminate it.
+
+ NlPrint((NL_PACK,
+ " Complex account name converted to '%Z'\n",
+ &AnsiString ));
+ }
+
+ }
+
+ //
+ // Ensure the resultant ANSI string fits in the buffer.
+ //
+
+ if ( (LONG)AnsiString.Length >=
+ (LONG)(BufferDescriptor->EndOfVariableData
+ - BufferDescriptor->FixedDataEnd ) ) {
+
+ NlPrint((NL_CRITICAL," String too long for buffer (error)\n" ));
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ //
+ // Copy the string into the buffer.
+ //
+
+ RtlCopyMemory( BufferDescriptor->FixedDataEnd,
+ AnsiString.Buffer,
+ AnsiString.Length + 1 );
+
+ PutUnalignedUshort( StringOffset, *RunningOffset );
+ BufferDescriptor->FixedDataEnd += AnsiString.Length + 1;
+ *RunningOffset += (USHORT) ( AnsiString.Length + 1 );
+
+ NlPrint((NL_PACK_VERBOSE,
+ " NewFixedDataEnd:%lx NewRunningOffset:0x%lx\n",
+ BufferDescriptor->FixedDataEnd,
+ *RunningOffset ));
+
+ // Status has already been set.
+
+ //
+ // Cleanup any locally allocated resources.
+ //
+
+Cleanup:
+ if ( AnsiStringAllocated ) {
+ RtlFreeOemString( &AnsiString );
+ }
+
+ return Status;
+}
+
+
+
+NTSTATUS
+NlPackUasUser (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo,
+ IN PNETLOGON_SESSION_KEY SessionKey,
+ IN LONG RotateCount
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified user into the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the user query.
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+ SessionKey - Session Key to encrypt the password with.
+
+ RotateCount - Number of bits to rotate logon hours by
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE UserHandle;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PUSER_ADD_SET up;
+ USHORT RunningOffset;
+ ULONG TempUlong;
+ LARGE_INTEGER TempTime;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_USER_INFO_BUFFER UserAll = NULL;
+ PSAMPR_GET_GROUPS_BUFFER Groups = NULL;
+ BOOLEAN DaclPresent;
+ PACL UserDacl;
+ BOOLEAN DaclDefaulted;
+ RPC_UNICODE_STRING UasLogonServer;
+
+ //
+ // Variables describes membership in the special groups.
+ //
+
+ DWORD Priv;
+ DWORD AuthFlags;
+ DWORD Flags;
+
+ //
+ // time conversion.
+ //
+ LARGE_INTEGER LocalTime;
+
+
+ //
+ // Initialization.
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+ RtlInitUnicodeString( (PUNICODE_STRING)&UasLogonServer, L"\\\\*" );
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasUser Rid=0x%lx\n", RelativeId));
+
+ //
+ // Open a handle to the specified user.
+ //
+
+ Status = SamrOpenUser( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &UserHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ UserHandle = NULL;
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: SamrOpenUser returns 0x%lx\n",
+ Status ));
+ return Status;
+ }
+
+ //
+ // Find out everything there is to know about the user.
+ //
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserAllInformation,
+ &UserAll );
+
+ if (!NT_SUCCESS(Status)) {
+ UserAll = NULL;
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: SamrQueryInformationUser returns 0x%lx\n",
+ Status ));
+ goto Cleanup;
+ }
+
+
+ //
+ // skip this account if this is a machine account. However add dummy
+ // delta record so that the serial number on the down level BDC is
+ // incremented correctly.
+ //
+
+ if ( UserAll->All.UserAccountControl & USER_MACHINE_ACCOUNT_MASK ) {
+
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasUser skipping machine account '%wZ' \n",
+ &UserAll->All.UserName ));
+
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ BufferDescriptor );
+ Status = STATUS_SUCCESS;
+
+ goto Cleanup;
+ }
+
+
+ //
+ // Determine the Priv and AuthFlags as a function of group membership.
+ //
+ // Determine all the groups this user is a member of
+ //
+
+ Status = SamrGetGroupsForUser( UserHandle,
+ &Groups );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Groups = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: SamGetGroupsForUser returns 0x%lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ Status = NlGetUserPriv(
+ Groups->MembershipCount, // Group Count
+ Groups->Groups, // Array of groups
+ RelativeId,
+ &Priv,
+ &AuthFlags );
+
+ if (!NT_SUCCESS(Status)) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: NlGetUserPriv returns 0x%lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+
+ //
+ // Get a pointer to the UserDacl from the Security Descriptor.
+ //
+
+ Status = RtlGetDaclSecurityDescriptor(
+ (PSECURITY_DESCRIPTOR)
+ UserAll->All.SecurityDescriptor.SecurityDescriptor,
+ &DaclPresent,
+ &UserDacl,
+ &DaclDefaulted );
+
+
+ if ( ! NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: RtlGetDaclSecurityObject returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ if ( !DaclPresent ) {
+ UserDacl = NULL;
+ }
+
+
+ //
+ // Determine the Account control flags
+ // (Don't replicate machine accounts)
+ //
+
+ Flags = NetpAccountControlToFlags(
+ UserAll->All.UserAccountControl,
+ UserDacl );
+
+ if ( Flags & UF_MACHINE_ACCOUNT_MASK ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ }
+
+ Flags &= UF_VALID_LM2X;
+
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = sizeof(USER_ADD_SET);
+ Status = NlPackUasHeader( DELTA_USERADD,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: NlPackUasHeader returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ up = (PUSER_ADD_SET) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += RunningOffset;
+
+
+
+ //
+ // Copy the password (Encrypted with the session key).
+ // The BDC/Member will decrypt it on the other side.
+ //
+ // If an LM compatible password is not available,
+ // just skip this account.
+ //
+ //
+
+ if ( UserAll->All.NtPasswordPresent &&
+ !UserAll->All.LmPasswordPresent ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ }
+
+ if( UserAll->All.LmOwfPassword.Buffer != NULL ) {
+
+ NlAssert( sizeof(LM_OWF_PASSWORD) == UserAll->All.LmOwfPassword.Length);
+
+ Status = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ (PLM_OWF_PASSWORD)UserAll->All.LmOwfPassword.Buffer,
+ (PLM_OWF_PASSWORD)SessionKey,
+ (PENCRYPTED_LM_OWF_PASSWORD) up->uas_password ) ;
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: RtlEncryptLmOwfPwdWithLmOwfPwd returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+ }
+ else {
+
+ //
+ // this account has no LM compatible password.
+ // Encrypt NULL OWF password.
+ //
+
+ LM_OWF_PASSWORD NullLmOwfPassword;
+
+ NlAssert( !UserAll->All.LmPasswordPresent );
+
+ Status = RtlCalculateLmOwfPassword( "", &NullLmOwfPassword );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: RtlCalculateLmOwfPassword returns "
+ "%lX\n", Status ));
+ goto Cleanup;
+ }
+
+ Status = RtlEncryptLmOwfPwdWithLmOwfPwd(
+ &NullLmOwfPassword,
+ (PLM_OWF_PASSWORD)SessionKey,
+ (PENCRYPTED_LM_OWF_PASSWORD) up->uas_password ) ;
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUser: RtlEncryptLmOwfPwdWithLmOwfPwd returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+ }
+
+ //
+ // If the LogonHours are not compatible with downlevel systems,
+ // just skip this account.
+ //
+ // Compatible logon hours are either ALL hours permitted, or
+ // LogonHours are specified in terms of hours per week.
+ //
+ // We could potentially convert other UnitsPerWeek to SAM_HOURS_PER_WEEK
+ // but when we're in UAS compatibility mode, other values should
+ // not occur.
+ //
+
+ if ( UserAll->All.LogonHours.LogonHours == NULL ) {
+
+ DWORD i;
+ for ( i=0; i<sizeof(up->uas_logon_hours); i++ ) {
+ up->uas_logon_hours[i] = 0xFF;
+ }
+
+ } else if ( UserAll->All.LogonHours.UnitsPerWeek ==
+ SAM_HOURS_PER_WEEK ) {
+
+ RtlCopyMemory(up->uas_logon_hours,
+ UserAll->All.LogonHours.LogonHours,
+ SAM_HOURS_PER_WEEK/8 );
+
+ //
+ // Convert from GMT relative to local time relative
+ //
+
+ (VOID) NetpRotateLogonHoursPhase2( up->uas_logon_hours,
+ SAM_HOURS_PER_WEEK,
+ RotateCount );
+
+ } else {
+ DWORD i;
+ Flags |= UF_ACCOUNTDISABLE;
+ for ( i=0; i<sizeof(up->uas_logon_hours); i++ ) {
+ up->uas_logon_hours[i] = 0xFF;
+ }
+ }
+
+
+ //
+ // Fill in the fixed length fields
+ //
+
+ OLD_TO_NEW_LARGE_INTEGER( UserAll->All.PasswordLastSet, TempTime );
+
+ SmbPutUlong( &up->uas_password_age,
+ NetpGetElapsedSeconds( &TempTime ) );
+ SmbPutUshort( &up->uas_priv, (USHORT) Priv );
+
+ SmbPutUlong( &up->uas_auth_flags, AuthFlags );
+
+ OLD_TO_NEW_LARGE_INTEGER( UserAll->All.LastLogon, TempTime );
+
+ if ( NT_SUCCESS(RtlSystemTimeToLocalTime(
+ &TempTime,
+ &LocalTime ))) {
+
+ if ( !RtlTimeToSecondsSince1970( &LocalTime, &TempUlong) ) {
+ TempUlong = 0;
+ }
+ }
+ else {
+ TempUlong = 0;
+ }
+ SmbPutUlong( &up->uas_last_logon, TempUlong );
+
+ OLD_TO_NEW_LARGE_INTEGER( UserAll->All.LastLogoff, TempTime );
+
+ if ( NT_SUCCESS(RtlSystemTimeToLocalTime(
+ &TempTime,
+ &LocalTime ))) {
+
+ if ( !RtlTimeToSecondsSince1970( &LocalTime, &TempUlong) ) {
+ TempUlong = 0;
+ }
+ }
+ else {
+ TempUlong = 0;
+ }
+ SmbPutUlong( &up->uas_last_logoff, TempUlong );
+
+ OLD_TO_NEW_LARGE_INTEGER( UserAll->All.AccountExpires, TempTime );
+
+ if ( NT_SUCCESS(RtlSystemTimeToLocalTime(
+ &TempTime,
+ &LocalTime ))) {
+
+ if ( !RtlTimeToSecondsSince1970( &LocalTime, &TempUlong) ) {
+ TempUlong = TIMEQ_FOREVER;
+ }
+ }
+ else {
+ TempUlong = TIMEQ_FOREVER;
+ }
+ SmbPutUlong( &up->uas_acct_expires, TempUlong );
+
+ SmbPutUlong( &up->uas_max_storage, USER_MAXSTORAGE_UNLIMITED );
+ SmbPutUshort( &up->uas_units_per_week, SAM_HOURS_PER_WEEK );
+
+
+ SmbPutUshort( &up->uas_bad_pw_count, UserAll->All.BadPasswordCount );
+ SmbPutUshort( &up->uas_num_logons, UserAll->All.LogonCount );
+
+
+ SmbPutUshort( &up->uas_country_code, UserAll->All.CountryCode );
+ SmbPutUshort( &up->uas_code_page, UserAll->All.CodePage );
+
+ OLD_TO_NEW_LARGE_INTEGER( UserAll->All.PasswordLastSet, TempTime );
+
+ if ( NT_SUCCESS(RtlSystemTimeToLocalTime(
+ &TempTime,
+ &LocalTime ))) {
+
+ if ( !RtlTimeToSecondsSince1970( &LocalTime, &TempUlong) ) {
+ TempUlong = 0;
+ }
+ }
+ else {
+ TempUlong = 0;
+ }
+ SmbPutUlong( &up->uas_last, TempUlong );
+
+ //
+ // Don't replicate password history. A downlevel BDC will never
+ // be promoted to a PDC.
+ //
+
+ RtlFillMemory( up->uas_old_passwds, sizeof(up->uas_old_passwds), 0xFF );
+
+
+
+ //
+ // Pack the variable length fields at the end of the record and leave
+ // and offset (from the top of this struct) in the struct.
+ //
+
+ Status = NlPackVarLenField( &UserAll->All.UserName,
+ BufferDescriptor,
+ &up->uas_name,
+ &RunningOffset,
+ LM20_UNLEN,
+ FALSE, // NOT OK to truncate
+ RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status == STATUS_INVALID_PARAMETER ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ } else {
+ goto Cleanup;
+ }
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.HomeDirectory,
+ BufferDescriptor,
+ &up->uas_home_dir,
+ &RunningOffset,
+ LM20_PATHLEN,
+ FALSE, // NOT OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status == STATUS_INVALID_PARAMETER ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ } else {
+ goto Cleanup;
+ }
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.AdminComment,
+ BufferDescriptor,
+ &up->uas_comment,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.ScriptPath,
+ BufferDescriptor,
+ &up->uas_script_path,
+ &RunningOffset,
+ LM20_PATHLEN,
+ FALSE, // NOT OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status == STATUS_INVALID_PARAMETER ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ } else {
+ goto Cleanup;
+ }
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.FullName,
+ BufferDescriptor,
+ &up->uas_full_name,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.UserComment,
+ BufferDescriptor,
+ &up->uas_usr_comment,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ Status = NlPackVarLenField( &UserAll->All.Parameters,
+ BufferDescriptor,
+ &up->uas_parms,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ //
+ // Downlevel BDCs expect blank separated workstation list.
+ //
+
+ NetpConvertWorkstationList( (PUNICODE_STRING) &UserAll->All.WorkStations );
+
+ Status = NlPackVarLenField( &UserAll->All.WorkStations,
+ BufferDescriptor,
+ &up->uas_workstations,
+ &RunningOffset,
+ MAXWORKSTATIONS * (LM20_CNLEN+1),
+ FALSE, // NOT OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status == STATUS_INVALID_PARAMETER ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ } else {
+ goto Cleanup;
+ }
+ }
+
+
+ // Copy the LogonServer constant "\\*" into the uas_logon_server field.
+ // SAM doesn't support this field.
+ Status = NlPackVarLenField( &UasLogonServer,
+ BufferDescriptor,
+ &up->uas_logon_server,
+ &RunningOffset,
+ LM20_UNCLEN+1,
+ FALSE, // NOT OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status == STATUS_INVALID_PARAMETER ) {
+ Flags |= UF_ACCOUNTDISABLE;
+ } else {
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Finally, store the flags.
+ //
+ // We may have or'ed in the account disable bit if the account could
+ // not properly be replicated to the downlevel client.
+ //
+ SmbPutUshort( &up->uas_flags, (USHORT) Flags );
+
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+ (VOID) SamrCloseHandle( &UserHandle );
+
+ if ( UserAll != NULL ) {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasUser '%wZ': returns %lX\n",
+ &UserAll->All.UserName,
+ Status ));
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserAll, UserAllInformation );
+ } else {
+ NlPrint((NL_SYNC_MORE, "NlPackUasUser: returns %lX\n", Status ));
+ }
+
+ if ( Groups != NULL ) {
+ SamIFree_SAMPR_GET_GROUPS_BUFFER( Groups );
+ }
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackUasGroup (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ PDB_INFO DBInfo,
+ PDWORD UasBuiltinGroups
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified group into the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the group query.
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle;
+ // SECURITY_INFORMATION SecurityInformation;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PGROUP_ADD_SET up;
+ USHORT RunningOffset;
+ USHORT TempUshort;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_GROUP_INFO_BUFFER GroupGeneral = NULL;
+
+
+ //
+ // Initialization.
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasGroup Rid=0x%lx\n", RelativeId));
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ Status = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &GroupHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroup: SamrOpenGroup returns %lX\n",
+ Status ));
+ return Status;
+ }
+
+ //
+ // Find out everything there is to know about the group.
+ //
+
+ Status = SamrQueryInformationGroup(
+ GroupHandle,
+ GroupGeneralInformation,
+ &GroupGeneral );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupGeneral = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroup: SamrQueryInformationGroup returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = offsetof( GROUP_ADD_SET, gas_groupname ),
+ Status = NlPackUasHeader( DELTA_GROUPADD,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroup: "
+ "NlPackUasHeader returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ up = (PGROUP_ADD_SET) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += RunningOffset;
+
+
+ //
+ // Pack the variable length fields at the end of the record.
+ //
+ // Since the group name is at a well-known offset, just put its
+ // offset in a temporary.
+ //
+
+ Status = NlPackVarLenField( &GroupGeneral->General.Name,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_GNLEN,
+ FALSE, // NOT OK to truncate
+ RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+ }
+
+ Status = NlPackVarLenField( &GroupGeneral->General.AdminComment,
+ BufferDescriptor,
+ &up->gas_comment,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+
+ //
+ // Set UasBuiltinGroup flag if we have packed one of the uas
+ // builtin group.
+ //
+
+ (VOID) SpecialGroupOp((PUNICODE_STRING) &GroupGeneral->General.Name,
+ UasBuiltinGroups );
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+ (VOID) SamrCloseHandle( &GroupHandle );
+
+ if ( GroupGeneral != NULL ) {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasGroup '%wZ': returns %lX\n",
+ &GroupGeneral->General.Name,
+ Status ));
+ SamIFree_SAMPR_GROUP_INFO_BUFFER( GroupGeneral,
+ GroupGeneralInformation );
+ } else {
+ NlPrint((NL_SYNC_MORE, "NlPackUasGroup: returns %lX\n", Status ));
+ }
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackUasBuiltinGroup(
+ IN DWORD Index,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDWORD UasBuiltinGroup
+ )
+/*++
+
+Routine Description:
+
+ Pack the UAS builtin groups (such as admins, users, guests ...) in
+ the given buffer. It uses UasBuiltinGroup flag to determine that the
+ given group is already packed in the buffer.
+
+Arguments:
+
+ Index - index of the built in group.
+
+ UasBuiltinGroup - is a flag that holds already packed groups status.
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ UNICODE_STRING GroupName;
+ UNICODE_STRING GroupComment;
+
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PGROUP_ADD_SET up;
+ USHORT RunningOffset;
+ USHORT TempUshort;
+ DWORD CurrentGroup = 0;
+
+#define UAS_BUILTIN_ADMINS_GROUP_INDEX 0x00
+#define UAS_BUILTIN_USERS_GROUP_INDEX 0x01
+#define UAS_BUILTIN_GUESTS_GROUP_INDEX 0x02
+
+#define UAS_BUILTIN__GROUP_COMMENT L"Uas Builtin group"
+
+ //
+ // Initialization.
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasBuiltinGroup entered\n" ));
+
+ switch( Index ) {
+ case UAS_BUILTIN_ADMINS_GROUP_INDEX:
+
+ if( (*UasBuiltinGroup & UAS_BUILTIN_ADMINS_GROUP) ) {
+ //
+ // this group is already packed so pack a dummy record here
+ // so that the entries returned will show the relatity.
+ //
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ BufferDescriptor );
+
+ goto Cleanup;
+ }
+ RtlInitUnicodeString( &GroupName, UAS_BUILTIN_ADMINS_GROUP_NAME );
+ CurrentGroup = UAS_BUILTIN_ADMINS_GROUP;
+
+ break;
+
+ case UAS_BUILTIN_USERS_GROUP_INDEX:
+
+ if( (*UasBuiltinGroup & UAS_BUILTIN_USERS_GROUP) ) {
+ //
+ // this group is already packed so pack a dummy record here
+ // so that the entries returned will show the relatity.
+ //
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ BufferDescriptor );
+
+ goto Cleanup;
+ }
+ RtlInitUnicodeString( &GroupName, UAS_BUILTIN_USERS_GROUP_NAME );
+ CurrentGroup = UAS_BUILTIN_USERS_GROUP;
+
+ break;
+
+ case UAS_BUILTIN_GUESTS_GROUP_INDEX:
+
+ if( (*UasBuiltinGroup & UAS_BUILTIN_GUESTS_GROUP) ) {
+ //
+ // this group is already packed so pack a dummy record here
+ // so that the entries returned will show the relatity.
+ //
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ BufferDescriptor );
+
+ goto Cleanup;
+ }
+ RtlInitUnicodeString( &GroupName, UAS_BUILTIN_GUESTS_GROUP_NAME);
+ CurrentGroup = UAS_BUILTIN_GUESTS_GROUP;
+
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL,
+ "NlPackUasBuiltinGroup: unexpected index value %lX\n",
+ Index ));
+
+ goto Cleanup;
+ }
+
+ RtlInitUnicodeString( &GroupComment, UAS_BUILTIN__GROUP_COMMENT);
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = offsetof( GROUP_ADD_SET, gas_groupname ),
+ Status = NlPackUasHeader( DELTA_GROUPADD,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ up = (PGROUP_ADD_SET) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += RunningOffset;
+
+
+ //
+ // Pack the variable length fields at the end of the record.
+ //
+ // Since the group name is at a well-known offset, just put its
+ // offset in a temporary.
+ //
+
+ Status = NlPackVarLenField( (RPC_UNICODE_STRING *)&GroupName,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_GNLEN,
+ FALSE, // NOT OK to truncate
+ 0 );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+ }
+
+ Status = NlPackVarLenField( (RPC_UNICODE_STRING *)&GroupComment,
+ BufferDescriptor,
+ &up->gas_comment,
+ &RunningOffset,
+ LM20_MAXCOMMENTSZ,
+ TRUE, // OK to truncate
+ 0 ); // Not an account name
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+ //
+ // set the group bit to indicate we have packed this group.
+ //
+
+ *UasBuiltinGroup |= CurrentGroup;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if( Status != STATUS_SUCCESS ) {
+
+ //
+ // restore buffer if we are unsuccessful
+ //
+
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasBuiltinGroup: returns %lX\n", Status ));
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlPackUasGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the membership of the specified group into
+ the specified buffer.
+
+
+ For these cases we will send names of users which
+ currently members of this group. The receiver will
+ use this list to call NetGroupSetUser to replace
+ existing list for this user (on requestor).
+
+ The format for these packets will be a count field
+ indicating the number of user names to follow the
+ group name which starts immediately after count.
+
+
+Arguments:
+
+ RelativeId - The relative Id of the group query.
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_GROUP_INFO_BUFFER GroupName = NULL;
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+ ULONG i;
+ ULONG UserMemberCount;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PGROUP_USERS up;
+ USHORT RunningOffset;
+ USHORT TempUshort;
+
+ //
+ // Initialization
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasGroupMember Rid=0x%lx\n", RelativeId));
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ Status = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &GroupHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: SamrOpenGroup returns %lX\n",
+ Status ));
+ return Status;
+ }
+
+ //
+ // Find out everything there is to know about the group.
+ //
+
+ Status = SamrQueryInformationGroup(
+ GroupHandle,
+ GroupNameInformation,
+ &GroupName );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupName = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: SamrQueryInformationGroup returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ MembersBuffer = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: SamrGetMembersInGroup returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = offsetof( GROUP_USERS, groupname ),
+ Status = NlPackUasHeader( DELTA_GROUPSETUSERS,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: NlPackUasHeader returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ up = (PGROUP_USERS) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += RunningOffset;
+
+
+ //
+ // Pack the variable length fields at the end of the record.
+ //
+ // Since the group name is at a well-known offset, just put its
+ // offset in a temporary.
+ //
+
+ Status = NlPackVarLenField( &GroupName->Name.Name,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_GNLEN,
+ FALSE, // NOT OK to truncate
+ RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Pack the member names immediately following the Group Name
+ //
+ // Count the number of members which are users.
+ // Downlevel systems only understand members that are users.
+ //
+
+ UserMemberCount = 0;
+
+ for ( i=0; i < MembersBuffer->MemberCount; i++ ) {
+
+ SAMPR_HANDLE UserHandle = NULL;
+ PSAMPR_USER_INFO_BUFFER UserAccount = NULL;
+
+ //
+ // open user account
+ //
+
+ Status = SamrOpenUser( DBInfo->DBHandle,
+ 0,
+ MembersBuffer->Members[i],
+ &UserHandle );
+
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: SamrOpenUser returns 0x%lx\n",
+ Status ));
+
+ goto Cleanup;
+ }
+
+ //
+ // query user information.
+ //
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserAccountInformation,
+ &UserAccount );
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasGroupMember: SamrQueryInformationUser returns 0x%lx\n",
+ Status ));
+
+ (VOID) SamrCloseHandle( &UserHandle );
+ goto Cleanup;
+ }
+
+ //
+ // ignore machine accounts.
+ //
+
+ if ( !(UserAccount->Account.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) ) {
+
+ UserMemberCount ++;
+ Status = NlPackVarLenField( &UserAccount->Account.UserName,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_UNLEN,
+ FALSE, // NOT OK to truncate
+ MembersBuffer->Members[i] );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+
+ (VOID) SamrCloseHandle( &UserHandle );
+
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserAccount,
+ UserAccountInformation );
+
+ goto Cleanup;
+ }
+ }
+ }
+ else {
+
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasGroupMember skipping machine account member '%wZ' \n",
+ &UserAccount->Account.UserName ));
+ }
+
+ //
+ // cleanup user info
+ //
+
+ (VOID) SamrCloseHandle( &UserHandle );
+ UserHandle = NULL;
+
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserAccount,
+ UserAccountInformation );
+ UserAccount = NULL;
+ }
+
+ SmbPutUshort( &up->count, (USHORT) UserMemberCount);
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ if ( GroupName != NULL ) {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasGroupMembers '%wZ': returns %lX\n",
+ &GroupName->Name.Name,
+ Status ));
+ SamIFree_SAMPR_GROUP_INFO_BUFFER( GroupName, GroupNameInformation );
+ } else {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasGroupMembers '%lX': returns %lX\n",
+ RelativeId,
+ Status ));
+ }
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackUasUserGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the group membership of the specified user
+ into the specified buffer.
+
+
+ For these cases we will send names of groups in which currently the
+ user is member. The receiver will use this list to call
+ NetUserSetGroups to replace existing list for this user (on
+ requestor).
+
+ The format for these packets will be a count field indicating the
+ number of user names to follow the user name which starts
+ immediately after count and then the group names.
+
+
+Arguments:
+
+ RelativeId - The relative Id of the group query.
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE UserHandle;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_USER_INFO_BUFFER UserAccount = NULL;
+ PSAMPR_GET_GROUPS_BUFFER GroupsBuffer = NULL;
+ ULONG i;
+ ULONG GroupMemberCount;
+ SAMPR_RETURNED_USTRING_ARRAY NameBuffer;
+ SAMPR_ULONG_ARRAY UseBuffer;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PUSER_GROUPS up;
+ USHORT RunningOffset;
+ USHORT TempUshort;
+
+ //
+ // Initialization
+ //
+
+ NameBuffer.Element = NULL;
+ UseBuffer.Element = NULL;
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ NlPrint((NL_SYNC_MORE, "NlPackUasUserGroupMember Rid=0x%lx\n", RelativeId));
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ Status = SamrOpenUser( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &UserHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ UserHandle = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: SamrOpenUser returns %lX\n",
+ Status ));
+ return Status;
+ }
+
+ //
+ // Find out user name.
+ //
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserAccountInformation,
+ &UserAccount );
+
+ if (!NT_SUCCESS(Status)) {
+ UserAccount = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: SamrQueryInformationUser returns %lX\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ //
+ // skip machine accounts
+ //
+
+ if ( UserAccount->Account.UserAccountControl &
+ USER_MACHINE_ACCOUNT_MASK ) {
+
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasUserGroupMember skipping machine account "
+ "groupmembership : '%wZ' \n",
+ &UserAccount->Account.UserName ));
+
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ BufferDescriptor );
+ Status = STATUS_SUCCESS;
+
+ goto Cleanup;
+ }
+
+
+ Status = SamrGetGroupsForUser( UserHandle, &GroupsBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupsBuffer = NULL;
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: SamrGetGroupsForUsers returns %lX\n", Status ));
+ goto Cleanup;
+ }
+
+ // Sam doesn't like looking up ID if Members is NULL
+ if ( GroupsBuffer->MembershipCount != 0 ) {
+
+ PULONG GroupIDs;
+
+ GroupIDs = (PULONG) MIDL_user_allocate(
+ GroupsBuffer->MembershipCount * sizeof (ULONG) );
+
+ if( GroupIDs == NULL ) {
+ Status = STATUS_NO_MEMORY;
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: out of memory %lX\n", Status ));
+ goto Cleanup;
+ }
+
+
+ for ( i = 0; i < GroupsBuffer->MembershipCount; i++ ) {
+ GroupIDs[i] = GroupsBuffer->Groups[i].RelativeId;
+ }
+
+ Status = SamrLookupIdsInDomain(
+ DBInfo->DBHandle,
+ GroupsBuffer->MembershipCount,
+ GroupIDs,
+ &NameBuffer,
+ &UseBuffer );
+
+ //
+ // freeup local array anyway.
+ //
+
+ MIDL_user_free( GroupIDs);
+
+ if (!NT_SUCCESS(Status)) {
+
+ NameBuffer.Element = NULL;
+ UseBuffer.Element = NULL;
+
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: SamrLookupIdsInDomain returns %lX\n", Status ));
+ goto Cleanup;
+ }
+
+ NlAssert( GroupsBuffer->MembershipCount == UseBuffer.Count );
+ NlAssert( GroupsBuffer->MembershipCount == NameBuffer.Count );
+ }
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = offsetof( USER_GROUPS, username ),
+ Status = NlPackUasHeader( DELTA_USERSETGROUPS,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ NlPrint((NL_CRITICAL,
+ "NlPackUasUserGroupMember: NlPackUasHeader returns %lX\n", Status ));
+ goto Cleanup;
+ }
+
+ up = (PUSER_GROUPS) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += RunningOffset;
+
+
+ //
+ // Pack the variable length fields at the end of the record.
+ //
+ // Since the group name is at a well-known offset, just put its
+ // offset in a temporary.
+ //
+
+ Status = NlPackVarLenField( &UserAccount->Account.UserName,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_UNLEN,
+ FALSE, // NOT OK to truncate
+ RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+
+ }
+
+ //
+ // Pack the member names immediately following the Group Name
+ //
+ // Count the number of groups in which user members.
+ // Downlevel systems only understand groups.
+ //
+
+ GroupMemberCount = 0;
+
+ for ( i=0; i < GroupsBuffer->MembershipCount; i++ ) {
+
+ if ( UseBuffer.Element[i] == SidTypeGroup ) {
+
+ //
+ // ignore special group membership.
+ //
+
+ if( !SpecialGroupOp( (PUNICODE_STRING) &NameBuffer.Element[i], NULL ) ) {
+
+ GroupMemberCount ++;
+ Status = NlPackVarLenField( &NameBuffer.Element[i],
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_UNLEN,
+ FALSE, // NOT OK to truncate
+ GroupsBuffer->Groups[i].RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+
+ }
+ }
+ }
+ }
+
+ SmbPutUshort( &up->count, (USHORT) GroupMemberCount);
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if( UserHandle != NULL ) {
+ (VOID) SamrCloseHandle( &UserHandle );
+ }
+
+ if ( GroupsBuffer != NULL ) {
+ SamIFree_SAMPR_GET_GROUPS_BUFFER( GroupsBuffer );
+ }
+
+ if ( UserAccount != NULL ) {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasUserGroupMembers '%wZ': returns %lX\n",
+ &UserAccount->Account.UserName,
+ Status ));
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserAccount, UserAccountInformation );
+
+ } else {
+ NlPrint((NL_SYNC_MORE,
+ "NlPackUasUserGroupMembers '%lX': returns %lX\n",
+ RelativeId,
+ Status ));
+ }
+
+ SamIFree_SAMPR_RETURNED_USTRING_ARRAY( &NameBuffer );
+ SamIFree_SAMPR_ULONG_ARRAY( &UseBuffer );
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackUasDomain (
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the sam domain into the specified buffer.
+
+Arguments:
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_INVALID_PARAMETER - The specified user has some attribute which
+ prevents it from being replicated to a downlevel client.
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_DOMAIN_INFO_BUFFER DomainLogoff = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainPassword = NULL;
+ LARGE_INTEGER TempTime;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PUSER_MODALS up;
+
+ //
+ // Initialization.
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ //
+ // Find out everything there is to know about the domain.
+ //
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainLogoffInformation,
+ &DomainLogoff );
+
+ if (!NT_SUCCESS(Status)) {
+ DomainLogoff = NULL;
+ goto Cleanup;
+ }
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainPasswordInformation,
+ &DomainPassword );
+
+ if (!NT_SUCCESS(Status)) {
+ DomainPassword = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+ // Fill in the record size too (since this is a fixed length record).
+ //
+
+ Status = NlPackUasHeader( DELTA_USERMODALSSET,
+ sizeof(USER_MODALS),
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ up = (PUSER_MODALS) BufferDescriptor->FixedDataEnd;
+ BufferDescriptor->FixedDataEnd += sizeof(USER_MODALS);
+
+
+ //
+ // Fill in the fixed length fields
+ //
+
+ SmbPutUshort( &up->umod_min_passwd_len,
+ DomainPassword->Password.MinPasswordLength );
+
+
+ OLD_TO_NEW_LARGE_INTEGER( DomainPassword->Password.MaxPasswordAge, TempTime );
+
+ SmbPutUlong(
+ &up->umod_max_passwd_age,
+ NetpDeltaTimeToSeconds( TempTime ));
+
+ OLD_TO_NEW_LARGE_INTEGER( DomainPassword->Password.MinPasswordAge, TempTime );
+
+ SmbPutUlong(
+ &up->umod_min_passwd_age,
+ NetpDeltaTimeToSeconds( TempTime ));
+
+ OLD_TO_NEW_LARGE_INTEGER( DomainLogoff->Logoff.ForceLogoff, TempTime );
+
+ SmbPutUlong( &up->umod_force_logoff,
+ NetpDeltaTimeToSeconds( TempTime ));
+
+
+ // Don't set the password history length greater than lanman's
+ if ( DomainPassword->Password.PasswordHistoryLength > DEF_MAX_PWHIST ) {
+ DomainPassword->Password.PasswordHistoryLength = DEF_MAX_PWHIST;
+ }
+ SmbPutUshort( &up->umod_password_hist_len,
+ DomainPassword->Password.PasswordHistoryLength);
+
+ // NT does do lockout (so the LM 2.1 BDCs in our domain shouldn't either)
+ SmbPutUshort( &up->umod_lockout_count, 0 );
+
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if ( DomainPassword != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainPassword,
+ DomainPasswordInformation );
+ }
+
+ if ( DomainLogoff != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainLogoff,
+ DomainLogoffInformation );
+ }
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+}
+
+
+
+NTSTATUS
+NlPackUasDelete (
+ IN NETLOGON_DELTA_TYPE DeltaType,
+ IN ULONG RelativeId,
+ IN LPWSTR AccountName,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of record deletion into the specified buffer.
+
+Arguments:
+
+ DeltaType - The specific delta type for this deletion.
+
+ RelativeId - The relative Id of the group or user to delete.
+
+ AccountName - The Account Name of the group or user to delete.
+
+ BufferDescriptor - Points to a structure which describes the allocated
+ buffer.
+ This Routine updates EndOfVariableData and FixedDataEnd to reflect the
+ newly packed information.
+
+ DBInfo - Database info describing the SAM database to read from
+
+Return Value:
+
+ STATUS_SUCCESS -- ALL OK
+
+ STATUS_INVALID_PARAMETER - The specified user has some attribute which
+ prevents it from being replicated to a downlevel client.
+
+ STATUS_MORE_ENTRIES - The record does not fit into the buffer.
+
+ STATUS_* - Other status codes.
+
+--*/
+{
+ NTSTATUS Status;
+ BUFFER_DESCRIPTOR OriginalBufferDescriptor;
+ UNICODE_STRING AccountNameString;
+
+ //
+ // Record to pack into buffer.
+ //
+
+ PUSHORT RecordSize; // Pointer to record size field in record header.
+ PUSER_GROUP_DEL up;
+ USHORT RunningOffset;
+ USHORT TempUshort;
+ BYTE UasDeltaType;
+
+ //
+ // Initialization.
+ //
+
+ OriginalBufferDescriptor = *BufferDescriptor;
+
+ //
+ // Pack the header into the return buffer and ensure the fixed length
+ // portion fits.
+ //
+
+ RunningOffset = 0; // There are no fixed length fields in this structure
+
+ if( (DeltaType == DeleteUser) ||
+ (DeltaType == RenameUser) ) {
+
+ UasDeltaType = DELTA_USERDEL;
+ }
+ else {
+
+ UasDeltaType = DELTA_GROUPDEL;
+ }
+
+ Status = NlPackUasHeader(
+ UasDeltaType,
+ RunningOffset,
+ &RecordSize,
+ BufferDescriptor );
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ up = (PUSER_GROUP_DEL) BufferDescriptor->FixedDataEnd;
+
+
+ //
+ // The group/user name to delete is the only field in the structure.
+ //
+ // Since the group/user name is at a well-known offset, just put its
+ // offset in a temporary.
+ //
+
+ RtlInitUnicodeString( &AccountNameString, AccountName );
+
+ Status = NlPackVarLenField( (PRPC_UNICODE_STRING)&AccountNameString,
+ BufferDescriptor,
+ &TempUshort,
+ &RunningOffset,
+ LM20_GNLEN,
+ FALSE, // NOT OK to truncate
+ RelativeId );
+
+ if ( Status != STATUS_SUCCESS ) {
+ if ( Status != STATUS_INVALID_PARAMETER ) {
+ goto Cleanup;
+ }
+ }
+
+
+ //
+ // Put the final record length into the header.
+ //
+
+ PutUnalignedUshort( RecordSize, RunningOffset + NETLOGON_DELTA_HEADER_SIZE);
+
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ //
+ // On error,
+ // return the buffer descriptor to where it was when this routine
+ // started to allow the caller to recover as it wishes.
+ //
+
+ if( Status != STATUS_SUCCESS ) {
+ *BufferDescriptor = OriginalBufferDescriptor;
+ }
+
+ return Status;
+ UNREFERENCED_PARAMETER( DBInfo );
+}
diff --git a/private/net/svcdlls/logonsrv/server/replutil.c b/private/net/svcdlls/logonsrv/server/replutil.c
new file mode 100644
index 000000000..7ab4927bc
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/replutil.c
@@ -0,0 +1,3608 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ replutil.c
+
+Abstract:
+
+ Low level functions for SSI Replication apis
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 22-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+ 04-Apr-1992 (madana)
+ Added support for LSA replication.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <align.h>
+#include <accessp.h> // NetpConvertWorkstationList
+#include <replutil.h> // Local procedure forwards
+#include <lsarepl.h>
+
+
+
+DWORD
+NlCopyUnicodeString (
+ IN PUNICODE_STRING InString,
+ OUT PUNICODE_STRING OutString
+ )
+
+/*++
+
+Routine Description:
+
+ This routine copies the input string to the output. It assumes that
+ the input string is allocated by MIDL_user_allocate() and sets the
+ input string buffer pointer to NULL so that the buffer will be not
+ freed on return.
+
+Arguments:
+
+ InString - Points to the UNICODE string to copy.
+
+ OutString - Points to the UNICODE string which will be updated to point
+ to the input string.
+
+Return Value:
+
+ Return the size of the MIDL buffer.
+
+--*/
+{
+ if ( InString->Length == 0 || InString->Buffer == NULL ) {
+ OutString->Length = 0;
+ OutString->MaximumLength = 0;
+ OutString->Buffer = NULL;
+ } else {
+ OutString->Length = InString->Length;
+ OutString->MaximumLength = InString->Length;
+ OutString->Buffer = InString->Buffer;
+ InString->Buffer = NULL;
+ }
+
+ return( OutString->MaximumLength );
+}
+
+
+DWORD
+NlCopyData(
+ IN LPBYTE *InData,
+ OUT LPBYTE *OutData,
+ DWORD DataLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine copies the input data pointer to output data pointer.
+ It assumes that the input data buffer is allocated by the
+ MIDL_user_allocate() and sets the input buffer buffer pointer to
+ NULL on return so that the data buffer will not be freed by SamIFree
+ rountine.
+
+Arguments:
+
+ InData - Points to input data buffer pointer.
+
+ OutString - Pointer to output data buffer pointer.
+
+ DataLength - Length of input data.
+
+Return Value:
+
+ Return the size of the data copied.
+
+--*/
+{
+ *OutData = *InData;
+ *InData = NULL;
+
+ return(DataLength);
+}
+
+
+VOID
+NlFreeDBDelta(
+ IN PNETLOGON_DELTA_ENUM Delta
+ )
+/*++
+
+Routine Description:
+
+ This routine will free the midl buffers that are allocated for
+ a delta. This routine does nothing but call the midl generated free
+ routine.
+
+Arguments:
+
+ Delta: pointer to the delta structure which has to be freed.
+
+Return Value:
+
+ nothing
+
+--*/
+{
+ if( Delta != NULL ) {
+ _fgs__NETLOGON_DELTA_ENUM (Delta);
+ }
+}
+
+
+VOID
+NlFreeDBDeltaArray(
+ IN PNETLOGON_DELTA_ENUM DeltaArray,
+ IN DWORD ArraySize
+ )
+/*++
+
+Routine Description:
+
+ This routine will free up all delta entries in enum array and the
+ array itself.
+
+Arguments:
+
+ Delta: pointer to the delta structure array.
+
+ ArraySize: num of delta structures in the array.
+
+Return Value:
+
+ nothing
+
+--*/
+{
+ DWORD i;
+
+ if( DeltaArray != NULL ) {
+
+ for( i = 0; i < ArraySize; i++) {
+ NlFreeDBDelta( &DeltaArray[i] );
+ }
+
+ MIDL_user_free( DeltaArray );
+ }
+}
+
+
+
+NTSTATUS
+NlPackSamUser (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified user into the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the user query.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+ SessionInfo: Info describing BDC that's calling us
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE UserHandle = NULL;
+ PNETLOGON_DELTA_USER DeltaUser;
+ PSAMPR_USER_INFO_BUFFER UserAll = NULL;
+
+
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing User Object %lx\n", RelativeId));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeUser;
+ Delta->DeltaID.Rid = RelativeId;
+ Delta->DeltaUnion.DeltaUser = NULL;
+
+ //
+ // Open a handle to the specified user.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenUser( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &UserHandle );
+ STOPSAMTIMER;
+
+
+ if (!NT_SUCCESS(Status)) {
+ UserHandle = NULL;
+ goto Cleanup;
+ }
+
+
+
+ //
+ // Query everything there is to know about this user.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationUser(
+ UserHandle,
+ UserInternal3Information,
+ &UserAll );
+ STOPSAMTIMER;
+
+
+ if (!NT_SUCCESS(Status)) {
+ UserAll = NULL;
+ goto Cleanup;
+ }
+
+
+ NlPrint((NL_SYNC_MORE,
+ "\t User Object name %wZ\n",
+ (PUNICODE_STRING)&UserAll->Internal3.I1.UserName));
+
+#define FIELDS_USED ( USER_ALL_USERNAME | \
+ USER_ALL_FULLNAME | \
+ USER_ALL_USERID | \
+ USER_ALL_PRIMARYGROUPID | \
+ USER_ALL_HOMEDIRECTORY | \
+ USER_ALL_HOMEDIRECTORYDRIVE | \
+ USER_ALL_SCRIPTPATH | \
+ USER_ALL_PROFILEPATH | \
+ USER_ALL_ADMINCOMMENT | \
+ USER_ALL_WORKSTATIONS | \
+ USER_ALL_LOGONHOURS | \
+ USER_ALL_LASTLOGON | \
+ USER_ALL_LASTLOGOFF | \
+ USER_ALL_BADPASSWORDCOUNT | \
+ USER_ALL_LOGONCOUNT | \
+ USER_ALL_PASSWORDLASTSET | \
+ USER_ALL_ACCOUNTEXPIRES | \
+ USER_ALL_USERACCOUNTCONTROL | \
+ USER_ALL_USERCOMMENT | \
+ USER_ALL_COUNTRYCODE | \
+ USER_ALL_CODEPAGE | \
+ USER_ALL_PARAMETERS | \
+ USER_ALL_NTPASSWORDPRESENT | \
+ USER_ALL_LMPASSWORDPRESENT | \
+ USER_ALL_PRIVATEDATA | \
+ USER_ALL_SECURITYDESCRIPTOR )
+
+ NlAssert( (UserAll->Internal3.I1.WhichFields & FIELDS_USED) == FIELDS_USED );
+
+
+
+ //
+ // Allocate a buffer to return to the caller.
+ //
+
+ DeltaUser = (PNETLOGON_DELTA_USER)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_USER) );
+
+ if (DeltaUser == NULL) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaUser, sizeof(NETLOGON_DELTA_USER) );
+
+ Delta->DeltaUnion.DeltaUser = DeltaUser;
+ *BufferSize += sizeof(NETLOGON_DELTA_USER);
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.UserName,
+ &DeltaUser->UserName );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.FullName,
+ &DeltaUser->FullName );
+
+ DeltaUser->UserId = UserAll->Internal3.I1.UserId;
+ DeltaUser->PrimaryGroupId = UserAll->Internal3.I1.PrimaryGroupId;
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.HomeDirectory,
+ &DeltaUser->HomeDirectory );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.HomeDirectoryDrive,
+ &DeltaUser->HomeDirectoryDrive );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.ScriptPath,
+ &DeltaUser->ScriptPath );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.AdminComment,
+ &DeltaUser->AdminComment );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.WorkStations,
+ &DeltaUser->WorkStations );
+
+ DeltaUser->LastLogon = UserAll->Internal3.I1.LastLogon;
+ DeltaUser->LastLogoff = UserAll->Internal3.I1.LastLogoff;
+
+ //
+ // Copy Logon Hours
+ //
+
+ DeltaUser->LogonHours.UnitsPerWeek = UserAll->Internal3.I1.LogonHours.UnitsPerWeek;
+ DeltaUser->LogonHours.LogonHours = UserAll->Internal3.I1.LogonHours.LogonHours;
+ UserAll->Internal3.I1.LogonHours.LogonHours = NULL; // Don't let SAM free this.
+ *BufferSize += (UserAll->Internal3.I1.LogonHours.UnitsPerWeek + 7) / 8;
+
+
+
+ DeltaUser->BadPasswordCount = UserAll->Internal3.I1.BadPasswordCount;
+ DeltaUser->LogonCount = UserAll->Internal3.I1.LogonCount;
+
+ DeltaUser->PasswordLastSet = UserAll->Internal3.I1.PasswordLastSet;
+ DeltaUser->AccountExpires = UserAll->Internal3.I1.AccountExpires;
+
+ //
+ // Don't copy lockout bit to BDC unless it understands it.
+ //
+
+ DeltaUser->UserAccountControl = UserAll->Internal3.I1.UserAccountControl;
+ if ( (SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT) == 0 ){
+ DeltaUser->UserAccountControl &= ~USER_ACCOUNT_AUTO_LOCKED;
+ }
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.UserComment,
+ &DeltaUser->UserComment );
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.Parameters,
+ &DeltaUser->Parameters );
+
+ DeltaUser->CountryCode = UserAll->Internal3.I1.CountryCode;
+ DeltaUser->CodePage = UserAll->Internal3.I1.CodePage;
+
+ //
+ // Set private data.
+ // Includes passwords and password history.
+ //
+
+ DeltaUser->PrivateData.SensitiveData = UserAll->Internal3.I1.PrivateDataSensitive;
+
+ if ( UserAll->Internal3.I1.PrivateDataSensitive ) {
+
+ CRYPT_BUFFER Data;
+
+ //
+ // encrypt private data using session key
+ // Re-use the SAM's buffer and encrypt it in place.
+ //
+
+ Data.Length = Data.MaximumLength = UserAll->Internal3.I1.PrivateData.Length;
+ Data.Buffer = (PUCHAR) UserAll->Internal3.I1.PrivateData.Buffer;
+ UserAll->Internal3.I1.PrivateData.Buffer = NULL;
+
+ Status = NlEncryptSensitiveData( &Data, SessionInfo );
+
+ if( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ DeltaUser->PrivateData.DataLength = Data.Length;
+ DeltaUser->PrivateData.Data = Data.Buffer;
+ } else {
+
+ DeltaUser->PrivateData.DataLength = UserAll->Internal3.I1.PrivateData.Length;
+ DeltaUser->PrivateData.Data = (PUCHAR) UserAll->Internal3.I1.PrivateData.Buffer;
+
+ UserAll->Internal3.I1.PrivateData.Buffer = NULL;
+ }
+
+ { // ?? Macro requires a Local named SecurityDescriptor
+ PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ SecurityDescriptor = &UserAll->Internal3.I1.SecurityDescriptor;
+ DELTA_SECOBJ_INFO(DeltaUser);
+ }
+
+ INIT_PLACE_HOLDER(DeltaUser);
+
+ //
+ // copy profile path in DummyStrings
+ //
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&UserAll->Internal3.I1.ProfilePath,
+ &DeltaUser->DummyString1 );
+
+ //
+ // Copy LastBadPasswordTime to DummyLong1 and DummyLong2.
+ //
+
+ DeltaUser->DummyLong1 = UserAll->Internal3.LastBadPasswordTime.HighPart;
+ DeltaUser->DummyLong2 = UserAll->Internal3.LastBadPasswordTime.LowPart;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+
+Cleanup:
+
+
+ STARTSAMTIMER;
+
+ if( UserHandle != NULL ) {
+ (VOID) SamrCloseHandle( &UserHandle );
+ }
+
+ if ( UserAll != NULL ) {
+ SamIFree_SAMPR_USER_INFO_BUFFER( UserAll, UserInternal3Information );
+ }
+
+ STOPSAMTIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack USER object:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSamGroup (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified group into the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the group query.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle = NULL;
+ PNETLOGON_DELTA_GROUP DeltaGroup;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+ PSAMPR_GROUP_INFO_BUFFER GroupGeneral = NULL;
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Group Object %lx\n", RelativeId ));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeGroup;
+ Delta->DeltaID.Rid = RelativeId;
+ Delta->DeltaUnion.DeltaGroup = NULL;
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &GroupHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+ STOPSAMTIMER;
+
+ QUERY_SAM_SECOBJ_INFO(GroupHandle);
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationGroup(
+ GroupHandle,
+ GroupGeneralInformation,
+ &GroupGeneral );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ GroupGeneral = NULL;
+ goto Cleanup;
+ }
+
+ NlPrint((NL_SYNC_MORE,
+ "\t Group Object name %wZ\n",
+ (PUNICODE_STRING)&GroupGeneral->General.Name ));
+
+ DeltaGroup = (PNETLOGON_DELTA_GROUP)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_GROUP) );
+
+ if( DeltaGroup == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaGroup, sizeof(NETLOGON_DELTA_GROUP) );
+
+ Delta->DeltaUnion.DeltaGroup = DeltaGroup;
+ *BufferSize += sizeof(NETLOGON_DELTA_GROUP);
+
+ *BufferSize = NlCopyUnicodeString(
+ (PUNICODE_STRING)&GroupGeneral->General.Name,
+ &DeltaGroup->Name );
+
+ DeltaGroup->RelativeId = RelativeId;
+ DeltaGroup->Attributes = GroupGeneral->General.Attributes;
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&GroupGeneral->General.AdminComment,
+ &DeltaGroup->AdminComment );
+
+ DeltaGroup->RelativeId = RelativeId;
+ DeltaGroup->Attributes = GroupGeneral->General.Attributes;
+
+ DELTA_SECOBJ_INFO(DeltaGroup);
+ INIT_PLACE_HOLDER(DeltaGroup);
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+ STARTSAMTIMER;
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ if ( SecurityDescriptor != NULL ) {
+ SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if ( GroupGeneral != NULL ) {
+ SamIFree_SAMPR_GROUP_INFO_BUFFER( GroupGeneral,
+ GroupGeneralInformation );
+ }
+
+ STOPSAMTIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack GROUP object:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSamGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the membership of the specified group into
+ the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the group query.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle = NULL;
+ DWORD Size;
+ PNETLOGON_DELTA_GROUP_MEMBER DeltaGroupMember;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing GroupMember Object %lx\n", RelativeId));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = ChangeGroupMembership;
+ Delta->DeltaID.Rid = RelativeId;
+ Delta->DeltaUnion.DeltaGroupMember = NULL;
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &GroupHandle );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Find out everything there is to know about the group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ MembersBuffer = NULL;
+ goto Cleanup;
+ }
+
+ DeltaGroupMember = (PNETLOGON_DELTA_GROUP_MEMBER)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_GROUP_MEMBER) );
+
+ if( DeltaGroupMember == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaGroupMember,
+ sizeof(NETLOGON_DELTA_GROUP_MEMBER) );
+
+ Delta->DeltaUnion.DeltaGroupMember = DeltaGroupMember;
+ *BufferSize += sizeof(NETLOGON_DELTA_GROUP_MEMBER);
+
+ if ( MembersBuffer->MemberCount != 0 ) {
+ Size = MembersBuffer->MemberCount * sizeof(*MembersBuffer->Members);
+
+ *BufferSize += NlCopyData(
+ (LPBYTE *)&MembersBuffer->Members,
+ (LPBYTE *)&DeltaGroupMember->MemberIds,
+ Size );
+
+ Size = MembersBuffer->MemberCount *
+ sizeof(*MembersBuffer->Attributes);
+
+ *BufferSize += NlCopyData(
+ (LPBYTE *)&MembersBuffer->Attributes,
+ (LPBYTE *)&DeltaGroupMember->Attributes,
+ Size );
+ }
+
+ DeltaGroupMember->MemberCount = MembersBuffer->MemberCount;
+
+ //
+ // Initialize placeholder strings to NULL.
+ //
+
+ DeltaGroupMember->DummyLong1 = 0;
+ DeltaGroupMember->DummyLong2 = 0;
+ DeltaGroupMember->DummyLong3 = 0;
+ DeltaGroupMember->DummyLong4 = 0;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+ STARTSAMTIMER;
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ STOPSAMTIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack GROUPMEMBER object:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSamAlias (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the specified alias into the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the alias query.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle = NULL;
+ PNETLOGON_DELTA_ALIAS DeltaAlias;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+
+ PSAMPR_ALIAS_INFO_BUFFER AliasGeneral = NULL;
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Alias Object %lx\n", RelativeId));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeAlias;
+ Delta->DeltaID.Rid = RelativeId;
+ Delta->DeltaUnion.DeltaAlias = NULL;
+
+ //
+ // Open a handle to the specified alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenAlias( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &AliasHandle );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+
+ QUERY_SAM_SECOBJ_INFO(AliasHandle);
+
+ //
+ // Determine the alias name.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationAlias(
+ AliasHandle,
+ AliasGeneralInformation,
+ &AliasGeneral );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ AliasGeneral = NULL;
+ goto Cleanup;
+ }
+
+ NlPrint((NL_SYNC_MORE, "\t Alias Object name %wZ\n",
+ (PUNICODE_STRING)&(AliasGeneral->General.Name)));
+
+ DeltaAlias = (PNETLOGON_DELTA_ALIAS)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ALIAS) );
+
+ if( DeltaAlias == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaAlias, sizeof(NETLOGON_DELTA_ALIAS) );
+
+ Delta->DeltaUnion.DeltaAlias = DeltaAlias;
+ *BufferSize += sizeof(NETLOGON_DELTA_ALIAS);
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&(AliasGeneral->General.Name),
+ &DeltaAlias->Name );
+
+ DeltaAlias->RelativeId = RelativeId;
+
+ DELTA_SECOBJ_INFO(DeltaAlias);
+ INIT_PLACE_HOLDER(DeltaAlias);
+
+ //
+ // copy comment string
+ //
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&(AliasGeneral->General.AdminComment),
+ &DeltaAlias->DummyString1 );
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+ STARTSAMTIMER;
+
+ if( AliasHandle != NULL ) {
+ (VOID) SamrCloseHandle( &AliasHandle );
+ }
+
+ if ( SecurityDescriptor != NULL ) {
+ SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+
+ if( AliasGeneral != NULL ) {
+
+ SamIFree_SAMPR_ALIAS_INFO_BUFFER (
+ AliasGeneral,
+ AliasGeneralInformation );
+ }
+
+ STOPSAMTIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to pack ALIAS object:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSamAliasMember (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the membership of the specified alias into
+ the specified buffer.
+
+Arguments:
+
+ RelativeId - The relative Id of the alias query.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle = NULL;
+ PNETLOGON_DELTA_ALIAS_MEMBER DeltaAliasMember;
+ DWORD i;
+
+ //
+ // Information returned from SAM
+ //
+
+ NLPR_SID_ARRAY Members;
+ PNLPR_SID_INFORMATION Sids;
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing AliasMember Object %lx\n", RelativeId));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = ChangeAliasMembership;
+ Delta->DeltaID.Rid = RelativeId;
+ Delta->DeltaUnion.DeltaAliasMember = NULL;
+
+ Members.Sids = NULL;
+
+
+ //
+ // Open a handle to the specified alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenAlias( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &AliasHandle );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Find out everything there is to know about the alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrGetMembersInAlias( AliasHandle,
+ (PSAMPR_PSID_ARRAY)&Members );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ Members.Sids = NULL;
+ goto Cleanup;
+ }
+
+
+ DeltaAliasMember = (PNETLOGON_DELTA_ALIAS_MEMBER)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ALIAS_MEMBER) );
+
+ if( DeltaAliasMember == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaAliasMember,
+ sizeof(NETLOGON_DELTA_ALIAS_MEMBER) );
+
+ Delta->DeltaUnion.DeltaAliasMember = DeltaAliasMember;
+ *BufferSize += sizeof(NETLOGON_DELTA_ALIAS_MEMBER);
+
+ //
+ // tie up sam return node to our return node
+ //
+
+ DeltaAliasMember->Members = Members;
+
+ //
+ // however, compute the MIDL buffer consumed for members node.
+ //
+
+ for(i = 0, Sids = Members.Sids; i < Members.Count; ++i, Sids++) {
+
+ *BufferSize += (sizeof(PNLPR_SID_INFORMATION) +
+ RtlLengthSid(Sids->SidPointer));
+
+ }
+
+ *BufferSize += sizeof(SAMPR_PSID_ARRAY);
+
+ //
+ // Initialize placeholder strings to NULL.
+ //
+
+ DeltaAliasMember->DummyLong1 = 0;
+ DeltaAliasMember->DummyLong2 = 0;
+ DeltaAliasMember->DummyLong3 = 0;
+ DeltaAliasMember->DummyLong4 = 0;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTSAMTIMER;
+
+ if( AliasHandle != NULL ) {
+ (VOID) SamrCloseHandle( &AliasHandle );
+ }
+
+ if ( Members.Sids != NULL ) {
+
+ //
+ // don't free this node because we have tied up this
+ // node to our return info to RPC which will free it up
+ // when it is done with it.
+ //
+ // however, free this node under error conditions
+ //
+
+ }
+
+ if( !NT_SUCCESS(Status) ) {
+
+ SamIFree_SAMPR_PSID_ARRAY( (PSAMPR_PSID_ARRAY)&Members );
+
+ if( Delta->DeltaUnion.DeltaAliasMember != NULL ) {
+ Delta->DeltaUnion.DeltaAliasMember->Members.Sids = NULL;
+ }
+
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPSAMTIMER;
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Timing for ALIASMEBER object packing:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSamDomain (
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize
+ )
+/*++
+
+Routine Description:
+
+ Pack a description of the sam domain into the specified buffer.
+
+Arguments:
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PNETLOGON_DELTA_DOMAIN DeltaDomain = NULL;
+
+ //
+ // Information returned from SAM
+ //
+
+ PSAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainGeneral = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainPassword = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainModified = NULL;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainLockout = NULL;
+
+ DEFPACKTIMER;
+ DEFSAMTIMER;
+
+ INITPACKTIMER;
+ INITSAMTIMER;
+
+ STARTPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "Packing Domain Object\n"));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = AddOrChangeDomain;
+ Delta->DeltaID.Rid = 0;
+ Delta->DeltaUnion.DeltaDomain = NULL;
+
+
+ QUERY_SAM_SECOBJ_INFO(DBInfo->DBHandle);
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainGeneralInformation,
+ &DomainGeneral );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ DomainGeneral = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // As a side effect, make sure our UAS Compatibility mode is correct.
+ //
+ NlGlobalUasCompatibilityMode =
+ DomainGeneral->General.UasCompatibilityRequired ;
+
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainPasswordInformation,
+ &DomainPassword );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ DomainPassword = NULL;
+ goto Cleanup;
+ }
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainModifiedInformation,
+ &DomainModified );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ DomainModified = NULL;
+ goto Cleanup;
+ }
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainLockoutInformation,
+ &DomainLockout );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ DomainLockout = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Fill in the delta structure
+ //
+
+
+ DeltaDomain = (PNETLOGON_DELTA_DOMAIN)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_DOMAIN) );
+
+ if( DeltaDomain == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // Zero the buffer so that cleanup will not access violate.
+ //
+
+ RtlZeroMemory( DeltaDomain, sizeof(NETLOGON_DELTA_DOMAIN) );
+
+ Delta->DeltaUnion.DeltaDomain = DeltaDomain;
+ *BufferSize += sizeof(NETLOGON_DELTA_DOMAIN);
+
+ *BufferSize += NlCopyUnicodeString(
+ (PUNICODE_STRING)&DomainGeneral->General.DomainName,
+ &DeltaDomain->DomainName );
+
+ *BufferSize = NlCopyUnicodeString(
+ (PUNICODE_STRING)&DomainGeneral->General.OemInformation,
+ &DeltaDomain->OemInformation );
+
+ DeltaDomain->ForceLogoff = DomainGeneral->General.ForceLogoff;
+ DeltaDomain->MinPasswordLength =
+ DomainPassword->Password.MinPasswordLength;
+ DeltaDomain->PasswordHistoryLength =
+ DomainPassword->Password.PasswordHistoryLength;
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ DomainPassword->Password.MaxPasswordAge,
+ DeltaDomain->MaxPasswordAge );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ DomainPassword->Password.MinPasswordAge,
+ DeltaDomain->MinPasswordAge );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ DomainModified->Modified.DomainModifiedCount,
+ DeltaDomain->DomainModifiedCount );
+
+ NEW_TO_OLD_LARGE_INTEGER(
+ DomainModified->Modified.CreationTime,
+ DeltaDomain->DomainCreationTime );
+
+
+ DELTA_SECOBJ_INFO(DeltaDomain);
+ INIT_PLACE_HOLDER(DeltaDomain);
+
+ //
+ // replicate PasswordProperties using reserved field.
+ //
+
+ DeltaDomain->DummyLong1 =
+ DomainPassword->Password.PasswordProperties;
+
+ //
+ // Replicate DOMAIN_LOCKOUT_INFORMATION using reserved field.
+ //
+
+ DeltaDomain->DummyString1.Buffer = (LPWSTR) DomainLockout;
+ DeltaDomain->DummyString1.MaximumLength =
+ DeltaDomain->DummyString1.Length = sizeof( DomainLockout->Lockout);
+ DomainLockout = NULL;
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ STARTSAMTIMER;
+
+ if ( SecurityDescriptor != NULL ) {
+ SamIFree_SAMPR_SR_SECURITY_DESCRIPTOR( SecurityDescriptor );
+ }
+
+ if ( DomainGeneral != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainGeneral,
+ DomainGeneralInformation );
+ }
+
+ if ( DomainPassword != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainPassword,
+ DomainPasswordInformation );
+ }
+
+ if ( DomainModified != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainModified,
+ DomainModifiedInformation );
+ }
+
+ if ( DomainLockout != NULL ) {
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainLockout,
+ DomainLockoutInformation );
+ }
+
+ STOPSAMTIMER;
+
+ if( !NT_SUCCESS(Status) ) {
+ NlFreeDBDelta( Delta );
+ *BufferSize = 0;
+ }
+
+ STOPPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Timing for DOMAIN object packing:\n"));
+ PRINTPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+}
+
+
+
+
+NTSTATUS
+NlUnpackSamUser (
+ IN PNETLOGON_DELTA_USER DeltaUser,
+ IN PDB_INFO DBInfo,
+ OUT PULONG ConflictingRID,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Set the Sam User to look like the specified buffer.
+
+Arguments:
+
+ DeltaUser - Description of the user.
+
+ SessionInfo - Info shared between PDC and BDC
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE UserHandle = NULL;
+ SAMPR_USER_INFO_BUFFER UserInfo;
+#ifdef notdef
+ NT_OWF_PASSWORD NtOwfPassword;
+ LM_OWF_PASSWORD LmOwfPassword;
+#endif // notdef
+
+ PUCHAR PrivateDataAllocated = NULL;
+ LPWSTR WorkstationList = NULL;
+
+ BOOLEAN SetUserNameField = FALSE;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking User Object (%lx) %wZ\n",
+ DeltaUser->UserId,
+ &DeltaUser->UserName ));
+
+ //
+ // Open a handle to the specified user.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamICreateAccountByRid(
+ DBInfo->DBHandle,
+ SamObjectUser,
+ DeltaUser->UserId,
+ (PRPC_UNICODE_STRING) &DeltaUser->UserName,
+ 0, // No desired access
+ &UserHandle,
+ ConflictingRID );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ if( (Status == STATUS_USER_EXISTS) &&
+ (DeltaUser->UserId == *ConflictingRID) ) {
+
+ //
+ // this user account has been renamed.
+ //
+
+ SetUserNameField = TRUE;
+
+ Status = SamrOpenUser(
+ DBInfo->DBHandle,
+ 0,
+ DeltaUser->UserId,
+ &UserHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ UserHandle = NULL;
+ goto Cleanup;
+ }
+ }
+ else {
+
+ NlPrint((NL_SYNC_MORE, "Parameters to SamICreateAccountByRid:\n"
+ "\tDomainHandle = %lx\n"
+ "\tAccountType = %lx\n"
+ "\tRelativeId = %lx\n"
+ "\tAccountName = %wZ\n"
+ "\tDesiredAccess = %lx\n"
+ "\tAccountHandle = %lx\n"
+ "\tConflictingAccountRid = %lx\n",
+ (DWORD)(DBInfo->DBHandle),
+ (DWORD)SamObjectUser,
+ (DWORD)DeltaUser->UserId,
+ &DeltaUser->UserName,
+ 0,
+ (DWORD)&UserHandle,
+ (DWORD)*ConflictingRID ));
+
+ NlPrint((NL_SYNC_MORE, "SamICreateAccountByRid returning %lx\n",
+ Status ));
+
+ UserHandle = NULL;
+ goto Cleanup;
+ }
+ }
+
+
+ //
+ // Set the attributes of the user.
+ //
+ // Notice that the actual text strings remain in the DeltaUser
+ // structure. I only copy a pointer to them to the user specific
+ // structure.
+ //
+ RtlZeroMemory( &UserInfo, sizeof(UserInfo) );
+ UserInfo.Internal3.I1.WhichFields = 0;
+
+ //
+ // if this account is renamed then set user name.
+ //
+
+ if( SetUserNameField ) {
+ UserInfo.Internal3.I1.UserName =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->UserName);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_USERNAME;
+ }
+
+ UserInfo.Internal3.I1.SecurityDescriptor.SecurityDescriptor =
+ ((PSECURITY_DESCRIPTOR) DeltaUser->SecurityDescriptor);
+ UserInfo.Internal3.I1.SecurityDescriptor.Length = DeltaUser->SecuritySize;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_SECURITYDESCRIPTOR;
+
+ UserInfo.Internal3.I1.FullName = * ((PRPC_UNICODE_STRING) &DeltaUser->FullName);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_FULLNAME;
+
+ UserInfo.Internal3.I1.PrimaryGroupId = DeltaUser->PrimaryGroupId;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_PRIMARYGROUPID;
+
+ UserInfo.Internal3.I1.HomeDirectory =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->HomeDirectory);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_HOMEDIRECTORY;
+
+ UserInfo.Internal3.I1.HomeDirectoryDrive =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->HomeDirectoryDrive);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_HOMEDIRECTORYDRIVE;
+
+ UserInfo.Internal3.I1.ScriptPath =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->ScriptPath);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_SCRIPTPATH;
+
+ UserInfo.Internal3.I1.ProfilePath =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->DummyString1);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_PROFILEPATH;
+
+ UserInfo.Internal3.I1.AdminComment =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->AdminComment);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_ADMINCOMMENT;
+
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_WORKSTATIONS;
+
+ //
+ // Don't replicate these. Retain the old values.
+ //
+
+ // UserInfo.Internal3.I1.LastLogon = DeltaUser->LastLogon;
+ // UserInfo.Internal3.I1.LastLogoff = DeltaUser->LastLogoff;
+ // UserInfo.Internal3.I1.LogonCount = DeltaUser->LogonCount;
+
+ UserInfo.Internal3.I1.PasswordLastSet = DeltaUser->PasswordLastSet;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_PASSWORDLASTSET;
+
+ UserInfo.Internal3.I1.LogonHours.UnitsPerWeek = DeltaUser->LogonHours.UnitsPerWeek;
+ UserInfo.Internal3.I1.LogonHours.LogonHours = DeltaUser->LogonHours.LogonHours;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_LOGONHOURS;
+
+ UserInfo.Internal3.I1.AccountExpires = DeltaUser->AccountExpires;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_ACCOUNTEXPIRES;
+
+ UserInfo.Internal3.I1.UserAccountControl = DeltaUser->UserAccountControl;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_USERACCOUNTCONTROL;
+
+ UserInfo.Internal3.I1.UserComment = *((PRPC_UNICODE_STRING) &DeltaUser->UserComment);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_USERCOMMENT;
+
+ UserInfo.Internal3.I1.CountryCode = DeltaUser->CountryCode;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_COUNTRYCODE;
+
+ UserInfo.Internal3.I1.CodePage = DeltaUser->CodePage;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_CODEPAGE;
+
+ UserInfo.Internal3.I1.Parameters = * ((PRPC_UNICODE_STRING) &DeltaUser->Parameters);
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_PARAMETERS;
+
+ //
+ // Set workstation list information.
+ //
+ UserInfo.Internal3.I1.WorkStations =
+ * ((PRPC_UNICODE_STRING) &DeltaUser->WorkStations);
+
+
+
+ //
+ // Set private data
+ // Includes passwords and password history.
+ //
+
+ if( DeltaUser->PrivateData.SensitiveData ) {
+
+ CRYPT_BUFFER EncryptedData;
+ CRYPT_BUFFER Data;
+
+ //
+ // need to decrypt the private data
+ //
+
+ EncryptedData.Length =
+ EncryptedData.MaximumLength =
+ DeltaUser->PrivateData.DataLength;
+
+ EncryptedData.Buffer = DeltaUser->PrivateData.Data;
+
+
+ Status = NlDecryptSensitiveData(
+ &EncryptedData,
+ &Data,
+ SessionInfo );
+
+ if( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+ PrivateDataAllocated = Data.Buffer;
+ UserInfo.Internal3.I1.PrivateData.Buffer = (WCHAR *) Data.Buffer;
+ UserInfo.Internal3.I1.PrivateData.Length = (USHORT) Data.Length;
+
+
+ } else {
+
+ UserInfo.Internal3.I1.PrivateData.Buffer = (WCHAR *) DeltaUser->PrivateData.Data;
+ UserInfo.Internal3.I1.PrivateData.Length = (USHORT)
+ DeltaUser->PrivateData.DataLength;
+ }
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_PRIVATEDATA;
+
+ //
+ // Copy LastBadPasswordTime from DummyLong1 and DummyLong2.
+ //
+
+ UserInfo.Internal3.I1.BadPasswordCount = DeltaUser->BadPasswordCount;
+ UserInfo.Internal3.LastBadPasswordTime.HighPart = DeltaUser->DummyLong1;
+ UserInfo.Internal3.LastBadPasswordTime.LowPart = DeltaUser->DummyLong2;
+ UserInfo.Internal3.I1.WhichFields |= USER_ALL_BADPASSWORDCOUNT;
+
+
+ //
+ // Finally, set the data in SAM
+ //
+
+ for (;;) {
+
+ NTSTATUS TempStatus;
+ SAMPR_HANDLE GroupHandle;
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationUser(
+ UserHandle,
+ UserInternal3Information,
+ &UserInfo );
+
+ STOPSAMTIMER;
+
+ //
+ // If the User isn't a member of his primary group,
+ // make him one.
+ //
+
+ if ( Status == STATUS_MEMBER_NOT_IN_GROUP ) {
+
+ NlPrint((NL_CRITICAL,
+ "User (%lx) %wZ: User isn't member of his primary group (%lx) -- recover.\n",
+ DeltaUser->UserId,
+ &DeltaUser->UserName,
+ UserInfo.Internal3.I1.PrimaryGroupId ));
+
+ //
+ // Open the user's primary group.
+ //
+
+
+ STARTSAMTIMER;
+ TempStatus = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ UserInfo.Internal3.I1.PrimaryGroupId,
+ &GroupHandle );
+ STOPSAMTIMER;
+
+
+ if ( !NT_SUCCESS(TempStatus)) {
+
+ NlPrint((NL_CRITICAL,
+ "User (%lx) %wZ: Failed to open user's primary group %lx -- Status = 0x%lx.\n",
+ DeltaUser->UserId,
+ &DeltaUser->UserName,
+ UserInfo.Internal3.I1.PrimaryGroupId,
+ TempStatus ));
+
+ goto Cleanup;
+ }
+
+ //
+ // Add this user as a member of the group.
+ //
+
+ STARTSAMTIMER;
+ TempStatus = SamrAddMemberToGroup( GroupHandle,
+ DeltaUser->UserId,
+ SE_GROUP_MANDATORY |
+ SE_GROUP_ENABLED_BY_DEFAULT |
+ SE_GROUP_ENABLED );
+
+ SamrCloseHandle( &GroupHandle );
+ STOPSAMTIMER;
+
+ if ( !NT_SUCCESS(TempStatus) ) {
+ NlPrint((NL_CRITICAL,
+ "User (%lx) %wZ: Failed to make user member of primary group %lx -- Status = 0x%lx.\n",
+ DeltaUser->UserId,
+ &DeltaUser->UserName,
+ UserInfo.Internal3.I1.PrimaryGroupId,
+ TempStatus ));
+ goto Cleanup;
+ }
+
+ continue;
+
+ }
+
+ //
+ // No other conditions are handled.
+ //
+
+ break;
+
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+
+
+ Status = STATUS_SUCCESS;
+
+
+ //
+ // All Done
+ //
+
+Cleanup:
+
+
+ if ( UserHandle != NULL ) {
+ STARTSAMTIMER;
+ SamrCloseHandle( &UserHandle );
+ STOPSAMTIMER;
+ }
+
+
+ if( PrivateDataAllocated != NULL ) {
+ MIDL_user_free( PrivateDataAllocated );
+ }
+
+ if ( WorkstationList != NULL ) {
+ RtlFreeHeap( RtlProcessHeap(), 0, WorkstationList );
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack USER object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlDeleteSamUser(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Delete an user object.
+
+Arguments:
+
+ DomainHandle - handle of the SAM domain.
+
+ Rid - Rid of the object to be deleted.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE UserHandle;
+
+ NlPrint((NL_SYNC_MORE, "Delete User Object %lx\n", Rid));
+
+ Status = SamrOpenUser( DomainHandle, 0, Rid, &UserHandle );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = SamrDeleteUser( &UserHandle );
+ }
+ else if ( Status == STATUS_NO_SUCH_USER ) {
+ Status = STATUS_SUCCESS;
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL, "Unable to delete User Object %lx\n", Rid));
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlDeleteSamGroup(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Delete an group object.
+
+Arguments:
+
+ DomainHandle - handle of the SAM domain.
+
+ Rid - Rid of the object to be deleted.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle;
+
+ NlPrint((NL_SYNC_MORE, "Delete Group Object %lx\n", Rid));
+
+ Status = SamrOpenGroup( DomainHandle, 0, Rid, &GroupHandle );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ Status = SamrDeleteGroup( &GroupHandle );
+
+ if ( Status == STATUS_MEMBER_IN_GROUP ) {
+
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+ DWORD i;
+
+ NlPrint((NL_SYNC_MORE, "Deleting Group Members before "
+ "deleting Group Object \n"));
+
+ //
+ // if we are unable to delete this group because we
+ // still have members in this group then remove members
+ // first and delete it again.
+ //
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ NlAssert( MembersBuffer->MemberCount != 0 );
+
+ for( i = 0; i < MembersBuffer->MemberCount; i++) {
+
+ NlPrint((NL_SYNC_MORE, "Deleting Group Member %lx\n",
+ MembersBuffer->Members[i] ));
+
+ Status = SamrRemoveMemberFromGroup(
+ GroupHandle,
+ MembersBuffer->Members[i] );
+
+ if ( !NT_SUCCESS(Status) ) {
+ break;
+ }
+ }
+ }
+ else {
+
+ MembersBuffer = NULL;
+ }
+
+ if ( NT_SUCCESS(Status) ) {
+
+ //
+ // since we have deleted all members successfully,
+ // delete the group now.
+ //
+
+ Status = SamrDeleteGroup( &GroupHandle );
+ }
+
+ //
+ // free up the sam resource consumed here.
+ //
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ }
+ }
+ else if ( Status == STATUS_NO_SUCH_GROUP ) {
+ Status = STATUS_SUCCESS;
+ }
+
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL, "Unable to delete Group Object %lx\n", Rid));
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlDeleteSamAlias(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ )
+/*++
+
+Routine Description:
+
+ Delete an alias object.
+
+Arguments:
+
+ DomainHandle - handle of the SAM domain.
+
+ Rid - Rid of the object to be deleted.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle;
+
+ NlPrint((NL_SYNC_MORE, "Delete Alias Object %lx\n", Rid));
+
+ Status = SamrOpenAlias( DomainHandle, 0, Rid, &AliasHandle );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ Status = SamrDeleteAlias( &AliasHandle );
+
+ if ( Status == STATUS_MEMBER_IN_ALIAS ) {
+
+ SAMPR_PSID_ARRAY Members = {0, NULL};
+ DWORD i;
+
+ NlPrint((NL_SYNC_MORE, "Deleting Alias Members before "
+ "deleting Alias Object \n"));
+
+ //
+ // if we are unable to delete this alias because we
+ // still have members in this alias then remove members
+ // first and delete it again.
+ //
+
+ Status = SamrGetMembersInAlias( AliasHandle, &Members );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ NlAssert( Members.Count != 0 );
+
+ for( i = 0; i < Members.Count; i++) {
+
+ NlPrint((NL_SYNC_MORE, "Deleting Alias Member: " ));
+ NlpDumpSid( NL_SYNC_MORE, Members.Sids[i].SidPointer );
+
+ Status = SamrRemoveMemberFromAlias(
+ AliasHandle,
+ Members.Sids[i].SidPointer );
+
+ if ( !NT_SUCCESS(Status) ) {
+ break;
+ }
+ }
+ }
+ else {
+
+ Members.Sids = NULL;
+ }
+
+ if ( NT_SUCCESS(Status) ) {
+
+ //
+ // since we have deleted all members successfully,
+ // delete the alias now.
+ //
+
+ Status = SamrDeleteAlias( &AliasHandle );
+ }
+
+ //
+ // free up the sam resource consumed here.
+ //
+
+ if ( Members.Sids != NULL ) {
+ SamIFree_SAMPR_PSID_ARRAY( (PSAMPR_PSID_ARRAY)&Members );
+ }
+
+ }
+ }
+ else if ( Status == STATUS_NO_SUCH_ALIAS ) {
+ Status = STATUS_SUCCESS;
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL, "Unable to delete Alias Object %lx\n", Rid));
+ }
+
+ return(Status);
+}
+
+
+NTSTATUS
+NlUnpackSamGroup (
+ IN PNETLOGON_DELTA_GROUP DeltaGroup,
+ IN PDB_INFO DBInfo,
+ OUT PULONG ConflictingRID
+ )
+/*++
+
+Routine Description:
+
+ Set the Sam Group to look like the specified buffer.
+
+Arguments:
+
+ DeltaGroup - Description of the group.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle = NULL;
+ SAMPR_GROUP_INFO_BUFFER GroupInfo;
+ SAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ BOOLEAN SetGroupNameField = FALSE;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Group Object (%lx) %wZ\n",
+ DeltaGroup->RelativeId,
+ &DeltaGroup->Name ));
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamICreateAccountByRid(
+ DBInfo->DBHandle,
+ SamObjectGroup,
+ DeltaGroup->RelativeId,
+ (PRPC_UNICODE_STRING) &DeltaGroup->Name,
+ 0, // No desired access
+ &GroupHandle,
+ ConflictingRID );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ if( (Status == STATUS_GROUP_EXISTS) &&
+ (DeltaGroup->RelativeId == *ConflictingRID) ) {
+
+ //
+ // this group account has been renamed.
+ //
+
+ SetGroupNameField = TRUE;
+
+ Status = SamrOpenGroup(
+ DBInfo->DBHandle,
+ 0,
+ DeltaGroup->RelativeId,
+ &GroupHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+ }
+ else {
+
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+ }
+
+ SET_SAM_SECOBJ_INFO(DeltaGroup, GroupHandle);
+
+ //
+ // Set the other attributes of the group.
+ //
+ // Notice that the actual text strings remain in the DeltaGroup
+ // structure. I only copy a pointer to them to the group specific
+ // structure.
+ //
+
+ //
+ // if this account is renamed, then set new group name.
+ //
+
+ if ( SetGroupNameField ) {
+
+ GroupInfo.Name.Name =
+ * (PRPC_UNICODE_STRING) &DeltaGroup->Name;
+
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationGroup(
+ GroupHandle,
+ GroupNameInformation,
+ &GroupInfo );
+
+ STOPSAMTIMER;
+ }
+
+ GroupInfo.Attribute.Attributes = DeltaGroup->Attributes;
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationGroup(
+ GroupHandle,
+ GroupAttributeInformation,
+ &GroupInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+
+ GroupInfo.AdminComment.AdminComment =
+ * ((PRPC_UNICODE_STRING) &DeltaGroup->AdminComment);
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationGroup(
+ GroupHandle,
+ GroupAdminCommentInformation,
+ &GroupInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // All Done
+ //
+
+Cleanup:
+
+ if ( GroupHandle != NULL ) {
+
+ STARTSAMTIMER;
+
+ SamrCloseHandle( &GroupHandle );
+
+ STOPSAMTIMER;
+
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack GROUP object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlUnpackSamGroupMember (
+ IN ULONG RelativeId,
+ IN PNETLOGON_DELTA_GROUP_MEMBER DeltaGroupMember,
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Set the Sam Group membership to look like the specified buffer.
+
+Arguments:
+
+ RelativeId - Relative Id of the group to open.
+
+ DeltaGroup - Description of the group membership.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE GroupHandle = NULL;
+
+ ULONG i;
+ ULONG j;
+
+ //
+ // Info returned from SAM.
+ //
+
+ PSAMPR_GET_MEMBERS_BUFFER OldMembersBuffer = NULL;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking GroupMember Object %lx\n", RelativeId));
+
+ //
+ // Open a handle to the specified group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenGroup( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &GroupHandle );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Determine the current membership of the group.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrGetMembersInGroup( GroupHandle, &OldMembersBuffer );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ OldMembersBuffer = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // For each new member,
+ // If the member doesn't currently exist, add it.
+ // If the member exists but the attributes are wrong, change them.
+ //
+
+ for ( i=0; i<DeltaGroupMember->MemberCount; i++ ) {
+ BOOL MemberAlreadyExists;
+
+ //
+ // Check if this new member is already a member.
+ //
+
+ MemberAlreadyExists = FALSE;
+ for ( j=0; j<OldMembersBuffer->MemberCount; j++ ) {
+ if ( OldMembersBuffer->Members[j] == DeltaGroupMember->MemberIds[i] ) {
+ MemberAlreadyExists = TRUE;
+ OldMembersBuffer->Members[j] = 0; // Mark that we've used this entry
+ break;
+ }
+ }
+
+ //
+ // If the membership is not there already,
+ // add the new membership.
+ //
+
+ if ( !MemberAlreadyExists ) {
+ STARTSAMTIMER;
+
+ Status = SamrAddMemberToGroup( GroupHandle,
+ DeltaGroupMember->MemberIds[i],
+ DeltaGroupMember->Attributes[i] );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // if this member is not created yet on the backup then
+ // ignore the error STATUS_NO_SUCH_USER, this user
+ // will be added to this group automatically when his
+ // account is created.
+ //
+ // We could let the redo log handle this problem, but that'll
+ // log an error to the event log.
+ //
+
+ if( Status != STATUS_NO_SUCH_USER ) {
+ goto Cleanup;
+ }
+ }
+
+ //
+ // If membership attributes are different,
+ // set the new attributes.
+ //
+
+ } else {
+
+ if ( DeltaGroupMember->Attributes[i] !=
+ OldMembersBuffer->Attributes[j] ) {
+
+ STARTSAMTIMER;
+
+ Status = SamrSetMemberAttributesOfGroup(
+ GroupHandle,
+ DeltaGroupMember->MemberIds[i],
+ DeltaGroupMember->Attributes[i] );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+
+ }
+
+ }
+
+ //
+ // Loop through the list of old members, deleting those that should
+ // no longer exist.
+ //
+
+ for ( j=0; j<OldMembersBuffer->MemberCount; j++ ) {
+ if ( OldMembersBuffer->Members[j] != 0 ) {
+
+ STARTSAMTIMER;
+
+ Status = SamrRemoveMemberFromGroup(
+ GroupHandle,
+ OldMembersBuffer->Members[j]);
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // if this is the member's primary group then the
+ // user does exist on the primary, this membership
+ // should goaway when we do cleanup.
+ //
+
+ if( Status != STATUS_MEMBERS_PRIMARY_GROUP ) {
+
+ goto Cleanup;
+ }
+ }
+ }
+ }
+
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // All Done
+ //
+
+Cleanup:
+
+ //
+ // Free up any locally used resources.
+ //
+
+ STARTSAMTIMER;
+
+ if ( OldMembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( OldMembersBuffer );
+ }
+
+ if ( GroupHandle != NULL ) {
+ SamrCloseHandle( &GroupHandle );
+ }
+
+ STOPSAMTIMER;
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack GROUP MEMBER object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlUnpackSamAlias (
+ IN PNETLOGON_DELTA_ALIAS DeltaAlias,
+ IN PDB_INFO DBInfo,
+ OUT PULONG ConflictingRID
+ )
+/*++
+
+Routine Description:
+
+ Set the Sam Alias to look like the specified buffer.
+
+Arguments:
+
+ DeltaAlias - Description of the alias.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle = NULL;
+
+ SAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ SAMPR_ALIAS_INFO_BUFFER AliasInfo;
+ BOOLEAN SetAliasNameField = FALSE;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Alias Object (%lx) %wZ\n",
+ DeltaAlias->RelativeId,
+ &DeltaAlias->Name
+ ));
+
+ //
+ // Open a handle to the specified alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamICreateAccountByRid(
+ DBInfo->DBHandle,
+ SamObjectAlias,
+ DeltaAlias->RelativeId,
+ (PRPC_UNICODE_STRING) &DeltaAlias->Name,
+ 0, // No desired access
+ &AliasHandle,
+ ConflictingRID );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+
+ if( (Status == STATUS_ALIAS_EXISTS) &&
+ (DeltaAlias->RelativeId == *ConflictingRID) ) {
+
+ //
+ // this group account has been renamed.
+ //
+
+ SetAliasNameField = TRUE;
+
+ Status = SamrOpenAlias(
+ DBInfo->DBHandle,
+ 0,
+ DeltaAlias->RelativeId,
+ &AliasHandle );
+
+ if (!NT_SUCCESS(Status)) {
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+ }
+ else {
+
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+ }
+
+ SET_SAM_SECOBJ_INFO(DeltaAlias, AliasHandle);
+
+ //
+ // set alias name if it has been renamed.
+ //
+
+ if ( SetAliasNameField ) {
+
+ AliasInfo.Name.Name =
+ * (PRPC_UNICODE_STRING) &DeltaAlias->Name;
+
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationAlias(
+ AliasHandle,
+ AliasNameInformation,
+ &AliasInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+
+ AliasInfo.AdminComment.AdminComment =
+ * (PRPC_UNICODE_STRING) &DeltaAlias->DummyString1;
+
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationAlias(
+ AliasHandle,
+ AliasAdminCommentInformation,
+ &AliasInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // All Done
+ //
+
+Cleanup:
+
+ if ( AliasHandle != NULL ) {
+
+ STARTSAMTIMER;
+
+ SamrCloseHandle( &AliasHandle );
+
+ STOPSAMTIMER;
+
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack ALIAS object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlUnpackSamAliasMember (
+ IN ULONG RelativeId,
+ IN PNETLOGON_DELTA_ALIAS_MEMBER DeltaAliasMember,
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Set the Sam Alias membership to look like the specified buffer.
+
+Arguments:
+
+ RelativeId - Relative Id of the alias to open.
+
+ DeltaAlias - Description of the alias membership.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ SAMPR_HANDLE AliasHandle = NULL;
+
+ PNLPR_SID_INFORMATION DeltaSid;
+ PSAMPR_SID_INFORMATION MemberSid;
+
+ ULONG i;
+ ULONG j;
+
+ PBOOL MemberFound = NULL;
+
+ //
+ // Info returned from SAM.
+ //
+
+ SAMPR_PSID_ARRAY Members;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking AliasMember Object %lx\n", RelativeId));
+
+ Members.Sids = NULL;
+
+ //
+ // Open a handle to the specified alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrOpenAlias( DBInfo->DBHandle,
+ 0, // No desired access
+ RelativeId,
+ &AliasHandle );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ AliasHandle = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Determine the current membership of the alias.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrGetMembersInAlias( AliasHandle, &Members );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ Members.Sids = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Setup MemberFound Array
+ //
+
+ if( Members.Count != 0) {
+
+ MemberFound = (PBOOL) NetpMemoryAllocate(
+ (sizeof(BOOL)) * Members.Count );
+
+ if( MemberFound == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ for ( j=0; j<Members.Count; j++ ) {
+
+ MemberFound[j] = FALSE;
+
+ }
+ }
+
+ //
+ // For each new member,
+ // If the member doesn't currently exist, add it.
+ // If the member exists but the attributes are wrong, change them.
+ //
+
+ DeltaSid = DeltaAliasMember->Members.Sids;
+
+ for ( i=0; i<DeltaAliasMember->Members.Count; i++, DeltaSid++ ) {
+
+ BOOL MemberAlreadyExists = FALSE;
+
+ //
+ // Check if this new member is already a member.
+ //
+
+ //
+ // first member sid pointer
+ //
+
+ MemberSid = Members.Sids;
+
+ for ( j=0; j<Members.Count; j++, MemberSid++ ) {
+
+
+ if ( RtlEqualSid( DeltaSid->SidPointer,
+ MemberSid->SidPointer ) ) {
+
+ MemberAlreadyExists = TRUE;
+ MemberFound[j] = TRUE; // Mark that we've used this entry
+
+ break;
+ }
+
+ }
+
+ //
+ // If the membership is not there already,
+ // add the new membership.
+ //
+
+ if ( !MemberAlreadyExists ) {
+
+ STARTSAMTIMER;
+
+ Status = SamrAddMemberToAlias(
+ AliasHandle,
+ (PRPC_SID)((*DeltaSid).SidPointer));
+
+ STOPSAMTIMER;
+
+
+ //
+ // If the newly added member doesn't exist,
+ // ignore the error.
+ // (Either this is a deleted user, and all is OK)
+ // (OR this is a newly added member and partial replication
+ // will pick it up.)
+ //
+ //
+ // However, DON'T IGNORE the above errors for BUILDIN
+ // database due to the following reason.
+ //
+ // During the initial database replication (or forced
+ // fullsync replication of all three databases) the ACCOUNT
+ // database is replicated first then the BUILTIN
+ // database. For some reason the ACCOUNT database
+ // fullsync replication fails, the fullsync replication
+ // of the BUILTIN database returns this error
+ // (STATUS_INVALID_MEMBER or STATUS_NO_SUCH_ERROR)
+ // because the builtin local group may contain the SID
+ // of the account database user/group which is not
+ // replicated successfully. So if we ignore this error,
+ // the builtin group membership is left incomplete.
+ //
+
+ if (!NT_SUCCESS(Status) &&
+ (( DBInfo->DBIndex == BUILTIN_DB) ||
+ ( (Status != STATUS_INVALID_MEMBER) &&
+ (Status != STATUS_NO_SUCH_MEMBER)) )) {
+
+ goto Cleanup;
+ }
+
+ }
+
+ }
+
+ //
+ // Loop through the list of old members, deleting those that should
+ // no longer exist.
+ //
+
+ MemberSid = Members.Sids;
+
+ for ( j=0; j<Members.Count; j++, MemberSid++ ) {
+
+ if ( MemberFound[j] == FALSE ) {
+
+ STARTSAMTIMER;
+
+ Status = SamrRemoveMemberFromAlias( AliasHandle,
+ MemberSid->SidPointer );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+ }
+
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // All Done
+ //
+
+Cleanup:
+
+ //
+ // Free up any locally used resources.
+ //
+
+ STARTSAMTIMER;
+
+ if ( Members.Sids != NULL ) {
+ SamIFree_SAMPR_PSID_ARRAY( &Members );
+ }
+
+ if ( AliasHandle != NULL ) {
+ SamrCloseHandle( &AliasHandle );
+ }
+
+ STOPSAMTIMER;
+
+ if( MemberFound != NULL ) {
+
+ NetpMemoryFree( MemberFound );
+
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack ALIASMEMBER object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlUnpackSamDomain (
+ IN PNETLOGON_DELTA_DOMAIN DeltaDomain,
+ IN PDB_INFO DBInfo
+ )
+/*++
+
+Routine Description:
+
+ Set the SamDomain to look like the specified buffer.
+
+Arguments:
+
+ DeltaDomain - Description of the domain.
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // Information as passed to SAM
+ //
+
+ SAMPR_DOMAIN_INFO_BUFFER DomainInfo;
+ SAMPR_SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+
+ PSAMPR_DOMAIN_INFO_BUFFER QueryDomainInfoBuf = NULL;
+
+ DEFUNPACKTIMER;
+ DEFSAMTIMER;
+
+ INITUNPACKTIMER;
+ INITSAMTIMER;
+
+ STARTUNPACKTIMER;
+
+ NlPrint((NL_SYNC_MORE, "UnPacking Domain Object\n"));
+
+ SET_SAM_SECOBJ_INFO(DeltaDomain, DBInfo->DBHandle);
+
+ //
+ // Set the other attributes of the domain.
+ //
+
+ //
+ // We can't set the domain name information, however we can compare
+ // the name information on the database with the one we received
+ // in the delta. If they don't match we return appropriate error.
+ //
+
+ STARTSAMTIMER;
+
+ Status = SamrQueryInformationDomain(
+ DBInfo->DBHandle,
+ DomainNameInformation,
+ &QueryDomainInfoBuf );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // compare name info.
+ //
+
+ if( !RtlEqualDomainName(
+ &DeltaDomain->DomainName,
+ (PUNICODE_STRING)&QueryDomainInfoBuf->Name.DomainName) ) {
+
+ Status = STATUS_NO_SUCH_DOMAIN; // ?? check status code.
+ goto Cleanup;
+ }
+
+ DomainInfo.Oem.OemInformation =
+ * ((PRPC_UNICODE_STRING) &DeltaDomain->OemInformation);
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationDomain(
+ DBInfo->DBHandle,
+ DomainOemInformation,
+ &DomainInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaDomain->ForceLogoff,
+ DomainInfo.Logoff.ForceLogoff );
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationDomain(
+ DBInfo->DBHandle,
+ DomainLogoffInformation,
+ &DomainInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ DomainInfo.Password.MinPasswordLength = DeltaDomain->MinPasswordLength;
+ DomainInfo.Password.PasswordHistoryLength = DeltaDomain->PasswordHistoryLength;
+ DomainInfo.Password.PasswordProperties = DeltaDomain->DummyLong1;
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaDomain->MaxPasswordAge,
+ DomainInfo.Password.MaxPasswordAge );
+
+ OLD_TO_NEW_LARGE_INTEGER(
+ DeltaDomain->MinPasswordAge,
+ DomainInfo.Password.MinPasswordAge );
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationDomain(
+ DBInfo->DBHandle,
+ DomainPasswordInformation,
+ &DomainInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ //
+ // If the PDC passed us lockout information,
+ // use it.
+ //
+ // NT 1.0 PDCs will pass a zero length.
+ //
+
+ if ( DeltaDomain->DummyString1.Length >= sizeof(DOMAIN_LOCKOUT_INFORMATION) ) {
+ RtlCopyMemory( &DomainInfo.Lockout,
+ DeltaDomain->DummyString1.Buffer,
+ DeltaDomain->DummyString1.Length );
+
+ STARTSAMTIMER;
+
+ Status = SamrSetInformationDomain(
+ DBInfo->DBHandle,
+ DomainLockoutInformation,
+ &DomainInfo );
+
+ STOPSAMTIMER;
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ }
+
+
+ //
+ // Don't unpack DomainModifiedCount and DomainCreationDate!!
+ // These will be handled separately during a full sync.
+ //
+
+ //
+ // All Done
+ //
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if ( QueryDomainInfoBuf != NULL ) {
+
+ STARTSAMTIMER;
+
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( QueryDomainInfoBuf,
+ DomainNameInformation );
+ STOPSAMTIMER;
+ }
+
+ STOPUNPACKTIMER;
+
+ NlPrint((NL_REPL_OBJ_TIME,"Time taken to unpack DOMAIN object:\n"));
+ PRINTUNPACKTIMER;
+ PRINTSAMTIMER;
+
+
+ return Status;
+
+}
+
+
+
+//
+// builtin domain support
+//
+
+NTSTATUS
+NlUnpackSam(
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ OUT PULONG ConflictingRID,
+ PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Install a delta into the local SAM database.
+
+Arguments:
+
+ Deltas - The delta to install
+
+ SessionInfo - Info shared between PDC and BDC
+
+Return Value:
+
+--*/
+{
+ NTSTATUS Status;
+
+ //
+ // Handle each delta type differently.
+ //
+
+ *ConflictingRID = 0;
+
+ switch ( Delta->DeltaType ) {
+ case AddOrChangeDomain:
+ Status = NlUnpackSamDomain(
+ Delta->DeltaUnion.DeltaDomain,
+ DBInfo);
+ break;
+
+ case AddOrChangeGroup:
+ Status = NlUnpackSamGroup(
+ Delta->DeltaUnion.DeltaGroup,
+ DBInfo,
+ ConflictingRID );
+ break;
+
+ case AddOrChangeAlias:
+ Status = NlUnpackSamAlias(
+ Delta->DeltaUnion.DeltaAlias,
+ DBInfo,
+ ConflictingRID );
+ break;
+
+ case AddOrChangeUser:
+ Status = NlUnpackSamUser(
+ Delta->DeltaUnion.DeltaUser,
+ DBInfo,
+ ConflictingRID,
+ SessionInfo );
+ break;
+
+ case ChangeGroupMembership:
+ Status = NlUnpackSamGroupMember(
+ Delta->DeltaID.Rid,
+ Delta->DeltaUnion.DeltaGroupMember,
+ DBInfo );
+ break;
+
+ case ChangeAliasMembership:
+ Status = NlUnpackSamAliasMember(
+ Delta->DeltaID.Rid,
+ Delta->DeltaUnion.DeltaAliasMember,
+ DBInfo );
+ break;
+
+ case DeleteGroup:
+ case DeleteGroupByName:
+ Status = NlDeleteSamGroup(
+ DBInfo->DBHandle,
+ Delta->DeltaID.Rid );
+
+ break;
+
+ case DeleteAlias:
+ Status = NlDeleteSamAlias(
+ DBInfo->DBHandle,
+ Delta->DeltaID.Rid );
+
+ break;
+
+ case DeleteUser:
+ case DeleteUserByName:
+ Status = NlDeleteSamUser(
+ DBInfo->DBHandle,
+ Delta->DeltaID.Rid );
+
+ break;
+
+ case AddOrChangeLsaPolicy:
+ Status = NlUnpackLsaPolicy(
+ Delta->DeltaUnion.DeltaPolicy,
+ DBInfo );
+ break;
+
+ case AddOrChangeLsaTDomain:
+ Status = NlUnpackLsaTDomain(
+ Delta->DeltaID.Sid,
+ Delta->DeltaUnion.DeltaTDomains,
+ DBInfo );
+ break;
+
+ case AddOrChangeLsaAccount:
+ Status = NlUnpackLsaAccount(
+ Delta->DeltaID.Sid,
+ Delta->DeltaUnion.DeltaAccounts,
+ DBInfo );
+ break;
+
+ case AddOrChangeLsaSecret:
+ Status = NlUnpackLsaSecret(
+ Delta->DeltaID.Name,
+ Delta->DeltaUnion.DeltaSecret,
+ DBInfo,
+ SessionInfo );
+ break;
+
+ case DeleteLsaTDomain:
+ Status = NlDeleteLsaTDomain(
+ Delta->DeltaID.Sid,
+ DBInfo );
+ break;
+
+ case DeleteLsaAccount:
+ Status = NlDeleteLsaAccount(
+ Delta->DeltaID.Sid,
+ DBInfo );
+ break;
+
+ case DeleteLsaSecret:
+ Status = NlDeleteLsaSecret(
+ Delta->DeltaID.Name,
+ DBInfo );
+ break;
+
+ // Nothing to unpack for this delta
+ case SerialNumberSkip:
+ Status = STATUS_SUCCESS;
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL, "NlUnpackSam: invalid delta type %lx\n", Delta->DeltaType ));
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlEncryptSensitiveData(
+ IN OUT PCRYPT_BUFFER Data,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Encrypt data using the the server session key.
+
+ Either DES or RC4 will be used depending on the negotiated flags in SessionInfo.
+
+Arguments:
+
+ Data: Pointer to the data to be decrypted. If the decrypted data is longer
+ than the encrypt data, this routine will allocate a buffer for
+ the returned data using MIDL_user_allocate and return a description to
+ that buffer here. In that case, this routine will free the buffer
+ containing the encrypted text data using MIDL_user_free.
+
+ SessionInfo: Info describing BDC that's calling us
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NTSTATUS Status;
+ DATA_KEY KeyData;
+
+
+ //
+ // If both sides support RC4 encryption, use it.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ NlEncryptRC4( Data->Buffer, Data->Length, SessionInfo );
+ Status = STATUS_SUCCESS;
+
+
+ //
+ // If the other side is running NT 1.0,
+ // use the slower DES based encryption.
+ //
+
+ } else {
+ CYPHER_DATA TempData;
+
+ //
+ // Build a data buffer to describe the encryption key.
+ //
+
+ KeyData.Length = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.Buffer = (PVOID)&SessionInfo->SessionKey;
+
+ //
+ // Build a data buffer to describe the encrypted data.
+ //
+
+ TempData.Length = 0;
+ TempData.MaximumLength = 0;
+ TempData.Buffer = NULL;
+
+ //
+ // First time make the encrypt call to determine the length.
+ //
+
+ Status = RtlEncryptData(
+ (PCLEAR_DATA)Data,
+ &KeyData,
+ &TempData );
+
+ if( Status != STATUS_BUFFER_TOO_SMALL ) {
+ return(Status);
+ }
+
+ //
+ // allocate output buffer.
+ //
+
+ TempData.MaximumLength = TempData.Length;
+ TempData.Buffer = MIDL_user_allocate( TempData.Length );
+
+ if( TempData.Buffer == NULL ) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ //
+ // Encrypt the data.
+ //
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlEncryptSensitiveData: Clear data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data->Buffer,
+ Data->Length / sizeof(DWORD) );
+ }
+
+ Status = RtlEncryptData(
+ (PCLEAR_DATA)Data,
+ &KeyData,
+ &TempData );
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlEncryptSensitiveData: Encrypted data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)TempData.Buffer,
+ TempData.Length / sizeof(DWORD) );
+ }
+
+ //
+ // Return either the clear text or encrypted buffer to the caller.
+ //
+
+ if( NT_SUCCESS(Status) ) {
+ MIDL_user_free( Data->Buffer );
+ Data->Length = TempData.Length;
+ Data->MaximumLength = TempData.MaximumLength;
+ Data->Buffer = TempData.Buffer;
+ } else {
+ MIDL_user_free( TempData.Buffer );
+ }
+
+ }
+
+ return( Status );
+
+}
+
+
+NTSTATUS
+NlDecryptSensitiveData(
+ IN PCRYPT_BUFFER Data,
+ OUT PCRYPT_BUFFER DecryptedData,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Decrypt data using the the server session key.
+
+ Either DES or RC4 will be used depending on the negotiated flags in SessionInfo.
+
+ Note: this routine doesn't decrypt in place since the caller typically
+ wants to save the encrypted data so that the operation can be retried.
+ Perhaps, I could mark the buffer that the decryption had already
+ taken place.
+
+Arguments:
+
+ Data: Pointer to the data to be decrypted.
+
+ DecryptedData: Returns a decriptor of the decypted data. The buffer
+ should be deallocated using MIDL_user_free.
+
+ SessionInfo: Info describing BDC that's calling us
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+
+
+ //
+ // If both sides support RC4 encryption, use it.
+ //
+
+ if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
+
+ //
+ // Allocate a buffer and copy the encrypted data into it.
+ // RC4 decrypts in place.
+ //
+
+ DecryptedData->Buffer = MIDL_user_allocate( Data->Length );
+ if ( DecryptedData->Buffer == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ DecryptedData->Length = DecryptedData->MaximumLength = Data->Length;
+ RtlCopyMemory( DecryptedData->Buffer, Data->Buffer, Data->Length );
+
+ NlDecryptRC4( DecryptedData->Buffer, DecryptedData->Length, SessionInfo );
+ return STATUS_SUCCESS;
+
+
+ //
+ // If the other side is running NT 1.0,
+ // use the slower DES based encryption.
+ //
+
+ } else {
+ NTSTATUS Status;
+ DATA_KEY KeyData;
+
+
+ //
+ // build keydata buffer.
+ //
+
+ KeyData.Length = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.Buffer = (PVOID)&SessionInfo->SessionKey;
+
+
+ //
+ // First time make the decrypt call to determine the length.
+ //
+
+ DecryptedData->Length = 0;
+ DecryptedData->MaximumLength = 0;
+ DecryptedData->Buffer = NULL;
+
+ Status = RtlDecryptData( Data, &KeyData, DecryptedData );
+
+ if( Status != STATUS_BUFFER_TOO_SMALL ) {
+
+ if( NT_SUCCESS(Status) ) {
+
+ //
+ // set return buffer
+ //
+
+ DecryptedData->Length = 0;
+ DecryptedData->MaximumLength = 0;
+ DecryptedData->Buffer = NULL;
+ }
+
+ return(Status);
+ }
+
+ //
+ // allocate output buffer.
+ //
+
+ DecryptedData->MaximumLength = DecryptedData->Length;
+ DecryptedData->Buffer = MIDL_user_allocate( DecryptedData->Length );
+
+ if( DecryptedData->Buffer == NULL ) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ //
+ // Decrypt the data
+ //
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlDecryptSensitiveData: Encrypted data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data->Buffer,
+ Data->Length / sizeof(DWORD) );
+ }
+
+ Status = RtlDecryptData( Data, &KeyData, DecryptedData);
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlDecryptSensitiveData: Clear data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)DecryptedData->Buffer,
+ DecryptedData->Length / sizeof(DWORD) );
+ }
+
+ //
+ // Free the buffer if we couldn't decrypt into it.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+ MIDL_user_free( DecryptedData->Buffer );
+ DecryptedData->Buffer = NULL;
+ }
+
+ return Status;
+
+ }
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/replutil.h b/private/net/svcdlls/logonsrv/server/replutil.h
new file mode 100644
index 000000000..41c292932
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/replutil.h
@@ -0,0 +1,235 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ replutil.h
+
+Abstract:
+
+ Low level functions for SSI Replication apis
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 22-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+--*/
+
+//
+// Description of the FullSync key in the registry. The FullSync key stores sync
+// data in the registry across reboots.
+//
+#define NL_FULL_SYNC_KEY "SYSTEM\\CurrentControlSet\\Services\\Netlogon\\FullSync"
+
+#define FULL_SYNC_KEY_VERSION 1
+
+typedef struct _FULL_SYNC_KEY {
+ ULONG Version;
+ SYNC_STATE SyncState;
+ ULONG ContinuationRid;
+ NTSTATUS CumulativeStatus;
+ LARGE_INTEGER PdcSerialNumber;
+ LARGE_INTEGER PdcDomainCreationTime;
+} FULL_SYNC_KEY, *PFULL_SYNC_KEY;
+
+//
+// replutil.c
+//
+
+DWORD
+NlCopyUnicodeString (
+ IN PUNICODE_STRING InString,
+ OUT PUNICODE_STRING OutString
+ );
+
+DWORD
+NlCopyData(
+ IN LPBYTE *InData,
+ OUT LPBYTE *OutData,
+ DWORD DataLength
+ );
+
+VOID
+NlFreeDBDelta(
+ IN PNETLOGON_DELTA_ENUM Delta
+ );
+
+VOID
+NlFreeDBDeltaArray(
+ IN PNETLOGON_DELTA_ENUM DeltaArray,
+ IN DWORD ArraySize
+ );
+
+NTSTATUS
+NlPackSamUser (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ OUT LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ );
+
+NTSTATUS
+NlPackSamGroup (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ );
+
+NTSTATUS
+NlPackSamGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ );
+
+NTSTATUS
+NlPackSamAlias (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ );
+
+NTSTATUS
+NlPackSamAliasMember (
+ IN ULONG RelativeId,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ LPDWORD BufferSize
+ );
+
+NTSTATUS
+NlPackSamDomain (
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ IN LPDWORD BufferSize
+ );
+
+NTSTATUS
+NlUnpackSam(
+ IN PNETLOGON_DELTA_ENUM Delta,
+ IN PDB_INFO DBInfo,
+ OUT PULONG ConflictingRID,
+ PSESSION_INFO SessionInfo
+ );
+
+NTSTATUS
+NlEncryptSensitiveData(
+ IN OUT PCRYPT_BUFFER Data,
+ IN PSESSION_INFO SessionInfo
+ );
+
+NTSTATUS
+NlDecryptSensitiveData(
+ IN PCRYPT_BUFFER Data,
+ OUT PCRYPT_BUFFER DecryptedData,
+ IN PSESSION_INFO SessionInfo
+ );
+
+//
+// repluas.c
+//
+
+NTSTATUS
+NlPackUasHeader(
+ IN BYTE Opcode,
+ IN DWORD InitialSize,
+ OUT PUSHORT *RecordSize,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor
+ );
+
+NTSTATUS
+NlPackUasUser (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo,
+ IN PNETLOGON_SESSION_KEY SessionKey,
+ IN LONG RotateCount
+ );
+
+NTSTATUS
+NlPackUasGroup (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo,
+ IN OUT PDWORD UasBuiltinGroups
+ );
+
+NTSTATUS
+NlPackUasBuiltinGroup(
+ IN DWORD Index,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDWORD UasBuiltinGroup
+ );
+
+NTSTATUS
+NlPackUasGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo
+ );
+
+NTSTATUS
+NlPackUasUserGroupMember (
+ IN ULONG RelativeId,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo
+ );
+
+NTSTATUS
+NlPackUasDomain (
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo
+ );
+
+NTSTATUS
+NlPackUasDelete (
+ IN NETLOGON_DELTA_TYPE DeltaType,
+ IN ULONG RelativeId,
+ IN LPWSTR AccountName,
+ IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
+ IN PDB_INFO DBInfo
+ );
+
+NTSTATUS
+NlDeleteSamUser(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ );
+
+NTSTATUS
+NlDeleteSamGroup(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ );
+
+NTSTATUS
+NlDeleteSamAlias(
+ SAMPR_HANDLE DomainHandle,
+ ULONG Rid
+ );
+
+//
+// lsrvrepl.c
+//
+
+VOID
+NlSetFullSyncKey(
+ ULONG DBIndex,
+ PFULL_SYNC_KEY FullSyncKey
+ );
diff --git a/private/net/svcdlls/logonsrv/server/sources b/private/net/svcdlls/logonsrv/server/sources
new file mode 100644
index 000000000..5cdbddbae
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/sources
@@ -0,0 +1,115 @@
+!IF 0
+
+Copyright (c) 1989-92 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
+
+#
+# The MAJORCOMP and MINORCOMP variables are defined
+# so that $(MAJORCOMP)$(MINORCOMP)filename can be used in
+# cross compiling to provide unique filenames in a flat namespace.
+#
+
+MAJORCOMP=net
+MINORCOMP=logonsrv
+
+TARGETNAME=netlogon
+TARGETPATH=$(BASEDIR)\public\sdk\lib
+
+TARGETTYPE=DYNLINK
+DLLENTRY=NetlogonDllInit
+
+TARGETLIBS= $(BASEDIR)\Public\Sdk\Lib\*\netlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\netapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\samsrv.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\kernel32.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\lsasrv.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcutil.lib \
+ $(BASEDIR)\public\sdk\lib\*\msv1_0.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcrt4.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcndr.lib
+
+INCLUDES=.;..;..\..\..\inc;..\..\..\..\inc
+
+#
+# Indicate that a .prf file exists.
+#
+
+NTPROFILEINPUT=yes
+
+#
+# Set RPC_SERVER so that MIDL generated stubs use the version of structures
+# that contain non-opaque PISID instead of PSID.
+#
+C_DEFINES=-DRPC_SERVER -DRPC_NO_WINDOWS_H
+
+!IFNDEF DISABLE_NET_UNICODE
+UNICODE=1
+NET_C_DEFINES=-DUNICODE
+!ENDIF
+
+SOURCES= \
+ announce.c \
+ changelg.c \
+ chutil.c \
+ chworker.c \
+ error.c \
+ logon_s.c \
+ logonapi.c \
+ lsarepl.c \
+ lsrvrepl.c \
+ lsrvutil.c \
+ mailslot.c \
+ netlogon.c \
+ netlogon.rc \
+ nlp.c \
+ nlsecure.c \
+ oldstub.c \
+ parse.c \
+ repluas.c \
+ replutil.c \
+ srvsess.c \
+ ssiapi.c \
+ ssiauth.c \
+ trustutl.c
+
+#
+# Next specify options for the compiler.
+#
+
+MSC_WARNING_LEVEL=/W3 /WX
+
+USE_CRTDLL=1
+
+UMTYPE=console
+UMAPPL=nltest
+UMRES=$(@R).res
+UMLIBS= obj\*\ssiauth.obj \
+ obj\*\chutil.obj \
+ obj\*\nltest1.obj \
+ $(BASEDIR)\Public\Sdk\Lib\*\netlib.lib \
+ $(BASEDIR)\Public\Sdk\Lib\*\samlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\netapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib
+
+NTTARGETFILE1=obj\*\nltest.res
+
+
diff --git a/private/net/svcdlls/logonsrv/server/srvsess.c b/private/net/svcdlls/logonsrv/server/srvsess.c
new file mode 100644
index 000000000..d83ebfa24
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/srvsess.c
@@ -0,0 +1,1728 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ srvsess.c
+
+Abstract:
+
+ Routines for managing the ServerSession structure.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 12-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+--*/
+
+//
+// Common include files.
+//
+
+#define INITSSI_ALLOCATE // Allocate all ssiinit.h global variables
+#include <logonsrv.h> // Include files common to entire service
+#undef INITSSI_ALLOCATE
+
+//
+// Include files specific to this .c file
+//
+
+#include <lmapibuf.h>
+#include <lmaudit.h>
+#include <lmerr.h>
+#include <lmserver.h>
+#include <lmshare.h>
+#include <tstring.h> // TOUPPER
+
+#define MAX_WOC_INTERROGATE 8 // 2 hours
+#define KILL_SESSION_TIME (4*4*24) // 4 Days
+
+
+DWORD
+NlGetHashVal(
+ IN LPSTR UpcaseOemComputerName,
+ IN DWORD HashTableSize
+ )
+/*++
+
+Routine Description:
+
+ Generate a HashTable index for the specified ComputerName.
+
+ Notice that all sessions for a particular ComputerName hash to the same
+ value. The ComputerName make a suitable hash key all by itself.
+ Also, at times we visit all the session entries for a particular
+ ComputerName. By using only the ComputerName as the hash key, I
+ can limit my search to the single hash chain.
+
+Arguments:
+
+ UpcaseOemComputerName - The upper case OEM name of the computer on
+ the client side of the secure channel setup.
+
+ HashTableSize - Number of entries in the hash table (must be a power of 2)
+
+Return Value:
+
+ Returns an index into the HashTable.
+
+--*/
+{
+ UCHAR c;
+ DWORD value = 0;
+
+ while (c = *UpcaseOemComputerName++) {
+ value += (DWORD) c;
+ }
+
+ return (value & (HashTableSize-1));
+}
+
+
+
+NTSTATUS
+NlAddBdcServerSession(
+ IN ULONG ServerRid,
+ IN PUNICODE_STRING AccountName OPTIONAL,
+ IN DWORD Flags
+ )
+/*++
+
+Routine Description:
+
+ Create a server session to represent this BDC account.
+
+Arguments:
+
+ ServerRid - Rid of server to add to list.
+
+ AccountName - Optionally specifies the account name of the account.
+
+ Flags - Specifies the initial SsFlags to associate with the entry.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+ PUNICODE_STRING ServerName;
+ WCHAR LocalServerName[CNLEN+1];
+ LONG LocalServerNameSize;
+
+ SAMPR_ULONG_ARRAY Use = {0, NULL};
+ SAMPR_RETURNED_USTRING_ARRAY Names = {0, NULL};
+
+ //
+ // If we were given an account name,
+ // just use it.
+
+ if ( AccountName != NULL ) {
+
+ ServerName = AccountName;
+
+ //
+ // Convert the specified ServerRid into a server name.
+ //
+
+ } else {
+
+
+ Status = SamrLookupIdsInDomain(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 1,
+ &ServerRid,
+ &Names,
+ &Use );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Names.Element = NULL;
+ Use.Element = NULL;
+ if ( Status = STATUS_NONE_MAPPED ) {
+ Status = STATUS_SUCCESS;
+ }
+ goto Cleanup;
+ }
+
+ NlAssert( Names.Count == 1 );
+ NlAssert( Names.Element != NULL );
+ NlAssert( Use.Count == 1 );
+ NlAssert( Use.Element != NULL );
+
+ if( Use.Element[0] != SidTypeUser ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ ServerName = (PUNICODE_STRING)&Names.Element[0];
+ }
+
+
+
+ //
+ // Build a zero terminated server name.
+ //
+ // Strip the trailing postfix.
+ //
+ // Ignore servers with malformed names. They aren't really BDCs so don't
+ // cloud the issue by failing to start netlogon.
+ //
+
+ LocalServerNameSize = ServerName->Length;
+ if ( (Flags & SS_LM_BDC) == 0 ) {
+ LocalServerNameSize -= SSI_ACCOUNT_NAME_POSTFIX_LENGTH * sizeof(WCHAR);
+ }
+
+ if ( LocalServerNameSize < 0 ||
+ LocalServerNameSize + sizeof(WCHAR) > sizeof(LocalServerName) ) {
+
+ NlPrint((NL_SERVER_SESS,
+ "NlAddBdcServerSession: %wZ: Skipping add of invalid server name\n",
+ ServerName ));
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( LocalServerName, ServerName->Buffer, LocalServerNameSize );
+ LocalServerName[ LocalServerNameSize / sizeof(WCHAR) ] = L'\0';
+
+
+
+ //
+ // Don't add ourselves to the list.
+ //
+
+ if ( NlNameCompare( LocalServerName,
+ NlGlobalUnicodeComputerName,
+ NAMETYPE_COMPUTER ) == 0 ) {
+
+ NlPrint((NL_SERVER_SESS,
+ "NlAddBdcServerSession: " FORMAT_LPWSTR
+ ": Skipping add of ourself\n",
+ LocalServerName ));
+
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ // Always force a pulse to a newly created server.
+ Status = NlInsertServerSession(
+ LocalServerName,
+ Flags | SS_FORCE_PULSE,
+ ServerRid,
+ NULL,
+ NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlAddBdcServerSession: " FORMAT_LPWSTR
+ ": Couldn't create server session entry (0x%lx)\n",
+ LocalServerName,
+ Status ));
+ goto Cleanup;
+ }
+
+ NlPrint((NL_SERVER_SESS,
+ "NlAddBdcServerSession: " FORMAT_LPWSTR ": Added %s BDC account\n",
+ LocalServerName,
+ (Flags & SS_LM_BDC) ? "LANMAN" : "NT" ));
+
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if( Names.Element != NULL ) {
+ SamIFree_SAMPR_RETURNED_USTRING_ARRAY( &Names );
+ }
+
+ if( Use.Element != NULL ) {
+ SamIFree_SAMPR_ULONG_ARRAY( &Use );
+ }
+
+ return Status;
+}
+
+
+
+NTSTATUS
+NlBuildLmBdcList(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Get the list of all Lanman DC's in this domain from SAM.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ Status of the operation.
+--*/
+{
+ NTSTATUS Status;
+
+ SAMPR_ULONG_ARRAY RelativeIdArray = {0, NULL};
+ SAMPR_ULONG_ARRAY UseArray = {0, NULL};
+ RPC_UNICODE_STRING GroupNameString;
+ SAMPR_HANDLE GroupHandle = NULL;
+ ULONG ServersGroupRid;
+
+ PSAMPR_GET_MEMBERS_BUFFER MembersBuffer = NULL;
+
+ ULONG i;
+
+
+ //
+ // Determine the RID of the Servers group.
+ //
+
+ RtlInitUnicodeString( (PUNICODE_STRING)&GroupNameString,
+ SSI_SERVER_GROUP_W );
+
+ Status = SamrLookupNamesInDomain(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 1,
+ &GroupNameString,
+ &RelativeIdArray,
+ &UseArray );
+
+ if ( !NT_SUCCESS(Status) ) {
+ RelativeIdArray.Element = NULL;
+ UseArray.Element = NULL;
+ // Its OK if the SERVERS group doesn't exist
+ if ( Status == STATUS_NONE_MAPPED ) {
+ Status = STATUS_SUCCESS;
+ }
+ goto Cleanup;
+ }
+
+ //
+ // We should get back exactly one entry of info back.
+ //
+
+ NlAssert( UseArray.Count == 1 );
+ NlAssert( UseArray.Element != NULL );
+ NlAssert( RelativeIdArray.Count == 1 );
+ NlAssert( RelativeIdArray.Element != NULL );
+
+ if ( UseArray.Element[0] != SidTypeGroup ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ ServersGroupRid = RelativeIdArray.Element[0];
+
+
+
+ //
+ // Open the SERVERS group
+ //
+
+ Status = SamrOpenGroup( NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ 0, // No desired access
+ ServersGroupRid,
+ &GroupHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+ GroupHandle = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Enumerate members in the SERVERS group.
+ //
+
+ Status = SamrGetMembersInGroup( GroupHandle, &MembersBuffer );
+
+ if (!NT_SUCCESS(Status)) {
+ MembersBuffer = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // For each member of the SERVERS group,
+ // add an entry in the downlevel servers table.
+ //
+
+ for ( i=0; i < MembersBuffer->MemberCount; i++ ) {
+
+ Status = NlAddBdcServerSession( MembersBuffer->Members[i],
+ NULL,
+ SS_BDC | SS_LM_BDC );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+ }
+
+
+ //
+ // Success
+ //
+
+ Status = STATUS_SUCCESS;
+
+
+
+ //
+ // Free locally used resources.
+ //
+
+Cleanup:
+
+ SamIFree_SAMPR_ULONG_ARRAY( &RelativeIdArray );
+ SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
+
+ if ( MembersBuffer != NULL ) {
+ SamIFree_SAMPR_GET_MEMBERS_BUFFER( MembersBuffer );
+ }
+
+ if( GroupHandle != NULL ) {
+ (VOID) SamrCloseHandle( &GroupHandle );
+ }
+
+ return Status;
+}
+
+//
+// Number of machine accounts read from SAM on each call
+//
+#define MACHINES_PER_PASS 250
+
+
+NTSTATUS
+NlBuildNtBdcList(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Get the list of all Nt Bdc DC's in this domain from SAM.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ Status of the operation.
+--*/
+{
+ NTSTATUS Status;
+ NTSTATUS SamStatus;
+
+ SAMPR_DISPLAY_INFO_BUFFER DisplayInformation;
+ PDOMAIN_DISPLAY_MACHINE MachineInformation = NULL;
+ ULONG SamIndex;
+
+
+
+ //
+ // Loop building a list of BDC names from SAM.
+ //
+ // On each iteration of the loop,
+ // get the next several machine accounts from SAM.
+ // determine which of those names are DC names.
+ // Merge the DC names into the list we're currently building of all DCs.
+ //
+
+ SamIndex = 0;
+ DisplayInformation.MachineInformation.Buffer = NULL;
+ do {
+ //
+ // Arguments to SamrQueryDisplayInformation
+ //
+ ULONG TotalBytesAvailable;
+ ULONG BytesReturned;
+ ULONG EntriesRead;
+
+ DWORD i;
+
+ //
+ // Get the list of machine accounts from SAM
+ //
+
+ SamStatus = SamrQueryDisplayInformation(
+ NlGlobalDBInfoArray[SAM_DB].DBHandle,
+ DomainDisplayMachine,
+ SamIndex,
+ MACHINES_PER_PASS,
+ 0xFFFFFFFF,
+ &TotalBytesAvailable,
+ &BytesReturned,
+ &DisplayInformation );
+
+ if ( !NT_SUCCESS(SamStatus) ) {
+ NlPrint((NL_CRITICAL,
+ "SamrQueryDisplayInformation failed: 0x%08lx\n",
+ Status));
+ Status = SamStatus;
+ goto Cleanup;
+ }
+
+ MachineInformation = (PDOMAIN_DISPLAY_MACHINE)
+ DisplayInformation.MachineInformation.Buffer;
+ EntriesRead = DisplayInformation.MachineInformation.EntriesRead;
+
+
+ //
+ // Set up for the next call to Sam.
+ //
+
+ if ( SamStatus == STATUS_MORE_ENTRIES ) {
+ SamIndex = MachineInformation[EntriesRead-1].Index + 1;
+ }
+
+
+ //
+ // Loop though the list of machine accounts finding the Server accounts.
+ //
+
+ for ( i=0; i<EntriesRead; i++ ) {
+
+ //
+ // Ensure the machine account is a server account.
+ //
+
+ if ( MachineInformation[i].AccountControl &
+ USER_SERVER_TRUST_ACCOUNT ) {
+
+
+ //
+ // Insert the server session.
+ //
+
+ Status = NlAddBdcServerSession(
+ MachineInformation[i].Rid,
+ &MachineInformation[i].Machine,
+ SS_BDC );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ }
+ }
+
+ //
+ // Free the buffer returned from SAM.
+ //
+ SamIFree_SAMPR_DISPLAY_INFO_BUFFER( &DisplayInformation,
+ DomainDisplayMachine );
+ DisplayInformation.MachineInformation.Buffer = NULL;
+
+ } while ( SamStatus == STATUS_MORE_ENTRIES );
+
+ //
+ // Success
+ //
+
+ Status = STATUS_SUCCESS;
+
+
+
+ //
+ // Free locally used resources.
+ //
+Cleanup:
+
+ SamIFree_SAMPR_DISPLAY_INFO_BUFFER( &DisplayInformation,
+ DomainDisplayMachine );
+
+ return Status;
+}
+
+
+
+
+VOID
+NlTransportOpen(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Initialize the list of transports
+
+Arguments:
+
+ None
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ PSERVER_TRANSPORT_INFO_0 TransportInfo0;
+ DWORD EntriesRead;
+ DWORD TotalEntries;
+ DWORD i;
+ DWORD BufferSize;
+ LPBYTE Where;
+
+ NlGlobalTransportCount = 0;
+ //
+ // Enumerate the transports supported by the server.
+ //
+
+ NetStatus = NetServerTransportEnum(
+ NULL, // local
+ 0, // level 0
+ (LPBYTE *) &TransportInfo0,
+ 0xFFFFFFFF, // PrefMaxLength
+ &EntriesRead,
+ &TotalEntries,
+ NULL ); // No resume handle
+
+ if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
+ NlPrint(( NL_CRITICAL, "Cannot NetServerTransportEnum %ld\n", NetStatus ));
+ return;
+ }
+
+ if ( EntriesRead == 0 ) {
+ NlPrint(( NL_CRITICAL, "NetServerTransportEnum returned 0 entries\n" ));
+ (VOID) NetApiBufferFree( TransportInfo0 );
+ return;
+ }
+
+ //
+ // Allocate a buffer to contain just the transport names.
+ //
+
+ BufferSize = 0;
+ for ( i=0; i<EntriesRead; i++ ) {
+ BufferSize += sizeof(LPWSTR) +
+ wcslen(TransportInfo0[i].svti0_transportname) * sizeof(WCHAR) +
+ sizeof(WCHAR);
+ }
+
+ NlGlobalTransportList = NetpMemoryAllocate( BufferSize );
+
+ if ( NlGlobalTransportList == NULL ) {
+ NlPrint(( NL_CRITICAL, "NlTransportOpen: no memory\n" ));
+ (VOID) NetApiBufferFree( TransportInfo0 );
+ return;
+ }
+
+ //
+ // Copy the transport names into the buffer.
+ //
+
+ Where = (LPBYTE)(&NlGlobalTransportList[EntriesRead]);
+
+ for ( i=0; i<EntriesRead; i++ ) {
+ DWORD Size;
+
+ NlGlobalTransportList[i] = (LPWSTR) Where;
+
+ Size = wcslen(TransportInfo0[i].svti0_transportname) * sizeof(WCHAR) +
+ sizeof(WCHAR);
+ RtlCopyMemory( Where,
+ TransportInfo0[i].svti0_transportname,
+ Size );
+ Where += Size;
+ NlPrint(( NL_SERVER_SESS, "Server Transport %ld: " FORMAT_LPWSTR "\n",
+ i,
+ TransportInfo0[i].svti0_transportname ));
+ }
+
+ NlGlobalTransportCount = EntriesRead;
+ (VOID) NetApiBufferFree( TransportInfo0 );
+ return;
+}
+
+LPWSTR
+NlTransportLookupTransportName(
+ IN LPWSTR TransportName
+ )
+/*++
+
+Routine Description:
+
+ Returns a transport name equal to the one passed in. However, the
+ returned transport name is static and need not be freed.
+
+Arguments:
+
+ TransportName - Name of the transport to look up
+
+Return Value:
+
+ NULL - on any error
+
+ Otherwise, returns a pointer to the transport name
+
+--*/
+{
+ DWORD i;
+
+ //
+ // If we're not initialized yet,
+ // just return
+ //
+
+ if ( TransportName == NULL || NlGlobalTransportCount == 0 ) {
+ return NULL;
+ }
+
+ //
+ // Find this transport in the list of transports.
+ //
+
+ for ( i=0; i<NlGlobalTransportCount; i++ ) {
+ if ( _wcsicmp( TransportName, NlGlobalTransportList[i] ) == 0 ) {
+ return NlGlobalTransportList[i];
+ }
+ }
+
+ return NULL;
+}
+
+LPWSTR
+NlTransportLookup(
+ IN LPWSTR ClientName
+ )
+/*++
+
+Routine Description:
+
+ Determine what transport the specified client is using to access this
+ server.
+
+Arguments:
+
+ ClientName - Name of the client connected to this server.
+
+Return Value:
+
+ NULL - The client isn't currently connected
+
+ Otherwise, returns a pointer to the transport name
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ PSESSION_INFO_502 SessionInfo502;
+ DWORD EntriesRead;
+ DWORD TotalEntries;
+ DWORD i;
+ DWORD BestTime;
+ DWORD BestEntry;
+ LPWSTR TransportName;
+
+ WCHAR UncClientName[UNCLEN+1];
+
+ //
+ // If we're not initialized yet,
+ // just return
+ //
+
+ if ( NlGlobalTransportCount == 0 ) {
+ return NULL;
+ }
+
+ //
+ // Enumerate all the sessions from the particular client.
+ //
+
+ UncClientName[0] = '\\';
+ UncClientName[1] = '\\';
+ wcscpy( &UncClientName[2], ClientName );
+
+ NetStatus = NetSessionEnum(
+ NULL, // local
+ UncClientName, // Client to query
+ NULL, // user name
+ 502,
+ (LPBYTE *)&SessionInfo502,
+ 1024, // PrefMaxLength
+ &EntriesRead,
+ &TotalEntries,
+ NULL ); // No resume handle
+
+ if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
+ NlPrint(( NL_CRITICAL,
+ "NlTransportLookup: " FORMAT_LPWSTR ": Cannot NetSessionEnum %ld\n",
+ UncClientName,
+ NetStatus ));
+ return NULL;
+ }
+
+ if ( EntriesRead == 0 ) {
+ NlPrint(( NL_CRITICAL,
+ "NlTransportLookup: " FORMAT_LPWSTR ": No session exists.\n",
+ UncClientName ));
+ (VOID) NetApiBufferFree( SessionInfo502 );
+ return NULL;
+ }
+
+ //
+ // Loop through the list of transports finding the best one.
+ //
+
+ BestTime = 0xFFFFFFFF;
+
+ for ( i=0; i<EntriesRead; i++ ) {
+#ifdef notdef
+ //
+ // We're only looking for null sessions
+ //
+ if ( SessionInfo502[i].sesi502_username != NULL ) {
+ continue;
+ }
+
+ NlPrint(( NL_SERVER_SESS, "NlTransportLookup: "
+ FORMAT_LPWSTR " as " FORMAT_LPWSTR " on " FORMAT_LPWSTR "\n",
+ UncClientName,
+ SessionInfo502[i].sesi502_username,
+ SessionInfo502[i].sesi502_transport ));
+#endif // notdef
+
+ //
+ // Find the latest session
+ //
+
+ if ( BestTime > SessionInfo502[i].sesi502_idle_time ) {
+
+ // NlPrint(( NL_SERVER_SESS, "NlTransportLookup: Best Entry\n" ));
+ BestEntry = i;
+ BestTime = SessionInfo502[i].sesi502_idle_time;
+ }
+ }
+
+ //
+ // If an entry was found,
+ // Find this transport in the list of transports.
+ //
+
+ if ( BestTime != 0xFFFFFFFF ) {
+ TransportName = NlTransportLookupTransportName(
+ SessionInfo502[BestEntry].sesi502_transport );
+ if ( TransportName == NULL ) {
+ NlPrint(( NL_CRITICAL,
+ "NlTransportLookup: " FORMAT_LPWSTR ": Transport not found\n",
+ SessionInfo502[BestEntry].sesi502_transport ));
+ } else {
+ NlPrint(( NL_SERVER_SESS,
+ "NlTransportLookup: " FORMAT_LPWSTR ": Use Transport " FORMAT_LPWSTR "\n",
+ UncClientName,
+ TransportName ));
+ }
+ } else {
+ TransportName = NULL;
+ }
+
+ (VOID) NetApiBufferFree( SessionInfo502 );
+ return TransportName;
+}
+
+
+VOID
+NlTransportClose(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Free the list of transports
+
+Arguments:
+
+ None
+
+Return Value:
+
+ Status of the operation
+
+--*/
+{
+ NetpMemoryFree( NlGlobalTransportList );
+ NlGlobalTransportList = NULL;
+ NlGlobalTransportCount = 0;
+}
+
+
+
+
+NTSTATUS
+NlInitSSI(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Allocate and Initialize SSI related data structures. It will
+ allocate two data structures: one to hold the hash table of pointers
+ (to linked list of member entries) and another to to serve as memory
+ pool.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NT Status Code
+
+--*/
+{
+ DWORD i;
+ NTSTATUS Status;
+
+ //
+ // Initialize the replicator critical section.
+ //
+
+ // InitializeCriticalSection( &NlGlobalReplicatorCritSect );
+ // InitializeCriticalSection( &NlGlobalTrustListCritSect );
+ InitializeCriticalSection( &NlGlobalServerSessionTableCritSect );
+ NlGlobalSSICritSectInit = TRUE;
+
+
+
+
+ //
+ // Allocate NlGlobalServerSessionHashTable on DCs
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ NlGlobalServerSessionHashTable = (PLIST_ENTRY)
+ NetpMemoryAllocate( sizeof(LIST_ENTRY) *SERVER_SESSION_HASH_TABLE_SIZE);
+
+ if ( NlGlobalServerSessionHashTable == NULL ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ return STATUS_NO_MEMORY;
+ }
+
+ for ( i=0; i< SERVER_SESSION_HASH_TABLE_SIZE; i++ ) {
+ InitializeListHead( &NlGlobalServerSessionHashTable[i] );
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // On the PDC,
+ // Initialize the server session table to contain all the BDCs.
+ //
+
+ if ( NlGlobalRole == RolePrimary ) {
+ Status = NlBuildLmBdcList();
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = NlBuildNtBdcList();
+ }
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Build a list of transports for later reference
+ //
+
+ NlTransportOpen();
+
+ return Status;
+}
+
+
+
+PSERVER_SESSION
+NlFindNamedServerSession(
+ IN LPWSTR ComputerName
+ )
+/*++
+
+Routine Description:
+
+ Find the specified entry in the Server Session Table.
+
+ Enter with the ServerSessionTable Sem locked
+
+
+Arguments:
+
+ ComputerName - The name of the computer on the client side of the
+ secure channel.
+
+Return Value:
+
+ Returns a pointer to pointer to the found entry. If there is no such
+ entry, return a pointer to NULL.
+
+--*/
+{
+ NTSTATUS Status;
+ PLIST_ENTRY ListEntry;
+ DWORD Index;
+ CHAR UpcaseOemComputerName[CNLEN+1];
+ ULONG OemComputerNameSize;
+
+ //
+ // Ensure the ServerSession Table is initialized.
+ //
+
+ if (NlGlobalServerSessionHashTable == NULL) {
+ return NULL;
+ }
+
+
+ //
+ // Convert the computername to uppercase OEM for easier comparison.
+ //
+
+ Status = RtlUpcaseUnicodeToOemN(
+ UpcaseOemComputerName,
+ sizeof(UpcaseOemComputerName)-1,
+ &OemComputerNameSize,
+ ComputerName,
+ wcslen(ComputerName)*sizeof(WCHAR) );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return NULL;
+ }
+
+ UpcaseOemComputerName[OemComputerNameSize] = '\0';
+
+
+
+ //
+ // Loop through this hash chain trying the find the right entry.
+ //
+
+ Index = NlGetHashVal( UpcaseOemComputerName, SERVER_SESSION_HASH_TABLE_SIZE );
+
+ for ( ListEntry = NlGlobalServerSessionHashTable[Index].Flink ;
+ ListEntry != &NlGlobalServerSessionHashTable[Index] ;
+ ListEntry = ListEntry->Flink) {
+
+ PSERVER_SESSION ServerSession;
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsHashList );
+
+ //
+ // Compare the worstation name
+ //
+
+ if ( lstrcmpA( UpcaseOemComputerName,
+ ServerSession->SsComputerName ) != 0 ) {
+ continue;
+ }
+
+ return ServerSession;
+ }
+
+ return NULL;
+}
+
+
+NTSTATUS
+NlInsertServerSession(
+ IN LPWSTR ComputerName,
+ IN DWORD Flags,
+ IN ULONG AccountRid,
+ IN PNETLOGON_CREDENTIAL AuthenticationSeed OPTIONAL,
+ IN PNETLOGON_CREDENTIAL AuthenticationResponse OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Inserts the described entry into the ServerSession Table.
+
+ The server session entry is created for two reasons: 1) it represents
+ the server side of a secure channel, and 2) on a PDC, it represents the
+ BDC account for a BDC in the domain. In the first role, it exists for
+ the duration of the secure channel (and this routine is called when the
+ client requests a challenge). In the second role, it exists as
+ long as the machine account exists (and this routine is called during
+ netlogon startup for each BDC account).
+
+ If an entry matching this ComputerName already exists
+ in the ServerSession Table, that entry will be overwritten.
+
+Arguments:
+
+ ComputerName - The name of the computer on the client side of the
+ secure channel.
+
+ Flags - Specifies the initial SsFlags to associate with the entry.
+ If the SS_BDC bit is set, the structure is considered to represent
+ a BDC account in the SAM database.
+
+ AccountRid - If this is a BDC session, this specifies the RID of the
+ server account.
+
+ AuthenticationSeed - Specifies the Initial Authentication Seed
+ to associate with the entry. Specified only if this call is
+ being made as result of a challenge request
+ (e.g. NetrServerRequestChallenge)
+
+ AuthenticationResponse - Specifies the Initial Authentication Response from
+ the remote system to associate with the entry. Specified only if
+ this call is being made as result of a challenge request
+ (e.g. NetrServerRequestChallenge)
+
+Return Value:
+
+ NT STATUS code.
+
+--*/
+{
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession;
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ //
+ // If the is no current Server Session table entry,
+ // allocate one.
+ //
+
+ ServerSession = NlFindNamedServerSession(ComputerName);
+ if (ServerSession == NULL) {
+ DWORD Index;
+ ULONG ComputerNameSize;
+
+ //
+ // Allocate the ServerSession Entry
+ //
+
+ ServerSession = NetpMemoryAllocate( sizeof(SERVER_SESSION) );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ return STATUS_NO_MEMORY;
+ }
+
+ RtlZeroMemory( ServerSession, sizeof(SERVER_SESSION) );
+
+
+ //
+ // Fill in the fields of the ServerSession entry.
+ //
+
+ ServerSession->SsSecureChannelType = NullSecureChannel;
+ ServerSession->SsSync = NULL;
+ InitializeListHead( &ServerSession->SsBdcList );
+ InitializeListHead( &ServerSession->SsPendingBdcList );
+
+ //
+ // Convert the computername to uppercase OEM for easier comparison.
+ //
+
+ Status = RtlUpcaseUnicodeToOemN(
+ ServerSession->SsComputerName,
+ sizeof(ServerSession->SsComputerName)-1,
+ &ComputerNameSize,
+ ComputerName,
+ wcslen(ComputerName)*sizeof(WCHAR) );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NetpMemoryFree( ServerSession );
+ UNLOCK_SERVER_SESSION_TABLE();
+ return Status;
+ }
+
+ ServerSession->SsComputerName[ComputerNameSize] = '\0';
+
+
+ //
+ // Link the allocated entry into the head of hash table.
+ //
+ // The theory is we lookup new entries more frequently than older
+ // entries.
+ //
+
+ Index = NlGetHashVal( ServerSession->SsComputerName, SERVER_SESSION_HASH_TABLE_SIZE );
+
+ InsertHeadList( &NlGlobalServerSessionHashTable[Index],
+ &ServerSession->SsHashList );
+
+ //
+ // Link this entry onto the tail of the Sequential ServerSessionTable.
+ //
+
+ InsertTailList( &NlGlobalServerSessionTable, &ServerSession->SsSeqList );
+
+
+
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ } else {
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL,
+ "NlInsertServerSession: Concurrent call detected.\n" ));
+
+ return STATUS_ACCESS_DENIED;
+ }
+ }
+
+
+ //
+ // Initialize BDC specific fields.
+ //
+
+ if ( Flags & SS_BDC ) {
+
+ //
+ // If we've already have an account for this BDC,
+ // Warn that there are multiple accounts.
+ //
+
+ if ( ServerSession->SsFlags & SS_BDC ) {
+ LPWSTR MsgStrings[1];
+
+ NlPrint((NL_CRITICAL,
+ "NlInsertServerSession: %s: has multiple machine accounts.\n",
+ ServerSession->SsComputerName ));
+ MsgStrings[0] = ComputerName;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonDuplicateMachineAccounts,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 1 );
+
+ } else {
+ //
+ // Insert this entry at the front of the list of BDCs
+ //
+
+ InsertHeadList( &NlGlobalBdcServerSessionList,
+ &ServerSession->SsBdcList );
+ NlGlobalBdcServerSessionCount ++;
+ }
+
+ if ( Flags & SS_LM_BDC ) {
+ NlAssert( ServerSession->SsLmBdcAccountRid == 0 );
+ ServerSession->SsLmBdcAccountRid = AccountRid;
+ } else {
+ NlAssert( ServerSession->SsNtBdcAccountRid == 0 );
+ ServerSession->SsNtBdcAccountRid = AccountRid;
+ }
+
+
+ }
+
+ //
+ // Update the Server Session entry to reflect this new secure channel setup
+ //
+
+ ServerSession->SsCheck = 0;
+ ServerSession->SsSecureChannelType = NullSecureChannel;
+ ServerSession->SsNegotiatedFlags = 0;
+ ServerSession->SsTransportName = NULL;
+ ServerSession->SsFlags = ((USHORT) Flags) |
+ (ServerSession->SsFlags & SS_PERMANENT_FLAGS);
+
+ if ( AuthenticationSeed != NULL ) {
+ ServerSession->SsAuthenticationSeed = *AuthenticationSeed;
+ }
+
+ if ( AuthenticationResponse != NULL ) {
+ NlAssert( sizeof(*AuthenticationResponse) <= sizeof(ServerSession->SsSessionKey ));
+ RtlCopyMemory( &ServerSession->SsSessionKey,
+ AuthenticationResponse,
+ sizeof( *AuthenticationResponse ) );
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+ return STATUS_SUCCESS;
+}
+
+
+
+VOID
+NlFreeServerSession(
+ IN PSERVER_SESSION ServerSession
+ )
+/*++
+
+Routine Description:
+
+ Free the specified Server Session table entry.
+
+ This routine is called with the Server Session table locked.
+
+Arguments:
+
+ ServerSession - Specifies a pointer to the server session entry
+ to delete.
+
+Return Value:
+
+--*/
+{
+
+
+ //
+ // If someone has an outstanding pointer to this entry,
+ // delay the deletion for now.
+ //
+
+ if ( ServerSession->SsFlags & SS_LOCKED ) {
+ ServerSession->SsFlags |= SS_DELETE_ON_UNLOCK;
+ NlPrint((NL_SERVER_SESS,
+ "NlFreeServerSession: %s: Tried to free locked server session\n",
+ ServerSession->SsComputerName ));
+ return;
+ }
+
+ //
+ // If this entry represents a BDC account,
+ // don't delete the entry until the account is deleted.
+ //
+
+ if ( ServerSession->SsLmBdcAccountRid != 0 ||
+ ServerSession->SsNtBdcAccountRid != 0 ) {
+ NlPrint((NL_SERVER_SESS,
+ "NlFreeServerSession: %s: Didn't delete server session with BDC account.\n",
+ ServerSession->SsComputerName ));
+ return;
+ }
+
+ NlPrint((NL_SERVER_SESS,
+ "NlFreeServerSession: %s: Freed server session\n",
+ ServerSession->SsComputerName ));
+
+ //
+ // Delink the entry from the hash list.
+ //
+
+ RemoveEntryList( &ServerSession->SsHashList );
+
+ //
+ // Delink the entry from the sequential list.
+ //
+
+ RemoveEntryList( &ServerSession->SsSeqList );
+
+
+ //
+ // Handle special cleanup for the BDC_SERVER_SESSION
+ //
+
+ if ( ServerSession->SsFlags & SS_BDC ) {
+
+ //
+ // Remove the entry from the list of BDCs
+ //
+
+ RemoveEntryList( &ServerSession->SsBdcList );
+ NlGlobalBdcServerSessionCount --;
+
+ //
+ // Remove the entry from the list of pending BDCs
+ //
+
+ if ( ServerSession->SsFlags & SS_PENDING_BDC ) {
+ NlRemovePendingBdc( ServerSession );
+ }
+
+
+ //
+ // Clean up an sync context for this entry.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ NetpMemoryFree( ServerSession->SsSync );
+ }
+
+ }
+
+ //
+ // Delete the entry
+ //
+
+ NetpMemoryFree( ServerSession );
+
+}
+
+
+VOID
+NlUnlockServerSession(
+ IN PSERVER_SESSION ServerSession
+ )
+/*++
+
+Routine Description:
+
+ Unlock the specified Server Session table entry.
+
+Arguments:
+
+ ServerSession - Specifies a pointer to the server session entry to unlock.
+
+Return Value:
+
+--*/
+{
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Unlock the entry.
+ //
+
+ NlAssert( ServerSession->SsFlags & SS_LOCKED );
+ ServerSession->SsFlags &= ~SS_LOCKED;
+
+ //
+ // If someone wanted to delete the entry while we had it locked,
+ // finish the deletion.
+ //
+
+ if ( ServerSession->SsFlags & SS_DELETE_ON_UNLOCK ) {
+ NlFreeServerSession( ServerSession );
+
+ //
+ // Indicate activity from the BDC
+ //
+
+ } else if (ServerSession->SsFlags & SS_PENDING_BDC) {
+ (VOID) NtQuerySystemTime( &ServerSession->SsLastPulseTime );
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+}
+
+
+
+VOID
+NlFreeLmBdcServerSession(
+ IN ULONG ServerRid
+ )
+/*++
+
+Routine Description:
+
+ Delete the specified Server Account from the Server Session list.
+
+Arguments:
+
+ ServerRid - Rid of server to add to list.
+
+Return Value:
+
+ None
+
+--*/
+{
+ PSERVER_SESSION ServerSession;
+ PLIST_ENTRY ListEntry;
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Ensure the ServerSession Table is initialized.
+ //
+
+ if (NlGlobalServerSessionHashTable == NULL) {
+ return;
+ }
+
+ //
+ // Loop through the BDC list trying the find the right entry.
+ //
+
+ for ( ListEntry = NlGlobalBdcServerSessionList.Flink ;
+ ListEntry != &NlGlobalBdcServerSessionList ;
+ ListEntry = ListEntry->Flink) {
+
+
+ ServerSession = CONTAINING_RECORD( ListEntry, SERVER_SESSION, SsBdcList );
+
+ if ( ServerRid == ServerSession->SsLmBdcAccountRid ) {
+ break;
+ }
+
+ }
+
+ if ( ListEntry == &NlGlobalBdcServerSessionList ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ NlPrint((NL_CRITICAL,
+ "NlFreeLmBdcServerSession: %lx: Couldn't find"
+ " server session entry for this RID.\n",
+ ServerRid ));
+ return;
+ }
+
+ //
+ // Clear the Account Rid so the ServerSession entry will be deleted
+ //
+
+ ServerSession->SsLmBdcAccountRid = 0;
+
+ //
+ // Actually delete the entry.
+ //
+
+ NlFreeServerSession( ServerSession );
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+}
+
+
+
+VOID
+NlFreeNamedServerSession(
+ IN LPWSTR ComputerName,
+ IN BOOLEAN AccountBeingDeleted
+ )
+/*++
+
+Routine Description:
+
+ Frees the specified entry in the ServerSession Table.
+
+Arguments:
+
+ ComputerName - The name of the computer on the client side of the
+ secure channel.
+
+ AccountBeingDeleted - True to indicate that the account for this server
+ session is being deleted.
+
+Return Value:
+
+ An NT status code.
+
+--*/
+{
+ PSERVER_SESSION ServerSession;
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Find the entry to delete.
+ //
+
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if ( ServerSession == NULL ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ return;
+ }
+
+ //
+ // If the account is being deleted,
+ // clear the account RID to allow the session structure to be deleted.
+ //
+ // (We might be deleting an workstation or trusted domain account here
+ // but that doesn't make any difference. In those cases, the account rid
+ // is already zero.)
+ //
+
+ if ( AccountBeingDeleted ) {
+ ServerSession->SsNtBdcAccountRid = 0;
+ }
+
+ //
+ // Actually delete the entry.
+ //
+
+ NlFreeServerSession( ServerSession );
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+}
+
+
+
+VOID
+NlFreeServerSessionForAccount(
+ IN PUNICODE_STRING AccountName
+ )
+/*++
+
+Routine Description:
+
+ Frees the specified entry in the ServerSession Table.
+
+Arguments:
+
+ AccountName - The name of the Account describing trust relationship being
+ deleted.
+
+Return Value:
+
+ None
+
+--*/
+{
+ WCHAR ComputerName[CNLEN+2]; // Extra for $ and \0
+
+ //
+ // Convert account name to a computer name by stripping the trailing
+ // postfix.
+ //
+
+ if ( AccountName->Length + sizeof(WCHAR) > sizeof(ComputerName) ||
+ AccountName->Length < SSI_ACCOUNT_NAME_POSTFIX_LENGTH * sizeof(WCHAR)){
+ return;
+ }
+
+ RtlCopyMemory( ComputerName, AccountName->Buffer, AccountName->Length );
+ ComputerName[ AccountName->Length / sizeof(WCHAR) -
+ SSI_ACCOUNT_NAME_POSTFIX_LENGTH ] = L'\0';
+
+ //
+ // Free the named server session (if any)
+ //
+
+ NlFreeNamedServerSession( ComputerName, TRUE );
+
+}
+
+
+
+VOID
+NlServerSessionScavenger(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Scavenge the ServerSession Table.
+
+ For now, just clean up the SyncContext if a client doesn't use it
+ for a while.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY ListEntry;
+
+ //
+ // Find the next table entry that needs to be scavenged
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+
+ for ( ListEntry = NlGlobalServerSessionTable.Flink ;
+ ListEntry != &NlGlobalServerSessionTable ;
+ ) {
+
+ PSERVER_SESSION ServerSession;
+
+ ServerSession =
+ CONTAINING_RECORD(ListEntry, SERVER_SESSION, SsSeqList);
+
+
+ //
+ // Grab a pointer to the next entry before deleting this one
+ //
+
+ ListEntry = ListEntry->Flink;
+
+ //
+ // Increment the number of times this entry has been checked.
+ //
+
+ ServerSession->SsCheck ++;
+
+
+ //
+ // If this entry in the Server Session table has been around for many
+ // days without the client calling,
+ // free it.
+ //
+ // We wait several days before deleting an old entry. If an entry is
+ // deleted, the client has to rediscover us which may cause a lot of
+ // net traffic. After several days, that additional traffic isn't
+ // significant.
+ //
+
+ if (ServerSession->SsCheck > KILL_SESSION_TIME ) {
+
+ NlPrint((NL_SERVER_SESS,
+ "NlServerSessionScavenger: %s: Free Server Session.\n",
+ ServerSession->SsComputerName ));
+
+ NlFreeServerSession( ServerSession );
+
+
+ //
+ // If this entry in the Server Session table has timed out,
+ // Clean up the SAM resources.
+ //
+
+ } else if (ServerSession->SsCheck > MAX_WOC_INTERROGATE) {
+
+ //
+ // Clean up the SYNC context for this session freeing up
+ // the SAM resources.
+ //
+ // We shouldn't timeout if the ServerSession Entry is locked,
+ // but we'll be careful anyway.
+ //
+
+ if ( (ServerSession->SsFlags & SS_LOCKED) == 0 &&
+ ServerSession->SsFlags & SS_BDC ) {
+
+ if ( ServerSession->SsSync != NULL ) {
+
+ NlPrint((NL_SERVER_SESS,
+ "NlServerSessionScavenger: %s: Cleanup Sync context.\n",
+ ServerSession->SsComputerName ));
+
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ NetpMemoryFree( ServerSession->SsSync );
+ ServerSession->SsSync = NULL;
+ }
+ }
+
+
+ }
+
+ } // end while
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/ssiapi.c b/private/net/svcdlls/logonsrv/server/ssiapi.c
new file mode 100644
index 000000000..f3f5e33fa
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/ssiapi.c
@@ -0,0 +1,6401 @@
+/*++
+
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ ssiapi.c
+
+Abstract:
+
+ Authentication and replication API routines (server side).
+
+Author:
+
+ Cliff Van Dyke (cliffv) 28-Jun-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+
+#include <lmerr.h>
+#include <replutil.h> // PackSamXXX()
+#include <lsarepl.h> // PackLsa ..
+#include <nlsecure.h> // Security information
+#include <nlrepl.h> // I_NetGetAnyDc
+#include <ntlsa.h> // LsaOpenPolicy, etc
+#include <secobj.h> // NetpAccessCheck
+#include <ssiapi.h>
+#include <tstring.h> // IS_PATH_SEPARATOR ...
+
+#include <lsarpc.h>
+#include <lsaisrv.h>
+#include <loghours.h>
+
+//
+// Define the maximum number of deltas returned on any single call
+//
+// Theoretically, MaxNumDeltas should be some function of
+// PreferredMaximumLength. However, by the time you allow for
+// the large swing in PreferredMaximumLength allowed by the BDC replication
+// Governor and then not wanting this buffer to be ridiculously large
+// when the full 128K is asked for, we find that 1000 entries is always
+// a reasonable compromise.
+//
+
+#define MAX_DELTA_COUNT 1000
+
+//
+// Maximum number of deltas that can be generated by a single change log entry.
+//
+#define MAX_DELTAS_PER_CHANGELOG 4
+
+
+NTSTATUS
+NlVerifyWorkstation(
+ IN LPWSTR ServerName OPTIONAL
+)
+
+/*++
+
+Routine Description:
+
+ Check the validity of the ServerName.
+
+Arguments:
+
+ ServerName - Name of the server this code is executing on.
+
+Return Value:
+
+ The status of the operation
+
+--*/
+{
+
+ //
+ // Check the Servername to ensure he wants to talk to us.
+ //
+
+ if ( ServerName != NULL ) {
+
+ if ( IS_PATH_SEPARATOR(ServerName[0]) &&
+ IS_PATH_SEPARATOR(ServerName[1])) {
+ ServerName += 2;
+ }
+
+ if ( NlNameCompare( ServerName,
+ NlGlobalUnicodeComputerName,
+ NAMETYPE_COMPUTER ) != 0 ) {
+
+ return STATUS_INVALID_COMPUTER_NAME;
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+NetrServerReqChallenge(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientChallenge,
+ OUT PNETLOGON_CREDENTIAL ServerChallenge
+ )
+/*++
+
+Routine Description:
+
+ This is the server side of I_NetServerReqChallenge.
+
+ I_NetServerReqChallenge is the first of two functions used by a client
+ Netlogon service to authenticate with another Netlogon service.
+ (See I_NetServerAuthenticate below.)
+
+ This function passes a challenge to the DC and the DC passes a challenge
+ back to the caller.
+
+Arguments:
+
+ PrimaryName -- Supplies the name of the DC we wish to authenticate with.
+
+ ComputerName -- Name of the machine making the call.
+
+ ClientCredential -- 64 bit challenge supplied by the BDC or member server.
+
+ ServerCredential -- Receives 64 bit challenge from the PDC.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,
+ "NetrServerReqChallenge: ClientChallenge = %lx %lx\n",
+ ((DWORD *) (ClientChallenge))[0],
+ ((DWORD *) (ClientChallenge))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Compute ServerChallenge to pass back to requestor
+ //
+
+ NlComputeChallenge(ServerChallenge);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,
+ "NetrServerReqChallenge: ServerChallenge = %lx %lx\n",
+ ((DWORD *) (ServerChallenge))[0],
+ ((DWORD *) (ServerChallenge))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Add this entry into the server session table.
+ //
+ // Remember both challenges until the corresponding I_NetAuthenticate call.
+ // Notice that both challenges are not yet SessionKey-encrypted
+ //
+
+ Status = NlInsertServerSession(
+ ComputerName,
+ SS_CHALLENGE, // challenge in progress
+ 0, // No Account rid
+ ClientChallenge,
+ ServerChallenge );
+
+ //
+ // Common exit point
+ //
+
+Cleanup:
+
+ //
+ // If the request failed, be carefull to not leak authentication
+ // information.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+ RtlZeroMemory( ServerChallenge, sizeof(*ServerChallenge) );
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NetrServerAuthenticate2(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential,
+ IN OUT PULONG NegotiatedFlags
+ )
+/*++
+
+Routine Description:
+
+ This is the server side of I_NetServerAuthenticate
+
+ I_NetServerAuthenticate is the second of two functions used by a client
+ Netlogon service to authenticate with another Netlogon service.
+ (See I_NetServerReqChallenge above.) Both a SAM or UAS server authenticates
+ using this function.
+
+ This function passes a credential to the DC and the DC passes a credential
+ back to the caller.
+
+
+Arguments:
+
+ PrimaryName -- Supplies the name of the DC we wish to authenticate with.
+
+ AccountName -- Name of the Account to authenticate with.
+
+ SecureChannelType -- The type of the account being accessed. This field must
+ be set to UasServerSecureChannel to indicate a call from downlevel (LanMan
+ 2.x and below) BDC or member server.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ ClientCredential -- 64 bit credential supplied by the BDC or member server.
+
+ ServerCredential -- Receives 64 bit credential from the PDC.
+
+ NegotiatedFlags -- Specifies flags indicating what features the BDC supports.
+ Returns a subset of those flags indicating what features the PDC supports.
+ The PDC/BDC should ignore any bits that it doesn't understand.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NetrServerAuthenticate entered: " FORMAT_LPWSTR " on account "
+ FORMAT_LPWSTR " (Negot: %lx)\n",
+ ComputerName, AccountName, *NegotiatedFlags ));
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // If CompatibilityMode is off,
+ // disallow this function for downlevel servers.
+ //
+
+ if ( SecureChannelType == UasServerSecureChannel && !NlGlobalUasCompatibilityMode ) {
+
+ NlPrint((NL_CRITICAL,"NetrServerAuthenticate "
+ "from LM 2.x and not compatibility mode\n"));
+
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint((NL_CRITICAL,"NetrServerAuthenticate: "
+ "Error from NlVerifyWorkstation 0x%lx\n", Status));
+
+ goto Cleanup;
+ }
+
+ //
+ // Ensure that this machine account is valid.
+ // (Do everything but check the password.)
+ //
+
+ Status = NlCheckMachineAccount( AccountName, SecureChannelType );
+
+ if (!NT_SUCCESS( Status )) {
+
+ NlPrint((NL_CRITICAL,
+ "NetrServerAuthenticate: No machine account: "
+ FORMAT_LPWSTR " on account " FORMAT_LPWSTR "\n",
+ ComputerName, AccountName ));
+
+ //
+ // return more appropriate error
+ //
+
+ if ( SecureChannelType != UasServerSecureChannel &&
+ Status == STATUS_NO_SUCH_USER ) {
+
+ Status = STATUS_NO_TRUST_SAM_ACCOUNT;
+ }
+
+ goto Cleanup;
+ }
+
+ //
+ // Compute the NegotiatedFlags both sides support
+ //
+
+ *NegotiatedFlags &= NETLOGON_SUPPORTS_MASK;
+
+
+ //
+ // Authenticate the caller.
+ //
+
+ Status = NlAuthenticate( AccountName,
+ SecureChannelType,
+ ComputerName,
+ ClientCredential,
+ ServerCredential,
+ *NegotiatedFlags );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NetrServerAuthenticate: Bad password: " FORMAT_LPWSTR
+ " on account " FORMAT_LPWSTR "\n",
+ ComputerName, AccountName ));
+
+ //
+ // return more appropriate error
+ //
+
+ if ( SecureChannelType != UasServerSecureChannel &&
+ Status == STATUS_NO_SUCH_USER ) {
+
+ Status = STATUS_NO_TRUST_SAM_ACCOUNT;
+ }
+
+ goto Cleanup;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NetrServerAuthenticate returns Success: " FORMAT_LPWSTR
+ " on account " FORMAT_LPWSTR " (Negot: %lx)\n",
+ ComputerName, AccountName, *NegotiatedFlags ));
+
+ //
+ // Common exit point
+ //
+
+Cleanup:
+
+ //
+ // If the request failed, be carefull to not leak authentication
+ // information.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+ RtlZeroMemory( ServerCredential, sizeof(*ServerCredential) );
+ }
+
+ //
+ // write event log
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ LPWSTR MsgStrings[3];
+
+ MsgStrings[0] = ComputerName;
+ MsgStrings[1] = AccountName;
+
+ if (Status == STATUS_NO_TRUST_SAM_ACCOUNT) {
+
+ NlpWriteEventlog( NELOG_NetlogonServerAuthNoTrustSamAccount,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) & Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 );
+
+ } else {
+
+ MsgStrings[2] = (LPWSTR) Status;
+
+ NlpWriteEventlog( NELOG_NetlogonServerAuthFailed,
+ EVENTLOG_ERROR_TYPE,
+ (LPBYTE) & Status,
+ sizeof(Status),
+ MsgStrings,
+ 3 | LAST_MESSAGE_IS_NTSTATUS );
+ }
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NetrServerAuthenticate(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_CREDENTIAL ClientCredential,
+ OUT PNETLOGON_CREDENTIAL ServerCredential
+ )
+/*++
+
+Routine Description:
+
+
+ This is the NT 1.0 version of I_NetServerAuthenicate2.
+ I_NetServerAuthenticate2 was introduced in NT 3.5 (December 1993).
+
+Arguments:
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+
+{
+ ULONG NegotiatedFlags = 0;
+
+ return NetrServerAuthenticate2( PrimaryName,
+ AccountName,
+ SecureChannelType,
+ ComputerName,
+ ClientCredential,
+ ServerCredential,
+ &NegotiatedFlags );
+
+}
+
+
+NTSTATUS
+NetrServerPasswordSet(
+ IN LPWSTR PrimaryName OPTIONAL,
+ IN LPWSTR AccountName,
+ IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword
+ )
+/*++
+
+Routine Description:
+
+ This function is used to change the password for the account being
+ used to maintain a secure channel. This function can only be called
+ by a server which has previously authenticated with a DC by calling
+ I_NetServerAuthenticate.
+
+ The call is made differently depending on the account type:
+
+ * A domain account password is changed from the PDC in the
+ trusting domain. The I_NetServerPasswordSet call is made to any
+ DC in the trusted domain.
+
+ * A server account password is changed from the specific server.
+ The I_NetServerPasswordSet call is made to the PDC in the domain
+ the server belongs to.
+
+ * A workstation account password is changed from the specific
+ workstation. The I_NetServerPasswordSet call is made to a DC in
+ the domain the server belongs to.
+
+ For domain accounts and workstation accounts, the server being called
+ may be a BDC in the specific domain. In that case, the BDC will
+ validate the request and pass it on to the PDC of the domain using
+ the server account secure channel. If the PDC of the domain is
+ currently not available, the BDC will return STATUS_NO_LOGON_SERVERS. Since
+ the UasNewPassword is passed encrypted by the session key, such a BDC
+ will decrypt the UasNewPassword using the original session key and
+ will re-encrypt it with the session key for its session to its PDC
+ before passing the request on.
+
+ This function uses RPC to contact the DC named by PrimaryName.
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to change the servers password
+ with. NULL indicates this call is a local call being made on
+ behalf of a UAS server by the XACT server.
+
+ AccountName -- Name of the account to change the password for.
+
+ AccountType -- The type of account being accessed. This field must
+ be set to UasServerAccount to indicate a call from a downlevel
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ UasNewPassword -- The new password for the server. This
+ Password is generated by automatic means using
+ random number genertaor seeded with the current Time
+ It is assumed that the machine generated password
+ was used as key to encrypt STD text and "sesskey"
+ obtained via Challenge/Authenticate sequence was
+ used to further encrypt it before passing to this api.
+ i.e. UasNewPassword = E2(E1(STD_TXT, PW), SK)
+
+Return Value:
+
+ NT status code.
+
+ STATUS_WRONG_PASSWORD - Indicates the server refuses to allow the password
+ to be changed. The client should continue to use the prior password.
+
+--*/
+{
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession;
+ LM_OWF_PASSWORD OwfPassword;
+ SAMPR_HANDLE UserHandle;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NetrServerPasswordSet: Comp=" FORMAT_LPWSTR
+ " Acc=" FORMAT_LPWSTR " Entered\n",
+ ComputerName,
+ AccountName ));
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+
+ //
+ // Get the Session key for this session.
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // decrypt the sessionkey from password
+ // i.e. OwfPassword = D2((E2(E1(STD_TXT, PW), SK)), SK)
+ // = E1(STD_TXT, PW)
+ // OwfPassword = One Way Function of the cleartext password.
+ //
+
+ if (Status = RtlDecryptLmOwfPwdWithLmOwfPwd(
+ UasNewPassword,
+ (PLM_OWF_PASSWORD) &ServerSession->SsSessionKey,
+ &OwfPassword )) {
+
+ UNLOCK_SERVER_SESSION_TABLE();
+ goto Cleanup;
+ }
+
+
+ //
+ // now verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ goto Cleanup;
+ }
+
+ //
+ // Check if we're refusing password changes
+ //
+ // Only refuse password changes if the client is a workstation and the
+ // client supports password changing.
+ //
+ // If this is a PDC and the request was passed-through a BDC,
+ // we don't have access to the NETLOGON_SUPPORTS flag of the workstation.
+ // As such, we don't return STATUS_WRONG_PASSWORD here. Rather, the
+ // solution is to set 'RefusePasswordChange' on all of the BDCs so they'll
+ // catch the password change attempt before passing through.
+ //
+
+ if ( NlGlobalRefusePasswordChangeParameter &&
+ ServerSession->SsSecureChannelType == WorkstationSecureChannel &&
+ (ServerSession->SsNegotiatedFlags & NETLOGON_SUPPORTS_REFUSE_CHANGE_PWD) != 0 ){
+
+ Status = STATUS_WRONG_PASSWORD;
+ UNLOCK_SERVER_SESSION_TABLE();
+ goto Cleanup;
+ }
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // If this machine is a BDC, just pass the request on to the PDC.
+ //
+
+ if ( NlGlobalRole == RoleBackup ) {
+ ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword;
+ NETLOGON_AUTHENTICATOR OurAuthenticator;
+ NETLOGON_AUTHENTICATOR OurReturnAuthenticator;
+
+ //
+ // Become a Writer of the ClientSession.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NetrServerPasswordSet: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlResetWriterClientSession( NlGlobalClientSession );
+ Status = NlGlobalClientSession->CsConnectionStatus;
+ goto Cleanup;
+ }
+
+ //
+ // Encrypt the password again with the session key.
+ // The PDC will decrypt it on the other side.
+ //
+
+ Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
+ &OwfPassword,
+ (PNT_OWF_PASSWORD) &NlGlobalClientSession->CsSessionKey,
+ &SessKeyEncrPassword) ;
+
+ if ( !NT_SUCCESS( Status )) {
+ NlPrint((NL_CRITICAL,
+ "NetrServerPasswordSet: "
+ "Cannot RtlEncryptNtOwfPwdWithNtOwfPed %lX\n",
+ Status));
+ NlResetWriterClientSession( NlGlobalClientSession );
+ // Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+
+ //
+ // Build the Authenticator for this request to the PDC.
+ //
+
+ NlBuildAuthenticator(
+ &NlGlobalClientSession->CsAuthenticationSeed,
+ &NlGlobalClientSession->CsSessionKey,
+ &OurAuthenticator);
+
+
+ //
+ // Change the password on the machine our connection is to.
+ //
+
+ Status = NlStartApiClientSession( NlGlobalClientSession, TRUE );
+
+ if ( NT_SUCCESS(Status) ) {
+ Status = I_NetServerPasswordSet( NlGlobalClientSession->CsUncServerName,
+ AccountName,
+ AccountType,
+ NlGlobalUnicodeComputerName,
+ &OurAuthenticator,
+ &OurReturnAuthenticator,
+ &SessKeyEncrPassword);
+ }
+
+ // NOTE: This call may drop the secure channel behind our back
+ (VOID) NlFinishApiClientSession( NlGlobalClientSession, TRUE );
+
+
+ //
+ // Now verify primary's authenticator and update our seed
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ||
+ !NlUpdateSeed( &NlGlobalClientSession->CsAuthenticationSeed,
+ &OurReturnAuthenticator.Credential,
+ &NlGlobalClientSession->CsSessionKey) ) {
+ Status = STATUS_TRUSTED_DOMAIN_FAILURE;
+ NlSetStatusClientSession( NlGlobalClientSession,
+ STATUS_ACCESS_DENIED );
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+
+ if ( !NT_SUCCESS( Status )) {
+ // Status = STATUS_NO_LOGON_SERVERS;
+ NlResetWriterClientSession( NlGlobalClientSession );
+ goto Cleanup;
+ }
+
+ NlResetWriterClientSession( NlGlobalClientSession );
+
+
+ //
+ // If this machine is a PDC,
+ // do the request locally.
+ //
+
+ } else {
+ SAMPR_USER_INFO_BUFFER UserInfo;
+
+ //
+ // now get the requestor's current password
+ //
+
+ //
+ // Open the user that represents this server.
+ //
+
+ Status = NlSamOpenNamedUser( AccountName, &UserHandle, NULL );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ //
+ // If the authentication is from an NT client,
+ // use the NT OWF Password,
+ // otherwise, use the LM OWF password.
+ //
+
+ UserInfo.Internal1.PasswordExpired = FALSE;
+ if ( AccountType == UasServerSecureChannel ) {
+ UserInfo.Internal1.NtPasswordPresent = FALSE;
+ UserInfo.Internal1.LmPasswordPresent = TRUE;
+ UserInfo.Internal1.EncryptedLmOwfPassword =
+ *((PENCRYPTED_LM_OWF_PASSWORD)(&OwfPassword));
+
+ } else {
+ UserInfo.Internal1.LmPasswordPresent = FALSE;
+ UserInfo.Internal1.NtPasswordPresent = TRUE;
+ UserInfo.Internal1.EncryptedNtOwfPassword =
+ *((PENCRYPTED_NT_OWF_PASSWORD)(&OwfPassword));
+ }
+
+ Status = SamrSetInformationUser(
+ UserHandle,
+ UserInternal1Information,
+ &UserInfo );
+
+ if (!NT_SUCCESS(Status)) {
+ goto Cleanup;
+ }
+
+ (VOID) SamrCloseHandle( &UserHandle );
+
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Common exit point
+ //
+
+Cleanup:
+
+ //
+ // If the request failed, be carefull to not leak authentication
+ // information.
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ) {
+ RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
+ }
+
+ NlPrint((NL_SESSION_SETUP,
+ "NetrServerPasswordSet: Comp=" FORMAT_LPWSTR
+ " Acc=" FORMAT_LPWSTR " returns 0x%lX\n",
+ ComputerName,
+ AccountName,
+ Status ));
+
+ return Status;
+}
+
+
+NTSTATUS
+NlPackSerialNumber (
+ IN PLARGE_INTEGER SerialNumber,
+ IN OUT PNETLOGON_DELTA_ENUM Delta,
+ IN LPDWORD BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Pack the specified serial number as a delta.
+
+Arguments:
+
+ SerialNumber - The serial number to pack.
+
+ Delta: pointer to the delta structure where the new delta will
+ be returned.
+
+ DBInfo: pointer to the database info structure.
+
+ BufferSize: size of MIDL buffer that is consumed for this delta is
+ returned here.
+
+ SessionInfo: Info describing BDC that's calling us
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ PNLPR_MODIFIED_COUNT DeltaSerialNumberSkip;
+ PSAMPR_USER_INFO_BUFFER UserAll = NULL;
+
+ //
+ // Only pack this delta if the BDC expects it.
+ //
+
+ NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG);
+ UNREFERENCED_PARAMETER(SessionInfo);
+
+ NlPrint(( NL_SYNC_MORE,
+ "Packing skip to serial number delta: %lx %lx\n",
+ SerialNumber->HighPart,
+ SerialNumber->LowPart ));
+
+ *BufferSize = 0;
+
+ Delta->DeltaType = SerialNumberSkip;
+ Delta->DeltaID.Rid = 0;
+ Delta->DeltaUnion.DeltaSerialNumberSkip = NULL;
+
+ //
+ // Allocate a buffer to return to the caller.
+ //
+
+ DeltaSerialNumberSkip = (PNLPR_MODIFIED_COUNT)
+ MIDL_user_allocate( sizeof(*DeltaSerialNumberSkip) );
+
+ if (DeltaSerialNumberSkip == NULL) {
+ return STATUS_NO_MEMORY;
+ }
+
+ *BufferSize += sizeof(*DeltaSerialNumberSkip);
+
+ //
+ // Copy the serial number into the buffer.
+ //
+
+ RtlCopyMemory( &DeltaSerialNumberSkip->ModifiedCount,
+ SerialNumber,
+ sizeof( DeltaSerialNumberSkip->ModifiedCount ) );
+
+ Delta->DeltaUnion.DeltaSerialNumberSkip = DeltaSerialNumberSkip;
+
+
+ //
+ // All Done
+ //
+
+ return STATUS_SUCCESS;
+}
+
+
+
+NTSTATUS
+NlPackSingleDelta (
+ IN PCHANGELOG_ENTRY ChangeLogEntry,
+ IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
+ OUT LPDWORD BufferConsumed,
+ IN PSESSION_INFO SessionInfo,
+ IN BOOLEAN ReturnSerialNumberDeltas
+ )
+/*++
+
+Routine Description:
+
+ Pack the deltas for a single change log entry.
+
+Arguments:
+
+ ChangeLogEntry - The Change Log Entry describing the account to pack.
+
+ DeltaArray - Describes the array of deltas. The appropriate deltas will
+ be added to the end of this array. The caller has guaranteed that
+ that is room for at least MAX_DELTAS_PER_CHANGELOG - 1
+ deltas to be added to the array.
+
+ BufferConsumed - returns the size of MIDL buffer that is consumed for the
+ returned deltas
+
+ SessionInfo: Info describing BDC that's calling us
+
+ ReturnSerialNumberDeltas -- True if serial number deltas should be returned
+ when needed.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PDB_INFO DBInfo;
+ DWORD BufferSize;
+
+ UNICODE_STRING UnicodeSecretName;
+ LPWSTR AccountName;
+ PSID Sid;
+
+ //
+ // Initialization
+ //
+
+ DBInfo = &NlGlobalDBInfoArray[ChangeLogEntry->DBIndex];
+ *BufferConsumed = 0;
+
+ //
+ // Macro to account for another delta array entry being consumed/returned
+ //
+
+# define MoveToNextDeltaArrayEntry( _BufferSize ) \
+ *BufferConsumed += (sizeof(NETLOGON_DELTA_ENUM) + _BufferSize); \
+ (DeltaArray->CountReturned)++;
+
+ //
+ // Put the data for the changelog entry into the user's buffer.
+ //
+
+ switch ( ChangeLogEntry->DeltaType ) {
+ case AddOrChangeDomain:
+ Status = NlPackSamDomain(
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ break;
+
+ case AddOrChangeGroup:
+ Status = NlPackSamGroup( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ break;
+
+ case ChangeGroupMembership: +
+ Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ break;
+
+ case RenameGroup:
+
+ //
+ // we treat the rename as three deltas.
+ // 1. AddorChangeGroup delta.
+ // Backup deletes the account with old name and creates
+ // an account with new name.
+ //
+ // 2. Delta to tell the BDC that delta (3) below is for the
+ // same serial number as delta (1) above.
+ //
+ // 3. ChangeGroupMembership delta.
+ // Backup readds all members to new group.
+ //
+
+ Status = NlPackSamGroup( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ if( !NT_SUCCESS( Status ) ) {
+ break;
+ }
+
+ MoveToNextDeltaArrayEntry( BufferSize );
+
+
+ if ( ReturnSerialNumberDeltas ) {
+
+ Status = NlPackSerialNumber(
+ &ChangeLogEntry->SerialNumber,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ &BufferSize,
+ SessionInfo );
+
+ if( !NT_SUCCESS( Status ) ) {
+ break;
+ }
+
+ MoveToNextDeltaArrayEntry( BufferSize );
+ }
+
+ Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ break;
+
+ case AddOrChangeUser:
+ case RenameUser:
+ Status = NlPackSamUser( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ break;
+
+ case AddOrChangeAlias:
+ Status = NlPackSamAlias( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ break;
+
+ case ChangeAliasMembership:
+ Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ break;
+
+ case RenameAlias:
+
+ //
+ // we treat the rename as two deltas.
+ // 1. AddorChangeAlias delta.
+ // Backup deletes the account with old name and creates
+ // an account with new name.
+ //
+ // 2. Delta to tell the BDC that delta (3) below is for the
+ // same serial number as delta (1) above.
+ //
+ // 3. ChangeAliasMembership delta.
+ // Backup readds all members to new alias.
+ //
+
+ Status = NlPackSamAlias( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ if( !NT_SUCCESS( Status ) ) {
+ break;
+ }
+
+ MoveToNextDeltaArrayEntry( BufferSize );
+
+ if ( ReturnSerialNumberDeltas ) {
+
+ Status = NlPackSerialNumber(
+ &ChangeLogEntry->SerialNumber,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ &BufferSize,
+ SessionInfo );
+
+ if( !NT_SUCCESS( Status ) ) {
+ break;
+ }
+
+ MoveToNextDeltaArrayEntry( BufferSize );
+ }
+
+ Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ break;
+
+ case AddOrChangeLsaPolicy:
+
+ Status = NlPackLsaPolicy(
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ break;
+
+ case AddOrChangeLsaTDomain:
+
+ NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ Status = NlPackLsaTDomain(
+ (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)),
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ break;
+
+ case AddOrChangeLsaAccount:
+
+ NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ Status = NlPackLsaAccount(
+ (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)),
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ break;
+
+ case AddOrChangeLsaSecret:
+
+ NlAssert( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED );
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ RtlInitUnicodeString(
+ &UnicodeSecretName,
+ (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)) );
+
+ Status = NlPackLsaSecret(
+ &UnicodeSecretName,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ break;
+
+ case DeleteGroup:
+ case DeleteGroupByName:
+ case DeleteUser:
+ case DeleteUserByName:
+
+ //
+ // If this is an NT 3.5 BDC,
+ // send the account name upon account deletion.
+
+ if ( ReturnSerialNumberDeltas ) {
+
+ //
+ // Send the NT 3.5 BDC a special delta type indicating the
+ // Name is attached.
+ //
+ if ( ChangeLogEntry->DeltaType == DeleteGroup ) {
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ DeleteGroupByName;
+ } else if ( ChangeLogEntry->DeltaType == DeleteUser ) {
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ DeleteUserByName;
+ } else {
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ ChangeLogEntry->DeltaType;
+ }
+
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid =
+ ChangeLogEntry->ObjectRid;
+
+
+ //
+ // Add the account name to the entry.
+ //
+
+ NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED);
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ BufferSize = (wcslen(
+ (LPWSTR) ((LPBYTE)ChangeLogEntry +
+ sizeof(CHANGELOG_ENTRY))) + 1 ) *
+ sizeof(WCHAR);
+
+ AccountName = (LPWSTR) MIDL_user_allocate( BufferSize );
+
+ if (AccountName == NULL) {
+ Status = STATUS_NO_MEMORY;
+ break;
+ }
+
+ wcscpy( AccountName,
+ (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
+
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].
+ DeltaUnion.DeltaDeleteGroup =
+ MIDL_user_allocate(sizeof(struct _NETLOGON_DELTA_DELETE));
+
+ if ((DeltaArray->Deltas)[DeltaArray->CountReturned].
+ DeltaUnion.DeltaDeleteGroup == NULL ) {
+ MIDL_user_free(AccountName);
+ Status = STATUS_NO_MEMORY;
+ break;
+ }
+
+ INIT_PLACE_HOLDER( (DeltaArray->Deltas)[DeltaArray->CountReturned].
+ DeltaUnion.DeltaDeleteGroup );
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].
+ DeltaUnion.DeltaDeleteGroup->AccountName = AccountName;
+
+ break; // out of switch
+ }
+
+ /* Drop through to handle NT 1.0 case. */
+
+ case DeleteAlias:
+
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ ChangeLogEntry->DeltaType;
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid =
+ ChangeLogEntry->ObjectRid;
+
+ BufferSize = 0;
+
+ break;
+
+ case DeleteLsaTDomain:
+ case DeleteLsaAccount:
+
+ NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ BufferSize =
+ RtlLengthSid( (PSID)((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
+
+ Sid = (PSID) MIDL_user_allocate( BufferSize );
+
+ if( Sid == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ break;
+ }
+
+ Status = RtlCopySid (
+ BufferSize,
+ Sid,
+ (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
+
+ if( !NT_SUCCESS( Status ) ) {
+ MIDL_user_free( Sid );
+ break;
+ }
+
+
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ ChangeLogEntry->DeltaType;
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Sid =
+ Sid;
+
+ break;
+
+ case DeleteLsaSecret:
+
+ NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED);
+
+ if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ BufferSize = (wcslen(
+ (LPWSTR) ((LPBYTE)ChangeLogEntry +
+ sizeof(CHANGELOG_ENTRY))) + 1 ) *
+ sizeof(WCHAR);
+
+ AccountName = (LPWSTR) MIDL_user_allocate( BufferSize );
+
+ if (AccountName == NULL) {
+ Status = STATUS_NO_MEMORY;
+ break;
+ }
+
+ wcscpy( AccountName,
+ (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
+
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
+ ChangeLogEntry->DeltaType;
+ (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Name =
+ AccountName;
+
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL, "NlPackSingleDelta: Invalid delta type in change log\n"));
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ if ( NT_SUCCESS(Status) ) {
+ MoveToNextDeltaArrayEntry( BufferSize );
+ }
+
+ return Status;
+#undef MoveToNextDeltaArrayEntry
+}
+
+
+NTSTATUS
+NetrDatabaseDeltas (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PNLPR_MODIFIED_COUNT NlDomainModifiedCount,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a NTLANMAN BDC or SAM member server to
+ request SAM-style account delta information from a SAM PDC. This
+ function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+ This function returns a list of deltas. A delta describes an
+ individual domain, user or group and all of the field values for that
+ object. The PDC maintains a list of deltas not including all of the
+ field values for that object. Rather, the PDC retrieves the field
+ values from SAM and returns those values from this call. The PDC
+ optimizes the data returned on this call by only returning the field
+ values for a particular object once on a single invocation of this
+ function. This optimizes the typical case where multiple deltas
+ exist for a single object (e.g., an application modified many fields
+ of the same user during a short period of time using different calls
+ to the SAM service).
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the deltas from.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ DatabaseID -- Identifies the databse for which the deltas are requested.
+ For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
+ databases may be defined later.
+
+ NlDomainModifiedCount -- Specifies the DomainModifiedCount of the
+ last delta retrieved by the server. Returns the
+ DomainModifiedCount of the last delta returned from the PDC
+ on this call.
+
+ Deltas -- Receives a pointer to a buffer where the information is
+ placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and
+ should call I_NetDataSync to do a full synchronization with
+ the PDC.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+
+--*/
+{
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession = NULL;
+ PCHANGELOG_ENTRY ChangeLogEntry = NULL;
+ BOOLEAN PackThisEntry = TRUE;
+
+ BOOL ChangelogLocked = FALSE;
+
+ PDB_INFO DBInfo;
+ LARGE_INTEGER RunningSerialNumber;
+ LARGE_INTEGER PackedSerialNumber;
+ LARGE_INTEGER OriginalSerialNumber;
+
+ DWORD BufferConsumed = 0;
+ DWORD BufferSize = 0;
+
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
+
+
+ SESSION_INFO SessionInfo;
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+ STARTSSIAPITIMER;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Initialization
+ //
+ if ( DatabaseID >= NUM_DBS ) {
+ return STATUS_INVALID_LEVEL;
+ }
+
+ *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
+
+ if( DeltaArray == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ DeltaArray->CountReturned = 0;
+ DeltaArray->Deltas = NULL;
+ SessionInfo.NegotiatedFlags = 0;
+
+
+ DBInfo = &NlGlobalDBInfoArray[DatabaseID];
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+ RtlCopyMemory( &RunningSerialNumber,
+ &NlDomainModifiedCount->ModifiedCount,
+ sizeof(RunningSerialNumber));
+
+ OriginalSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
+ PackedSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseDeltas: " FORMAT_LPWSTR " partial sync called by " FORMAT_LPWSTR
+ " SerialNumber:%lx %lx.\n",
+ DBInfo->DBName,
+ ComputerName,
+ RunningSerialNumber.HighPart,
+ RunningSerialNumber.LowPart ));
+
+ //
+ // Retrieve the requestor's entry to get sessionkey
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ // Don't log this event since it happens in nature after a reboot
+ // or after we scavenge the server session.
+ goto CleanupNoEventlog;
+ }
+
+ //
+ // Allow this call only on ServerSecureChannel.
+ //
+
+ if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: authentication failed.\n" ));
+
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Prevent entry from being deleted, but drop the global lock.
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: Concurrent call detected.\n" ));
+
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+ ServerSession->SsFlags |= SS_LOCKED;
+
+ SessionInfo.SessionKey = ServerSession->SsSessionKey;
+ SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+ //
+ // If the BDC is in sync,
+ // simply return.
+ //
+
+ LOCK_CHANGELOG();
+ ChangelogLocked = TRUE;
+
+ if ( RunningSerialNumber.QuadPart ==
+ NlGlobalChangeLogDesc.SerialNumber[DatabaseID].QuadPart ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ //
+ // Get a copy of the appropriate entry in the change_log.
+ // Note that the record_id contains last record received by client.
+ //
+
+ if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry(
+ &NlGlobalChangeLogDesc,
+ RunningSerialNumber,
+ DBInfo->DBIndex,
+ NULL ))== NULL) {
+
+ //
+ // Handle the case where the BDC has more recent changes than we do.
+ //
+ // Just return our newest change log entry with the same promotion count.
+ // The BDC will realize what's going on and un-do its newer changes.
+ //
+ // Only do this if our PromotionCount is greater than the BDCs. If
+ // our promotion count is equal to that of the BDC, either our change log
+ // has wrapped, or the BDC is royally confused.
+ //
+ // Don't be tempted to return a change log entry with an
+ // older promotion count. We'd have no way of knowing which delta
+ // to actually return to the caller.
+ //
+
+ if ( ((NlGlobalChangeLogDesc.SerialNumber[DatabaseID].HighPart &
+ NlGlobalChangeLogPromotionMask) >
+ (RunningSerialNumber.HighPart & NlGlobalChangeLogPromotionMask)) &&
+ (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO) ) {
+
+ ChangeLogEntry = NlFindPromotionChangeLogEntry(
+ &NlGlobalChangeLogDesc,
+ RunningSerialNumber,
+ DBInfo->DBIndex );
+
+ //
+ // Don't actually pack this change log entry. We've found it
+ // so we can pack a "serial number" delta. But the BDC already
+ // has this particular change.
+ //
+
+ PackThisEntry = FALSE;
+ }
+
+ if ( ChangeLogEntry == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "NetrDatabaseDeltas: "
+ "delta not found in cache, returning full required.\n" ));
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ } else {
+ NlPrint((NL_SYNC, "NetrDatabaseDeltas: BDC more recent than PDC (recovering).\n" ));
+ }
+ }
+
+ UNLOCK_CHANGELOG();
+ ChangelogLocked = FALSE;
+
+ //
+ // Allocate memory for delta buffer.
+ //
+
+ DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+ if( DeltaArray->Deltas == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaArray->Deltas,
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+
+ //
+ // Loop packing deltas as long as there is room for more deltas
+ //
+ // In some cases we pack multiple deltas on the wire for one entry in the
+ // change log, we want to ensure that all of these deltas are sent to
+ // the BDC on a single call.
+ //
+
+ while ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG <= MAX_DELTA_COUNT ) {
+
+ //
+ // If the serial number of the delta being packed isn't the one
+ // expected by the BDC, tell the BDC what the serial number is.
+ //
+
+ if ( ChangeLogEntry->SerialNumber.QuadPart !=
+ PackedSerialNumber.QuadPart + 1 ) {
+
+ if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG){
+
+ Status = NlPackSerialNumber(
+ &ChangeLogEntry->SerialNumber,
+ &((DeltaArray->Deltas)
+ [DeltaArray->CountReturned]),
+ &BufferSize,
+ &SessionInfo );
+ if( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+ BufferConsumed += BufferSize;
+ DeltaArray->CountReturned ++;
+
+ //
+ // If we're not really going to pack the entry,
+ // pretend that we already have.
+ //
+ if ( !PackThisEntry) {
+ PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
+ }
+ }
+
+ }
+
+
+ if ( PackThisEntry ) {
+
+ //
+ // Put the data for the changelog entry into the user's buffer.
+ //
+
+ Status = NlPackSingleDelta( ChangeLogEntry,
+ DeltaArray,
+ &BufferSize,
+ &SessionInfo,
+ (BOOLEAN)((SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG) != 0) );
+
+ //
+ // If we successfully put the delta into the delta array,
+ // do the bookwork
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ BufferConsumed += BufferSize;
+
+ PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
+
+ NlPrint((NL_SYNC_MORE,
+ "NetrDatabaseDeltas: Modified count of the "
+ "packed record: %lx %lx\n",
+ ChangeLogEntry->SerialNumber.HighPart,
+ ChangeLogEntry->SerialNumber.LowPart ));
+
+
+ //
+ // In the case where an user/group/alias record was
+ // added and deleted before the delta was made we will
+ // trace the change log and see there is correpondance
+ // delete log. If we found one then ignore this delta
+ // and proceed to the next delta. If we couldn't find
+ // one then return error STATUS_SYNCHRONIZATION_REQUIRED.
+ //
+
+ } else if ( IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) {
+
+ if( !NlRecoverChangeLog(ChangeLogEntry) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NetrDatabaseDeltas: "
+ "object not found in database, returning full "
+ "required (%lx).\n", Status ));
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+
+ IF_DEBUG( BREAKPOINT ) {
+ NlAssert( FALSE );
+ }
+
+ goto Cleanup;
+
+ } else {
+
+ //
+ // We found a delete delta, so ignore the original delta.
+ //
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // All other errors are fatal
+ //
+
+ } else {
+ goto Cleanup;
+ }
+ }
+
+ PackThisEntry = TRUE;
+
+
+ //
+ // Free up used temp. record
+ //
+
+ RunningSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
+ NetpMemoryFree(ChangeLogEntry);
+ ChangeLogEntry = NULL;
+
+ //
+ // If we've returned all the entries, we're all done.
+ //
+
+ LOCK_CHANGELOG();
+ ChangelogLocked = TRUE;
+
+ if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry(
+ &NlGlobalChangeLogDesc,
+ RunningSerialNumber,
+ DBInfo->DBIndex,
+ NULL )) == NULL) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ UNLOCK_CHANGELOG();
+ ChangelogLocked = FALSE;
+
+
+ //
+ // Don't return more data to the caller than he wants.
+ //
+
+ if( BufferConsumed >= PreferredMaximumLength) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ //
+ // If we're debugging replication, return only one change to the caller.
+ //
+#if DBG
+ if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+#endif // DBG
+
+
+ //
+ // If the service is going down, stop packing deltas and
+ // return to the caller.
+ //
+
+ if( NlGlobalTerminate ) {
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseDeltas is asked to return "
+ "when the service is going down.\n"));
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ }
+
+ Status = STATUS_MORE_ENTRIES;
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ LPWSTR MsgStrings[2];
+
+ MsgStrings[0] = ComputerName;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncCallFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+
+ } else {
+
+ //
+ // Log the successful replication only if deltas have been returned
+ // to the caller.
+ //
+ if ( DeltaArray->CountReturned != 0 ) {
+ LPWSTR MsgStrings[2];
+ WCHAR CountBuffer[20]; // random size
+
+ MsgStrings[0] = ComputerName;
+
+ ultow( DeltaArray->CountReturned, CountBuffer, 10);
+ MsgStrings[1] = CountBuffer;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncCallSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 2 );
+ }
+
+ }
+
+
+ //
+ // Free up locally allocated resources.
+ //
+
+CleanupNoEventlog:
+
+ //
+ // Copy the serial number back to the caller
+ //
+
+ if ( NT_SUCCESS(Status)) {
+
+ RtlCopyMemory( &NlDomainModifiedCount->ModifiedCount,
+ &PackedSerialNumber,
+ sizeof(PackedSerialNumber));
+
+
+ //
+ // If this is an NT1.0 BDC,
+ // Only remember the latest Serial Number it asked for, AND
+ // force it the call back once it has updated the SerialNumber
+ // so we know what that serial number is.
+ //
+ // NT 3.5 BDCs "persistently" try to update their database to the
+ // PDCs version once they get a pulse indicating their database is
+ // out of date.
+ //
+
+ if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_PERSISTENT_BDC) == 0 ) {
+
+ //
+ // Use the SerialNumber the BDC originally passed us.
+ //
+
+ PackedSerialNumber.QuadPart = OriginalSerialNumber.QuadPart;
+
+ //
+ // If we're returning any deltas at all,
+ // force the BDC to call us back.
+ //
+
+ if ( Status == STATUS_SUCCESS && DeltaArray->CountReturned != 0 ) {
+ Status = STATUS_MORE_ENTRIES;
+ }
+
+ }
+
+ //
+ // If we weren't successful,
+ // Don't return any deltas.
+ //
+
+ } else {
+ if ( DeltaArray->Deltas != NULL ) {
+ NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
+ DeltaArray->Deltas = NULL;
+ }
+ DeltaArray->CountReturned = 0;
+
+ }
+
+ if ( ChangelogLocked ) {
+ UNLOCK_CHANGELOG();
+ }
+
+ if( ChangeLogEntry != NULL) {
+ NetpMemoryFree( ChangeLogEntry );
+ }
+
+ //
+ // Unlock the server session entry if we've locked it.
+ //
+
+ if ( ServerSession != NULL ) {
+
+ //
+ // If we are successfully returning these deltas to the BDC,
+ // update our tables to reflect the changes.
+ //
+
+ if ( Status == STATUS_SUCCESS ) {
+ NlPrimaryAnnouncementFinish( ServerSession,
+ DatabaseID,
+ &PackedSerialNumber );
+
+ }
+ NlUnlockServerSession( ServerSession );
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseDeltas: " FORMAT_LPWSTR " returning (0x%lx) to "
+ FORMAT_LPWSTR "\n",
+ DBInfo->DBName,
+ Status,
+ ComputerName ));
+
+ STOPSSIAPITIMER;
+
+ NlPrint((NL_REPL_TIME,"NetrDatabaseDeltas Time:\n"));
+ PRINTSSIAPITIMER;
+
+ return Status;
+
+}
+
+
+
+NTSTATUS
+NlSyncSamDatabase(
+ IN PSERVER_SESSION ServerSession,
+ IN DWORD DatabaseID,
+ IN SYNC_STATE RestartState,
+ IN OUT PULONG SyncContext,
+ IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
+ IN DWORD PreferredMaximumLength,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ This function is a real worker for the NetrDatabaseSync function and
+ retrieves a SAM database in the delta buffer.
+
+ This function uses the find-first find-next model to return portions
+ of the SAM database at a time. The SAM database is returned as a
+ list of deltas like those returned from I_NetDatabaseDeltas. The
+ following deltas are returned for each domain:
+
+ * One AddOrChangeDomain delta, followed by
+
+ * One AddOrChangeGroup delta for each group, followed by,
+
+ * One AddOrChangeUser delta for each user, followed by
+
+ * One ChangeGroupMembership delta for each group followed by,
+
+ * One AddOrChangeAlias delta for each alias, followed by,
+
+ * One ChangeAliasMembership delta for each alias.
+
+
+Arguments:
+
+ ServerSession -- pointer to connection context.
+
+ DatabaseID -- Identifies the databse for which the deltas are requested.
+ For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
+ databases may be defined later.
+
+ RestartState -- Specifies whether this is a restart of the full sync and how
+ to interpret SyncContext. This value should be NormalState unless this
+ is the restart of a full sync.
+
+ However, if the caller is continuing a full sync after a reboot,
+ the following values are used:
+
+ GroupState - SyncContext is the global group rid to continue with.
+ UserState - SyncContext is the user rid to continue with
+ GroupMemberState - SyncContext is the global group rid to continue with
+ AliasState - SyncContext should be zero to restart at first alias
+ AliasMemberState - SyncContext should be zero to restart at first alias
+
+ One cannot continue the LSA database in this way.
+
+ SyncContext -- Specifies context needed to continue the
+ operation. The caller should treat this as an opaque
+ value. The value should be zero before the first call.
+
+ DeltaArray -- Pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+ SessionInfo - Information shared between PDC and BDC.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PSAM_SYNC_CONTEXT SamDBContext;
+
+ PDB_INFO DBInfo;
+
+ DWORD BufferConsumed = 0;
+ DWORD BufferSize;
+
+ DBInfo = &NlGlobalDBInfoArray[DatabaseID];
+
+
+ //
+ // Allocate memory for delta buffer.
+ //
+
+ DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+ if( DeltaArray->Deltas == NULL ) {
+
+
+ NlPrint((NL_CRITICAL,
+ "NlSyncSamDatabase: Can't allocate %d bytes\n",
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ));
+
+ return( STATUS_NO_MEMORY );
+ }
+
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaArray->Deltas,
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+
+ //
+ // If this is the first call or an explicit restart call,
+ // allocate and initialize the sync context.
+ //
+
+ if ( *SyncContext == 0 || RestartState != NormalState ) {
+
+ //
+ // If there already is a sync context,
+ // delete it.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ } else {
+
+ ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) );
+ if ( ServerSession->SsSync == NULL ) {
+
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Initialize all the fields in the newly allocated resume handle
+ // to indicate that SAM has never yet been called.
+ //
+
+ INIT_SYNC_CONTEXT( ServerSession->SsSync, SamDBContextType );
+
+ SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
+ SamDBContext->SyncSerial = 1;
+
+ //
+ // Compute the continuation state based on the input parameters
+ //
+
+ switch ( RestartState ) {
+ case NormalState:
+
+ //
+ // Put the description of the Domain at the front of the buffer for the
+ // first call.
+ //
+
+ Status = NlPackSamDomain( &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ (DeltaArray->CountReturned)++;
+ BufferConsumed += BufferSize;
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ SamDBContext->SyncState = GroupState;
+ SamDBContext->SamEnumHandle = 0;
+ break;
+
+ case AliasState:
+ case AliasMemberState:
+ if ( *SyncContext != 0 ) {
+ NlPrint(( NL_CRITICAL,
+ "NlSyncSamDatabase: Cannot restart alias enumeration.\n" ));
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+ /* Drop Through */
+
+ case GroupState:
+ case UserState:
+ case GroupMemberState:
+ SamDBContext->SyncState = RestartState;
+ SamDBContext->SamEnumHandle = *SyncContext;
+ break;
+
+ default:
+ NlPrint(( NL_CRITICAL,
+ "NlSyncSamDatabase: Invalid RestartState passed %ld.\n",
+ RestartState ));
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto Cleanup;
+
+
+ }
+
+ } else {
+
+ NlAssert( ServerSession->SsSync != NULL);
+
+ if( ServerSession->SsSync == NULL) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ NlAssert( ServerSession->SsSync->DBContextType ==
+ SamDBContextType);
+
+ if( ServerSession->SsSync->DBContextType !=
+ SamDBContextType ) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
+
+ NlAssert( SamDBContext->SyncSerial == *SyncContext );
+
+ if( SamDBContext->SyncSerial != *SyncContext ) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ SamDBContext->SyncSerial++;
+ }
+
+ //
+ // Loop for each entry placed in the output buffer
+ //
+ // Each iteration of the loop below puts one more entry into the array
+ // returned to the caller. The algorithm is split into 2 parts. The
+ // first part checks to see if we need to retrieve more information from
+ // SAM and gets the description of several users or group from SAM in a
+ // single call. The second part puts a single entry into the buffer
+ // returned to the caller.
+ //
+
+ while ( SamDBContext->SyncState != SamDoneState ) {
+
+ //
+ // If we've filled out pre-allocated array,
+ // return now.
+ //
+ if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+
+ //
+ // Get more information from SAM
+ //
+ // Handle when we've not yet called SAM or we've already consumed
+ // all of the information returned on a previous call to SAM.
+ //
+ // This is a 'while' rather than an 'if' to handle the case
+ // where SAM returns zero entries.
+ //
+
+ while ( SamDBContext->Index >= SamDBContext->Count ) {
+
+ //
+ // Free any previous buffer returned from SAM.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ }
+
+ //
+ // If we've already gotten everything from SAM,
+ // we've finished all of the groups,
+ //
+ // If we've just done the groups,
+ // go on to do the users.
+ //
+ // If we've just done the users,
+ // go on to do the group memberships.
+ //
+ // If we've just done the group memberships,
+ // go on to do the alias.
+ //
+ // If we've just done the alias,
+ // go on to do the alias membership.
+ //
+ // If we've just done the alias memberships,
+ // we're all done.
+ //
+
+ if ( SamDBContext->SamAllDone ) {
+
+ SamDBContext->SamEnumHandle = 0;
+ SamDBContext->Index = 0;
+ SamDBContext->Count = 0;
+ SamDBContext->SamAllDone = FALSE;
+
+ if (SamDBContext->SyncState == GroupState ) {
+
+
+ NlPrint((NL_SYNC,
+ "NlSyncSamDatabase: packing user records.\n"));
+
+ SamDBContext->SyncState = UserState;
+ } else if (SamDBContext->SyncState == UserState ) {
+
+ NlPrint((NL_SYNC,
+ "NlSyncSamDatabase: "
+ "packing groupmember records.\n"));
+
+ SamDBContext->SyncState = GroupMemberState;
+ } else if (SamDBContext->SyncState == GroupMemberState ){
+
+ NlPrint((NL_SYNC,
+ "NlSyncSamDatabase: packing alias records.\n"));
+
+ SamDBContext->SyncState = AliasState;
+ } else if (SamDBContext->SyncState == AliasState ){
+
+ NlPrint((NL_SYNC,
+ "NlSyncSamDatabase: "
+ " packing aliasmember records.\n"));
+
+ SamDBContext->SyncState = AliasMemberState ;
+ } else if (SamDBContext->SyncState == AliasMemberState ){
+
+ NlPrint((NL_SYNC,
+ "NlSyncSamDatabase: packing done.\n"));
+
+ SamDBContext->SyncState = SamDoneState;
+ Status = STATUS_SUCCESS;
+ }
+
+ break;
+ }
+
+ //
+ // Do the actual enumeration
+ //
+
+ if (SamDBContext->SyncState == GroupState ||
+ SamDBContext->SyncState == GroupMemberState ) {
+
+ Status = SamIEnumerateAccountRids(
+ DBInfo->DBHandle,
+ SAM_GLOBAL_GROUP_ACCOUNT,
+ SamDBContext->SamEnumHandle, // Return RIDs greater than this
+ SAM_SYNC_PREF_MAX,
+ &SamDBContext->Count,
+ &SamDBContext->RidArray );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ SamDBContext->RidArray = NULL;
+ goto Cleanup;
+ }
+
+ if ( SamDBContext->Count != 0 ) {
+ SamDBContext->SamEnumHandle =
+ SamDBContext->RidArray[SamDBContext->Count-1];
+ }
+
+ } else if (SamDBContext->SyncState == UserState ) {
+
+
+ Status = SamIEnumerateAccountRids(
+ DBInfo->DBHandle,
+ SAM_USER_ACCOUNT,
+ SamDBContext->SamEnumHandle, // Return RIDs greater than this
+ SAM_SYNC_PREF_MAX,
+ &SamDBContext->Count,
+ &SamDBContext->RidArray );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ SamDBContext->RidArray = NULL;
+ goto Cleanup;
+ }
+
+ if ( SamDBContext->Count != 0 ) {
+ SamDBContext->SamEnumHandle =
+ SamDBContext->RidArray[SamDBContext->Count-1];
+ }
+
+ } else if (SamDBContext->SyncState == AliasState ||
+ SamDBContext->SyncState == AliasMemberState ) {
+
+ Status = SamrEnumerateAliasesInDomain(
+ DBInfo->DBHandle,
+ &SamDBContext->SamEnumHandle,
+ &SamDBContext->SamEnum,
+ SAM_SYNC_PREF_MAX,
+ &SamDBContext->Count );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ SamDBContext->SamEnum = NULL;
+ goto Cleanup;
+ }
+
+ NlAssert( SamDBContext->Count ==
+ SamDBContext->SamEnum->EntriesRead );
+
+ }
+
+
+ //
+ // If SAM says there is more information,
+ // just ensure he returned something to us on this call.
+ //
+
+ if ( Status == STATUS_MORE_ENTRIES ) {
+ NlAssert( SamDBContext->Count != 0 );
+
+ //
+ // If SAM says he's returned all of the information,
+ // remember not to ask SAM for more.
+ //
+
+ } else {
+ SamDBContext->SamAllDone = TRUE;
+ }
+
+ SamDBContext->Index = 0;
+ }
+
+ //
+ // Place this entry into the return buffer.
+ //
+
+ if ( SamDBContext->Count > 0 ) {
+
+ if (SamDBContext->SyncState == GroupState ) {
+ Status = NlPackSamGroup(
+ SamDBContext->RidArray[SamDBContext->Index],
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ } else if (SamDBContext->SyncState == UserState ) {
+ Status = NlPackSamUser(
+ SamDBContext->RidArray[SamDBContext->Index],
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ } else if (SamDBContext->SyncState == GroupMemberState ) {
+ Status = NlPackSamGroupMember(
+ SamDBContext->RidArray[SamDBContext->Index],
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ } else if (SamDBContext->SyncState == AliasState ) {
+ Status = NlPackSamAlias(
+ SamDBContext->SamEnum->
+ Buffer[SamDBContext->Index].RelativeId,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ } else if (SamDBContext->SyncState == AliasMemberState ) {
+ Status = NlPackSamAliasMember(
+ SamDBContext->SamEnum->
+ Buffer[SamDBContext->Index].RelativeId,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+ }
+
+ //
+ // If there was a real error or this group didn't fit,
+ // return to the caller.
+ //
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ SamDBContext->Index ++;
+ (DeltaArray->CountReturned)++;
+ BufferConsumed +=
+ (sizeof(NETLOGON_DELTA_ENUM) + BufferSize);
+
+ if( BufferConsumed >= PreferredMaximumLength) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ //
+ // If we're debugging replication, return only one change to the caller.
+ //
+#if DBG
+ if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+#endif // DBG
+
+ //
+ // if the service is going down, stop packing records and
+ // return to the caller.
+ //
+ // Don't alarm the caller with the status code. He'll find out
+ // on the next call that we're no longer here.
+ //
+
+ if( NlGlobalTerminate ) {
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseSync is asked to return "
+ "when the service is going down.\n"));
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ }
+ }
+
+Cleanup:
+
+ //
+ // Set the return parameters to the proper values.
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+ *SyncContext = SamDBContext->SyncSerial;
+
+ } else {
+ if ( DeltaArray->Deltas != NULL ) {
+ NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
+ DeltaArray->Deltas = NULL;
+ }
+ DeltaArray->CountReturned = 0;
+ *SyncContext = 0;
+
+ NlPrint((NL_CRITICAL,
+ "NlSyncSamDatabase: returning unsuccessful (%lx).\n",
+ Status));
+
+ }
+
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NlSyncLsaDatabase(
+ IN PSERVER_SESSION ServerSession,
+ IN OUT PULONG SyncContext,
+ IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
+ IN DWORD PreferredMaximumLength,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ This function is a real worker for the NetrDatabaseSync function and
+ retrieves the LSA database in the delta buffer.
+
+ This function uses the find-first find-next model to return portions
+ of the SAM database at a time. The SAM database is returned as a
+ list of deltas like those returned from I_NetDatabaseDeltas. The
+ following deltas are returned for each domain:
+
+ * One AddOrChangeLsaPolicy delta, followed by,
+
+ * One AddOrChangeLsaAccounts delta for each lsa account, followed by,
+
+ * One AddOrChangeLsaTDomain delta for each trusted domain, followed by,
+
+ * One AddOrChangeLsaSecret delta for each lsa secret.
+
+
+Arguments:
+
+ ServerSession -- pointer to connection context.
+
+ SyncContext -- Specifies context needed to continue the
+ operation. The caller should treat this as an opaque
+ value. The value should be zero before the first call.
+
+ DeltaArray -- Pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+ SessionInfo - Information shared between PDC and BDC.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PLSA_SYNC_CONTEXT LsaDBContext;
+
+ PDB_INFO DBInfo;
+
+ DWORD BufferConsumed = 0;
+ DWORD BufferSize;
+ BOOL IgnoreDeltaObject = FALSE;
+
+ DBInfo = &NlGlobalDBInfoArray[LSA_DB];
+
+
+ //
+ // Allocate memory for delta buffer.
+ //
+
+ DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+ if( DeltaArray->Deltas == NULL ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSyncLsaDatabase: Can't allocate %d bytes\n",
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ));
+
+ return( STATUS_NO_MEMORY );
+ }
+
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaArray->Deltas,
+ MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
+
+ //
+ // If this is the first call, allocate and initialize the sync context.
+ //
+
+ if ( *SyncContext == 0 ) {
+
+ //
+ // If there already is a sync context,
+ // delete it.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ }
+ else {
+
+ ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) );
+ if ( ServerSession->SsSync == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Initialize all the fields in the newly allocated resume handle
+ // to indicate that SAM has never yet been called.
+ //
+
+ INIT_SYNC_CONTEXT( ServerSession->SsSync, LsaDBContextType );
+
+ LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa);
+
+ LsaDBContext->SyncState = AccountState;
+ LsaDBContext->SyncSerial = 1;
+ LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
+
+
+ NlPrint((NL_SYNC,
+ "NlSyncLsaDatabase: "
+ "Starting full sync, packing lsa account records\n"));
+
+ //
+ // Put the description of the Policy at the front of the buffer for the
+ // first call.
+ //
+
+ Status = NlPackLsaPolicy(
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ (DeltaArray->CountReturned)++;
+ BufferConsumed += BufferSize;
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+ } else {
+
+ if( ServerSession->SsSync == NULL ) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ NlAssert( ServerSession->SsSync->DBContextType ==
+ LsaDBContextType);
+
+ if( ServerSession->SsSync->DBContextType !=
+ LsaDBContextType) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa);
+
+ NlAssert( LsaDBContext->SyncSerial == *SyncContext );
+
+ if( LsaDBContext->SyncSerial != *SyncContext ) {
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ LsaDBContext->SyncSerial++;
+ }
+
+ //
+ // Loop for each entry placed in the output buffer
+ //
+ // Each iteration of the loop below puts one more entry into the array
+ // returned to the caller. The algorithm is split into 2 parts.
+ // The first part checks to see if we need to retrieve more information
+ // from LSA and gets the description of several accounts, TDomain or
+ // Secret from LSA in a single call. The second part puts a single
+ // entry into the buffer returned to the caller.
+ //
+
+ while ( LsaDBContext->SyncState != LsaDoneState ) {
+
+ //
+ // If we've filled out pre-allocated array,
+ // return now.
+ //
+ if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ //
+ // Get more information from LSA
+ //
+ // Handle when we've not yet called LSA or we've already consumed
+ // all of the information returned on a previous call to SAM.
+ //
+ // This is a 'while' rather than an 'if' to handle the case
+ // where LSA returns zero entries.
+ //
+
+ while ( LsaDBContext->Index >= LsaDBContext->Count ) {
+
+ //
+ // Free any previous buffer returned from SAM.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ }
+
+
+ //
+ // If we've already gotten everything from LSA,
+ // we've finished all of the accounts,
+ //
+ // If we've just done the accounts,
+ // go on to do the TDomains.
+ //
+ // If we've just done the TDomains,
+ // go on to do the Secrets
+ //
+ // If we've just done the Secret,
+ // we're all done.
+ //
+
+ if ( LsaDBContext->LsaAllDone ) {
+
+ LsaDBContext->LsaEnumHandle = 0;
+ LsaDBContext->Index = 0;
+ LsaDBContext->Count = 0;
+ LsaDBContext->LsaAllDone = FALSE;
+
+ if (LsaDBContext->SyncState == AccountState ) {
+
+
+ NlPrint((NL_SYNC,
+ "NlSyncLsaDatabase: "
+ " packing TDomain records.\n"));
+
+ LsaDBContext->SyncState = TDomainState;
+ } else if (LsaDBContext->SyncState == TDomainState ) {
+
+ NlPrint((NL_SYNC,
+ "NlSyncLsaDatabase: packing secret records.\n"));
+
+ LsaDBContext->SyncState = SecretState;
+ } else if (LsaDBContext->SyncState == SecretState ) {
+
+
+ NlPrint((NL_SYNC,
+ "NlSyncLsaDatabase: packing done.\n"));
+
+ LsaDBContext->SyncState = LsaDoneState;
+ LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
+ Status = STATUS_SUCCESS;
+ }
+
+ break;
+ }
+
+ if (LsaDBContext->SyncState == AccountState ) {
+
+ LsaDBContext->LsaEnumBufferType = AccountEnumBuffer;
+
+ Status = LsarEnumerateAccounts(
+ DBInfo->DBHandle,
+ &LsaDBContext->LsaEnumHandle,
+ &LsaDBContext->LsaEnum.Account,
+ SAM_SYNC_PREF_MAX);
+
+ if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
+ LsaDBContext->Count =
+ LsaDBContext->LsaEnum.Account.EntriesRead;
+ }
+
+ } else if (LsaDBContext->SyncState == TDomainState ) {
+
+ LsaDBContext->LsaEnumBufferType = TDomainEnumBuffer;
+
+ Status = LsarEnumerateTrustedDomains(
+ DBInfo->DBHandle,
+ &LsaDBContext->LsaEnumHandle,
+ &LsaDBContext->LsaEnum.TDomain,
+ SAM_SYNC_PREF_MAX);
+
+ if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
+ LsaDBContext->Count =
+ LsaDBContext->LsaEnum.TDomain.EntriesRead;
+ }
+
+ } else if (LsaDBContext->SyncState == SecretState ) {
+
+ LsaDBContext->LsaEnumBufferType = SecretEnumBuffer;
+
+ Status = LsaIEnumerateSecrets(
+ DBInfo->DBHandle,
+ &LsaDBContext->LsaEnumHandle,
+ &LsaDBContext->LsaEnum.Secret,
+ SAM_SYNC_PREF_MAX,
+ &LsaDBContext->Count );
+
+ }
+
+ //
+ // If LSA says there is more information,
+ // just ensure he returned something to us on this call.
+ //
+
+ if ( Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
+ NlAssert( LsaDBContext->Count != 0 );
+
+ //
+ // If LSA says he's returned all of the information,
+ // remember not to ask it for more.
+ //
+
+ } else if ( Status == STATUS_NO_MORE_ENTRIES ) {
+ LsaDBContext->LsaAllDone = TRUE;
+ LsaDBContext->Count = 0;
+
+ //
+ // Any other error is fatal
+ //
+
+ } else {
+
+ LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
+ LsaDBContext->Count = 0;
+ goto Cleanup;
+
+ }
+
+ LsaDBContext->Index = 0;
+ }
+
+
+ //
+ // Place this entry into the return buffer.
+ //
+
+ if ( LsaDBContext->Count > 0 ) {
+
+ if (LsaDBContext->SyncState == AccountState ) {
+
+ Status = NlPackLsaAccount(
+ LsaDBContext->LsaEnum.Account.
+ Information[LsaDBContext->Index].Sid,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ } else if (LsaDBContext->SyncState == TDomainState ) {
+
+ Status = NlPackLsaTDomain(
+ LsaDBContext->LsaEnum.TDomain.
+ Information[LsaDBContext->Index].Sid,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize );
+
+ } else if (LsaDBContext->SyncState == SecretState ) {
+
+ PUNICODE_STRING SecretName;
+
+ SecretName =
+ &((PUNICODE_STRING)LsaDBContext->LsaEnum.Secret)
+ [LsaDBContext->Index];
+
+ //
+ // ignore local secret objects.
+ //
+
+ if( (SecretName->Length / sizeof(WCHAR) >
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) &&
+ (_wcsnicmp( SecretName->Buffer,
+ LSA_GLOBAL_SECRET_PREFIX,
+ LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0)) {
+
+ Status = NlPackLsaSecret(
+ SecretName,
+ &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
+ DBInfo,
+ &BufferSize,
+ SessionInfo );
+
+ } else {
+ Status = STATUS_SUCCESS;
+ IgnoreDeltaObject = TRUE;
+ BufferSize = 0;
+ }
+
+ }
+
+ //
+ // If there was a real error or this group didn't fit,
+ // return to the caller.
+ //
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ LsaDBContext->Index ++;
+
+ //
+ // if this object is ignored, don't modify return values.
+ //
+
+ if ( !IgnoreDeltaObject ) {
+
+ (DeltaArray->CountReturned)++;
+ BufferConsumed +=
+ (sizeof(NETLOGON_DELTA_ENUM) + BufferSize);
+
+ if( BufferConsumed >= PreferredMaximumLength) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+ //
+ // If we're debugging replication, return only one change to the caller.
+ //
+#if DBG
+ if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+#endif // DBG
+ } else {
+ IgnoreDeltaObject = FALSE;
+ }
+ }
+ }
+
+Cleanup:
+
+ //
+ // Set the return parameters to the proper values.
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+ *SyncContext = LsaDBContext->SyncSerial;
+
+ } else {
+ if ( DeltaArray->Deltas != NULL ) {
+ NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
+ DeltaArray->Deltas = NULL;
+ }
+ DeltaArray->CountReturned = 0;
+ *SyncContext = 0;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ NlPrint((NL_CRITICAL,
+ "NlSyncLsaDatabase: returning unsuccessful (%lx).\n",
+ Status));
+ }
+
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NetrDatabaseSync (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PULONG SyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ NT 1.0 version of NetrDatabaseSync2. Don't pass the RestartState parameter.
+ Sync Context is all that is needed to identify the state.
+
+Arguments:
+
+ Same as NetrDatabaseSync2 (with the exception mentioned above).
+
+Return Value:
+
+ Save as NetrDatabaseSync2.
+
+--*/
+{
+ return NetrDatabaseSync2(
+ PrimaryName,
+ ComputerName,
+ Authenticator,
+ ReturnAuthenticator,
+ DatabaseID,
+ NormalState,
+ SyncContext,
+ DeltaArrayRet,
+ PreferredMaximumLength );
+
+}
+
+
+NTSTATUS
+NetrDatabaseSync2 (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN SYNC_STATE RestartState,
+ IN OUT PULONG SyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
+ IN DWORD PreferredMaximumLength
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a BDC server to request
+ the entire SAM/LSA database from a PDC in NTLANMAN-style format.
+ This function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the deltas from.
+
+ ComputerName -- Name of the BDC or member server making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ DatabaseID -- Identifies the databse for which the deltas are requested.
+ For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
+ databases may be defined later.
+
+ RestartState -- Specifies whether this is a restart of the full sync and how
+ to interpret SyncContext. This value should be NormalState unless this
+ is the restart of a full sync.
+
+ However, if the caller is continuing a full sync after a reboot,
+ the following values are used:
+
+ GroupState - SyncContext is the global group rid to continue with.
+ UserState - SyncContext is the user rid to continue with
+ GroupMemberState - SyncContext is the global group rid to continue with
+ AliasState - SyncContext should be zero to restart at first alias
+ AliasMemberState - SyncContext should be zero to restart at first alias
+
+ One cannot continue the LSA database in this way.
+
+ SyncContext -- Specifies context needed to continue the
+ operation. The caller should treat this as an opaque
+ value. The value should be zero before the first call.
+
+ DeltaArray -- Receives a pointer to a buffer where the information
+ is placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+ PreferredMaximumLength - Preferred 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.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_MORE_ENTRIES -- The replicant should call again to get more
+ data.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+
+--*/
+{
+ NTSTATUS Status;
+
+ PSERVER_SESSION ServerSession = NULL;
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
+
+ SESSION_INFO SessionInfo;
+ PDB_INFO DBInfo;
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+ STARTSSIAPITIMER;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ if ( DatabaseID >= NUM_DBS ) {
+ return STATUS_INVALID_LEVEL;
+ }
+
+ DBInfo = &NlGlobalDBInfoArray[DatabaseID];
+
+ //
+ // Initialization
+ //
+
+ *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
+
+ if( DeltaArray == NULL ) {
+ return(STATUS_NO_MEMORY);
+ }
+
+ DeltaArray->Deltas = NULL;
+ DeltaArray->CountReturned = 0;
+
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseSync: " FORMAT_LPWSTR " full sync called by " FORMAT_LPWSTR " State: %ld Context: 0x%lx.\n",
+ DBInfo->DBName,
+ ComputerName,
+ RestartState,
+ *SyncContext ));
+
+
+ //
+ // Retrieve the requestor's entry to get sessionkey
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ // Don't log this event since it happens in nature after a reboot
+ // or after we scavenge the server session.
+ goto CleanupNoEventLog;
+ }
+
+ //
+ // Allow this call only on ServerSecureChannel.
+ //
+
+ if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL,
+ "NetrDatabaseSync: authentication failed.\n" ));
+
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Prevent entry from being deleted, but drop the global lock.
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseSync: Concurrent call detected.\n" ));
+
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+ ServerSession->SsFlags |= SS_LOCKED;
+
+
+ SessionInfo.SessionKey = ServerSession->SsSessionKey;
+ SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ if( DatabaseID == LSA_DB ) {
+
+ NlAssert( RestartState == NormalState );
+
+ Status = NlSyncLsaDatabase( ServerSession,
+ SyncContext,
+ DeltaArray,
+ PreferredMaximumLength,
+ &SessionInfo );
+ } else {
+
+ Status = NlSyncSamDatabase( ServerSession,
+ DatabaseID,
+ RestartState,
+ SyncContext,
+ DeltaArray,
+ PreferredMaximumLength,
+ &SessionInfo );
+
+ }
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ LPWSTR MsgStrings[2];
+
+ MsgStrings[0] = ComputerName;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonFullSyncCallFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+
+ }
+ else {
+
+ LPWSTR MsgStrings[2];
+ WCHAR CountBuffer[20]; // random size
+
+ MsgStrings[0] = ComputerName;
+
+ ultow( DeltaArray->CountReturned, CountBuffer, 10);
+ MsgStrings[1] = CountBuffer;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonFullSyncCallSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 2 );
+
+ }
+
+ //
+ // Unlock the server session entry if we've locked it.
+ //
+CleanupNoEventLog:
+
+ if ( ServerSession != NULL ) {
+
+ //
+ // If we're done, free up the context structure,
+ //
+
+ if ( Status != STATUS_MORE_ENTRIES && ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+
+ NetpMemoryFree( ServerSession->SsSync );
+ ServerSession->SsSync = NULL;
+ }
+
+ //
+ // If we are successfully returning these deltas to the BDC,
+ // update our tables to reflect the changes.
+ //
+
+ if ( Status == STATUS_SUCCESS ) {
+ NlPrimaryAnnouncementFinish( ServerSession,
+ DatabaseID,
+ NULL );
+
+ }
+
+ NlUnlockServerSession( ServerSession );
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseSync: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR " Context: 0x%lx.\n",
+ DBInfo->DBName,
+ Status,
+ ComputerName,
+ *SyncContext ));
+
+ STOPSSIAPITIMER;
+
+ NlPrint((NL_REPL_TIME,"NetrDatabaseSync Time:\n"));
+ PRINTSSIAPITIMER;
+
+ return Status;
+
+}
+
+
+NTSTATUS
+NetrDatabaseRedo(
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN LPBYTE OrigChangeLogEntry,
+ IN DWORD ChangeLogEntrySize,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a SAM BDC to request infomation about a single
+ account. This function can only be called by a server which has previously
+ authenticated with the PDC by calling I_NetServerAuthenticate. This
+ function uses RPC to contact the Netlogon service on the PDC.
+
+Arguments:
+
+ PrimaryName -- Name of the PDC to retrieve the delta from.
+
+ ComputerName -- Name of the BDC making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ ChangeLogEntry -- A description of the account to be queried.
+
+ ChangeLogEntrySize -- Size (in bytes) of the ChangeLogEntry.
+
+ DeltaArrayRet -- Receives a pointer to a buffer where the information is
+ placed. The information returned is an array of
+ NETLOGON_DELTA_ENUM structures.
+
+Return Value:
+
+ STATUS_SUCCESS -- The function completed successfully.
+
+ STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
+ the PDC.
+
+--*/
+{
+ PCHANGELOG_ENTRY ChangeLogEntry;
+
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession = NULL;
+
+ LPWSTR MsgStrings[2];
+
+ DWORD BufferSize;
+
+ PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
+ SESSION_INFO SessionInfo;
+
+ DEFSSIAPITIMER;
+
+ INITSSIAPITIMER;
+ STARTSSIAPITIMER;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Initialization
+ //
+
+ ChangeLogEntry = (PCHANGELOG_ENTRY) OrigChangeLogEntry;
+ if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize ) ||
+ ChangeLogEntry->DBIndex >= NUM_DBS ) {
+ Status = STATUS_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseRedo: " FORMAT_LPWSTR " redo sync called by " FORMAT_LPWSTR
+ " with this change log entry:\n",
+ NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
+ ComputerName ));
+
+#if DBG
+ PrintChangeLogEntry( ChangeLogEntry );
+#endif // DBG
+
+ //
+ // The change log entry really represents an object and not an operation.
+ // Therefore, convert the delta type from whatever was passed to an
+ // "AddOrChange" operation. Then NlPackSingleDelta will return everything
+ // we know about the object.
+ //
+
+ ChangeLogEntry->DeltaType = NlGlobalAddDeltaType[ChangeLogEntry->DeltaType];
+
+ *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
+ MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
+
+ if( DeltaArray == NULL ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ DeltaArray->CountReturned = 0;
+ DeltaArray->Deltas = NULL;
+ SessionInfo.NegotiatedFlags = 0;
+
+
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+ //
+ // Retrieve the requestor's entry to get sessionkey
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ // Don't log this event since it happens in nature after a reboot
+ // or after we scavenge the server session.
+ goto CleanupNoEventlog;
+ }
+
+ //
+ // Allow this call only on ServerSecureChannel.
+ //
+
+ if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseRedo: authentication failed.\n" ));
+
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Prevent entry from being deleted, but drop the global lock.
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrDatabaseRedo: Concurrent call detected.\n" ));
+
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+ ServerSession->SsFlags |= SS_LOCKED;
+
+ SessionInfo.SessionKey = ServerSession->SsSessionKey;
+ SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+ //
+ // Allocate memory for delta buffer.
+ //
+
+ DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
+ MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) );
+
+ if( DeltaArray->Deltas == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+
+ //
+ // wipe off the buffer so that cleanup will not be in fault.
+ //
+
+ RtlZeroMemory( DeltaArray->Deltas,
+ MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) );
+
+
+ //
+ // Put the data for the changelog entry into the user's buffer.
+ //
+
+ Status = NlPackSingleDelta( ChangeLogEntry,
+ DeltaArray,
+ &BufferSize,
+ &SessionInfo,
+ FALSE );
+
+
+ //
+ // If the only problem is that the object no longer exists,
+ // return a delta asking the BDC to delete the object.
+ //
+
+ if ( !NT_SUCCESS(Status) &&
+ IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) {
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseRedo: " FORMAT_LPWSTR " object no longer exists (0x%lx) "
+ FORMAT_LPWSTR "\n",
+ NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
+ Status,
+ ComputerName ));
+
+ //
+ // Convert the change log entry into an appropriate delete delta type and
+ // try again.
+ //
+
+ ChangeLogEntry->DeltaType = NlGlobalDeleteDeltaType[ChangeLogEntry->DeltaType];
+
+ Status = NlPackSingleDelta( ChangeLogEntry,
+ DeltaArray,
+ &BufferSize,
+ &SessionInfo,
+ FALSE );
+
+ }
+
+Cleanup:
+
+ //
+ // write event log
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ MsgStrings[0] = ComputerName;
+ MsgStrings[1] = (LPWSTR) Status;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncCallFailed,
+ EVENTLOG_WARNING_TYPE,
+ (LPBYTE)&Status,
+ sizeof(Status),
+ MsgStrings,
+ 2 | LAST_MESSAGE_IS_NTSTATUS );
+
+ } else {
+
+ //
+ // Log the successful replication only if deltas have been returned
+ // to the caller.
+ //
+ if ( DeltaArray->CountReturned != 0 ) {
+ LPWSTR MsgStrings[2];
+ WCHAR CountBuffer[20]; // random size
+
+ MsgStrings[0] = ComputerName;
+
+ ultow( DeltaArray->CountReturned, CountBuffer, 10);
+ MsgStrings[1] = CountBuffer;
+
+ NlpWriteEventlog(
+ NELOG_NetlogonPartialSyncCallSuccess,
+ EVENTLOG_INFORMATION_TYPE,
+ NULL,
+ 0,
+ MsgStrings,
+ 2 );
+ }
+
+ }
+
+
+ //
+ // Free up locally allocated resources.
+ //
+
+CleanupNoEventlog:
+
+ //
+ // If we weren't successful,
+ // Don't return any deltas.
+ //
+
+ if ( !NT_SUCCESS(Status)) {
+ if ( DeltaArray->Deltas != NULL ) {
+ NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
+ DeltaArray->Deltas = NULL;
+ }
+ DeltaArray->CountReturned = 0;
+ }
+
+ //
+ // Unlock the server session entry if we've locked it.
+ //
+
+ if ( ServerSession != NULL ) {
+ NlUnlockServerSession( ServerSession );
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrDatabaseRedo: " FORMAT_LPWSTR " returning (0x%lx) to "
+ FORMAT_LPWSTR "\n",
+ NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
+ Status,
+ ComputerName ));
+
+ STOPSSIAPITIMER;
+
+ NlPrint((NL_REPL_TIME,"NetrDatabaseRedo Time:\n"));
+ PRINTSSIAPITIMER;
+
+ return Status;
+
+}
+
+
+
+NTSTATUS
+NetrAccountDeltas (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN PUAS_INFO_0 RecordId,
+ IN DWORD Count,
+ IN DWORD Level,
+ OUT LPBYTE Buffer,
+ IN DWORD BufferSize,
+ OUT PULONG CountReturned,
+ OUT PULONG TotalEntries,
+ OUT PUAS_INFO_0 NextRecordId
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a UAS BDC or UAS member server to request
+ UAS-style account change information. This function can only be
+ called by a server which has previously authenticated with the PDC by
+ calling I_NetServerAuthenticate.
+
+ This function is only called by the XACT server upon receipt of a
+ I_NetAccountDeltas XACT SMB from a UAS BDC or a UAS member server.
+ As such, many of the parameters are opaque since the XACT server
+ doesn't need to interpret any of that data. This function uses RPC
+ to contact the Netlogon service.
+
+ The LanMan 3.0 SSI Functional Specification describes the operation
+ of this function.
+
+Arguments:
+
+ PrimaryName -- Must be NULL to indicate this call is a local call
+ being made on behalf of a UAS server by the XACT server.
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ RecordId -- Supplies an opaque buffer indicating the last record
+ received from a previous call to this function.
+
+ Count -- Supplies the number of Delta records requested.
+
+ Level -- Reserved. Must be zero.
+
+ Buffer -- Returns opaque data representing the information to be
+ returned.
+
+ BufferSize -- Size of buffer in bytes.
+
+ CountReturned -- Returns the number of records returned in buffer.
+
+ TotalEntries -- Returns the total number of records available.
+
+ NextRecordId -- Returns an opaque buffer identifying the last
+ record received by this function.
+
+
+Return Value:
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ PSERVER_SESSION ServerSession = NULL;
+ PCHANGELOG_ENTRY ChangeLogEntry = NULL;
+
+ BOOL ChangelogLocked = FALSE;
+ BUFFER_DESCRIPTOR BufferDescriptor;
+
+ PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB];
+ NETLOGON_SESSION_KEY SessionKey;
+
+ DWORD DummyFlag;
+ LONG RotateCount;
+ BOOL RotateCountComputed = FALSE;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+
+ //
+ // If CompatibilityMode is off,
+ // disallow this function for downlevel servers.
+ //
+
+ if ( !NlGlobalUasCompatibilityMode ) {
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Initialization
+ //
+
+ *CountReturned = 0;
+ *TotalEntries = 0;
+ *NextRecordId = *RecordId;
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrAccountDeltas: UAS partial sync called by " FORMAT_LPWSTR
+ " with SerialNumber 0x%lx.\n",
+ ComputerName,
+ RecordId->SerialNumber ));
+
+ //
+ // we need to retrieve the requestor's entry to get sessionkey
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // allow this call to go through only on UasServerSecureChannel.
+ //
+
+ if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // now verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+ SessionKey = ServerSession->SsSessionKey;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+
+
+ //
+ // The requestor should have gotten his 'DomainModifiedCount' from
+ // a UasChange record we broadcast. Therefore, It should be less than
+ // or equal to the current DomainModifiedCount as set by SAM. If
+ // not, force a sync.
+ //
+ // A Downlevel machine only has the least significant 32 bits of the
+ // DomainModifiedCount. We'll just compare the least significant portion
+ // knowing that a sync will be forced on wraparound.
+ //
+
+ LOCK_CHANGELOG();
+ ChangelogLocked = TRUE;
+
+ if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart <
+ RecordId->SerialNumber ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart ==
+ RecordId->SerialNumber ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ *TotalEntries = NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart -
+ RecordId->SerialNumber;
+
+ //
+ // Get a copy pointer to appropriate entry in change_log of primary.
+ // Note that the RecordId contains last record received by client.
+ //
+
+ ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( RecordId->SerialNumber );
+
+ if ( ChangeLogEntry == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "NetrDatabaseDeltas: "
+ "delta not found in cache, returning full required.\n" ));
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ UNLOCK_CHANGELOG();
+ ChangelogLocked = FALSE;
+
+ //
+ // Build a buffer descriptor describing the buffer the caller passed in.
+ //
+
+ if ( Buffer == NULL || BufferSize == 0 ) {
+
+ Status = STATUS_BUFFER_TOO_SMALL;
+
+ if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) {
+
+ //
+ // since this client has already overflowed the
+ // databuffer once, he can't handle big size delta.
+ //
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ goto Cleanup;
+ }
+
+ BufferDescriptor.Buffer = Buffer;
+ BufferDescriptor.AllocSize = BufferSize;
+
+ BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer;
+ BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer +
+ BufferDescriptor.AllocSize;
+
+ //
+ // Loop through the delta table replicating each entry in the delta table.
+ //
+
+ for (;;) {
+
+ //
+ // If we've returned all the entries the caller wants, we're all done.
+ //
+
+ if ( (Count--) <= 0 ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+
+ //
+ // Put the data for the changelog entry into the user's buffer.
+ //
+
+ switch ( ChangeLogEntry->DeltaType ) {
+ case AddOrChangeDomain:
+ Status = NlPackUasDomain( &BufferDescriptor, DBInfo);
+ break;
+
+ case AddOrChangeGroup:
+ Status = NlPackUasGroup( ChangeLogEntry->ObjectRid,
+ &BufferDescriptor,
+ DBInfo,
+ &DummyFlag );
+ break;
+
+ case AddOrChangeUser:
+
+
+ //
+ // If this is a user account whose membership in Domain Users
+ // hasn't been communicated to the lanman BDC,
+ // do so.
+ //
+
+ if ( ChangeLogEntry->Flags & CHANGELOG_DOMAINUSERS_CHANGED ) {
+ Status = NlPackUasUserGroupMember(
+ ChangeLogEntry->ObjectRid,
+ &BufferDescriptor,
+ DBInfo );
+
+ //
+ // Otherwise this is just a added or changed user
+ //
+
+ } else {
+
+ //
+ // Compute the RotateCount for LogonHours
+ //
+ // Do it only once.
+ //
+
+ if ( !RotateCountComputed ) {
+ if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) {
+ Status = STATUS_INTERNAL_ERROR;
+ goto Cleanup;
+ }
+ RotateCountComputed = TRUE;
+ }
+
+ Status = NlPackUasUser( ChangeLogEntry->ObjectRid,
+ &BufferDescriptor,
+ DBInfo,
+ &SessionKey,
+ RotateCount );
+ }
+
+ break;
+
+ case ChangeGroupMembership:
+ Status = NlPackUasGroupMember( ChangeLogEntry->ObjectRid,
+ &BufferDescriptor,
+ DBInfo );
+ break;
+
+ case DeleteGroup:
+ case DeleteUser:
+ case RenameUser:
+ case RenameGroup:
+ if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
+ Status = NlPackUasDelete(
+ ChangeLogEntry->DeltaType,
+ ChangeLogEntry->ObjectRid,
+ (LPWSTR)
+ (((LPBYTE)ChangeLogEntry) + sizeof(CHANGELOG_ENTRY)),
+ &BufferDescriptor,
+ DBInfo );
+ } else {
+ Status = STATUS_NO_SUCH_USER;
+ }
+ break;
+
+ case AddOrChangeAlias:
+ case ChangeAliasMembership:
+ case DeleteAlias:
+ case RenameAlias:
+
+#define DELTA_RESERVED_OPCODE 255
+ {
+
+ //
+ // This record is incompatible with a downlevel
+ // system, send a dummy record over to the downlevel system to
+ // ensure the SerialNumber gets updated appropriately.
+ //
+
+ PUSHORT RecordSize; // ptr to record size field in record header.
+ Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
+ 0,
+ &RecordSize,
+ &BufferDescriptor );
+ }
+ break;
+
+ default:
+ NlPrint((NL_CRITICAL,
+ "NetrAccountDeltas: invalid delta type in change log\n"));
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ break;
+ }
+
+ //
+ // If the buffer is too small to fit this entry,
+ // If we returned at least one entry, simply tell the caller more
+ // are available.
+ //
+
+ if (Status == STATUS_MORE_ENTRIES) {
+ if (*CountReturned == 0) {
+
+ if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) {
+
+ //
+ // since this client has already overflowed the
+ // databuffer once, he can't handle big size delta.
+ //
+
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+ else {
+
+ Status = STATUS_BUFFER_TOO_SMALL;
+
+ //
+ // remember that this client data buffer overflowed.
+ //
+
+ ServerSession->SsFlags |= SS_UAS_BUFFER_OVERFLOW;
+ }
+ }
+ goto Cleanup;
+ }
+
+ //
+ // ?? The follow is not taken care for the down level.
+ //
+ // In the case where an user/group/alias record was
+ // added and deleted before the delta was made we will
+ // trace the change log and see there is correpondance
+ // delete log. If we found one then ignore this delta
+ // and proceed to the next delta. If we couldn't find
+ // one then return error STATUS_SYNCHRONIZATION_REQUIRED.
+ //
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+
+ //
+ // Tell the caller he has another entry returned.
+ //
+
+ (*CountReturned)++;
+ NextRecordId->SerialNumber = ChangeLogEntry->SerialNumber.LowPart;
+
+ //
+ // Free up used temp. record
+ //
+
+ NetpMemoryFree(ChangeLogEntry);
+ ChangeLogEntry = NULL;
+
+
+ //
+ // If we've returned all the entries, we're all done.
+ //
+
+ LOCK_CHANGELOG();
+ ChangelogLocked = TRUE;
+
+ ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( NextRecordId->SerialNumber );
+
+ if ( ChangeLogEntry == NULL ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ UNLOCK_CHANGELOG();
+ ChangelogLocked = FALSE;
+
+ //
+ // If we're debugging replication, return only one change to the caller.
+ //
+#if DBG
+ if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+#endif // DBG
+
+ //
+ // if the service is going down, stop packing deltas and
+ // return to the caller.
+ //
+
+ if( NlGlobalTerminate ) {
+
+ NlPrint((NL_CRITICAL, "NetrAccountDeltas is asked to return "
+ "when the service is going down.\n"));
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+
+
+ }
+
+Cleanup:
+
+ //
+ // In the case where an user/group record was added and deleted
+ // before the delta was made we will map the errors such that
+ // the requesting machine will have to re-synchronize. It is
+ // the easiest, not neccessarily the best, way to get both
+ // machines in sync.
+ //
+
+ if (Status == STATUS_NO_SUCH_USER || Status == STATUS_NO_SUCH_GROUP ||
+ Status == STATUS_NONE_MAPPED ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ //
+ // reset buffer over flag in server session structure
+ //
+
+ if( (Status != STATUS_BUFFER_TOO_SMALL) &&
+ (ServerSession != NULL) &&
+ (ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW) ) {
+
+ ServerSession->SsFlags &= ~SS_UAS_BUFFER_OVERFLOW;
+ }
+
+ //
+ // Free up locally allocated resources.
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+ *CountReturned = 0;
+ }
+
+ //
+ // There are always at least as many as we returned
+ //
+ if ( *TotalEntries < *CountReturned ) {
+ *TotalEntries = *CountReturned;
+ }
+
+ if ( ChangelogLocked ) {
+ UNLOCK_CHANGELOG();
+ }
+
+ if( ChangeLogEntry != NULL) {
+ NetpMemoryFree( ChangeLogEntry );
+ }
+
+ NlPrint((NL_SYNC,
+ "NetrAccountDeltas: UAS partial sync returns %lx to "
+ FORMAT_LPWSTR " Count: %ld Total:%ld\n",
+ Status,
+ ComputerName,
+ *CountReturned,
+ *TotalEntries ));
+
+
+ return Status;
+
+ UNREFERENCED_PARAMETER( Level );
+
+}
+
+
+
+
+NTSTATUS
+NetrAccountSync (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD Reference,
+ IN DWORD Level,
+ OUT LPBYTE Buffer,
+ IN DWORD BufferSize,
+ OUT PULONG CountReturned,
+ OUT PULONG TotalEntries,
+ OUT PULONG NextReference,
+ OUT PUAS_INFO_0 LastRecordId
+ )
+/*++
+
+Routine Description:
+
+ This function is used by a UAS BDC or UAS member server to request
+ the entire user accounts database. This function can only be called
+ by a server which has previously authenticated with the PDC by
+ calling I_NetServerAuthenticate.
+
+ This function is only called by the XACT server upon receipt of a
+ I_NetAccountSync XACT SMB from a UAS BDC or a UAS member server. As
+ such, many of the parameters are opaque since the XACT server doesn't
+ need to interpret any of that data. This function uses RPC to
+ contact the Netlogon service.
+
+ The LanMan 3.0 SSI Functional Specification describes the operation
+ of this function.
+
+ "reference" and "next_reference" are treated as below.
+
+ 1. "reference" should hold either 0 or value of "next_reference"
+ from previous call to this API.
+ 2. Send the modals and ALL group records in the first call. The API
+ expects the buffer to be large enough to hold this info (worst
+ case size would be
+ MAXGROUP * (sizeof(struct group_info_1) + MAXCOMMENTSZ)
+ + sizeof(struct user_modals_info_0)
+ which, for now, will be 256 * (26 + 49) + 16 = 19216 bytes
+
+Arguments:
+
+ PrimaryName -- Must be NULL to indicate this call is a local call
+ being made on behalf of a UAS server by the XACT server.
+
+ ComputerName -- Name of the BDC or member making the call.
+
+ Authenticator -- supplied by the server.
+
+ ReturnAuthenticator -- Receives an authenticator returned by the PDC.
+
+ Reference -- Supplies find-first find-next handle returned by the
+ previous call to this function or 0 if it is the first call.
+
+ Level -- Reserved. Must be zero.
+
+ Buffer -- Returns opaque data representing the information to be
+ returned.
+
+ BufferLen -- Length of buffer in bytes.
+
+ CountReturned -- Returns the number of records returned in buffer.
+
+ TotalEntries -- Returns the total number of records available.
+
+ NextReference -- Returns a find-first find-next handle to be
+ provided on the next call.
+
+ LastRecordId -- Returns an opaque buffer identifying the last
+ record received by this function.
+
+
+Return Value:
+
+ NT status code.
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ BUFFER_DESCRIPTOR BufferDescriptor;
+ PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
+
+ PSERVER_SESSION ServerSession = NULL;
+ PCHAR Where;
+ PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB];
+
+ PSAM_SYNC_CONTEXT SamDBContext;
+
+ LONG RotateCount;
+ BOOL RotateCountComputed = FALSE;
+
+ //
+ // This API is not supported on workstations.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // If CompatibilityMode is off,
+ // disallow this function for downlevel servers.
+ //
+
+ if ( !NlGlobalUasCompatibilityMode ) {
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Initialization
+ //
+
+ *TotalEntries = 0;
+ *CountReturned = 0;
+ *NextReference = 0;
+
+
+ //
+ // Check the primary name.
+ //
+
+ Status = NlVerifyWorkstation( PrimaryName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto Cleanup;
+ }
+
+
+ NlPrint((NL_SYNC,
+ "NetrAccountSync: UAS full sync called by " FORMAT_LPWSTR
+ " Reference= %lx.\n",
+ ComputerName,
+ Reference ));
+
+ //
+ // we need to retrieve the requestor's entry to get sessionkey
+ //
+
+ LOCK_SERVER_SESSION_TABLE();
+ ServerSession = NlFindNamedServerSession( ComputerName );
+
+ if (ServerSession == NULL) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // allow this call to go through only on UasServerSecureChannel.
+ //
+
+ if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ ServerSession = NULL;
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // now verify the Authenticator and update seed if OK
+ //
+
+ Status = NlCheckAuthenticator( ServerSession,
+ Authenticator,
+ ReturnAuthenticator);
+
+ if ( !NT_SUCCESS(Status) ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+ ServerSession = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Prevent entry from being deleted, but drop the global lock.
+ //
+ // Beware of server with two concurrent calls outstanding
+ // (must have rebooted.)
+ //
+
+ if (ServerSession->SsFlags & SS_LOCKED ) {
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ NlPrint((NL_CRITICAL, "NetrAccountSync: Concurrent call detected.\n" ));
+ ServerSession = NULL;
+ Status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+ ServerSession->SsFlags |= SS_LOCKED;
+
+ UNLOCK_SERVER_SESSION_TABLE();
+
+ //
+ // Build a buffer descriptor describing the buffer the caller passed in.
+ //
+
+ if ( Buffer == NULL || BufferSize == 0 ) {
+ Status = STATUS_BUFFER_TOO_SMALL;
+ goto Cleanup;
+ }
+
+ BufferDescriptor.Buffer = Buffer;
+ BufferDescriptor.AllocSize = BufferSize;
+
+ BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer;
+ BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer +
+ BufferDescriptor.AllocSize;
+
+ //
+ // Compute the total number of entries.
+ //
+ //
+ // Calculate total entries i.e. total records avaialable
+ // modal rec + # group rec + # user rec (plus group membership
+ // information) + 3 record for UasBuiltinGroups
+ //
+
+ Status = SamrQueryInformationDomain( DBInfo->DBHandle,
+ DomainGeneralInformation,
+ &DomainInfo );
+ if ( !NT_SUCCESS(Status) ) {
+ DomainInfo = NULL;
+ goto Cleanup;
+ }
+
+ *TotalEntries = 1 + (DomainInfo->General.GroupCount * 2) +
+ DomainInfo->General.UserCount +
+ 3;
+
+ NlPrint((NL_SYNC,
+ "NetrAccountSync: GroupCount: %ld UserCount: %ld\n",
+ DomainInfo->General.GroupCount,
+ DomainInfo->General.UserCount ));
+
+
+
+ //
+ // Warn the user if there are too many global groups in the domain.
+ //
+ // Lanman only support 255 groups. However this includes the global groups
+ // LOCAL, ADMINS, USERS, and GUESTS. So only 251 real global groups are
+ // allowed before the Lanman BDC goes into an infinite full sync.
+ //
+ if (!NlGlobalTooManyGlobalGroups && DomainInfo->General.GroupCount > 251 ) {
+ NlGlobalTooManyGlobalGroups = TRUE;
+
+ NlpWriteEventlog (
+ NELOG_NetlogonTooManyGlobalGroups,
+ EVENTLOG_ERROR_TYPE,
+ NULL,
+ 0,
+ NULL,
+ 0 );
+ }
+
+ SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation );
+
+ //
+ // If this is the first call, allocate and initialize the sync context.
+ //
+
+ if ( Reference == 0 ) {
+
+ //
+ // If there already is a sync context,
+ // delete it.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ } else {
+
+ ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) );
+ if ( ServerSession->SsSync == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Initialize all the fields in the newly allocated resume handle
+ // to indicate that SAM has never yet been called.
+ //
+
+ INIT_SYNC_CONTEXT( ServerSession->SsSync, SamDBContextType );
+
+ SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
+
+ SamDBContext->SyncState = GroupState;
+ SamDBContext->SyncSerial = 1;
+
+ //
+ // On the first record only, return the current serial number of
+ // the database so the caller can use it as a description of the
+ // database.
+ //
+
+ LastRecordId->SerialNumber =
+ NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart;
+ if (!RtlTimeToSecondsSince1970( &DBInfo->CreationTime,
+ &LastRecordId->TimeCreated )) {
+ NlPrint((NL_CRITICAL,
+ "NetAccountSync: DomainCreationTime can't be converted\n" ));
+ LastRecordId->TimeCreated = 0;
+ }
+ Where = LastRecordId->ComputerName;
+ NetpLogonPutOemString(
+ NlGlobalAnsiComputerName,
+ sizeof(LastRecordId->ComputerName),
+ &Where );
+
+ //
+ // Put the description of the Domain at the front of the buffer for the
+ // first call.
+ //
+
+ Status = NlPackUasDomain( &BufferDescriptor, DBInfo );
+
+ if ( Status != STATUS_SUCCESS ) {
+ Status = STATUS_BUFFER_TOO_SMALL;
+ goto Cleanup;
+ }
+
+ (*CountReturned)++;
+
+ } else {
+ if ( (ServerSession->SsSync == NULL) ||
+ (ServerSession->SsSync->DBContextType != SamDBContextType) ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
+
+ if ( SamDBContext->SyncSerial != Reference ) {
+ Status = STATUS_SYNCHRONIZATION_REQUIRED;
+ goto Cleanup;
+ }
+
+ SamDBContext->SyncSerial++;
+ }
+
+ //
+ // Loop for each entry placed in the output buffer
+ //
+ // Each iteration of the loop below puts one more entry into the array
+ // returned to the caller. The algorithm is split into 2 parts. The
+ // first part checks to see if we need to retrieve more information from
+ // SAM and gets the description of several users or groups from SAM in a
+ // single call. The second part puts a single entry into the buffer
+ // returned to the caller.
+ //
+
+ while ( SamDBContext->SyncState != SamDoneState ) {
+
+ //
+ // Get more information from SAM
+ //
+ // Handle when we've not yet called SAM or we've already consumed
+ // all of the information returned on a previous call to SAM.
+ //
+ // This is a 'while' rather than an 'if' to handle the case
+ // where SAM returns zero entries.
+ //
+
+ while ( SamDBContext->Index >= SamDBContext->Count ) {
+
+ //
+ // Free any previous buffer returned from SAM.
+ //
+
+ if ( ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+ }
+
+ //
+ // If we've already gotten everything from SAM,
+ // we've finished all of the groups,
+ //
+ // If we've just done the groups,
+ // go on to do the users.
+ //
+ // If we've just done the users,
+ // go on to do the group memberships.
+ //
+ // If we've just done the group memberships,
+ // we're all done.
+ //
+
+ if ( SamDBContext->SamAllDone ) {
+
+ SamDBContext->SamEnumHandle = 0;
+ SamDBContext->Index = 0;
+ SamDBContext->Count = 0;
+ SamDBContext->SamAllDone = FALSE;
+ SamDBContext->UasBuiltinGroups = 0;
+
+ if (SamDBContext->SyncState == GroupState ) {
+ SamDBContext->SyncState = UasBuiltinGroupState;
+ } else if (SamDBContext->SyncState == UasBuiltinGroupState ) {
+ SamDBContext->SyncState = UserState;
+ } else if (SamDBContext->SyncState == UserState ) {
+ SamDBContext->SyncState = SamDoneState;
+ Status = STATUS_SUCCESS;
+ }
+
+ break;
+ }
+
+ //
+ // Do the actual enumeration
+ //
+
+ if (SamDBContext->SyncState == GroupState) {
+
+ Status = SamrEnumerateGroupsInDomain(
+ DBInfo->DBHandle,
+ &SamDBContext->SamEnumHandle,
+ &SamDBContext->SamEnum,
+ SAM_SYNC_PREF_MAX,
+ &SamDBContext->Count );
+
+ } else if (SamDBContext->SyncState == UasBuiltinGroupState) {
+
+ SamDBContext->SamEnum = NULL;
+ SamDBContext->Count = UAS_BUILTIN_GROUPS_COUNT;
+
+ Status = STATUS_SUCCESS;
+
+ } else if (SamDBContext->SyncState == UserState ) {
+
+ Status = SamrEnumerateUsersInDomain(
+ DBInfo->DBHandle,
+ &SamDBContext->SamEnumHandle,
+ 0, // enumerate all accounts.
+ &SamDBContext->SamEnum,
+ SAM_SYNC_PREF_MAX,
+ &SamDBContext->Count );
+
+ } else {
+ NlPrint((NL_CRITICAL,
+ "NetrAccountSync: Invalid state: %ld\n",
+ SamDBContext->SyncState ));
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ SamDBContext->SamEnum = NULL;
+ goto Cleanup;
+ }
+
+#if DBG
+ if( SamDBContext->SamEnum != NULL ) {
+ NlAssert( SamDBContext->Count ==
+ SamDBContext->SamEnum->EntriesRead );
+ }
+#endif // DBG
+
+ //
+ // If SAM says there is more information,
+ // just ensure he returned something to us on this call.
+ //
+
+ if ( Status == STATUS_MORE_ENTRIES ) {
+ if ( SamDBContext->Count == 0 ) {
+ Status = STATUS_INTERNAL_ERROR;
+ goto Cleanup;
+ }
+
+ //
+ // If SAM says he's returned all of the information,
+ // remember not to ask SAM for more.
+ //
+
+ } else {
+ SamDBContext->SamAllDone = TRUE;
+ }
+
+ SamDBContext->Index = 0;
+ }
+
+ //
+ // Place this entry into the return buffer.
+ //
+
+ if ( SamDBContext->Count > 0 ) {
+
+ if (SamDBContext->SyncState == GroupState ) {
+ Status = NlPackUasGroup(
+ SamDBContext->SamEnum->
+ Buffer[SamDBContext->Index].RelativeId,
+ &BufferDescriptor,
+ DBInfo,
+ &SamDBContext->UasBuiltinGroups );
+
+ } else if (SamDBContext->SyncState == UasBuiltinGroupState ) {
+ Status = NlPackUasBuiltinGroup(
+ SamDBContext->Index,
+ &BufferDescriptor,
+ &SamDBContext->UasBuiltinGroups );
+
+ } else if (SamDBContext->SyncState == UserState ) {
+
+ BUFFER_DESCRIPTOR SavedBufferDescriptor;
+
+
+ //
+ // Compute the RotateCount for LogonHours
+ //
+ // Do it only once.
+ //
+
+ if ( !RotateCountComputed ) {
+ if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) {
+ Status = STATUS_INTERNAL_ERROR;
+ goto Cleanup;
+ }
+ RotateCountComputed = TRUE;
+ }
+
+ //
+ // save buffer info so that we can restore it when
+ // we can't place user record and its group
+ // membership record in single transmit buffer.
+ //
+
+ SavedBufferDescriptor = BufferDescriptor;
+
+ Status = NlPackUasUser(
+ SamDBContext->SamEnum->
+ Buffer[SamDBContext->Index].RelativeId,
+ &BufferDescriptor,
+ DBInfo,
+ &ServerSession->SsSessionKey,
+ RotateCount );
+
+ //
+ // if we have successfully packed the user record, then
+ // place its group membership record immediately.
+ //
+
+ if ( Status == STATUS_SUCCESS ) {
+
+ Status = NlPackUasUserGroupMember(
+ SamDBContext->SamEnum->
+ Buffer[SamDBContext->Index].RelativeId,
+ &BufferDescriptor,
+ DBInfo );
+
+ if ( Status == STATUS_SUCCESS ) {
+
+ //
+ // increment record count
+ //
+
+ (*CountReturned)++;
+
+ } else {
+
+ BufferDescriptor = SavedBufferDescriptor;
+ }
+ }
+ }
+
+
+ //
+ // If the record was properly packed,
+ // just go on to the next record.
+ //
+
+ if ( Status != STATUS_SUCCESS ) {
+ goto Cleanup;
+ }
+
+ SamDBContext->Index ++;
+ (*CountReturned)++;
+
+ //
+ // If we're debugging replication, return only one change to the caller.
+ //
+#if DBG
+ if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+#endif // DBG
+
+ //
+ // if the service is going down, stop packing records and
+ // return to the caller.
+ //
+
+ if( NlGlobalTerminate ) {
+
+ NlPrint((NL_CRITICAL, "NetrAccountSync is asked to return "
+ "when the service is going down.\n"));
+ Status = STATUS_MORE_ENTRIES;
+ goto Cleanup;
+ }
+ }
+ }
+
+Cleanup:
+
+ //
+ // Set the return parameters to the proper values.
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+ if ( Status == STATUS_MORE_ENTRIES ) {
+ *NextReference = SamDBContext->SyncSerial;
+ } else {
+ *NextReference = (ULONG) -1;
+
+ }
+ } else {
+ *CountReturned = 0;
+ *NextReference = 0;
+ }
+
+
+ //
+ // Unlock the server session entry if we've locked it.
+ //
+
+ if ( ServerSession != NULL ) {
+
+ //
+ // If we're done, free up the context structure,
+ //
+
+ if ( Status != STATUS_MORE_ENTRIES && ServerSession->SsSync != NULL ) {
+ CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
+
+ NetpMemoryFree( ServerSession->SsSync );
+ ServerSession->SsSync = NULL;
+ }
+
+ NlUnlockServerSession( ServerSession );
+ }
+
+ NlPrint((NL_SYNC,
+ "NetrAccountSync: UAS full sync returns %lx to "
+ FORMAT_LPWSTR "\n",
+ Status,
+ ComputerName ));
+
+ return Status;
+
+ UNREFERENCED_PARAMETER( Level );
+
+}
+
+
+NET_API_STATUS
+NetrLogonControl(
+ IN LPWSTR ServerName OPTIONAL,
+ IN DWORD FunctionCode,
+ IN DWORD QueryLevel,
+ OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
+ )
+
+/*++
+
+Routine Description:
+
+ This function controls various aspects of the Netlogon service. It
+ can be used to request that a BDC ensure that its copy of the SAM
+ database is brought up to date. It can, also, be used to determine
+ if a BDC currently has a secure channel open to the PDC.
+
+ Only an Admin, Account Operator or Server Operator may call this
+ function.
+
+Arguments:
+
+ ServerName - The name of the remote server.
+
+ FunctionCode - Defines the operation to be performed. The valid
+ values are:
+
+ FunctionCode Values
+
+ NETLOGON_CONTROL_QUERY - No operation. Merely returns the
+ information requested.
+
+ NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
+ to be brought in sync with the copy on the PDC. This
+ operation does NOT imply a full synchronize. The
+ Netlogon service will merely replicate any outstanding
+ differences if possible.
+
+ NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
+ completely new copy of the SAM database from the PDC.
+ This operation will perform a full synchronize.
+
+ NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
+ to replicate now.
+
+ QueryLevel - Indicates what information should be returned from
+ the Netlogon Service. Must be 1.
+
+ QueryInformation - Returns a pointer to a buffer which contains the
+ requested information. The buffer must be freed using
+ NetApiBufferFree.
+
+
+Return Value:
+
+ NERR_Success: the operation was successful
+
+ ERROR_NOT_SUPPORTED: Function code is not valid on the specified
+ server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ QueryInformation->NetlogonInfo1 = NULL;
+
+ switch( QueryLevel ) {
+ case (1):
+ break;
+ case (2):
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+
+ default:
+ NetStatus = ERROR_INVALID_LEVEL;
+ goto Cleanup;
+ }
+
+ //
+ // ensure the input data is valid.
+ //
+
+ switch( FunctionCode ) {
+ case NETLOGON_CONTROL_QUERY:
+ case NETLOGON_CONTROL_REPLICATE:
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+
+#if DBG
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+ case NETLOGON_CONTROL_BREAKPOINT:
+#endif // DBG
+
+ break;
+
+ default:
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+
+ }
+
+ NetStatus = NetrLogonControl2Ex(
+ ServerName,
+ FunctionCode,
+ QueryLevel,
+ NULL,
+ QueryInformation );
+
+Cleanup:
+
+ return( NetStatus );
+}
+
+NET_API_STATUS
+NetrLogonControl2(
+ IN LPWSTR ServerName OPTIONAL,
+ IN DWORD FunctionCode,
+ IN DWORD QueryLevel,
+ IN PNETLOGON_CONTROL_DATA_INFORMATION InputData,
+ OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
+ )
+
+/*++
+
+Routine Description:
+
+ Same as NetrLogonControl2Ex.
+
+ A client should never pass a QueryLevel of 4 to this procedure. We don't check since, if
+ they did, it's too late now. The client will access violate upon return.
+
+Arguments:
+
+ Same as NetrLogonControl2Ex.
+
+Return Value:
+
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ NetStatus = NetrLogonControl2Ex(
+ ServerName,
+ FunctionCode,
+ QueryLevel,
+ InputData,
+ QueryInformation );
+
+
+ return NetStatus;
+}
+
+
+
+NET_API_STATUS
+NetrLogonControl2Ex(
+ IN LPWSTR ServerName OPTIONAL,
+ IN DWORD FunctionCode,
+ IN DWORD QueryLevel,
+ IN PNETLOGON_CONTROL_DATA_INFORMATION InputData,
+ OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
+ )
+
+/*++
+
+Routine Description:
+
+ This function controls various aspects of the Netlogon service. It
+ can be used to request that a BDC ensure that its copy of the SAM
+ database is brought up to date. It can, also, be used to determine
+ if a BDC currently has a secure channel open to the PDC.
+
+ Only an Admin, Account Operator or Server Operator may call this
+ function.
+
+Arguments:
+
+ ServerName - The name of the remote server.
+
+ FunctionCode - Defines the operation to be performed. The valid
+ values are:
+
+ FunctionCode Values
+
+ NETLOGON_CONTROL_QUERY - No operation. Merely returns the
+ information requested.
+
+ NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
+ to be brought in sync with the copy on the PDC. This
+ operation does NOT imply a full synchronize. The
+ Netlogon service will merely replicate any outstanding
+ differences if possible.
+
+ NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
+ completely new copy of the SAM database from the PDC.
+ This operation will perform a full synchronize.
+
+ NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
+ to replicate now.
+
+ NETLOGON_CONTROL_REDISCOVER: Forces a DC to rediscover the
+ specified trusted domain DC.
+
+ NETLOGON_CONTROL_TC_QUERY: Query the status of the specified
+ trusted domain secure channel.
+
+ NETLOGON_CONTROL_TRANSPORT_NOTIFY: Notifies netlogon that a new transport
+ has been added. Currently, it merely resets discovery timeouts allowing
+ all secure channel discoveries to be retried immediately. However, the
+ intention is to later add support for anything similar. The intention is that
+ a client can call this function after a new transport has been added (e.g., it
+ dialed a RAS link) and immediately before calling Netlogon (e.g., indirectly
+ by doing an LsaLogonUser).
+
+ QueryLevel - Indicates what information should be returned from
+ the Netlogon Service. Must be 1.
+
+ InputData - According to the function code specified this parameter
+ will carry input data. NETLOGON_CONTROL_REDISCOVER and
+ NETLOGON_CONTROL_TC_QUERY function code specify the trusted
+ domain name (LPWSTR type) here.
+ NETLOGON_CONTROL_FIND_USER function code specifies the user name
+ (LPWSTR type) here.
+
+ QueryInformation - Returns a pointer to a buffer which contains the
+ requested information. The buffer must be freed using
+ NetApiBufferFree.
+
+
+Return Value:
+
+ NERR_Success: the operation was successful
+
+ ERROR_NOT_SUPPORTED: Function code is not valid on the specified
+ server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ NTSTATUS Status;
+ DWORD Flags = 0;
+ DWORD i;
+ DWORD InfoSize;
+ ACCESS_MASK DesiredAccess;
+
+ UNICODE_STRING DomainName;
+ PCLIENT_SESSION ClientSession = NULL;
+ LPWSTR TDCName = NULL;
+ LPWSTR TrustedDomainName = NULL;
+ WCHAR TDCBuffer[UNCLEN+1];
+
+
+ UNREFERENCED_PARAMETER( ServerName );
+
+ //
+ // Ensure the QueryLevel is valid
+ //
+
+ QueryInformation->NetlogonInfo1 = NULL;
+
+ switch( QueryLevel ) {
+ case (1):
+ case (2):
+ case (3):
+ case (4):
+ break;
+ default:
+ NetStatus = ERROR_INVALID_LEVEL;
+ goto Cleanup;
+ }
+
+ //
+ // ensure the input data is valid.
+ //
+
+ switch( FunctionCode ) {
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_FIND_USER:
+#if DBG
+ case NETLOGON_CONTROL_SET_DBFLAG:
+#endif // DBG
+
+ NlAssert( InputData != NULL );
+ if( InputData == NULL ) {
+ NetStatus = ERROR_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ //
+ // compute access mask.
+ //
+
+ switch ( FunctionCode ) {
+
+ case NETLOGON_CONTROL_QUERY:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_TRANSPORT_NOTIFY:
+ DesiredAccess = NETLOGON_QUERY_ACCESS;
+ break;
+
+ case NETLOGON_CONTROL_REPLICATE:
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_FIND_USER:
+#if DBG
+ case NETLOGON_CONTROL_BREAKPOINT:
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+#endif // DBG
+ default:
+ DesiredAccess = NETLOGON_CONTROL_ACCESS;
+ break;
+ }
+
+
+ //
+ // Perform access validation on the caller.
+ //
+
+ NetStatus = NetpAccessCheck(
+ NlGlobalNetlogonSecurityDescriptor, // Security descriptor
+ DesiredAccess, // Desired access
+ &NlGlobalNetlogonInfoMapping ); // Generic mapping
+
+ if ( NetStatus != NERR_Success) {
+ NetStatus = ERROR_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+
+ //
+ // Handle the various FunctionCodes
+ //
+
+ switch ( FunctionCode ) {
+
+ //
+ // On a query, do nothing but return status.
+ //
+
+ case NETLOGON_CONTROL_QUERY:
+ NlPrint((NL_MISC, "QUERY function received.\n" ));
+ break;
+
+ //
+ // Force a replication on a BDC.
+ //
+
+ case NETLOGON_CONTROL_REPLICATE:
+
+ //
+ // This FunctionCode is only valid on a BDC
+ //
+
+ if ( NlGlobalRole != RoleBackup ) {
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Force a replicate on all databases.
+ //
+
+ NlPrint((NL_SYNC, "Force PARTIAL SYNC function received.\n" ));
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ for( i = 0; i < NUM_DBS; i++ ) {
+ NlGlobalDBInfoArray[i].UpdateRqd = TRUE;
+ }
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ //
+ // Start the replicator now.
+ //
+
+ (VOID) NlStartReplicatorThread( 0 );
+
+ break;
+
+
+
+ //
+ // Force a full synchronize on a BDC.
+ //
+
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+
+ //
+ // This FunctionCode is only valid on a BDC
+ //
+
+ if ( NlGlobalRole != RoleBackup ) {
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Force a SYNC on all databases.
+ //
+
+ NlPrint((NL_SYNC, "Force FULL SYNC function received.\n" ));
+
+ for( i = 0; i < NUM_DBS; i++ ) {
+ (VOID) NlForceStartupSync( &NlGlobalDBInfoArray[i] );
+
+ //
+ // Do a complete full sync (don't restart it).
+ //
+ NlSetFullSyncKey( i, NULL );
+ }
+
+ //
+ // Stop the replicator.
+ //
+ // It might be in the middle of a full sync. This'll force it to
+ // start over again.
+ //
+ // It might be waiting for 5 minutes to start it's next iteration.
+ // This'll force it to start NOW.
+ //
+ // It might have marked that it's already done a full sync. This'll
+ // force it to do another one.
+ //
+
+ NlStopReplicator();
+
+
+ //
+ // Start the replicator now.
+ //
+
+ (VOID) NlStartReplicatorThread( 0 );
+
+ break;
+
+
+
+ //
+ // Force a PDC to broadcast a database change record.
+ //
+
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+
+ //
+ // This FunctionCode is only valid on a PDC
+ //
+
+ if ( NlGlobalRole != RolePrimary ) {
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Simply send the announcement. Any BDC that is out of date
+ // will replicate any changes.
+ //
+
+ NlPrint((NL_SYNC, "PDC REPLICATE function received.\n" ));
+ NlPrimaryAnnouncement( ANNOUNCE_FORCE );
+
+ break;
+
+
+ //
+ // Force to rediscover trusted domain DCs.
+ //
+
+ case NETLOGON_CONTROL_REDISCOVER:
+
+ NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_REDISCOVER function received.\n" ));
+
+ NlAssert( InputData->TrustedDomainName != NULL );
+ if( InputData->TrustedDomainName == NULL ) {
+
+ NlPrint((NL_CRITICAL, "NetrLogonControl called with "
+ "function code NETLOGON_CONTROL_REDISCOVER "
+ "specified NULL trusted domain name. \n" ));
+
+ NetStatus = ERROR_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+ RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
+
+ //
+ // get client structure for the specified domain.
+ //
+
+ ClientSession = NlFindNamedClientSession( &DomainName );
+
+ if( ClientSession == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "NetrLogonControl can't find the client structure of "
+ "the domain %wZ specified.\n", &DomainName ));
+
+ NetStatus = ERROR_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+
+
+ //
+ // Force Discovery of a DC
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NetrLogonControl2: Can't become writer of client session.\n" ));
+ NetStatus = ERROR_NO_LOGON_SERVERS;
+ goto Cleanup;
+ } else {
+ NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
+ Status = NlDiscoverDc( ClientSession, DT_Synchronous );
+ NlResetWriterClientSession( ClientSession );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NetrLogonControl: %wZ: Discovery failed %lx\n",
+ &ClientSession->CsDomainName,
+ Status ));
+
+ NetStatus = NetpNtStatusToApiStatus( Status );
+ goto Cleanup;
+ }
+ }
+
+ break;
+
+ case NETLOGON_CONTROL_TC_QUERY:
+ NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TC_QUERY function received.\n" ));
+ break;
+
+ //
+ // A client has added a new transport and needs us to use it.
+ // Mark all the client sessions that its OK to authentication NOW.
+ //
+
+ case NETLOGON_CONTROL_TRANSPORT_NOTIFY: {
+ PLIST_ENTRY ListEntry;
+ NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TRANSPORT_NOTIFY function received.\n" ));
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ LOCK_TRUST_LIST();
+
+ //
+ // Mark each entry to indicate we've not tried to authenticate recently
+ //
+
+ if ( NlGlobalClientSession != NULL ) {
+ if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint(( NL_SESSION_SETUP,
+ " %wZ: Zero LastAuth\n",
+ &NlGlobalClientSession->CsDomainName ));
+ NlGlobalClientSession->CsLastAuthenticationTry.QuadPart = 0;
+ }
+ }
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ if ( ClientSession->CsState != CS_AUTHENTICATED ) {
+ NlPrint(( NL_SESSION_SETUP,
+ " %wZ: Zero LastAuth\n",
+ &ClientSession->CsDomainName ));
+ ClientSession->CsLastAuthenticationTry.QuadPart = 0;
+ }
+ }
+
+ UNLOCK_TRUST_LIST();
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ ClientSession = NULL;
+ break;
+ }
+
+ //
+ // Find a user in one of the trusted domains.
+ //
+
+ case NETLOGON_CONTROL_FIND_USER:
+ NlPrint((NL_MISC, "NETLOGON_CONTROL_FIND_USER function received for %ws.\n", InputData->UserName ));
+
+ //
+ // Find a user in one of the trusted domains.
+ //
+ // Allow machine accounts just as a handy extension.
+ // Don't find "Local User" accounts since we can't pass through to them
+ //
+ ClientSession = NlPickDomainWithAccount (
+ InputData->UserName,
+ USER_NORMAL_ACCOUNT | USER_MACHINE_ACCOUNT_MASK );
+
+ break;
+
+#if DBG
+ //
+ // Force a breakpoint
+ //
+
+ case NETLOGON_CONTROL_BREAKPOINT:
+ KdPrint(( "I_NetLogonControl Break Point\n"));
+ DbgBreakPoint();
+ break;
+
+ //
+ // Change the debug flags
+ //
+
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ NlGlobalTrace = InputData->DebugFlag;
+ NlPrint((NL_MISC,"NlGlobalTrace is set to %lx\n", NlGlobalTrace ));
+
+ break;
+
+ //
+ // Truncate the log file
+ //
+
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+
+ NlOpenDebugFile( TRUE );
+ NlPrint((NL_MISC, "TRUNCATE_LOG function received.\n" ));
+ break;
+
+ //
+ // Backup changelog file
+ //
+
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+
+ NetStatus = NlBackupChangeLogFile();
+ NlPrint((NL_MISC, "BACKUP_CHANGE_LOG function received, (%ld).\n", NetStatus ));
+ break;
+
+#endif // DBG
+
+ //
+ // All other function codes are invalid.
+ //
+
+ default:
+ NetStatus = ERROR_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+
+ //
+ // allocate return info structure.
+ //
+
+ switch( QueryLevel ) {
+ case (1):
+ InfoSize = sizeof(NETLOGON_INFO_1);
+ break;
+ case (2):
+ InfoSize = sizeof(NETLOGON_INFO_2);
+ break;
+ case (3):
+ InfoSize = sizeof(NETLOGON_INFO_3);
+ break;
+ case (4):
+ InfoSize = sizeof(NETLOGON_INFO_4);
+ break;
+ }
+
+ QueryInformation->NetlogonInfo1 = MIDL_user_allocate( InfoSize );
+
+ if ( QueryInformation->NetlogonInfo1 == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+
+ //
+ // Return DomainName and DC Name.
+ //
+ switch( QueryLevel ) {
+ case (4):
+ switch ( FunctionCode ) {
+ case NETLOGON_CONTROL_FIND_USER:
+
+ if (ClientSession == NULL) {
+ NetStatus = NERR_UserNotFound;
+ goto Cleanup;
+ }
+
+ //
+ // Capture the name of the server
+ // (even if it is an empty string.)
+ //
+
+ Status = NlCaptureServerClientSession( ClientSession, TDCBuffer );
+
+ TDCName = NetpAllocWStrFromWStr( TDCBuffer );
+
+ if ( TDCName == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+ QueryInformation->NetlogonInfo4->netlog4_trusted_dc_name = TDCName;
+
+ //
+ // Capture the name of the domain.
+ //
+
+ TrustedDomainName = NetpAllocWStrFromWStr( ClientSession->CsDomainName.Buffer );
+
+ if ( TrustedDomainName == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+ QueryInformation->NetlogonInfo4->netlog4_trusted_domain_name = TrustedDomainName;
+ break;
+
+ default:
+ NetStatus = ERROR_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+ break;
+
+ //
+ // Return queried profile information.
+ //
+ case (3):
+ QueryInformation->NetlogonInfo3->netlog3_flags = 0;
+ QueryInformation->NetlogonInfo3->netlog3_logon_attempts =
+ MsvGetLogonAttemptCount();
+ QueryInformation->NetlogonInfo3->netlog3_reserved1 = 0;
+ QueryInformation->NetlogonInfo3->netlog3_reserved2 = 0;
+ QueryInformation->NetlogonInfo3->netlog3_reserved3 = 0;
+ QueryInformation->NetlogonInfo3->netlog3_reserved4 = 0;
+ QueryInformation->NetlogonInfo3->netlog3_reserved5 = 0;
+ break;
+
+ //
+ // Return secure channel specific information.
+ //
+ case (2):
+ switch ( FunctionCode ) {
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+
+ if( ClientSession == NULL ) {
+
+ NlAssert( InputData->TrustedDomainName != NULL );
+ if( InputData->TrustedDomainName == NULL ) {
+
+ NlPrint((NL_CRITICAL,
+ "NetrLogonControl called to query at info "
+ "level specified NULL trusted domain name. \n" )) ;
+ NetStatus = ERROR_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+ RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
+
+ //
+ // get client structure for the specified domain.
+ //
+
+ ClientSession = NlFindNamedClientSession( &DomainName );
+
+ if( ClientSession == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "NetrLogonControl can't find the client structure of "
+ "the domain %wZ specified.\n", &DomainName ));
+
+ NetStatus = ERROR_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // Capture the name of the server
+ // (even if it is an empty string.)
+ //
+
+ Status = NlCaptureServerClientSession( ClientSession, TDCBuffer );
+ QueryInformation->NetlogonInfo2->netlog2_tc_connection_status =
+ NetpNtStatusToApiStatus(Status);
+
+ TDCName = NetpAllocWStrFromWStr( TDCBuffer );
+
+ if ( TDCName == NULL ) {
+ NetStatus = ERROR_NOT_ENOUGH_MEMORY;
+ goto Cleanup;
+ }
+
+ QueryInformation->NetlogonInfo2->netlog2_trusted_dc_name = TDCName;
+ break;
+
+ default:
+ NetStatus = ERROR_INVALID_PARAMETER;
+ goto Cleanup;
+ }
+
+ //
+ // fall through to fill other fields of the info structure.
+ //
+
+
+ //
+ // Return status of secure channel to PDC.
+ //
+ case (1):
+
+ //
+ // If this is a BDC, query how replication is going.
+ //
+
+ if ( NlGlobalRole == RoleBackup ) {
+
+ //
+ // If this is a BDC tell the caller whether the replicator is running,
+ //
+
+ EnterCriticalSection( &NlGlobalReplicatorCritSect );
+ if ( IsReplicatorRunning() ) {
+ Flags |= NETLOGON_REPLICATION_IN_PROGRESS;
+ }
+ LeaveCriticalSection( &NlGlobalReplicatorCritSect );
+
+ EnterCriticalSection( &NlGlobalDbInfoCritSect );
+ for( i = 0; i < NUM_DBS; i++ ) {
+ if ( NlGlobalDBInfoArray[i].UpdateRqd ) {
+ Flags |= NETLOGON_REPLICATION_NEEDED;
+ }
+ if ( NlGlobalDBInfoArray[i].FullSyncRequired ) {
+ Flags |= NETLOGON_FULL_SYNC_REPLICATION;
+ }
+ if ( NlGlobalRedoLogDesc.EntryCount[i] != 0 ) {
+ Flags |= NETLOGON_REDO_NEEDED | NETLOGON_REPLICATION_NEEDED;
+ }
+ }
+ LeaveCriticalSection( &NlGlobalDbInfoCritSect );
+
+ }
+
+ //
+ // Fill in the return buffer
+ //
+
+ QueryInformation->NetlogonInfo1->netlog1_flags = Flags;
+ if ( NlGlobalRole == RolePrimary ) {
+ QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
+ NERR_Success;
+ } else {
+ QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
+ NetpNtStatusToApiStatus(
+ NlGlobalClientSession->CsConnectionStatus);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ NetStatus = NERR_Success;
+
+ //
+ // Free up locally used resources.
+ //
+Cleanup:
+
+ if( ClientSession != NULL ) {
+ NlUnrefClientSession( ClientSession );
+ }
+
+ if ( NetStatus != NERR_Success ) {
+
+ if ( QueryInformation->NetlogonInfo1 != NULL ) {
+ MIDL_user_free( QueryInformation->NetlogonInfo1 );
+ QueryInformation->NetlogonInfo1 = NULL;
+ }
+
+ if ( TDCName != NULL ) {
+ MIDL_user_free( TDCName );
+ }
+ if ( TrustedDomainName != NULL ) {
+ MIDL_user_free( TrustedDomainName );
+ }
+
+ }
+
+ return NetStatus;
+}
+
+
+NET_API_STATUS
+NetrGetAnyDCName (
+ IN LPWSTR ServerName OPTIONAL,
+ IN LPWSTR DomainName OPTIONAL,
+ OUT LPWSTR *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of the any domain controller for a trusted domain.
+
+ The domain controller found in guaranteed to have be up at one point during
+ this API call.
+
+Arguments:
+
+ ServerName - name of remote server (null for local)
+
+ DomainName - name of domain (null for primary domain)
+
+ Buffer - Returns a pointer to an allcated buffer containing the
+ servername of a DC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using NetApiBufferFree.
+
+Return Value:
+
+ ERROR_SUCCESS - Success. Buffer contains DC name prefixed by \\.
+
+ ERROR_NO_LOGON_SERVERS - No DC could be found
+
+ ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.
+
+ ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
+ broken.
+
+ ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
+ broken or the password is broken.
+
+ ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
+ domain controller of the specified domain.
+
+--*/
+{
+ NTSTATUS Status;
+
+ UNICODE_STRING DomainNameString;
+ UNICODE_STRING UncDcName;
+
+ UNREFERENCED_PARAMETER( ServerName );
+
+ //
+ // Fill in the primary domain name if the caller didn't specify one.
+ //
+
+ if ( DomainName == NULL || *DomainName == L'\0' ) {
+ RtlInitUnicodeString( &DomainNameString, NlGlobalUnicodeDomainName );
+ } else {
+ RtlInitUnicodeString( &DomainNameString, DomainName );
+ }
+
+ Status = I_NetGetAnyDCName( &DomainNameString,
+ &UncDcName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return NetpNtStatusToApiStatus(Status);
+ }
+
+ *Buffer = UncDcName.Buffer;
+ return NERR_Success;
+
+
+}
+
+
+NTSTATUS
+I_NetGetAnyDCName (
+ IN PUNICODE_STRING DomainName,
+ OUT PUNICODE_STRING Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of the any domain controller for a trusted domain.
+
+ The domain controller found in guaranteed to have be up at one point during
+ this API call. The machine is also guaranteed to be a DC in the domain
+ specified.
+
+ The caller of this routine should not have any locks held (it calls the
+ LSA back in several instances). This routine may take some time to execute.
+
+Arguments:
+
+ DomainName - name of domain
+
+ UncDcName - Returns a pointer to an allcated buffer containing the
+ servername of a DC of the domain. The server name is prefixed
+ by \\. The buffer should be deallocated using MIDL_user_free.
+
+Return Value:
+
+ STATUS_SUCCESS - Success. Buffer contains DC name prefixed by \\.
+
+ STATUS_NO_LOGON_SERVERS - No DC could be found
+
+ STATUS_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.
+
+ STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
+ broken.
+
+ STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
+ broken or the password is broken.
+
+ STATUS_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
+ domain controller of the specified domain.
+
+
+--*/
+{
+ NTSTATUS Status;
+
+ PCLIENT_SESSION ClientSession = NULL;
+ ULONG DiscoveryDone = FALSE;
+
+ UNICODE_STRING UncDcNameString;
+ WCHAR UncDcName[UNCLEN+1];
+
+ LSA_HANDLE LsaHandle = NULL;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomain = NULL;
+ PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomain = NULL;
+
+ RtlInitUnicodeString( Buffer, NULL );
+
+ //
+ // If netlogon is not running (LSA is calling us directly),
+ // don't let the caller proceed.
+
+ if ( NlGlobalChangeLogNetlogonState != NetlogonStarted ) {
+ Status = STATUS_NETLOGON_NOT_STARTED;
+ goto Cleanup;
+ }
+
+ //
+ // On the PDC or BDC,
+ // find the Client session for the domain.
+ // On workstations,
+ // find the primary domain client session.
+ //
+
+ ClientSession = NlFindNamedClientSession( DomainName );
+
+ if ( ClientSession == NULL ) {
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: No such trusted domain\n",
+ DomainName ));
+ Status = STATUS_NO_SUCH_DOMAIN;
+ goto Cleanup;
+ }
+
+
+ //
+ // Don't give up unless we've done discovery.
+
+ do {
+
+ //
+ // If we don't currently know the name of the server,
+ // discover one.
+ //
+
+ if ( ClientSession->CsState == CS_IDLE ) {
+
+ //
+ // If we've tried to authenticate recently,
+ // don't bother trying again.
+ //
+
+ if ( !NlTimeToReauthenticate( ClientSession ) ) {
+ Status = ClientSession->CsConnectionStatus;
+ goto Cleanup;
+
+ }
+
+ //
+ // Discover a DC
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+
+ // Check again now that we're the writer
+ if ( ClientSession->CsState == CS_IDLE ) {
+ Status = NlDiscoverDc( ClientSession, DT_Synchronous );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlResetWriterClientSession( ClientSession );
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: Discovery failed %lx\n",
+ DomainName,
+ Status ));
+ goto Cleanup;
+ }
+
+ DiscoveryDone = TRUE;
+ }
+
+ NlResetWriterClientSession( ClientSession );
+
+ }
+
+
+
+ //
+ // Capture a copy of the DC the session is to.
+ //
+
+ Status = NlCaptureServerClientSession( ClientSession, UncDcName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ continue;
+ }
+
+
+ //
+ // Cleanup resources from the previous iteration of the loop
+ //
+
+ if ( LsaHandle != NULL ) {
+ (VOID) LsaClose( LsaHandle );
+ LsaHandle = NULL;
+ }
+
+ if ( AccountDomain != NULL ) {
+ (VOID) LsaFreeMemory( AccountDomain );
+ AccountDomain= NULL;
+ }
+
+ if ( PrimaryDomain != NULL ) {
+ (VOID) LsaFreeMemory( PrimaryDomain );
+ PrimaryDomain = NULL;
+ }
+
+
+ //
+ // Open the policy database on the machine and query its primary and
+ // account domains.
+ //
+
+ RtlInitUnicodeString( &UncDcNameString, UncDcName );
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
+
+ Status = LsaOpenPolicy( &UncDcNameString,
+ &ObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &LsaHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ"
+ ": LsaOpenPolicy failed on " FORMAT_LPWSTR " %lx\n",
+ DomainName,
+ UncDcName,
+ Status ));
+
+ Status = STATUS_NO_LOGON_SERVERS;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ Status = LsaQueryInformationPolicy( LsaHandle,
+ PolicyPrimaryDomainInformation,
+ &PrimaryDomain );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy "
+ "(Primary) failed on " FORMAT_LPWSTR " %lx\n",
+ DomainName,
+ UncDcName,
+ Status ));
+
+ Status = STATUS_NO_LOGON_SERVERS;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ Status = LsaQueryInformationPolicy( LsaHandle,
+ PolicyAccountDomainInformation,
+ &AccountDomain );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy "
+ "(Account) failed on " FORMAT_LPWSTR " %lx\n",
+ DomainName,
+ UncDcName,
+ Status ));
+
+ Status = STATUS_NO_LOGON_SERVERS;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+
+ //
+ // Ensure the machine is really a member of the queried domain.
+ //
+
+ if ( !RtlEqualDomainName( DomainName, &PrimaryDomain->Name ) ) {
+
+ Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: "
+ "Domain name mismatch %wZ from " FORMAT_LPWSTR ".\n",
+ DomainName,
+ &PrimaryDomain->Name,
+ UncDcName ));
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ //
+ // Ensure the machine is still a DC.
+ //
+
+ if ( AccountDomain->DomainSid == NULL ||
+ PrimaryDomain->Sid == NULL ||
+ !RtlEqualSid( AccountDomain->DomainSid,
+ PrimaryDomain->Sid ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: "
+ "Not-LanManNt mismatch from " FORMAT_LPWSTR ".\n",
+ DomainName,
+ UncDcName ));
+
+ Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ //
+ // Ensure the domain has the right sid.
+ //
+
+ if ( PrimaryDomain->Sid == NULL ||
+ !RtlEqualSid( ClientSession->CsDomainId,
+ PrimaryDomain->Sid ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "I_NetGetAnyDcName: %wZ: "
+ "Sid mismatch from " FORMAT_LPWSTR ".\n",
+ DomainName,
+ UncDcName ));
+
+ Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+
+ //
+ // We've found it.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ } while ( !NT_SUCCESS(Status) && !DiscoveryDone );
+
+
+
+ //
+ // Free any locally used resources.
+ //
+Cleanup:
+
+ //
+ // Don't divulge too much to the caller.
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ) {
+ Status = STATUS_NO_TRUST_SAM_ACCOUNT;
+ }
+
+ if ( ClientSession != NULL ) {
+ NlUnrefClientSession( ClientSession );
+ }
+
+ if ( LsaHandle != NULL ) {
+ (VOID) LsaClose( LsaHandle );
+ }
+
+ if ( AccountDomain != NULL ) {
+ (VOID) LsaFreeMemory( AccountDomain );
+ }
+
+ if ( PrimaryDomain != NULL ) {
+ (VOID) LsaFreeMemory( PrimaryDomain );
+ }
+
+
+
+ //
+ // Return the DCName to the caller.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ LPWSTR AllocatedUncDcName;
+
+ AllocatedUncDcName = NetpAllocWStrFromWStr( UncDcName );
+
+ if ( AllocatedUncDcName == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ } else {
+ RtlInitUnicodeString( Buffer, AllocatedUncDcName );
+ }
+ }
+
+ return Status;
+}
diff --git a/private/net/svcdlls/logonsrv/server/ssiapi.h b/private/net/svcdlls/logonsrv/server/ssiapi.h
new file mode 100644
index 000000000..cc973de2d
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/ssiapi.h
@@ -0,0 +1,81 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ ssiapi.h
+
+Abstract:
+
+ Declartions of APIs used between Netlogon Services for the NT to NT case.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 25-Jul-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//////////////////////////////////////////////////////////////////////
+//
+// API Interfaces used only between Netlogon and itself.
+//
+//////////////////////////////////////////////////////////////////////
+
+
+NTSTATUS
+I_NetDatabaseDeltas (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PNLPR_MODIFIED_COUNT DomainModifiedCount,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+I_NetDatabaseSync (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN OUT PULONG SamSyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+I_NetDatabaseSync2 (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN DWORD DatabaseID,
+ IN SYNC_STATE RestartState,
+ IN OUT PULONG SamSyncContext,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray,
+ IN DWORD PreferredMaximumLength
+ );
+
+NTSTATUS
+I_NetDatabaseRedo (
+ IN LPWSTR PrimaryName,
+ IN LPWSTR ComputerName,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
+ IN LPBYTE ChangeLogEntry,
+ IN DWORD ChangeLogEntrySize,
+ OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArray
+ );
+
diff --git a/private/net/svcdlls/logonsrv/server/ssiauth.c b/private/net/svcdlls/logonsrv/server/ssiauth.c
new file mode 100644
index 000000000..16bd7cf2e
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/ssiauth.c
@@ -0,0 +1,788 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ ssiauth.c
+
+Abstract:
+
+ Authentication related functions
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 12-Jul-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <lmerr.h> // NERR_*
+
+
+LONG NlGlobalSessionCounter = 0;
+
+
+VOID
+NlMakeSessionKey(
+ IN PNT_OWF_PASSWORD CryptKey,
+ IN PNETLOGON_CREDENTIAL ClientChallenge,
+ IN PNETLOGON_CREDENTIAL ServerChallenge,
+ OUT PNETLOGON_SESSION_KEY SessionKey
+ )
+/*++
+
+Routine Description:
+
+ Build an encryption key for use in authentication for
+ this RequestorName.
+
+Arguments:
+
+ CryptKey -- The OWF password of the user account being used.
+
+ ClientChallenge -- 8 byte (64 bit) number generated by caller
+
+ ServerChallenge -- 8 byte (64 bit) number generated by primary
+
+ SessionKey -- 8 byte (64 bit) number generated at both ends
+
+Return Value:
+
+ TRUE: Success
+ FALSE: Failure
+
+ NT status code.
+
+--*/
+{
+ NTSTATUS Status;
+ BLOCK_KEY BlockKey;
+ NETLOGON_SESSION_KEY TempSessionKey;
+
+ //
+ // we will have a 112 bit key (64 bit encrypted rest padded with 0s)
+ //
+
+ RtlZeroMemory(SessionKey, sizeof(NETLOGON_SESSION_KEY));
+
+ //
+ // SessionKey = C + P (arithmetic sum ignore carry)
+ //
+
+ *((unsigned long * ) SessionKey) =
+ *((unsigned long * ) ClientChallenge) +
+ *((unsigned long * ) ServerChallenge);
+
+ *((unsigned long * )((LPBYTE)SessionKey + 4)) =
+ *((unsigned long * )((LPBYTE)ClientChallenge + 4)) +
+ *((unsigned long * )((LPBYTE)ServerChallenge + 4));
+
+
+ //
+ // CryptKey is our 16 byte key to be used as described in codespec
+ // use first 7 bytes of CryptKey for first encryption
+ //
+
+ RtlCopyMemory( &BlockKey, CryptKey, BLOCK_KEY_LENGTH );
+
+ Status = RtlEncryptBlock(
+ (PCLEAR_BLOCK) SessionKey, // Clear text
+ &BlockKey, // Key
+ (PCYPHER_BLOCK) &TempSessionKey); // Cypher Block
+
+ NlAssert( NT_SUCCESS( Status ) );
+
+
+ //
+ // Further encrypt the encrypted "SessionKey" using upper 7 bytes
+ //
+
+ NlAssert( LM_OWF_PASSWORD_LENGTH == 2*BLOCK_KEY_LENGTH+2 );
+
+ RtlCopyMemory( &BlockKey,
+ ((PUCHAR)CryptKey) + 2 + BLOCK_KEY_LENGTH,
+ BLOCK_KEY_LENGTH );
+
+ Status = RtlEncryptBlock(
+ (PCLEAR_BLOCK) &TempSessionKey, // Clear text
+ &BlockKey, // Key
+ (PCYPHER_BLOCK) SessionKey); // Cypher Block
+
+ NlAssert( NT_SUCCESS( Status ) );
+
+ return;
+}
+
+
+NTSTATUS
+NlCheckAuthenticator(
+ IN OUT PSERVER_SESSION ServerSession,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator
+ )
+/*++
+
+Routine Description:
+
+ Verify that supplied Authenticator is valid.
+ It is intended for use by the server side after initial authentication
+ has succeeded. This routine will modify the seed by
+ first adding the time-of-day received from the Authenticator
+ and then by incrementing it.
+
+ A ReturnAuthenticator is built based on the final seed.
+
+Arguments:
+
+ ServerSession - Pointer to the ServerSession structure. The following
+ fields are used:
+
+ SsAuthenticationSeed - Supplies the seed used for authentication and
+ returns the updated seed.
+
+ SsSessionKey - The session key used for encryption.
+
+ SsCheck - Is zeroed to indicate successful communication with the client.
+
+ Authenticator - The authenticator passed by the caller.
+
+ ReturnAuthenticator - The authenticator we'll return to the caller.
+
+Return Value:
+
+ STATUS_SUCCESS;
+ STATUS_ACCESS_DENIED;
+ STATUS_TIME_DIFFERENCE_AT_DC;
+
+--*/
+{
+
+ NETLOGON_CREDENTIAL TargetCredential;
+
+
+#ifdef notdef // Doesn't work if caller in different time zone
+
+ LARGE_INTEGER TimeNow;
+ long timeofday;
+ long timediff;
+
+ //
+ // First check if time-of-day is rational.
+ //
+
+ NtQuerySystemTime( &TimeNow );
+ RtlTimeToSecondsSince1970( &TimeNow, &timeofday );
+
+ timediff = timeofday - Authenticator->timestamp;
+ if (timediff < 0) {
+ timediff = Authenticator->timestamp - timeofday;
+ }
+
+ if (timediff > RATIONAL_TIME) {
+ return STATUS_TIME_DIFFERENCE_AT_DC;
+ }
+#endif // notdef
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlCheckAuthenticator: Seed = %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0],
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: SessionKey = %lx %lx %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsSessionKey))[0],
+ ((DWORD *) (&ServerSession->SsSessionKey))[1],
+ ((DWORD *) (&ServerSession->SsSessionKey))[2],
+ ((DWORD *) (&ServerSession->SsSessionKey))[3]));
+
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Client Authenticator GOT = %lx %lx\n",
+ ((DWORD *) (&Authenticator->Credential))[0],
+ ((DWORD *) (&Authenticator->Credential))[1]));
+
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Time = %lx\n",
+ ((DWORD *) (&Authenticator->timestamp))[0] ));
+#endif // BAD_ALIGNMENT
+
+
+
+ //
+ // modify the seed before computing auth_credential for verification
+ // Two long words are added and overflow carry (if any) ignored
+ // This will leave upper 4 bytes unchanged
+ //
+
+ *((unsigned long * ) &ServerSession->SsAuthenticationSeed) += Authenticator->timestamp;
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Seed + TIME = %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0],
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Compute TargetCredential to verify the one supplied in the Authenticator
+ //
+
+ NlComputeCredentials( &ServerSession->SsAuthenticationSeed,
+ &TargetCredential,
+ &ServerSession->SsSessionKey );
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Client Authenticator MADE = %lx %lx\n",
+ ((DWORD *) (&TargetCredential))[0],
+ ((DWORD *) (&TargetCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+ //
+ // verify the computed credentials with those supplied
+ // Authenticator must have used seed + time_of_day as seed
+ //
+
+ if (RtlCompareMemory( &Authenticator->Credential,
+ &TargetCredential,
+ sizeof(TargetCredential)) !=
+ sizeof(TargetCredential)) {
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // modify our seed before computing the ReturnAuthenticator.
+ // The requestor will increment his seed if he matches this credentials.
+ //
+
+ (*((unsigned long * ) &ServerSession->SsAuthenticationSeed))++;
+
+ //
+ // compute ClientCredential to send back to requestor
+ //
+
+ NlComputeCredentials( &ServerSession->SsAuthenticationSeed,
+ &ReturnAuthenticator->Credential,
+ &ServerSession->SsSessionKey);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Server Authenticator SEND = %lx %lx\n",
+ ((DWORD *) (&ReturnAuthenticator->Credential))[0],
+ ((DWORD *) (&ReturnAuthenticator->Credential))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,
+ "NlCheckAuthenticator: Seed + time + 1= %lx %lx\n",
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0],
+ ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Indicate successful communication with the client
+ //
+
+ ServerSession->SsCheck = 0;
+ ServerSession->SsPulseTimeoutCount = 0;
+ ServerSession->SsFlags &= ~SS_PULSE_SENT;
+
+ return STATUS_SUCCESS;
+
+}
+
+
+VOID
+NlComputeCredentials(
+ IN PNETLOGON_CREDENTIAL Challenge,
+ OUT PNETLOGON_CREDENTIAL Credential,
+ IN PNETLOGON_SESSION_KEY SessionKey
+ )
+/*++
+
+Routine Description:
+
+ Calculate the credentials by encrypting the 8 byte
+ challenge with first 7 bytes of sessionkey and then
+ further encrypting it by next 7 bytes of sessionkey.
+
+Arguments:
+
+ Challenge - Supplies the 8 byte (64 bit) challenge
+
+ Credential - Returns the 8 byte (64 bit) number generated
+
+ SessionKey 14 byte (112 bit) encryption key
+
+Return Value:
+
+ NONE
+
+--*/
+{
+ NTSTATUS Status;
+ BLOCK_KEY BlockKey;
+ CYPHER_BLOCK IntermediateBlock;
+
+ RtlZeroMemory(Credential, sizeof(*Credential));
+
+ //
+ // use first 7 bytes of SessionKey for first encryption
+ //
+
+ RtlCopyMemory( &BlockKey, SessionKey, BLOCK_KEY_LENGTH );
+
+ Status = RtlEncryptBlock( (PCLEAR_BLOCK) Challenge, // Cleartext
+ &BlockKey, // Key
+ &IntermediateBlock ); // Cypher Block
+
+ NlAssert( NT_SUCCESS(Status) );
+
+ //
+ // further encrypt the encrypted Credential using next 7 bytes
+ //
+
+ RtlCopyMemory( &BlockKey,
+ ((PUCHAR)SessionKey) + BLOCK_KEY_LENGTH,
+ BLOCK_KEY_LENGTH );
+
+ Status = RtlEncryptBlock( (PCLEAR_BLOCK) &IntermediateBlock, // Cleartext
+ &BlockKey, // Key
+ Credential ); // Cypher Block
+
+ NlAssert( NT_SUCCESS(Status) );
+
+ return;
+
+}
+
+
+
+VOID
+NlComputeChallenge(
+ OUT PNETLOGON_CREDENTIAL Challenge
+ )
+
+/*++
+
+Routine Description:
+
+ Generates a 64 bit challenge
+
+ Make an 8 byte seed by filling in BigTime i.e. seconds
+ since Jan 1 1970 in lower four bytes and a counter in
+ upper four bytes. Counter is incremented after each use.
+ This seed is used as encryption key to encrypt standard
+ text which will be used as challenge.
+
+Arguments:
+
+ Challenge - Returns the computed challenge
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+ char Seed[PWLEN];
+ LM_OWF_PASSWORD BigChallenge;
+ LARGE_INTEGER TimeNow;
+
+
+ RtlZeroMemory(Seed, sizeof(Seed) );
+
+ //
+ // we need to remember ClientChallenge and RequestorName for future use
+ // put these into shared seg SSISEG
+ // NlGlobalSessionCounter is a global initialized to 0 at UAS init time
+ //
+
+
+ Status = NtQuerySystemTime( &TimeNow );
+ NlAssert( NT_SUCCESS(Status) );
+
+ Status = RtlTimeToSecondsSince1970( &TimeNow, ((unsigned long * ) Seed) );
+ NlAssert( NT_SUCCESS(Status) );
+
+ *((unsigned long * ) & Seed[4]) = NlGlobalSessionCounter++;
+
+ //
+ // Create ClientChallenge
+ //
+ // NOTE: RtlCalculateLmOwfPassword() will generate 16 byte txt
+ //
+
+ Status = RtlCalculateLmOwfPassword(Seed, &BigChallenge);
+ NlAssert( NT_SUCCESS(Status) );
+
+ //
+ // we need (or will use) only 8 bytes of this info
+ //
+
+ RtlCopyMemory(Challenge, &BigChallenge, sizeof(Challenge) );
+
+ return;
+}
+
+
+
+VOID
+NlBuildAuthenticator(
+ IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed,
+ IN PNETLOGON_SESSION_KEY SessionKey,
+ OUT PNETLOGON_AUTHENTICATOR Authenticator
+ )
+/*++
+
+Routine Description:
+
+ Build the authenticator to be sent to primary.
+ This routine will modify the seed by adding the
+ time-of-day before computing the credentials.
+
+Arguments:
+
+ AuthenticationSeed -- The current authentication seed. This seed will
+ have the current time of day added to it prior to building the
+ Authenticator.
+
+ SessionKey - The Session Key used for encrypting the Authenticator.
+
+ Authenticator - The Authenticator to pass to the PDC for the current
+ call.
+
+Return Value:
+
+ NT Status code
+
+--*/
+{
+ NTSTATUS Status;
+ LARGE_INTEGER TimeNow;
+
+ //
+ // Use the current time of day to modify the authentication seed
+ //
+
+ RtlZeroMemory(Authenticator, sizeof(*Authenticator));
+
+ Status = NtQuerySystemTime( &TimeNow );
+ NlAssert( NT_SUCCESS(Status) );
+
+ Status = RtlTimeToSecondsSince1970( &TimeNow, &Authenticator->timestamp );
+ NlAssert( NT_SUCCESS(Status) );
+
+ //
+ // Modify the AuthenticationSeed before computing auth_credential for
+ // verification .
+ //
+ // Two long words are added and overflow carry (if any) ignored
+ // This will leave upper 4 bytes unchanged
+ //
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Old Seed = %lx %lx\n",
+ ((DWORD *) (AuthenticationSeed))[0],
+ ((DWORD *) (AuthenticationSeed))[1]));
+
+ NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Time = %lx\n",
+ ((DWORD *) (&Authenticator->timestamp))[0] ));
+#endif // BAD_ALIGNMENT
+
+
+
+ *((unsigned long * ) AuthenticationSeed) += Authenticator->timestamp;
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: New Seed = %lx %lx\n",
+ ((DWORD *) (AuthenticationSeed))[0],
+ ((DWORD *) (AuthenticationSeed))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,
+ "NlBuildAuthenticator: SessionKey = %lx %lx %lx %lx\n",
+ ((DWORD *) (SessionKey))[0],
+ ((DWORD *) (SessionKey))[1],
+ ((DWORD *) (SessionKey))[2],
+ ((DWORD *) (SessionKey))[3]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // compute AuthenticationSeed to verify the one supplied by Requestor
+ //
+
+ NlComputeCredentials( AuthenticationSeed,
+ &Authenticator->Credential,
+ SessionKey);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Client Authenticator = %lx %lx\n",
+ ((DWORD *) (&Authenticator->Credential))[0],
+ ((DWORD *) (&Authenticator->Credential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ return;
+
+}
+
+
+BOOL
+NlUpdateSeed(
+ IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed,
+ IN PNETLOGON_CREDENTIAL TargetCredential,
+ IN PNETLOGON_SESSION_KEY SessionKey
+ )
+/*++
+
+Routine Description:
+
+ Called by the initiator of a communication over the secure channel
+ following a successful transaction.
+
+ The PDC would have incremented the seed so we must do so also.
+
+ We also verify that the incremented seed builds a credential identical
+ to the one passed back by the PDC.
+
+Arguments:
+
+ AuthenticationSeed - Pointer to the AuthenticationSeed to be incremented.
+
+ TargetCredential - Supplies the Credential that the incremented
+ AuthenticationSeed should encrypt to.
+
+ SessionKey - Supplies the encryption key to use for the encryption.
+
+Return Value:
+
+ TRUE: Success
+ FALSE: Failure
+
+--*/
+{
+ NETLOGON_CREDENTIAL NewCredential;
+
+ //
+ // modify our AuthenticationSeed before computing NewCredential to check
+ // those returned from primary (NewSeed = AuthenticationSeed+1)
+ //
+
+ (*((unsigned long * ) AuthenticationSeed))++;
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Seed + time + 1= %lx %lx\n",
+ ((DWORD *) (AuthenticationSeed))[0],
+ ((DWORD *) (AuthenticationSeed))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ //
+ // Compute ClientCredential to check which came from primary
+ //
+
+ NlComputeCredentials(AuthenticationSeed, &NewCredential, SessionKey);
+
+
+#ifdef BAD_ALIGNMENT
+ NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Server Authenticator GOT = %lx %lx\n",
+ ((DWORD *) (TargetCredential))[0],
+ ((DWORD *) (TargetCredential))[1]));
+
+
+ NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Server Authenticator MADE = %lx %lx\n",
+ ((DWORD *) (&NewCredential))[0],
+ ((DWORD *) (&NewCredential))[1]));
+#endif // BAD_ALIGNMENT
+
+
+ if (RtlCompareMemory( TargetCredential,
+ &NewCredential,
+ sizeof(NewCredential)) !=
+ sizeof(NewCredential)) {
+ return FALSE;
+ }
+
+ //
+ // Done
+ //
+
+ return TRUE;
+
+}
+
+
+VOID
+NlEncryptRC4(
+ IN OUT PVOID Buffer,
+ IN ULONG BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Encrypt data using RC4 with the session key as the key.
+
+Arguments:
+
+ Buffer -- Buffer containing the data to encrypt in place.
+
+ BufferSize -- Size (in bytes) of Buffer.
+
+ SessionInfo -- Info describing secure channel
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NTSTATUS NtStatus;
+ DATA_KEY KeyData;
+ CRYPT_BUFFER Data;
+
+ //
+ // Build a data buffer to describe the encryption key.
+ //
+
+ KeyData.Length = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.Buffer = (PVOID)&SessionInfo->SessionKey;
+
+ NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION );
+
+ //
+ // Build a data buffer to decribe the encrypted data.
+ //
+
+ Data.Length = Data.MaximumLength = BufferSize;
+ Data.Buffer = Buffer;
+
+ //
+ // Encrypt the data.
+ //
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlEncryptRC4: Clear data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data.Buffer,
+ Data.Length / sizeof(DWORD) );
+ }
+
+ NtStatus = RtlEncryptData2( &Data, &KeyData );
+ NlAssert( NT_SUCCESS(NtStatus) );
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlEncryptRC4: Encrypted data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data.Buffer,
+ Data.Length / sizeof(DWORD) );
+ }
+
+}
+
+
+VOID
+NlDecryptRC4(
+ IN OUT PVOID Buffer,
+ IN ULONG BufferSize,
+ IN PSESSION_INFO SessionInfo
+ )
+/*++
+
+Routine Description:
+
+ Decrypt data using RC4 with the session key as the key.
+
+Arguments:
+
+ Buffer -- Buffer containing the data to decrypt in place.
+
+ BufferSize -- Size (in bytes) of Buffer.
+
+ SessionInfo -- Info describing secure channel
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NTSTATUS NtStatus;
+ DATA_KEY KeyData;
+ CRYPT_BUFFER Data;
+
+ //
+ // Build a data buffer to describe the encryption key.
+ //
+
+ KeyData.Length = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY);
+ KeyData.Buffer = (PVOID)&SessionInfo->SessionKey;
+
+ NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION );
+
+ //
+ // Build a data buffer to decribe the encrypted data.
+ //
+
+ Data.Length = Data.MaximumLength = BufferSize;
+ Data.Buffer = Buffer;
+
+ //
+ // Encrypt the data.
+ //
+
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlDecryptRC4: Encrypted data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data.Buffer,
+ Data.Length / sizeof(DWORD) );
+ }
+
+ NtStatus = RtlDecryptData2( &Data, &KeyData );
+ NlAssert( NT_SUCCESS(NtStatus) );
+
+ IF_DEBUG( ENCRYPT ) {
+ NlPrint((NL_ENCRYPT, "NlDecryptRC4: Clear data\n" ));
+ NlpDumpHexData( NL_ENCRYPT,
+ (LPDWORD)Data.Buffer,
+ Data.Length / sizeof(DWORD) );
+ }
+
+}
diff --git a/private/net/svcdlls/logonsrv/server/ssidelta.h b/private/net/svcdlls/logonsrv/server/ssidelta.h
new file mode 100644
index 000000000..afce04538
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/ssidelta.h
@@ -0,0 +1,164 @@
+/*++
+
+Copyright (c) 1987-1991 Microsoft Corporation
+
+Module Name:
+
+ ssidelta.h
+
+Abstract:
+
+ Structure definitions for communicating deltas to downlevel UAS
+ BDC's and Member Servers.
+
+ This file contains information about the structures used for
+ replicating user and group records under SSI. These structs
+ are used to efficiently pack the information in these records.
+
+Author:
+
+ Ported from Lan Man 2.0
+
+Revision History:
+
+ 23-Aug-1991 (cliffv)
+ Ported to NT. Converted to NT style.
+
+ Madana - Fixed several bugs.
+
+--*/
+
+//
+// Force misalignment of the following structures
+//
+
+#ifndef NO_PACKING
+#include <packon.h>
+#endif // ndef NO_PACKING
+
+//
+// Valid flags to pass to an LM 2.x system
+//
+// Don't pass UF_HOMEDIR_REQUIRED since NT doesn't enforce the bit and
+// has no UI to set it. So, if the bit gets set by mistake (e.g., portuas or
+// netcmd) and there is no home directory, a lanman replicates forever because
+// of the bad data.
+
+#define UF_VALID_LM2X ( \
+ UF_SCRIPT | \
+ UF_ACCOUNTDISABLE | \
+ UF_PASSWD_NOTREQD | \
+ UF_PASSWD_CANT_CHANGE \
+ )
+
+//
+// Each UAS delta is prefixed by a 3-byte header. See NlPackUasHeader
+// for a description of the header.
+//
+
+#define NETLOGON_DELTA_HEADER_SIZE 3
+
+//
+// Maximum number of workstations allowed on downlevel systems
+//
+#define MAXWORKSTATIONS 8
+
+//
+// The header contains an opcode describing the particular delta.
+//
+
+#define DELTA_USERADD 1
+#define DELTA_USERDEL 2
+#define DELTA_USERSETINFO 3
+#define DELTA_USERSETGROUPS 4
+#define DELTA_USERMODALSSET 5
+#define DELTA_GROUPADD 6
+#define DELTA_GROUPDEL 7
+#define DELTA_GROUPSETINFO 8
+#define DELTA_GROUPADDUSER 9
+#define DELTA_GROUPDELUSER 10
+#define DELTA_GROUPSETUSERS 11
+#define DELTA_RESERVED_OPCODE 255
+
+
+typedef struct _USER_ADD_SET {
+ CHAR uas_password[LM_OWF_PASSWORD_LENGTH];
+ UCHAR uas_logon_hours[SAM_HOURS_PER_WEEK/8];
+ _ULONG( uas_password_age );
+ _USHORT( uas_priv );
+ _USHORT( uas_flags );
+ _ULONG( uas_auth_flags );
+ _ULONG( uas_last_logon );
+ _ULONG( uas_last_logoff );
+ _ULONG( uas_acct_expires );
+ _ULONG( uas_max_storage );
+ _USHORT( uas_units_per_week );
+ _USHORT( uas_bad_pw_count );
+ _USHORT( uas_num_logons );
+ _USHORT( uas_country_code );
+ _USHORT( uas_code_page );
+ _ULONG( uas_last );
+ CHAR uas_old_passwds[DEF_MAX_PWHIST * LM_OWF_PASSWORD_LENGTH];
+
+ //
+ // The following fields are misaligned and are only set via the
+ // NlPackVarLenField routine.
+ //
+
+ USHORT uas_name;
+ USHORT uas_home_dir;
+ USHORT uas_comment;
+ USHORT uas_script_path;
+ USHORT uas_full_name;
+ USHORT uas_usr_comment;
+ USHORT uas_parms;
+ USHORT uas_workstations;
+ USHORT uas_logon_server;
+} USER_ADD_SET, *PUSER_ADD_SET;
+
+
+
+typedef struct _USER_MODALS {
+ _USHORT( umod_min_passwd_len );
+ _ULONG( umod_max_passwd_age );
+ _ULONG( umod_min_passwd_age );
+ _ULONG( umod_force_logoff );
+ _USHORT( umod_password_hist_len );
+ _USHORT( umod_lockout_count );
+} USER_MODALS, *PUSER_MODALS;
+
+typedef struct _GROUP_ADD_SET {
+ USHORT gas_comment;
+ CHAR gas_groupname[1];
+} GROUP_ADD_SET, *PGROUP_ADD_SET;
+
+//
+// groupname followed by list of usernames. count == # users in list
+//
+typedef struct _GROUP_USERS {
+ _USHORT( count);
+ CHAR groupname[1];
+} GROUP_USERS, *PGROUP_USERS;
+
+//
+// username followed by list of groupnames. count == # groups in list
+//
+typedef struct _USER_GROUPS {
+ _USHORT( count);
+ CHAR username[1];
+} USER_GROUPS, *PUSER_GROUPS;
+
+//
+// username/groupname
+//
+typedef struct _USER_GROUP_DEL {
+ CHAR name[1];
+} USER_GROUP_DEL, *PUSER_GROUP_DEL;
+
+//
+// Turn structure packing back off
+//
+
+#ifndef NO_PACKING
+#include <packoff.h>
+#endif // ndef NO_PACKING
diff --git a/private/net/svcdlls/logonsrv/server/ssiinit.h b/private/net/svcdlls/logonsrv/server/ssiinit.h
new file mode 100644
index 000000000..857c29965
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/ssiinit.h
@@ -0,0 +1,1375 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ ssiinit.h
+
+Abstract:
+
+ Private global variables, defines, and routine declarations used for
+ to implement SSI.
+
+Author:
+
+ Cliff Van Dyke (cliffv) 25-Jul-1991
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+ 02-Jan-1992 (madana)
+ added support for builtin/multidomain replication.
+
+ 04-10-1992 (madana)
+ added support for LSA replication.
+
+--*/
+
+//
+// srvsess.c will #include this file with INITSSI_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef INITSSI_ALLOCATE
+#define EXTERN
+#else
+#define EXTERN extern
+#endif
+
+// general purpose mainfests
+
+//
+// How frequently we scavenge the LogonTable.
+//
+#define LOGON_INTERROGATE_PERIOD (15*60*1000) // make it 15 mins
+
+
+//
+// How long we wait for a discovery response.
+//
+#define DISCOVERY_PERIOD (5*1000) // 5 seconds
+
+//
+// Maximum time we'll wait during full sync in an attempt to decrease
+// wan link utilization.
+//
+#define MAX_SYNC_SLEEP_TIME (60*60*1000) // 1 hour
+
+
+//
+// How big a buffer we request on a SAM delta or a SAM sync.
+//
+#define SAM_DELTA_BUFFER_SIZE (128*1024)
+
+//
+// The size of the largest mailslot message.
+//
+// All mailslot messages we receive are broadcast. The Win32 spec says
+// the limit on broadcast mailslot is 400 bytes. Really it is
+// 444 bytes (512 minus SMB header etc) - size of the mailslot name.
+// I'll use 444 to ensure this size is the largest I'll ever need.
+//
+
+#define NETLOGON_MAX_MS_SIZE 444
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Client Session definitions
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// An internal timer used to schedule a periodic event.
+//
+
+typedef struct _TIMER {
+ LARGE_INTEGER StartTime; // Start of period (NT absolute time)
+ DWORD Period; // length of period (miliseconds)
+} TIMER, *PTIMER;
+
+//
+// Client session.
+//
+// Structure to define the client side of a session to a DC.
+//
+
+typedef struct _CLIENT_SESSION {
+
+ //
+ // Each client session entry is in a doubly linked list defined by
+ // NlGlobalTrustList.
+ //
+ // Access serialized by NlGlobalTrustListCritSect.
+ //
+
+ LIST_ENTRY CsNext;
+
+
+ //
+ // Time when the last authentication attempt was made.
+ //
+ // When the CsState is CS_AUTHENTICATED, this field is the time the
+ // secure channel was setup.
+ //
+ // When the CsState is CS_IDLE, this field is the time of the last
+ // failed discovery or session setup. Or it may be zero, to indicate
+ // that it is OK to do another discovery at any time.
+ //
+ // When the CsState is CS_DC_PICKED, this field is zero indicating it is
+ // OK to do the session setup at any time. Or it may be the time of the
+ // last failed session set if different threads did the setup/discovery.
+ //
+ // Access serialized by NlGlobalDcDiscoveryCritSect
+ //
+
+ LARGE_INTEGER CsLastAuthenticationTry;
+
+ //
+ // Time when the last discovery attempt was made.
+ //
+ // The time is the time of completion of the last discovery attempt regardless
+ // of the success or failure of that attemp.
+ //
+ // Access serialized by NlGlobalDcDiscoveryCritSect
+ //
+
+ LARGE_INTEGER CsLastDiscoveryTime;
+
+ //
+ // Each API call made across this secure channel is timed by this timer.
+ // If the timer expires, the session to the server is forcefully
+ // terminated to ensure the client doesn't hang for a dead server.
+ //
+ // Access serialized by NlGlobalTrustListCritSect.
+ //
+
+ TIMER CsApiTimer;
+
+#define SHORT_API_CALL_PERIOD (45*1000) // Logon API lasts 45 seconds
+#define LONG_API_CALL_PERIOD (15*60*1000) // Replication API 15 minute
+#define BINDING_CACHE_PERIOD (3*60*1000) // Cache RPC handle for 3 minutes
+#define WRITER_WAIT_PERIOD NlGlobalShortApiCallPeriod // Max time to wait to become writer
+
+ //
+ // Name of the domain this connection is to
+ //
+
+ UNICODE_STRING CsDomainName;
+
+ //
+ // Domain ID of the domain this connection is to
+ //
+
+ PSID CsDomainId;
+
+ //
+ // Type of CsAccountName
+ //
+
+ NETLOGON_SECURE_CHANNEL_TYPE CsSecureChannelType;
+
+ //
+ // State of the connection to the server.
+ //
+ // Access serialized by NlGlobalDcDiscoveryCritSect
+ // This field can be read without the crit sect locked if
+ // the answer will only be used as a hint.
+ //
+
+ DWORD CsState;
+
+#define CS_IDLE 0 // No session is currently active
+#define CS_DC_PICKED 1 // The session has picked a DC for session
+#define CS_AUTHENTICATED 2 // The session is currently active
+
+
+ //
+ // Status of latest attempt to contact the server.
+ //
+ // When the CsState is CS_AUTHENTICATED, this field is STATUS_SUCCESS.
+ //
+ // When the CsState is CS_IDLE, this field is a non-successful status.
+ //
+ // When the CsState is CS_DC_PICKED, this field is the same non-successful
+ // status from when the CsState was last CS_IDLE.
+ //
+ // Access serialized by NlGlobalDcDiscoveryCritSect
+ // This field can be read without the crit sect locked if
+ // the answer will only be used as a hint.
+ //
+
+ NTSTATUS CsConnectionStatus;
+
+ //
+ // Access serialized by NlGlobalTrustListCritSect.
+ //
+
+ DWORD CsFlags;
+
+#define CS_UPDATE_PASSWORD 0x01 // Set if the password has already
+ // been changed on the client and
+ // needs changing on the server.
+
+#define CS_PASSWORD_REFUSED 0x02 // Set if DC refused a password change.
+
+#define CS_DELETE_ON_UNREF 0x04 // Delete entry when CsReferenceCount
+ // reaches zero.
+
+#define CS_WRITER 0x08 // Entry is being modified
+
+#define CS_HANDLE_TIMER 0x10 // Set if we need to handle timer expiration
+
+#define CS_CHECK_PASSWORD 0x20 // Set if we need to check the password
+
+#define CS_PICK_DC 0x40 // Set if we need to Pick a DC
+
+#define CS_REDISCOVER_DC 0x80 // Set when we need to Rediscover a DC
+
+#define CS_BINDING_CACHED 0x100 // Set if the binding handle is cached
+
+#define CS_HANDLE_API_TIMER 0x200 // Set if we need to handle API timer expiration
+
+ //
+ // Flags describing capabilities of both client and server.
+ //
+
+ ULONG CsNegotiatedFlags;
+
+ //
+ // Time Number of authentication attempts since last success.
+ //
+ // Access serialized by CsWriterSemaphore.
+ //
+
+ DWORD CsAuthAlertCount;
+
+ //
+ // Number of threads referencing this entry.
+ //
+ // Access serialized by NlGlobalTrustListCritSect.
+ //
+
+ DWORD CsReferenceCount;
+
+ //
+ // Writer semaphore.
+ //
+ // This semaphore is locked whenever there is a writer modifying
+ // fields in this client session.
+ //
+
+ HANDLE CsWriterSemaphore;
+
+
+ //
+ // The following fields are used by the NlDcDiscoveryMachine to keep track
+ // of discovery state.
+ //
+ // Access serialized by NlGlobalDcDiscoveryCritSect
+ //
+
+ DWORD CsDiscoveryRetryCount;
+
+ DWORD CsDiscoveryFlags;
+
+#define CS_DISCOVERY_IN_PROGRESS 0x01 // Discovery is currently happening
+#define CS_DISCOVERY_ASYNCHRONOUS 0x02 // Waiting on NlGlobalDiscoveryTimer
+
+ //
+ // This event is set to indicate that discovery is not in progress on this
+ // client session.
+ //
+
+ HANDLE CsDiscoveryEvent;
+
+ //
+ // API timout count. After each logon/logoff API call made to the
+ // server this count is incremented if the time taken to execute the
+ // this API is more than the specified TIMEOUT.
+ //
+ // Access serialized by CsWriterSemaphore.
+ //
+
+ DWORD CsTimeoutCount;
+
+#define MAX_DC_TIMEOUT_COUNT 2
+#define MAX_WKSTA_TIMEOUT_COUNT 2 // drop the session after this
+ // many timeouts and when it is
+ // time to reauthenticate.
+
+#define MAX_DC_API_TIMEOUT (long) (15L*1000L) // 15 seconds
+#define MAX_WKSTA_API_TIMEOUT (long) (15L*1000L) // 15 seconds
+
+#define MAX_DC_REAUTHENTICATION_WAIT (long) (5L*60L*1000L) // 5 mins
+#define MAX_WKSTA_REAUTHENTICATION_WAIT (long) (5L*60L*1000L) // 5 mins
+
+ //
+ // Authentication information.
+ //
+ // Access serialized by CsWriterSemaphore.
+ //
+
+ NETLOGON_CREDENTIAL CsAuthenticationSeed;
+ NETLOGON_SESSION_KEY CsSessionKey;
+
+ //
+ // Transport the server was discovered on.
+ //
+
+ LPWSTR CsTransportName;
+
+ //
+ // Name of the server this connection is to.
+ //
+ // Access serialized by CsWriterSemaphore or NlGlobalDcDiscoveryCritSect.
+ // Modification from Null to non-null serialized by
+ // NlGlobalDcDiscoveryCritSect
+ // (Modification from non-null to null requires both to be locked.)
+ //
+
+ WCHAR CsUncServerName[UNCLEN+1];
+
+
+ //
+ // Name of the account used to contact server
+ //
+
+ WCHAR CsAccountName[SSI_ACCOUNT_NAME_LENGTH+1];
+
+
+
+} CLIENT_SESSION, * PCLIENT_SESSION;
+
+//
+// To serialize access to trust list and NlGlobalClientSession
+//
+
+EXTERN CRITICAL_SECTION NlGlobalTrustListCritSect;
+
+//
+// The list of trusted domains
+//
+
+EXTERN LIST_ENTRY NlGlobalTrustList;
+EXTERN DWORD NlGlobalTrustListLength; // Number of entries in NlGlobalTrustList
+
+//
+// For workstations and non-DC servers,
+// maintain a list of domains trusted by our primary domain.
+//
+
+typedef struct {
+ CHAR DomainName[DNLEN+1];
+} TRUSTED_DOMAIN, *PTRUSTED_DOMAIN;
+
+EXTERN PTRUSTED_DOMAIN NlGlobalTrustedDomainList;
+EXTERN DWORD NlGlobalTrustedDomainCount;
+EXTERN BOOL NlGlobalTrustedDomainListKnown;
+EXTERN LARGE_INTEGER NlGlobalTrustedDomainListTime;
+
+//
+// For BDC, these are the credentials used to communicate with the PDC.
+// For a workstation, these are the credentials used to communicate with a DC.
+//
+
+EXTERN PCLIENT_SESSION NlGlobalClientSession;
+
+#define LOCK_TRUST_LIST() EnterCriticalSection( &NlGlobalTrustListCritSect )
+#define UNLOCK_TRUST_LIST() LeaveCriticalSection( &NlGlobalTrustListCritSect )
+
+//
+// Serialize DC Discovery activities
+//
+
+EXTERN CRITICAL_SECTION NlGlobalDcDiscoveryCritSect;
+
+//
+// Timer for timing out async DC discovery
+//
+
+EXTERN TIMER NlGlobalDcDiscoveryTimer;
+EXTERN DWORD NlGlobalDcDiscoveryCount;
+
+//
+// Timer for timing out API calls to trusted domains
+//
+// Serialized using NlGlobalTrustListCritSect.
+//
+
+EXTERN TIMER NlGlobalApiTimer;
+EXTERN DWORD NlGlobalBindingHandleCount;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Server Session definitions
+//
+/////////////////////////////////////////////////////////////////////////////
+
+//
+// Sam Sync Context.
+//
+// A Sam sync context is maintained on the PDC for each BDC/member currently
+// doing a full sync.
+//
+typedef struct _SAM_SYNC_CONTEXT {
+
+ //
+ // The Sync state indicates tracks the progression of the sync.
+ //
+
+ SYNC_STATE SyncState;
+
+ //
+ // A serial number indicating the number of times the BDC/member
+ // has called us. We use this as a resume handle.
+ //
+
+ ULONG SyncSerial;
+
+ //
+ // The current Sam Enumeration information
+ //
+
+ SAM_ENUMERATE_HANDLE SamEnumHandle; // Current Sam Enum Handle
+ PSAMPR_ENUMERATION_BUFFER SamEnum; // Sam returned buffer
+ PULONG RidArray; // Array of enumerated Rids
+ ULONG Index; // Index to current entry
+ ULONG Count; // Total Number of entries
+
+ BOOL SamAllDone; // True, if Sam has completed
+
+#define UAS_BUILTIN_ADMINS_GROUP 0x01 // bit 0
+#define UAS_BUILTIN_USERS_GROUP 0x02 // bit 1
+#define UAS_BUILTIN_GUESTS_GROUP 0x04 // bit 2
+
+#define UAS_BUILTIN_GROUPS_COUNT 0x03
+
+#define UAS_BUILTIN_ADMINS_GROUP_NAME L"ADMINS"
+#define UAS_BUILTIN_USERS_GROUP_NAME L"USERS"
+#define UAS_BUILTIN_GUESTS_GROUP_NAME L"GUESTS"
+
+ DWORD UasBuiltinGroups; // flag to determine the
+ // presence of uas builtin
+ // groups
+
+} SAM_SYNC_CONTEXT, *PSAM_SYNC_CONTEXT;
+
+#define SAM_SYNC_PREF_MAX 1024 // Preferred max for Sam Sync
+
+
+//
+// Lsa Sync Context.
+//
+// A Lsa sync context is maintained on the PDC for each BDC/member
+// currently doing a full sync.
+//
+typedef struct _LSA_SYNC_CONTEXT {
+
+ //
+ // The Sync state indicates tracks the progression of the sync.
+ //
+
+ enum {
+ AccountState,
+ TDomainState,
+ SecretState,
+ LsaDoneState
+ } SyncState;
+
+ //
+ // A serial number indicating the number of times the BDC/member
+ // has called us. We use this as a resume handle.
+ //
+
+ ULONG SyncSerial;
+
+ //
+ // The current Lsa Enumeration information
+ //
+
+ LSA_ENUMERATION_HANDLE LsaEnumHandle; // Current Lsa Enum Handle
+
+ enum {
+ AccountEnumBuffer,
+ TDomainEnumBuffer,
+ SecretEnumBuffer,
+ EmptyEnumBuffer
+ } LsaEnumBufferType;
+
+ union {
+ LSAPR_ACCOUNT_ENUM_BUFFER Account;
+ LSAPR_TRUSTED_ENUM_BUFFER TDomain;
+ PVOID Secret;
+ } LsaEnum; // Lsa returned buffer
+
+ ULONG Index; // Index to current entry
+ ULONG Count; // Total Number of entries
+
+ BOOL LsaAllDone; // True, if Lsa has completed
+
+} LSA_SYNC_CONTEXT, *PLSA_SYNC_CONTEXT;
+
+//
+// union of lsa and sam context
+//
+
+typedef struct _SYNC_CONTEXT {
+ enum {
+ LsaDBContextType,
+ SamDBContextType
+ } DBContextType;
+
+ union {
+ LSA_SYNC_CONTEXT Lsa;
+ SAM_SYNC_CONTEXT Sam;
+ } DBContext;
+} SYNC_CONTEXT, *PSYNC_CONTEXT;
+
+//
+// Macro used to free any resources allocated by SAM.
+//
+// ?? check LsaIFree_LSAPR_* call parameters.
+//
+
+#define CLEAN_SYNC_CONTEXT( _Sync ) { \
+ if ( (_Sync)->DBContextType == LsaDBContextType ) { \
+ if ( (_Sync)->DBContext.Lsa.LsaEnumBufferType != \
+ EmptyEnumBuffer) { \
+ if ( (_Sync)->DBContext.Lsa.LsaEnumBufferType == \
+ AccountEnumBuffer) { \
+ LsaIFree_LSAPR_ACCOUNT_ENUM_BUFFER( \
+ &((_Sync)->DBContext.Lsa.LsaEnum.Account) ); \
+ } \
+ else if ( (_Sync)->DBContext.Lsa.LsaEnumBufferType == \
+ TDomainEnumBuffer) { \
+ LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( \
+ &((_Sync)->DBContext.Lsa.LsaEnum.TDomain) ); \
+ } \
+ else { \
+ MIDL_user_free( (_Sync)->DBContext.Lsa.LsaEnum.Secret );\
+ } \
+ (_Sync)->DBContext.Lsa.LsaEnumBufferType = \
+ EmptyEnumBuffer; \
+ } \
+ } else { \
+ if ( (_Sync)->DBContext.Sam.SamEnum != NULL ) { \
+ SamIFree_SAMPR_ENUMERATION_BUFFER( \
+ (_Sync)->DBContext.Sam.SamEnum ); \
+ (_Sync)->DBContext.Sam.SamEnum = NULL; \
+ } \
+ if ( (_Sync)->DBContext.Sam.RidArray != NULL ) { \
+ MIDL_user_free( (_Sync)->DBContext.Sam.RidArray );\
+ (_Sync)->DBContext.Sam.RidArray = NULL; \
+ } \
+ } \
+}
+
+//
+// Macro to initialize Sync Context
+//
+#define INIT_SYNC_CONTEXT( _Sync, _ContextType ) { \
+ RtlZeroMemory( (_Sync), sizeof( *(_Sync) ) ) ; \
+ (_Sync)->DBContextType = (_ContextType) ; \
+}
+
+//
+// macros for dynamic tuning.
+//
+
+#define IS_BDC_CHANNEL( _ChannelType ) \
+ ( (_ChannelType) == ServerSecureChannel || \
+ (_ChannelType) == UasServerSecureChannel )
+
+//
+// Server Session structure
+//
+// This structure represents the server side of a connection to a DC.
+//
+
+typedef struct _SERVER_SESSION {
+ //
+ // Each server session entry is in a doubly linked list for each hash bucket.
+ //
+
+ LIST_ENTRY SsHashList;
+
+ //
+ // Each server session entry is in a doubly linked list defined by
+ // NlGlobalServerSessionTable.
+ //
+
+ LIST_ENTRY SsSeqList;
+
+ //
+ // List of all BDCs headed by NlGlobalBdcServerSessionList.
+ //
+ // (The field is set only on BDC server session entries)
+ //
+ // Access serialized by NlGlobalServerSessionTableCritSect.
+ //
+
+ LIST_ENTRY SsBdcList;
+
+ //
+ // List of BDC's which have a pulse pending.
+ //
+
+ LIST_ENTRY SsPendingBdcList;
+
+ //
+ // Time when the last pulse was sent to this machine
+ //
+ // (The field is set only on BDC server session entries)
+ //
+
+ LARGE_INTEGER SsLastPulseTime;
+
+ //
+ // Current serial numbers of each database on the BDC.
+ //
+ // (The field is set only on BDC server session entries)
+ //
+
+ LARGE_INTEGER SsBdcDbSerialNumber[NUM_DBS];
+
+ //
+ // The computername uniquely identifies this server session entry.
+ //
+
+ NETLOGON_SECURE_CHANNEL_TYPE SsSecureChannelType;
+ CHAR SsComputerName[CNLEN+1];
+
+ //
+ // Rid of the server account
+ //
+ // (The field is set only on BDC server session entries)
+ //
+
+ ULONG SsLmBdcAccountRid;
+ ULONG SsNtBdcAccountRid;
+
+ //
+ // The number of times there has been no response to a pulse.
+ //
+
+ USHORT SsPulseTimeoutCount;
+
+ //
+ // The number of times this entry has been scavanged.
+ //
+
+ USHORT SsCheck;
+
+ //
+ // Flags describing the state of the current entry.
+ // See the SS_ defines below.
+ //
+
+ USHORT SsFlags;
+
+#define SS_CHALLENGE 0x0001 // Challenge is in progress
+#define SS_AUTHENTICATED 0x0002 // Remote side has be authenticated
+
+#define SS_LOCKED 0x0004 // Delay deletion requests for this entry
+ // While set, SsSessionKey may be referenced
+#define SS_DELETE_ON_UNLOCK 0x0008 // Delete entry when it is unlocked
+
+#define SS_BDC 0x0010 // BDC account exists for this Client
+#define SS_LM_BDC 0x0020 // Lanman BDC account exists for this entry is a
+#define SS_PENDING_BDC 0x0040 // BDC is on pending BDC list.
+
+#define SS_UAS_BUFFER_OVERFLOW 0x0100 // Previous downlevel API call
+ // returned STATUS_BUFFER_TOO_SMALL
+#define SS_FORCE_PULSE 0x0200 // Force a pulse message to this BDC.
+#define SS_PULSE_SENT 0x0400 // Pulse has been sent but has not
+ // been responded to yet
+#define SS_LSA_REPL_NEEDED 0x2000 // BDC needs LSA DB replicated
+#define SS_ACCOUNT_REPL_NEEDED 0x4000 // BDC needs SAM Account DB replicated
+#define SS_BUILTIN_REPL_NEEDED 0x8000 // BDC needs SAM Builtin DB replicated
+#define SS_REPL_MASK 0xE000 // BDC needs replication mask
+
+// Don't clear these on session setup
+#define SS_PERMANENT_FLAGS \
+ ( SS_BDC | SS_LM_BDC | SS_PENDING_BDC | SS_FORCE_PULSE | SS_REPL_MASK )
+
+ //
+ // Flags describing capabilities of both client and server.
+ //
+
+ ULONG SsNegotiatedFlags;
+
+ //
+ // Transport the client connected over.
+ //
+
+ LPWSTR SsTransportName;
+
+
+ //
+ // This is the ClientChallenge (during the challenge phase) and later
+ // the ClientCredential (after authentication is complete).
+ //
+
+ NETLOGON_CREDENTIAL SsAuthenticationSeed;
+
+ //
+ // This is the ServerChallenge (during the challenge phase) and later
+ // the SessionKey (after authentication is complete).
+ //
+
+ NETLOGON_SESSION_KEY SsSessionKey;
+
+ //
+ // A pointer to the Sync context.
+ //
+ // (The field is set only on BDC server session entries)
+ //
+
+ PSYNC_CONTEXT SsSync;
+
+} SERVER_SESSION, *PSERVER_SESSION;
+
+
+//
+// Structure shared by all PDC and BDC sync routines.
+// (And other users of secure channels.)
+//
+
+typedef struct _SESSION_INFO {
+
+ //
+ // Session Key shared by both client and server.
+ //
+
+ NETLOGON_SESSION_KEY SessionKey;
+
+ //
+ // Flags describing capabilities of both client and server.
+ //
+
+ ULONG NegotiatedFlags;
+
+} SESSION_INFO, *PSESSION_INFO;
+
+
+
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Structures and variables describing the database info.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct _DB_Info {
+ LARGE_INTEGER CreationTime; // database creation time
+ DWORD DBIndex; // index of Database table
+ SAM_HANDLE DBHandle; // database handle to access
+ PSID DBId; // database ID
+ LPWSTR DBName; // Name of the database
+ DWORD DBSessionFlag; // SS_ Flag representing this database
+
+ // Access to the following three fields are serialized by
+ // the NlGlobalDbInfoCritSect.
+ BOOLEAN UpdateRqd; // need to update the database
+ BOOLEAN FullSyncRequired; // Full sync needed on this database
+ BOOLEAN SyncDone; // Full sync has already been done on database
+
+ WCHAR PrimaryName[CNLEN+1]; // Primary this database last replicated from
+} DB_INFO, *PDB_INFO;
+
+EXTERN CRITICAL_SECTION NlGlobalDbInfoCritSect;
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Global variables
+//
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// Critical section serializing startup and stopping of the replicator thread.
+//
+EXTERN CRITICAL_SECTION NlGlobalReplicatorCritSect;
+EXTERN BOOL NlGlobalSSICritSectInit;
+
+
+
+
+
+//
+// Table of all Server Sessions
+// The size of the hash table must be a power-of-2.
+//
+#define SERVER_SESSION_HASH_TABLE_SIZE 64
+EXTERN CRITICAL_SECTION NlGlobalServerSessionTableCritSect;
+EXTERN PLIST_ENTRY NlGlobalServerSessionHashTable;
+EXTERN LIST_ENTRY NlGlobalServerSessionTable;
+EXTERN LIST_ENTRY NlGlobalBdcServerSessionList;
+EXTERN ULONG NlGlobalBdcServerSessionCount;
+
+//
+// List of all BDC's the PDC has sent a pulse to.
+//
+
+EXTERN LIST_ENTRY NlGlobalPendingBdcList;
+EXTERN ULONG NlGlobalPendingBdcCount;
+EXTERN TIMER NlGlobalPendingBdcTimer;
+
+#define LOCK_SERVER_SESSION_TABLE() \
+ EnterCriticalSection( &NlGlobalServerSessionTableCritSect )
+#define UNLOCK_SERVER_SESSION_TABLE() \
+ LeaveCriticalSection( &NlGlobalServerSessionTableCritSect )
+
+//
+// List of transports clients might connect to
+//
+EXTERN LPWSTR *NlGlobalTransportList;
+EXTERN DWORD NlGlobalTransportCount;
+
+#if DBG
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFPACKTIMER DWORD PackTimer, PackTimerTicks
+
+#define INITPACKTIMER PackTimer = 0;
+
+#define STARTPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ PackTimerTicks = GetTickCount(); \
+ }
+
+#define STOPPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ PackTimer += GetTickCount() - PackTimerTicks; \
+ }
+
+
+#define PRINTPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ NlPrint((NL_REPL_OBJ_TIME,"\tTime Taken to PACK this object = %d msecs\n", \
+ PackTimer )); \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFUNPACKTIMER DWORD UnpackTimer, UnpackTimerTicks
+
+#define INITUNPACKTIMER UnpackTimer = 0;
+
+#define STARTUNPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ UnpackTimerTicks = GetTickCount(); \
+ }
+
+#define STOPUNPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ UnpackTimer += GetTickCount() - \
+ UnpackTimerTicks; \
+ }
+
+
+#define PRINTUNPACKTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ NlPrint((NL_REPL_OBJ_TIME,"\tTime Taken to UNPACK this object = %d msecs\n", \
+ UnpackTimer )); \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFSAMTIMER DWORD SamTimer, SamTimerTicks
+
+#define INITSAMTIMER SamTimer = 0;
+
+#define STARTSAMTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ SamTimerTicks = GetTickCount(); \
+ }
+
+#define STOPSAMTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ SamTimer += GetTickCount() - SamTimerTicks; \
+ }
+
+
+#define PRINTSAMTIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ NlPrint((NL_REPL_OBJ_TIME,"\tTime spent in SAM calls = %d msecs\n", \
+ SamTimer )); \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFLSATIMER DWORD LsaTimer, LsaTimerTicks
+
+#define INITLSATIMER LsaTimer = 0;
+
+#define STARTLSATIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ LsaTimerTicks = GetTickCount(); \
+ }
+
+#define STOPLSATIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ LsaTimer += GetTickCount() - LsaTimerTicks; \
+ }
+
+
+#define PRINTLSATIMER \
+ IF_DEBUG( REPL_OBJ_TIME ) { \
+ NlPrint((NL_REPL_OBJ_TIME,"\tTime spent in LSA calls = %d msecs\n", \
+ LsaTimer )); \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFSSIAPITIMER DWORD SsiApiTimer, SsiApiTimerTicks
+
+#define INITSSIAPITIMER SsiApiTimer = 0;
+
+#define STARTSSIAPITIMER \
+ IF_DEBUG( REPL_TIME ) { \
+ SsiApiTimerTicks = GetTickCount(); \
+ }
+
+#define STOPSSIAPITIMER \
+ IF_DEBUG( REPL_TIME ) { \
+ SsiApiTimer += GetTickCount() - \
+ SsiApiTimerTicks; \
+ }
+
+
+#define PRINTSSIAPITIMER \
+ IF_DEBUG( REPL_TIME ) { \
+ NlPrint((NL_REPL_TIME,"\tTime Taken by this SSIAPI call = %d msecs\n", \
+ SsiApiTimer )); \
+ }
+
+#else // DBG
+
+#define DEFPACKTIMER
+#define INITPACKTIMER
+#define STARTPACKTIMER
+#define STOPPACKTIMER
+#define PRINTPACKTIMER
+
+#define DEFUNPACKTIMER
+#define DEFUNPACKTICKS
+#define INITUNPACKTIMER
+#define STARTUNPACKTIMER
+#define STOPUNPACKTIMER
+#define PRINTUNPACKTIMER
+
+#define DEFSAMTIMER
+#define INITSAMTIMER
+#define STARTSAMTIMER
+#define STOPSAMTIMER
+#define PRINTSAMTIMER
+
+#define DEFLSATIMER
+#define INITLSATIMER
+#define STARTLSATIMER
+#define STOPLSATIMER
+#define PRINTLSATIMER
+
+#define DEFSSIAPITIMER
+#define INITSSIAPITIMER
+#define STARTSSIAPITIMER
+#define STOPSSIAPITIMER
+#define PRINTSSIAPITIMER
+
+#endif // DBG
+
+//
+// macros used in pack and unpack routines
+//
+
+#define SECURITYINFORMATION OWNER_SECURITY_INFORMATION | \
+ GROUP_SECURITY_INFORMATION | \
+ SACL_SECURITY_INFORMATION | \
+ DACL_SECURITY_INFORMATION
+
+#define INIT_PLACE_HOLDER(_x) \
+ RtlInitString( (PSTRING) &(_x)->DummyString1, NULL ); \
+ RtlInitString( (PSTRING) &(_x)->DummyString2, NULL ); \
+ RtlInitString( (PSTRING) &(_x)->DummyString3, NULL ); \
+ RtlInitString( (PSTRING) &(_x)->DummyString4, NULL ); \
+ (_x)->DummyLong1 = 0; \
+ (_x)->DummyLong2 = 0; \
+ (_x)->DummyLong3 = 0; \
+ (_x)->DummyLong4 = 0;
+
+#define QUERY_LSA_SECOBJ_INFO(_x) \
+ STARTLSATIMER; \
+ Status = LsarQuerySecurityObject( \
+ (_x), \
+ SECURITYINFORMATION, \
+ &SecurityDescriptor );\
+ STOPLSATIMER; \
+\
+ if (!NT_SUCCESS(Status)) { \
+ SecurityDescriptor = NULL; \
+ goto Cleanup; \
+ }
+
+#define QUERY_SAM_SECOBJ_INFO(_x) \
+ STARTSAMTIMER; \
+ Status = SamrQuerySecurityObject( \
+ (_x), \
+ SECURITYINFORMATION, \
+ &SecurityDescriptor );\
+ STOPSAMTIMER; \
+\
+ if (!NT_SUCCESS(Status)) { \
+ SecurityDescriptor = NULL; \
+ goto Cleanup; \
+ }
+
+
+#define SET_LSA_SECOBJ_INFO(_x, _y) \
+ SecurityDescriptor.Length = (_x)->SecuritySize; \
+ SecurityDescriptor.SecurityDescriptor = (_x)->SecurityDescriptor; \
+\
+ STARTLSATIMER; \
+ Status = LsarSetSecurityObject( \
+ (_y), \
+ (_x)->SecurityInformation, \
+ &SecurityDescriptor ); \
+ STOPLSATIMER; \
+\
+ if (!NT_SUCCESS(Status)) { \
+ goto Cleanup; \
+ }
+
+#define SET_SAM_SECOBJ_INFO(_x, _y) \
+ SecurityDescriptor.Length = (_x)->SecuritySize; \
+ SecurityDescriptor.SecurityDescriptor = (_x)->SecurityDescriptor; \
+\
+ STARTSAMTIMER; \
+ Status = SamrSetSecurityObject( \
+ (_y), \
+ (_x)->SecurityInformation, \
+ &SecurityDescriptor ); \
+ STOPSAMTIMER; \
+\
+ if (!NT_SUCCESS(Status)) { \
+ goto Cleanup; \
+ }
+
+
+#define DELTA_SECOBJ_INFO(_x) \
+ (_x)->SecurityInformation = SECURITYINFORMATION;\
+ (_x)->SecuritySize = SecurityDescriptor->Length;\
+\
+ *BufferSize += NlCopyData( \
+ (LPBYTE *)&SecurityDescriptor->SecurityDescriptor, \
+ (LPBYTE *)&(_x)->SecurityDescriptor, \
+ SecurityDescriptor->Length );
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Procedure forwards.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// ssiapi.c
+//
+
+NTSTATUS
+NlVerifyWorkstation(
+ IN LPWSTR PrimaryName OPTIONAL
+);
+
+
+//
+// srvsess.c
+//
+
+LPWSTR
+NlTransportLookupTransportName(
+ IN LPWSTR TransportName
+ );
+
+LPWSTR
+NlTransportLookup(
+ IN LPWSTR ClientName
+ );
+
+VOID
+NlTransportClose(
+ VOID
+ );
+
+NTSTATUS
+NlInitSSI(
+ VOID
+ );
+
+
+PSERVER_SESSION
+NlFindNamedServerSession(
+ IN LPWSTR ComputerName
+ );
+
+NTSTATUS
+NlInsertServerSession(
+ IN LPWSTR ComputerName,
+ IN DWORD Flags,
+ IN ULONG AccountRid,
+ IN PNETLOGON_CREDENTIAL AuthenticationSeed OPTIONAL,
+ IN PNETLOGON_CREDENTIAL AuthenticationResponse OPTIONAL
+ );
+
+NTSTATUS
+NlAddBdcServerSession(
+ IN ULONG ServerRid,
+ IN PUNICODE_STRING AccountName OPTIONAL,
+ IN DWORD Flags
+ );
+
+VOID
+NlFreeServerSession(
+ IN PSERVER_SESSION ServerSession
+ );
+
+VOID
+NlUnlockServerSession(
+ IN PSERVER_SESSION ServerSession
+ );
+
+VOID
+NlFreeLmBdcServerSession(
+ IN ULONG ServerRid
+ );
+
+VOID
+NlFreeNamedServerSession(
+ IN LPWSTR ComputerName,
+ IN BOOLEAN AccountBeingDeleted
+ );
+
+VOID
+NlFreeServerSessionForAccount(
+ IN PUNICODE_STRING AccountName
+ );
+
+VOID
+NlServerSessionScavenger(
+ VOID
+ );
+
+
+//
+// ssiauth.c
+//
+
+
+VOID
+NlMakeSessionKey(
+ IN PNT_OWF_PASSWORD CryptKey,
+ IN PNETLOGON_CREDENTIAL ClientChallenge,
+ IN PNETLOGON_CREDENTIAL ServerChallenge,
+ OUT PNETLOGON_SESSION_KEY SessionKey
+ );
+
+NTSTATUS
+NlCheckAuthenticator(
+ IN OUT PSERVER_SESSION ServerServerSession,
+ IN PNETLOGON_AUTHENTICATOR Authenticator,
+ OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator
+ );
+
+VOID
+NlComputeCredentials(
+ IN PNETLOGON_CREDENTIAL Challenge,
+ OUT PNETLOGON_CREDENTIAL Credential,
+ IN PNETLOGON_SESSION_KEY SessionKey
+ );
+
+VOID
+NlComputeChallenge(
+ OUT PNETLOGON_CREDENTIAL Challenge
+ );
+
+VOID
+NlBuildAuthenticator(
+ IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed,
+ IN PNETLOGON_SESSION_KEY SessionKey,
+ OUT PNETLOGON_AUTHENTICATOR Authenticator
+ );
+
+BOOL
+NlUpdateSeed(
+ IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed,
+ IN PNETLOGON_CREDENTIAL TargetCredential,
+ IN PNETLOGON_SESSION_KEY SessionKey
+ );
+
+VOID
+NlEncryptRC4(
+ IN OUT PVOID Buffer,
+ IN ULONG BufferSize,
+ IN PSESSION_INFO SessionInfo
+ );
+
+VOID
+NlDecryptRC4(
+ IN OUT PVOID Buffer,
+ IN ULONG BufferSize,
+ IN PSESSION_INFO SessionInfo
+ );
+
+//
+// trustutl.c
+//
+
+PCLIENT_SESSION
+NlFindNamedClientSession(
+ IN PUNICODE_STRING DomainName
+ );
+
+PCLIENT_SESSION
+NlAllocateClientSession(
+ IN PUNICODE_STRING DomainName,
+ IN PSID DomainId,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType
+ );
+
+VOID
+NlFreeClientSession(
+ IN PCLIENT_SESSION ClientSession
+ );
+
+VOID
+NlRefClientSession(
+ IN PCLIENT_SESSION ClientSession
+ );
+
+VOID
+NlUnrefClientSession(
+ IN PCLIENT_SESSION ClientSession
+ );
+
+BOOL
+NlTimeoutSetWriterClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN DWORD Timeout
+ );
+
+VOID
+NlResetWriterClientSession(
+ IN PCLIENT_SESSION ClientSession
+ );
+
+NTSTATUS
+NlCaptureServerClientSession (
+ IN PCLIENT_SESSION ClientSession,
+ OUT WCHAR UncServerName[UNCLEN+1]
+ );
+
+VOID
+NlSetStatusClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN NTSTATUS CsConnectionStatus
+ );
+
+NTSTATUS
+NlInitTrustList(
+ VOID
+ );
+
+NTSTATUS
+NlUpdateTrustListBySid (
+ IN PSID DomainId,
+ IN PUNICODE_STRING DomainName OPTIONAL
+ );
+
+VOID
+NlPickTrustedDcForEntireTrustList(
+ VOID
+ );
+
+NTSTATUS
+NlSetTrustedDomainList (
+ IN LPWSTR TrustedDomainList,
+ IN BOOL TrustedDomainListKnown
+ );
+
+VOID
+NlSaveTrustedDomainList (
+ IN LPWSTR TrustedDomainList
+ );
+
+NET_API_STATUS
+NlReadRegTrustedDomainList (
+ IN LPWSTR NewDomainName OPTIONAL,
+ IN BOOL DeleteName,
+ OUT LPWSTR *TrustedDomainList,
+ OUT PBOOL TrustedDomainListKnown
+ );
+
+BOOLEAN
+NlIsDomainTrusted (
+ IN PUNICODE_STRING DomainName
+ );
+
+typedef enum _DISCOVERY_TYPE {
+ DT_Asynchronous,
+ DT_Synchronous,
+ DT_DeadDomain
+} DISCOVERY_TYPE;
+
+NTSTATUS
+NlDiscoverDc (
+ IN OUT PCLIENT_SESSION ClientSession,
+ IN DISCOVERY_TYPE DiscoveryType
+ );
+
+VOID
+NlDcDiscoveryExpired (
+ IN BOOLEAN Exitting
+ );
+
+NTSTATUS
+NlDcDiscoveryHandler (
+ IN PNETLOGON_SAM_LOGON_RESPONSE Message,
+ IN DWORD MessageSize,
+ IN LPWSTR TransportName,
+ IN DWORD Version
+ );
+
+PCLIENT_SESSION
+NlPickDomainWithAccount (
+ IN LPWSTR AccountName,
+ IN ULONG AllowableAccountControlBits
+ );
+
+NTSTATUS
+NlStartApiClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN BOOLEAN QuickApiCall
+ );
+
+BOOLEAN
+NlFinishApiClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN BOOLEAN OkToKillSession
+ );
+
+VOID
+NlTimeoutApiClientSession(
+ VOID
+ );
+
+#undef EXTERN
diff --git a/private/net/svcdlls/logonsrv/server/trustutl.c b/private/net/svcdlls/logonsrv/server/trustutl.c
new file mode 100644
index 000000000..33249ccaf
--- /dev/null
+++ b/private/net/svcdlls/logonsrv/server/trustutl.c
@@ -0,0 +1,4976 @@
+/*++
+
+Copyright (c) 1987-1992 Microsoft Corporation
+
+Module Name:
+
+ trustutl.c
+
+Abstract:
+
+ Utilities manange of trusted domain list.
+
+Author:
+
+ 30-Jan-92 (cliffv)
+
+Environment:
+
+ User mode only.
+ Contains NT-specific code.
+ Requires ANSI C extensions: slash-slash comments, long external names.
+
+Revision History:
+
+--*/
+
+//
+// Common include files.
+//
+
+#include <logonsrv.h> // Include files common to entire service
+
+//
+// Include files specific to this .c file
+//
+
+#include <ntlsa.h>
+#include <alertmsg.h> // ALERT_* defines
+#include <align.h>
+#include <config.h> // net config helpers.
+#include <confname.h> // SECTION_ equates, NETLOGON_KEYWORD_ equates.
+#include <lmerr.h>
+#include <stdlib.h> // C library functions (rand, etc)
+#include <tstring.h>
+#include <lmapibuf.h>
+#include <lmuse.h> // NetUseDel
+#include <names.h> // NetpIsUserNameValid
+#include <nlbind.h> // Netlogon RPC binding cache routines
+
+
+
+PCLIENT_SESSION
+NlFindNamedClientSession(
+ IN PUNICODE_STRING DomainName
+ )
+/*++
+
+Routine Description:
+
+ Find the specified entry in the Trust List.
+
+Arguments:
+
+ DomainName - The name of the domain to find.
+
+Return Value:
+
+ Returns a pointer to the found entry.
+ The found entry is returned referenced and must be dereferenced using
+ NlUnrefClientSession.
+
+ If there is no such entry, return NULL.
+
+--*/
+{
+ PCLIENT_SESSION ClientSession = NULL;
+
+ //
+ // On DC, look up the domain in the trusted domain list.
+ //
+
+ if ( NlGlobalRole == RoleBackup || NlGlobalRole == RolePrimary ) {
+ PLIST_ENTRY ListEntry;
+
+ //
+ // Lookup the ClientSession with the TrustList locked and reference
+ // the found entry before dropping the lock.
+ //
+
+ LOCK_TRUST_LIST();
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession =
+ CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
+
+ if ( RtlEqualDomainName( &ClientSession->CsDomainName,
+ DomainName ) ) {
+ NlRefClientSession( ClientSession );
+ break;
+ }
+
+ ClientSession = NULL;
+
+ }
+
+ UNLOCK_TRUST_LIST();
+ }
+
+ //
+ // On a workstation or BDC, refer to the Primary domain.
+ //
+
+ if ( (NlGlobalRole == RoleBackup && ClientSession == NULL) ||
+ NlGlobalRole == RoleMemberWorkstation ) {
+
+ if ( RtlEqualDomainName( &NlGlobalUnicodeDomainNameString,
+ DomainName ) ) {
+ ClientSession = NlGlobalClientSession;
+ NlRefClientSession( ClientSession );
+ } else {
+ ClientSession = NULL;
+ }
+
+ }
+
+ return ClientSession;
+
+}
+
+
+
+PCLIENT_SESSION
+NlAllocateClientSession(
+ IN PUNICODE_STRING DomainName,
+ IN PSID DomainId,
+ IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType
+ )
+/*++
+
+Routine Description:
+
+ Allocate a ClientSession structure and initialize it.
+
+ The allocated entry is returned referenced and must be dereferenced using
+ NlUnrefClientSession.
+
+Arguments:
+
+ DomainName - Specifies the DomainName of the entry.
+
+ DomainId - Specifies the DomainId of the Domain.
+
+ SecureChannelType -- Type of secure channel this ClientSession structure
+ will represent.
+
+Return Value:
+
+--*/
+{
+ PCLIENT_SESSION ClientSession;
+ ULONG ClientSessionSize;
+ ULONG SidSize;
+ PCHAR Where;
+
+ //
+ // Determine the size of the ClientSession structure.
+ //
+
+ SidSize = RtlLengthSid( DomainId );
+
+ if ( DomainName->Length > DNLEN * sizeof(WCHAR) ) {
+ NlPrint((NL_CRITICAL,
+ "NlAllocateClientSession given "
+ "too long domain name %wZ\n", DomainName ));
+ return NULL;
+ }
+
+ ClientSessionSize = sizeof(CLIENT_SESSION) +
+ SidSize +
+ DomainName->Length + sizeof(WCHAR);
+
+ //
+ // Allocate the Client Session Entry
+ //
+
+ ClientSession = NetpMemoryAllocate( ClientSessionSize );
+
+ if (ClientSession == NULL) {
+ return NULL;
+ }
+
+ RtlZeroMemory( ClientSession, ClientSessionSize );
+
+
+ //
+ // Initialize misc. fields.
+ //
+
+ ClientSession->CsDomainId = NULL;
+ *ClientSession->CsUncServerName = L'\0';
+ ClientSession->CsTransportName = NULL;
+ ClientSession->CsSecureChannelType = SecureChannelType;
+ ClientSession->CsState = CS_IDLE;
+ ClientSession->CsReferenceCount = 1;
+ ClientSession->CsConnectionStatus = STATUS_NO_LOGON_SERVERS;
+ ClientSession->CsDiscoveryRetryCount = 0;
+ ClientSession->CsDiscoveryFlags = 0;
+ ClientSession->CsTimeoutCount = 0;
+ ClientSession->CsApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+
+ InitializeListHead( &ClientSession->CsNext );
+
+ //
+ // Build the account name as a function of the SecureChannelType.
+ //
+
+ switch (SecureChannelType) {
+ case WorkstationSecureChannel:
+ case ServerSecureChannel:
+ wcscpy( ClientSession->CsAccountName, NlGlobalUnicodeComputerName );
+ wcscat( ClientSession->CsAccountName, SSI_ACCOUNT_NAME_POSTFIX);
+ break;
+
+ case TrustedDomainSecureChannel:
+ wcscpy( ClientSession->CsAccountName, NlGlobalUnicodeDomainName );
+ wcscat( ClientSession->CsAccountName, SSI_ACCOUNT_NAME_POSTFIX);
+ break;
+
+ default:
+ NetpMemoryFree( ClientSession );
+ return NULL;
+ }
+
+ //
+ // Create the writer semaphore.
+ //
+
+ ClientSession->CsWriterSemaphore = CreateSemaphore(
+ NULL, // No special security
+ 1, // Initially not locked
+ 1, // At most 1 unlocker
+ NULL ); // No name
+
+ if ( ClientSession->CsWriterSemaphore == NULL ) {
+ NetpMemoryFree( ClientSession );
+ return NULL;
+ }
+
+
+
+
+ //
+ // Create the Discovery event.
+ //
+
+ ClientSession->CsDiscoveryEvent = CreateEvent(
+ NULL, // No special security
+ TRUE, // Manual Reset
+ FALSE, // No discovery initially happening
+ NULL ); // No name
+
+ if ( ClientSession->CsDiscoveryEvent == NULL ) {
+ CloseHandle( ClientSession->CsWriterSemaphore );
+ NetpMemoryFree( ClientSession );
+ return NULL;
+ }
+
+
+
+ //
+ // Copy the DomainId and DomainName to the buffer.
+ //
+
+ Where = (PCHAR)(ClientSession + 1);
+
+ NlAssert( Where == ROUND_UP_POINTER( Where, ALIGN_DWORD) );
+ ClientSession->CsDomainId = (PSID) Where;
+ NetpLogonPutBytes( DomainId, SidSize, &Where );
+
+ NlAssert( Where == ROUND_UP_POINTER( Where, ALIGN_WCHAR) );
+ ClientSession->CsDomainName.Buffer = (LPWSTR) Where;
+ ClientSession->CsDomainName.Length = DomainName->Length;
+ ClientSession->CsDomainName.MaximumLength = (USHORT)
+ (DomainName->Length + sizeof(WCHAR));
+ NetpLogonPutBytes( DomainName->Buffer, DomainName->Length, &Where );
+ *(Where++) = '\0';
+ *(Where++) = '\0';
+
+ return ClientSession;
+
+
+}
+
+
+VOID
+NlFreeClientSession(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Free the specified Trust list entry.
+
+ This routine is called with the Trust List locked.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry to delete.
+
+Return Value:
+
+--*/
+{
+
+ //
+ // If someone has an outstanding pointer to this entry,
+ // delay the deletion for now.
+ //
+
+ if ( ClientSession->CsReferenceCount > 0 ) {
+ ClientSession->CsFlags |= CS_DELETE_ON_UNREF;
+ return;
+ }
+
+ //
+ // If this is a trusted domain secure channel,
+ // Delink the entry from the sequential list.
+ //
+
+ if (ClientSession->CsSecureChannelType == TrustedDomainSecureChannel ) {
+ RemoveEntryList( &ClientSession->CsNext );
+ NlGlobalTrustListLength --;
+ }
+
+ //
+ // Close the discovery event if it exists.
+ //
+
+ if ( ClientSession->CsDiscoveryEvent != NULL ) {
+ CloseHandle( ClientSession->CsDiscoveryEvent );
+ }
+
+ //
+ // Close the write synchronization handles.
+ //
+
+ (VOID) CloseHandle( ClientSession->CsWriterSemaphore );
+
+ //
+ // If there is an rpc binding handle to this server,
+ // unbind it.
+
+ if ( ClientSession->CsFlags & CS_BINDING_CACHED ) {
+
+ //
+ // Indicate the handle is no longer bound
+ //
+
+ NlGlobalBindingHandleCount --;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlFreeClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
+ &ClientSession->CsDomainName,
+ ClientSession->CsUncServerName ));
+ (VOID) NlBindingRemoveServerFromCache( ClientSession->CsUncServerName );
+ }
+
+ //
+ // Delete the entry
+ //
+
+ NetpMemoryFree( ClientSession );
+
+}
+
+
+VOID
+NlRefClientSession(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Mark the specified client session as referenced.
+
+ On Entry,
+ The trust list must be locked.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ //
+ // Simply increment the reference count.
+ //
+
+ ClientSession->CsReferenceCount ++;
+}
+
+
+
+VOID
+NlUnrefClientSession(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Mark the specified client session as unreferenced.
+
+ On Entry,
+ The trust list entry must be referenced by the caller.
+ The caller must not be a writer of the trust list entry.
+
+ The trust list may be locked. But this routine will lock it again to
+ handle those cases where it isn't already locked.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry.
+
+Return Value:
+
+--*/
+{
+
+ LOCK_TRUST_LIST();
+
+ //
+ // Dereference the entry.
+ //
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ ClientSession->CsReferenceCount --;
+
+ //
+ // If we're the last referencer and
+ // someone wanted to delete the entry while we had it referenced,
+ // finish the deletion.
+ //
+
+ if ( ClientSession->CsReferenceCount == 0 &&
+ (ClientSession->CsFlags & CS_DELETE_ON_UNREF) ) {
+ NlFreeClientSession( ClientSession );
+ }
+
+ UNLOCK_TRUST_LIST();
+
+}
+
+
+
+
+BOOL
+NlTimeoutSetWriterClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN DWORD Timeout
+ )
+/*++
+
+Routine Description:
+
+ Become a writer of the specified client session but fail the operation if
+ we have to wait more than Timeout milliseconds.
+
+ A writer can "write" many of the fields in the client session structure.
+ See the comments in ssiinit.h for details.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The trust list entry must be referenced by the caller.
+ The caller must NOT be a writer of the trust list entry.
+
+ Actually, the trust list can be locked if the caller passes in a short
+ timeout (for instance, zero milliseconds.) Specifying a longer timeout
+ violates the locking order.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry.
+
+ Timeout - Maximum time (in milliseconds) to wait for a previous writer.
+
+Return Value:
+
+ TRUE - The caller is now the writer of the client session.
+
+ FALSE - The operation has timed out.
+
+--*/
+{
+ DWORD WaitStatus;
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+
+ //
+ // Wait for other writers to finish.
+ //
+
+ WaitStatus = WaitForSingleObject( ClientSession->CsWriterSemaphore, Timeout );
+
+ if ( WaitStatus != 0 ) {
+ NlPrint(( NL_CRITICAL,
+ "NlTimeoutSetWriterClientSession timed out: %ld\n",
+ WaitStatus ));
+ return FALSE;
+ }
+
+
+ //
+ // Become a writer.
+ //
+ LOCK_TRUST_LIST();
+ ClientSession->CsFlags |= CS_WRITER;
+ UNLOCK_TRUST_LIST();
+
+ return TRUE;
+
+}
+
+
+
+VOID
+NlResetWriterClientSession(
+ IN PCLIENT_SESSION ClientSession
+ )
+/*++
+
+Routine Description:
+
+ Stop being a writer of the specified client session.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The trust list entry must be referenced by the caller.
+ The caller must be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry.
+
+Return Value:
+
+--*/
+{
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ NlAssert( ClientSession->CsFlags & CS_WRITER );
+
+
+ //
+ // Stop being a writer.
+ //
+
+ LOCK_TRUST_LIST();
+ ClientSession->CsFlags &= ~CS_WRITER;
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // Allow writers to try again.
+ //
+
+ if ( !ReleaseSemaphore( ClientSession->CsWriterSemaphore, 1, NULL ) ) {
+ NlPrint((NL_CRITICAL,
+ "ReleaseSemaphore CsWriterSemaphore returned %ld\n",
+ GetLastError() ));
+ }
+
+}
+
+
+
+VOID
+NlSetStatusClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN NTSTATUS CsConnectionStatus
+ )
+/*++
+
+Routine Description:
+
+ Set the connection state for this client session.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The trust list entry must be referenced by the caller.
+ The caller must be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry.
+
+ CsConnectionStatus - the status of the connection.
+
+Return Value:
+
+--*/
+{
+ BOOLEAN UnbindFromServer = FALSE;
+ WCHAR UncServerName[UNCLEN+1];
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ NlAssert( ClientSession->CsFlags & CS_WRITER );
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlSetStatusClientSession: %wZ: Set connection status to %lx\n",
+ &ClientSession->CsDomainName,
+ CsConnectionStatus ));
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ ClientSession->CsConnectionStatus = CsConnectionStatus;
+ if ( NT_SUCCESS(CsConnectionStatus) ) {
+ ClientSession->CsState = CS_AUTHENTICATED;
+
+ //
+ // Handle setting the connection status to an error condition.
+ //
+
+ } else {
+
+ //
+ // If there is an rpc binding handle to this server,
+ // unbind it.
+
+ LOCK_TRUST_LIST();
+ if ( ClientSession->CsFlags & CS_BINDING_CACHED ) {
+
+ //
+ // Indicate the handle is no longer bound
+ //
+
+ ClientSession->CsFlags &= ~CS_BINDING_CACHED;
+ NlGlobalBindingHandleCount --;
+
+ //
+ // Capture the ServerName
+ //
+
+ wcscpy( UncServerName, ClientSession->CsUncServerName );
+ UnbindFromServer = TRUE;
+ }
+ UNLOCK_TRUST_LIST();
+
+ //
+ // If this is a BDC that just lost it's PDC,
+ // Indicate we don't know who the PDC is anymore.
+ //
+
+ if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
+ NlSetPrimaryName( NULL );
+ }
+
+ //
+ // Indicate discovery is needed (And can be done at any time.)
+ //
+
+ ClientSession->CsState = CS_IDLE;
+ *ClientSession->CsUncServerName = L'\0';
+ ClientSession->CsTransportName = NULL;
+ ClientSession->CsTimeoutCount = 0;
+ ClientSession->CsLastAuthenticationTry.QuadPart = 0;
+
+ //
+ // Don't be tempted to clear CsAuthenticationSeed and CsSessionKey here.
+ // Even though the secure channel is gone, NlFinishApiClientSession may
+ // have dropped it. The caller of NlFinishApiClientSession will use
+ // the above two fields after the session is dropped in an attempt to
+ // complete the final call on the secure channel.
+ //
+
+
+ }
+
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+
+ //
+ // Now that I have as many resources unlocked as possible,
+ // Unbind from this server.
+ //
+
+ if ( UnbindFromServer ) {
+ NlPrint((NL_SESSION_SETUP,
+ "NlSetStatusClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
+ &ClientSession->CsDomainName,
+ UncServerName ));
+ (VOID) NlBindingRemoveServerFromCache( UncServerName );
+ }
+
+}
+
+
+NTSTATUS
+NlInitTrustList(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Initialize the in-memory trust list to match LSA's version.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSA_ENUMERATION_HANDLE EnumerationContext = 0;
+ LSAPR_TRUSTED_ENUM_BUFFER LsaTrustList = {0, NULL};
+ ULONG LsaTrustListLength = 0;
+ ULONG LsaTrustListIndex = 0;
+ BOOL LsaAllDone = FALSE;
+
+
+ //
+ // Mark each entry in the trust list for deletion
+ //
+
+ //
+ // Loop through the LSA's list of domains
+ //
+ // For each entry found,
+ // If the entry already exits in the trust list,
+ // remove the mark for deletion.
+ // else
+ // allocate a new entry.
+ //
+
+ for (;; LsaTrustListIndex ++ ) {
+ PUNICODE_STRING DomainName;
+ PSID DomainId;
+
+ //
+ // Get more trusted domain names from the LSA.
+ //
+
+ if ( LsaTrustListIndex >= LsaTrustListLength ) {
+
+ //
+ // If we've already gotten everything from LSA,
+ // go delete entries that should be deleted.
+ //
+
+ if ( LsaAllDone ) {
+ break;
+ }
+
+ //
+ // Free any previous buffer returned from LSA.
+ //
+
+ if ( LsaTrustList.Information != NULL ) {
+
+ LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( &LsaTrustList );
+ LsaTrustList.Information = NULL;
+ }
+
+ //
+ // Do the actual enumeration
+ //
+
+ Status = LsarEnumerateTrustedDomains(
+ NlGlobalPolicyHandle,
+ &EnumerationContext,
+ &LsaTrustList,
+ 1024);
+
+ LsaTrustListLength = LsaTrustList.EntriesRead;
+
+ // If Lsa says he's returned all of the information,
+ // remember not to ask Lsa for more.
+ //
+
+ if ( Status == STATUS_NO_MORE_ENTRIES ) {
+ LsaAllDone = TRUE;
+ break;
+
+ //
+ // If Lsa says there is more information, just ensure he returned
+ // something to us on this call.
+ //
+
+ } else if ( NT_SUCCESS(Status) ) {
+ if ( LsaTrustListLength == 0 ) {
+ Status = STATUS_BUFFER_TOO_SMALL;
+ goto Cleanup;
+ }
+
+ //
+ // All other status' are errors.
+ //
+ } else {
+ goto Cleanup;
+ }
+
+ LsaTrustListIndex = 0;
+ }
+
+ //
+ // At this point LsaTrustList[LsaTrustListIndex] is the next entry
+ // returned from the LSA.
+ //
+
+ DomainName =
+ (PUNICODE_STRING)
+ &(LsaTrustList.Information[LsaTrustListIndex].Name);
+
+ DomainId =
+ (PSID)LsaTrustList.Information[LsaTrustListIndex].Sid;
+
+ NlPrint((NL_SESSION_SETUP, "NlInitTrustList: %wZ in LSA\n",
+ DomainName ));
+
+ if ( DomainName->Length > DNLEN * sizeof(WCHAR) ) {
+ NlPrint((NL_CRITICAL,
+ "LsarEnumerateTrustedDomains returned "
+ "too long domain name %wZ\n", DomainName ));
+ continue;
+ }
+
+ if ( RtlEqualDomainName( &NlGlobalUnicodeDomainNameString,
+ DomainName ) ) {
+ NlPrint((NL_SESSION_SETUP, "NlInitTrustList: %wZ "
+ "ignoring trust relationship to our own domain\n",
+ DomainName ));
+ continue;
+ }
+
+ //
+ // Update the in-memory trust list to match the LSA.
+ //
+
+ Status = NlUpdateTrustListBySid ( DomainId, DomainName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto Cleanup;
+ }
+
+
+ }
+
+
+ //
+ // Trust list successfully updated.
+ //
+ Status = STATUS_SUCCESS;
+
+Cleanup:
+
+ if ( LsaTrustList.Information != NULL ) {
+ LsaIFree_LSAPR_TRUSTED_ENUM_BUFFER( &LsaTrustList );
+ }
+
+ return Status;
+}
+
+
+
+
+VOID
+NlPickTrustedDcForEntireTrustList(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ For each domain in the trust list where the DC has not been
+ available for at least 45 seconds, try to select a new DC.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ PLIST_ENTRY ListEntry;
+ PCLIENT_SESSION ClientSession;
+
+
+ LOCK_TRUST_LIST();
+
+ //
+ // Mark each entry to indicate we need to pick a DC.
+ //
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ ClientSession->CsFlags |= CS_PICK_DC;
+ }
+
+
+ //
+ // Loop thru the trust list finding secure channels needing the DC
+ // to be picked.
+ //
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ //
+ // If we've already done this entry,
+ // skip this entry.
+ //
+ if ( (ClientSession->CsFlags & CS_PICK_DC) == 0 ) {
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+ ClientSession->CsFlags &= ~CS_PICK_DC;
+
+ //
+ // If the DC is already picked,
+ // skip this entry.
+ //
+ if ( ClientSession->CsState != CS_IDLE ) {
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+
+ //
+ // Reference this entry while picking the DC.
+ //
+
+ NlRefClientSession( ClientSession );
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // Check if we've tried to authenticate recently.
+ // (Don't call NlTimeToReauthenticate with the trust list locked.
+ // It locks NlGlobalDcDiscoveryCritSect. That's the wrong locking
+ // order.)
+ //
+
+ if ( NlTimeToReauthenticate( ClientSession ) ) {
+
+ //
+ // Try to pick the DC for the session.
+ //
+
+ if ( NlTimeoutSetWriterClientSession( ClientSession, 10*1000 ) ) {
+ (VOID) NlDiscoverDc( ClientSession, DT_DeadDomain );
+ NlResetWriterClientSession( ClientSession );
+ }
+
+ }
+
+ //
+ // Since we dropped the trust list lock,
+ // we'll start the search from the front of the list.
+ //
+
+ NlUnrefClientSession( ClientSession );
+ LOCK_TRUST_LIST();
+
+ ListEntry = NlGlobalTrustList.Flink ;
+
+ }
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // On a BDC,
+ // ensure we know who the PDC is.
+ //
+ // In NT 3.1, we relied on the fact that the PDC sent us pulses every 5
+ // minutes. For NT 3.5, the PDC backs off after 3 such failed attempts and
+ // will only send a pulse every 2 hours. So, we'll take on the
+ // responsibility
+ //
+ if ( NlGlobalRole == RoleBackup &&
+ NlGlobalClientSession->CsState == CS_IDLE ) {
+
+
+
+ //
+ // Check if we've tried to authenticate recently.
+ // (Don't call NlTimeToReauthenticate with the trust list locked.
+ // It locks NlGlobalDcDiscoveryCritSect. That's the wrong locking
+ // order.)
+ //
+
+ NlRefClientSession( NlGlobalClientSession );
+ if ( NlTimeToReauthenticate( NlGlobalClientSession ) ) {
+
+ //
+ // Try to pick the DC for the session.
+ //
+
+ if ( NlTimeoutSetWriterClientSession( NlGlobalClientSession, 10*1000 ) ) {
+ (VOID) NlDiscoverDc( NlGlobalClientSession, DT_DeadDomain );
+ NlResetWriterClientSession( NlGlobalClientSession );
+ }
+
+ }
+ NlUnrefClientSession( NlGlobalClientSession );
+
+ }
+
+}
+
+
+BOOL
+NlReadSamLogonResponse (
+ IN HANDLE ResponseMailslotHandle,
+ IN LPWSTR AccountName,
+ OUT LPDWORD Opcode,
+ OUT LPWSTR *UncLogonServer
+ )
+
+/*++
+
+Routine Description:
+
+ Read a response from to a SamLogonRequest.
+
+Arguments:
+
+ ResponseMailslotHandle - Handle of mailslot to read.
+
+ AccountName - Name of the account the response is for.
+
+ Opcode - Returns the opcode from the message. This will be one of
+ LOGON_SAM_LOGON_RESPONSE or LOGON_SAM_USER_UNKNOWN.
+
+ UncLogonServer - Returns the UNC name of the logon server that responded.
+ This buffer is only returned if a valid message was received.
+ The buffer returned should be freed via NetpMemoryFree.
+
+
+Return Value:
+
+ TRUE: a valid message was received.
+ FALSE: a valid message was not received.
+
+--*/
+{
+ CHAR ResponseBuffer[MAX_RANDOM_MAILSLOT_RESPONSE];
+ PNETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
+ DWORD SamLogonResponseSize;
+ LPWSTR LocalServerName;
+ LPWSTR LocalUserName;
+ PCHAR Where;
+ DWORD Version;
+ DWORD VersionFlags;
+
+ //
+ // Loop ignoring responses which are garbled.
+ //
+
+ for ( ;; ) {
+
+ //
+ // Read the response from the response mailslot
+ // (This mailslot is set up with a 5 second timeout).
+ //
+
+ if ( !ReadFile( ResponseMailslotHandle,
+ ResponseBuffer,
+ sizeof(ResponseBuffer),
+ &SamLogonResponseSize,
+ NULL ) ) {
+
+ IF_DEBUG( MAILSLOT ) {
+ NET_API_STATUS NetStatus;
+ NetStatus = GetLastError();
+
+ if ( NetStatus != ERROR_SEM_TIMEOUT ) {
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: "
+ "cannot read response mailslot: %ld\n",
+ NetStatus ));
+ }
+ }
+ return FALSE;
+ }
+
+ SamLogonResponse = (PNETLOGON_SAM_LOGON_RESPONSE) ResponseBuffer;
+
+ NlPrint((NL_MAILSLOT_TEXT, "NlReadSamLogonResponse opcode 0x%x\n",
+ SamLogonResponse->Opcode ));
+
+ NlpDumpBuffer(NL_MAILSLOT_TEXT, SamLogonResponse, SamLogonResponseSize);
+
+ //
+ // Ensure the opcode is expected.
+ // (Ignore responses from paused DCs, too.)
+ //
+
+ if ( SamLogonResponse->Opcode != LOGON_SAM_LOGON_RESPONSE &&
+ SamLogonResponse->Opcode != LOGON_SAM_USER_UNKNOWN ) {
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: response opcode not valid. 0x%lx\n",
+ SamLogonResponse->Opcode ));
+ continue;
+ }
+
+ //
+ // Ensure the version is expected.
+ //
+
+ Version = NetpLogonGetMessageVersion( SamLogonResponse,
+ &SamLogonResponseSize,
+ &VersionFlags );
+
+ if ( Version != LMNT_MESSAGE ) {
+ NlPrint((NL_CRITICAL,"NlReadSamLogonResponse: version not valid 0x%lx 0x%lx.\n",
+ Version, VersionFlags ));
+ continue;
+ }
+
+ //
+ // Pick up the name of the server that responded.
+ //
+
+ Where = (PCHAR) &SamLogonResponse->UnicodeLogonServer;
+ if ( !NetpLogonGetUnicodeString(
+ SamLogonResponse,
+ SamLogonResponseSize,
+ &Where,
+ sizeof(SamLogonResponse->UnicodeLogonServer),
+ &LocalServerName ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: "
+ "server name not formatted right\n"));
+ continue;
+ }
+
+ //
+ // Ensure this is a UNC name.
+ //
+
+ if ( LocalServerName[0] != '\\' || LocalServerName[1] != '\\' ) {
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: server name isn't UNC name\n"));
+ continue;
+
+ }
+
+ //
+ // Pick up the name of the account the response is for.
+ //
+
+ if ( !NetpLogonGetUnicodeString(
+ SamLogonResponse,
+ SamLogonResponseSize,
+ &Where,
+ sizeof(SamLogonResponse->UnicodeUserName ),
+ &LocalUserName ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: User name not formatted right\n"));
+ continue;
+ }
+
+ //
+ // If the response is for the correct account,
+ // break out of the loop.
+ //
+
+ if ( NlNameCompare( AccountName, LocalUserName, NAMETYPE_USER) == 0 ) {
+ break;
+ }
+
+ NlPrint((NL_CRITICAL,
+ "NlReadSamLogonResponse: User name " FORMAT_LPWSTR
+ " s.b. " FORMAT_LPWSTR ".\n",
+ LocalUserName,
+ AccountName ));
+
+
+ }
+
+ //
+ // Return the info to the caller.
+ //
+
+ *Opcode = SamLogonResponse->Opcode;
+ *UncLogonServer = NetpMemoryAllocate(
+ (wcslen(LocalServerName) + 1) * sizeof(WCHAR) );
+
+ if ( *UncLogonServer == NULL ) {
+ NlPrint((NL_CRITICAL, "NlReadSamLogonResponse: Not enough memory\n"));
+ return FALSE;
+ }
+
+ wcscpy( (*UncLogonServer), LocalServerName );
+
+ return TRUE;
+
+}
+
+
+VOID
+NlSaveTrustedDomainList (
+ IN LPWSTR TrustedDomainList
+ )
+
+/*++
+
+Routine Description:
+
+ Save the list of trusted domains to the registry.
+
+Arguments:
+
+ TrustedDomainList - Specifies a list of trusted domains in MULTI_SZ format.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+
+ LPNET_CONFIG_HANDLE SectionHandle;
+ LPWSTR LocalTrustedDomainList;
+
+
+ //
+ // Open the NetLogon configuration section.
+ //
+
+ NetStatus = NetpOpenConfigData(
+ &SectionHandle,
+ NULL, // no server name.
+ SERVICE_NETLOGON,
+ FALSE ); // we write access
+
+ if ( NetStatus != NO_ERROR ) {
+ NlPrint((NL_CRITICAL,
+ "NlSaveTrustedDomainList: NetpOpenConfigData failed: %ld\n",
+ NetStatus ));
+
+ } else {
+
+ //
+ // Convert an empty list to a recognizable form
+ //
+
+ if ( TrustedDomainList == NULL ) {
+ LocalTrustedDomainList = L"\0";
+ } else {
+ LocalTrustedDomainList = TrustedDomainList;
+ }
+
+
+
+ //
+ // Write the domain list to the registry
+ //
+
+ NetStatus = NetpSetConfigTStrArray(
+ SectionHandle,
+ NETLOGON_KEYWORD_TRUSTEDDOMAINLIST,
+ LocalTrustedDomainList );
+
+ if ( NetStatus != NO_ERROR ) {
+ NlPrint((NL_CRITICAL,
+ "NlSaveTrustedDomainList: NetpSetConfigTStrArray failed: %ld\n",
+ NetStatus ));
+ }
+
+ (VOID) NetpCloseConfigData( SectionHandle );
+ }
+
+ return;
+}
+
+
+NET_API_STATUS
+NlReadRegTrustedDomainList (
+ IN LPWSTR NewDomainName OPTIONAL,
+ IN BOOL DeleteName,
+ OUT LPWSTR *TrustedDomainList,
+ OUT PBOOL TrustedDomainListKnown
+ )
+
+/*++
+
+Routine Description:
+
+ Read the list of trusted domains from the registry.
+
+Arguments:
+
+ NewDomainName - New DomainName of this domain. When this machine joins a domain,
+ NCPA caches the trusted domain list where we can find it. That ensures the
+ trusted domain list is available upon reboot even before we dial via RAS. Winlogon
+ can therefore get the trusted domain list from us under those circumstances.
+
+ DeleteName - TRUE if the name is to be deleted upon successful completion.
+
+ TrustedDomainList - Returns a list of trusted domains in MULTI_SZ format. Buffer
+ must be freed using NetApiBufferFree.
+
+ TrustedDomainListKnown - Returns true if we know the trusted domain list.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NET_API_STATUS NetStatus;
+ LPNET_CONFIG_HANDLE SectionHandle = NULL;
+ WCHAR ValueName[sizeof(NETLOGON_KEYWORD_TRUSTEDDOMAINLIST)/sizeof(WCHAR)+1+DNLEN+1];
+
+
+
+ //
+ // Open the NetLogon configuration section.
+ //
+
+ *TrustedDomainListKnown = FALSE;
+ *TrustedDomainList = NULL;
+
+ NetStatus = NetpOpenConfigData(
+ &SectionHandle,
+ NULL, // no server name.
+ SERVICE_NETLOGON,
+ !DeleteName ); // Get Write access if deleting.
+
+ if ( NetStatus != NO_ERROR ) {
+ NlPrint((NL_CRITICAL,
+ "NlReadRegTrustedDomainList: NetpOpenConfigData failed: %ld\n",
+ NetStatus ));
+ }
+
+ //
+ // Get the "TrustedDomainList" configured parameter
+ //
+
+ if ( NewDomainName == NULL ) {
+ *ValueName = L'\0';
+ } else {
+ wcscpy( ValueName, NewDomainName );
+ wcscat( ValueName, L"_" );
+ }
+ wcscat( ValueName, NETLOGON_KEYWORD_TRUSTEDDOMAINLIST );
+
+ NetStatus = NetpGetConfigTStrArray (
+ SectionHandle,
+ ValueName,
+ TrustedDomainList ); // Must be freed by NetApiBufferFree().
+
+ //
+ // Handle the default
+ //
+
+ if (NetStatus == NERR_CfgParamNotFound) {
+ *TrustedDomainList = NULL;
+ } else if (NetStatus != NO_ERROR) {
+ NlPrint((NL_CRITICAL,
+ "NlReadRegTrustedDomainList: NetpGetConfigTStrArray failed: %ld\n",
+ NetStatus ));
+ goto Cleanup;
+ } else {
+ *TrustedDomainListKnown = TRUE;
+ }
+
+ if ( DeleteName && *TrustedDomainListKnown) {
+ NET_API_STATUS TempNetStatus;
+ TempNetStatus = NetpDeleteConfigKeyword ( SectionHandle, ValueName );
+
+ if ( TempNetStatus != NO_ERROR ) {
+ NlPrint((NL_CRITICAL,
+ "NlReadRegTrustedDomainList: NetpDeleteConfigKeyword failed: %ld\n",
+ TempNetStatus ));
+ }
+ }
+
+ NetStatus = NO_ERROR;
+
+Cleanup:
+ if ( SectionHandle != NULL ) {
+ (VOID) NetpCloseConfigData( SectionHandle );
+ }
+
+ return NetStatus;
+}
+
+
+NTSTATUS
+NlGetTrustedDomainList (
+ IN LPWSTR UncDcName,
+ OUT LPWSTR *TrustedDomainList
+ )
+
+/*++
+
+Routine Description:
+
+ Get the list of trusted domains from the specified DC.
+
+Arguments:
+
+ UncDcName - Specifies the name of a DC in the domain.
+
+ TrustedDomainList - Returns a list of trusted domains in MULTI_SZ format.
+ This list should be freed via NetpMemoryFree
+
+Return Value:
+
+ STATUS_SUCCESS - if the trust list was successfully returned
+
+--*/
+{
+ NTSTATUS Status;
+
+ LSA_HANDLE LsaHandle = NULL;
+ UNICODE_STRING UncDcNameString;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+ LSA_ENUMERATION_HANDLE EnumerationContext;
+ BOOLEAN AllDone = FALSE;
+
+ LPWSTR CurrentBuffer = NULL;
+ DWORD CurrentSize = 0;
+
+ PLSA_TRUST_INFORMATION TrustList = NULL;
+
+
+ //
+ // Open the policy database on the DC
+ //
+
+ RtlInitUnicodeString( &UncDcNameString, UncDcName );
+
+ InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
+
+ Status = LsaOpenPolicy( &UncDcNameString,
+ &ObjectAttributes,
+ POLICY_VIEW_LOCAL_INFORMATION,
+ &LsaHandle );
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlGetTrustedDomainList: " FORMAT_LPWSTR
+ ": LsaOpenPolicy failed: %lx\n",
+ UncDcName,
+ Status ));
+
+ LsaHandle = NULL;
+ goto Cleanup;
+
+ }
+
+ //
+ // Allocate the buffer in case there are no domains.
+ //
+
+ CurrentBuffer = NetpMemoryAllocate( sizeof(WCHAR) );
+
+ if ( CurrentBuffer == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ *CurrentBuffer = L'\0';
+
+ //
+ // Loop getting a list of trusted domains
+ //
+
+ EnumerationContext = 0;
+
+ do {
+ ULONG CountReturned;
+ ULONG i;
+ DWORD Size;
+ LPWSTR NewBuffer;
+ LPWSTR CurrentLoc;
+
+ //
+ // Free any buffers from a previous iteration.
+ //
+ if ( TrustList != NULL ) {
+ (VOID) LsaFreeMemory( TrustList );
+ }
+
+ //
+ // Get more trusted domains names
+ //
+
+ Status = LsaEnumerateTrustedDomains(
+ LsaHandle,
+ &EnumerationContext,
+ (PVOID *) &TrustList,
+ 0xFFFFFFFF,
+ &CountReturned );
+
+ if ( Status == STATUS_NO_MORE_ENTRIES ) {
+ AllDone = TRUE;
+ Status = STATUS_SUCCESS;
+ }
+
+ if ( !NT_SUCCESS(Status) && Status != STATUS_NO_MORE_ENTRIES ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlGetTrustedDomainList: " FORMAT_LPWSTR
+ ": LsaEnumerateTrustedDomains failed: %lx\n",
+ UncDcName,
+ Status ));
+
+ TrustList = NULL;
+ goto Cleanup;
+ }
+
+ if ( CountReturned == 0 ) {
+ continue;
+ }
+
+
+ //
+ // Determine the size of names returned on this call.
+ //
+
+ Size = 0;
+ for ( i=0; i<CountReturned; i++ ) {
+ Size += TrustList[i].Name.Length + sizeof(WCHAR);
+ }
+
+ //
+ // Reallocate the buffer.
+ //
+
+ NewBuffer = NetpMemoryReallocate( CurrentBuffer, Size + CurrentSize + sizeof(WCHAR) );
+
+ if ( NewBuffer == NULL ) {
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ CurrentBuffer = NewBuffer;
+ CurrentLoc = &NewBuffer[CurrentSize/sizeof(WCHAR)];
+ CurrentSize += Size;
+
+ //
+ // Handle each trusted domain.
+ //
+
+ for ( i=0; i<CountReturned; i++ ) {
+ LPWSTR CurrentDomainName;
+
+ //
+ // Copy the new domain name into the buffer
+ //
+
+ CurrentDomainName = CurrentLoc;
+
+ RtlCopyMemory( CurrentLoc,
+ TrustList[i].Name.Buffer,
+ TrustList[i].Name.Length );
+ CurrentLoc += TrustList[i].Name.Length / sizeof(WCHAR);
+
+ *(CurrentLoc++) = L'\0';
+ *CurrentLoc = L'\0'; // Place double terminator each time
+
+ //
+ // Ensure the SID of the trusted domain isn't the domain sid of this
+ // machine.
+ //
+
+ if ( RtlEqualSid( TrustList[i].Sid, NlGlobalDBInfoArray[SAM_DB].DBId )) {
+
+ LPWSTR AlertStrings[3];
+
+ //
+ // alert admin.
+ //
+
+ AlertStrings[0] = NlGlobalUnicodeComputerName;
+ AlertStrings[1] = CurrentDomainName;
+ AlertStrings[2] = NULL;
+
+ RaiseAlert( ALERT_NetLogonSidConflict,
+ AlertStrings );
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonSidConflict,
+ EVENTLOG_ERROR_TYPE,
+ TrustList[i].Sid,
+ RtlLengthSid( TrustList[i].Sid ),
+ AlertStrings,
+ 2 );
+
+ }
+ }
+
+ } while ( !AllDone );
+
+ //
+ // Save the collected information to the registry
+ //
+
+ NlSaveTrustedDomainList ( CurrentBuffer );
+
+ //
+ // Remember the list in globals
+ //
+
+ NlSetTrustedDomainList ( CurrentBuffer, TRUE );
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Free any locally used resources.
+ //
+Cleanup:
+
+ if ( LsaHandle != NULL ) {
+ (VOID) LsaClose( LsaHandle );
+ }
+
+ if ( TrustList != NULL ) {
+ (VOID) LsaFreeMemory( TrustList );
+ }
+
+ if ( !NT_SUCCESS(Status) ) {
+ if ( CurrentBuffer != NULL ) {
+ NetpMemoryFree( CurrentBuffer );
+ CurrentBuffer = NULL;
+ }
+ }
+ *TrustedDomainList = CurrentBuffer;
+ return Status;
+}
+
+
+NTSTATUS
+NlSetTrustedDomainList (
+ IN LPWSTR TrustedDomainList,
+ IN BOOL TrustedDomainListKnown
+ )
+
+/*++
+
+Routine Description:
+
+ Set the domain list in globals
+
+Arguments:
+
+ TrustedDomainList - Specifies a list of trusted domains in MULTI_SZ format.
+
+ TrustedDomainListKnown - TRUE if TrustedDomainList has been retrieved
+ from a DC in the domain.
+
+Return Value:
+
+ Status of the operation.
+
+ Upon failure, the previous list remains intact.
+
+--*/
+{
+ PTRUSTED_DOMAIN LocalTrustedDomainList;
+ DWORD LocalTrustedDomainCount;
+ DWORD LocalTrustedDomainSize;
+ DWORD i;
+
+ PTRUSTED_DOMAIN OldList;
+ LPWSTR CurrentEntry;
+
+
+ //
+ // If the new list is zero length,
+ // don't bother allocating anything.
+ //
+
+ if ( TrustedDomainList == NULL ) {
+ LocalTrustedDomainList = NULL;
+ LocalTrustedDomainCount = 0;
+
+ //
+ // Otherwise, build a buffer of the trusted domain list
+ //
+
+ } else {
+
+ //
+ // Allocate a buffer for the new list
+ //
+
+ LocalTrustedDomainCount = NetpTStrArrayEntryCount( TrustedDomainList );
+
+ LocalTrustedDomainList = NetpMemoryAllocate(
+ LocalTrustedDomainCount *
+ sizeof(TRUSTED_DOMAIN) );
+
+ if ( LocalTrustedDomainList == NULL && LocalTrustedDomainCount != 0 ) {
+ return STATUS_NO_MEMORY;
+ }
+
+ //
+ // Copy the names to the new structure upper casing them and
+ // converting to OEM.
+ //
+
+ NlPrint((NL_LOGON, "NlSetTrustedDomainList: New trusted domain list:\n" ));
+ CurrentEntry = TrustedDomainList;
+
+ for ( i=0; i<LocalTrustedDomainCount; i++ ) {
+ NTSTATUS Status;
+ UNICODE_STRING UnicodeString;
+ OEM_STRING OemString;
+
+ NlPrint((NL_LOGON, " " FORMAT_LPWSTR "\n", CurrentEntry ));
+
+ //
+ // Convert the input string to OEM
+ //
+
+ RtlInitUnicodeString( &UnicodeString, CurrentEntry );
+
+ OemString.Buffer = LocalTrustedDomainList[i].DomainName;
+ OemString.MaximumLength = sizeof(LocalTrustedDomainList[i].DomainName);
+
+ Status = RtlUpcaseUnicodeStringToOemString(
+ &OemString,
+ &UnicodeString,
+ FALSE ); // Don't Allocate dest
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NlPrint(( NL_CRITICAL,
+ "Can't convert to OEM: " FORMAT_LPWSTR ": %lX\n",
+ CurrentEntry,
+ Status ));
+
+ NetpMemoryFree( LocalTrustedDomainList );
+ return Status;
+ }
+
+
+ CurrentEntry += (UnicodeString.Length / sizeof(WCHAR)) + 1;
+
+ }
+ }
+
+ //
+ // Swap in the new list
+
+ LOCK_TRUST_LIST();
+ OldList = NlGlobalTrustedDomainList;
+ NlGlobalTrustedDomainList = LocalTrustedDomainList;
+ NlGlobalTrustedDomainCount = LocalTrustedDomainCount;
+ NlGlobalTrustedDomainListKnown = TrustedDomainListKnown;
+ NtQuerySystemTime( &NlGlobalTrustedDomainListTime );
+ UNLOCK_TRUST_LIST();
+
+ //
+ // Free the old list.
+ //
+
+ if ( OldList != NULL ) {
+ NetpMemoryFree( OldList );
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+BOOLEAN
+NlIsDomainTrusted (
+ IN PUNICODE_STRING DomainName
+ )
+
+/*++
+
+Routine Description:
+
+ Determine if the specified domain is trusted.
+
+ If the trusted domain list has not been obtained from the DC,
+ indicate the domain is trusted. This causes the caller fall back to
+ the prior behaviour of indicating that the DC cannot be contacted.
+ This status code is special cased in MSV1_0 to indicate that cached
+ credentials should be tried. This ensures that a newly upgraded RAS
+ client continues to use cached credentials until it dials in the first
+ time.
+
+Arguments:
+
+
+ DomainName - Name of the domain to query.
+
+Return Value:
+
+ TRUE - if the domain name specified is a trusted domain.
+
+--*/
+{
+ NTSTATUS Status;
+ DWORD i;
+
+ OEM_STRING OemString;
+ CHAR OemBuffer[DNLEN+1];
+
+ OEM_STRING CurrentDomainName;
+
+ //
+ // If the no domain name was specified,
+ // indicate the domain is not trusted.
+ //
+
+ if ( DomainName == NULL || DomainName->Length == 0 ) {
+ return FALSE;
+ }
+
+ //
+ // Convert the input string to OEM
+ //
+
+ OemString.MaximumLength = sizeof(OemBuffer);
+ OemString.Buffer = OemBuffer;
+
+ Status = RtlUpcaseUnicodeStringToOemString( &OemString,
+ DomainName,
+ FALSE ); // Don't Allocate dest
+
+ if ( !NT_SUCCESS( Status ) ) {
+ return FALSE;
+ }
+
+ //
+ // Consider the Primary Domain to be a trusted domain, too
+ //
+
+ RtlInitString( &CurrentDomainName, NlGlobalAnsiDomainName );
+
+ if ( RtlEqualString( &OemString, &CurrentDomainName, FALSE ) ) {
+ return TRUE;
+ }
+
+ //
+ // If we have no trusted domain list,
+ // fall back to previous behavior.
+ //
+
+ if ( !NlGlobalTrustedDomainListKnown ) {
+ return TRUE;
+ }
+
+ //
+ // Compare the input trusted domain name to each element in the list
+ //
+
+ LOCK_TRUST_LIST();
+ for ( i=0; i<NlGlobalTrustedDomainCount; i++ ) {
+
+ RtlInitString( &CurrentDomainName,
+ NlGlobalTrustedDomainList[i].DomainName );
+
+ //
+ // Simply compare the bytes (both are already uppercased)
+ //
+ if ( RtlEqualString( &OemString, &CurrentDomainName, FALSE ) ) {
+ UNLOCK_TRUST_LIST();
+ return TRUE;
+ }
+
+ }
+ UNLOCK_TRUST_LIST();
+
+ //
+ // All other domains aren't trusted.
+ //
+
+ return FALSE;
+}
+
+
+//
+// Define the Actions to NlDcDiscoveryMachine.
+//
+
+typedef enum {
+ StartDiscovery,
+ DcFoundMessage,
+ DcNotFoundMessage,
+ DcTimerExpired
+} DISCOVERY_ACTION;
+
+//
+// number of broadcastings to get DC before reporting DC not found
+// error.
+//
+
+#define MAX_DC_RETRIES 3
+
+
+NTSTATUS
+NlDcDiscoveryMachine(
+ IN OUT PCLIENT_SESSION ClientSession,
+ IN DISCOVERY_ACTION Action,
+ IN LPWSTR UncDcName OPTIONAL,
+ IN LPWSTR TransportName OPTIONAL,
+ IN LPSTR ResponseMailslotName OPTIONAL,
+ IN DISCOVERY_TYPE DiscoveryType
+ )
+
+/*++
+
+Routine Description:
+
+ State machine to get the name of a DC in a domain.
+
+Arguments:
+
+ ClientSession -- Client session structure whose DC is to be picked.
+ The Client Session structure must be referenced.
+
+ Action -- The event which just occurred.
+
+ UncDcName -- If the Action is DcFoundMessage, this is the name of the newly
+ found domain controller.
+
+ TransportName -- If the Action is DcFoundMessage, this is the name of the
+ transport the domain controller can be reached on.
+
+ ResponseMailslotName -- If action is StartDiscovery or DcTimerExpired,
+ this name is the name of the mailslot that the response is sent to.
+
+ DiscoveryType -- Indicate synchronous, Asynchronous, or rediscovery of a
+ "Dead domain".
+
+
+Return Value:
+
+ STATUS_SUCCESS - if DC was found.
+ STATUS_PENDING - if discovery is still in progress and the caller should
+ call again in DISCOVERY_PERIOD with the DcTimerExpired action.
+
+ STATUS_NO_LOGON_SERVERS - if DC was not found.
+ STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
+ an account for this machine.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PNETLOGON_SAM_LOGON_REQUEST SamLogonRequest = NULL;
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ //
+ // Handle a new request to start discovery and timer expiration.
+ //
+ switch (Action) {
+ case StartDiscovery:
+ case DcTimerExpired: {
+
+ DWORD DomainSidSize;
+ ULONG AllowableAccountControlBits;
+
+ WCHAR NetlogonMailslotName[DNLEN+NETLOGON_NT_MAILSLOT_LEN+5];
+ PCHAR Where;
+
+ //
+ // If discovery is currently going on,
+ // ignore this new request.
+ // If discovery isn't currently going on,
+ // ignore a timer expiration.
+ //
+
+ if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) &&
+ Action == StartDiscovery ){
+ Status = STATUS_SUCCESS;
+ goto Ignore;
+
+ } else if (
+ (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) == 0 &&
+ Action == DcTimerExpired ){
+ if ( ClientSession->CsState == CS_IDLE ) {
+ Status = ClientSession->CsConnectionStatus;
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+ goto Ignore;
+ }
+
+
+ //
+ // Increment/set the retry count
+ //
+
+ if ( Action == StartDiscovery ) {
+ ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_IN_PROGRESS;
+ ClientSession->CsDiscoveryRetryCount = 0;
+
+
+ NlAssert( ClientSession->CsDiscoveryEvent != NULL );
+
+ if ( !ResetEvent( ClientSession->CsDiscoveryEvent ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: ResetEvent failed %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ GetLastError() ));
+ }
+
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: Start Discovery\n",
+ ClientSession->CsDomainName.Buffer ));
+ } else {
+ ClientSession->CsDiscoveryRetryCount ++;
+ if ( ClientSession->CsDiscoveryRetryCount == MAX_DC_RETRIES ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: Discovery failed\n",
+ ClientSession->CsDomainName.Buffer ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: Discovery retry %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ ClientSession->CsDiscoveryRetryCount ));
+ }
+
+
+ //
+ // Determine the Account type we're looking for.
+ //
+
+ if ( ClientSession->CsSecureChannelType == WorkstationSecureChannel ) {
+ AllowableAccountControlBits = USER_WORKSTATION_TRUST_ACCOUNT;
+ } else if ( ClientSession->CsSecureChannelType ==
+ TrustedDomainSecureChannel ) {
+ AllowableAccountControlBits = USER_INTERDOMAIN_TRUST_ACCOUNT;
+ } else if ( ClientSession->CsSecureChannelType ==
+ ServerSecureChannel ) {
+ AllowableAccountControlBits = USER_SERVER_TRUST_ACCOUNT;
+ } else {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: "
+ "invalid SecureChannelType retry %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ ClientSession->CsSecureChannelType ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ //
+ // Initialization memory for the logon request message.
+ //
+
+ DomainSidSize = RtlLengthSid( ClientSession->CsDomainId );
+
+ SamLogonRequest = NetpMemoryAllocate(
+ sizeof(NETLOGON_SAM_LOGON_REQUEST) +
+ DomainSidSize +
+ sizeof(DWORD) // for SID alignment on 4 byte boundary
+ );
+
+ if( SamLogonRequest == NULL ) {
+ NlPrint(( NL_CRITICAL, "NlDcDiscoveryMachine can't allocate memory\n"));
+ // This isn't the real status, but callers handle this status
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+
+ //
+ // Build the query message.
+ //
+
+ SamLogonRequest->Opcode = LOGON_SAM_LOGON_REQUEST;
+ SamLogonRequest->RequestCount =
+ (USHORT) ClientSession->CsDiscoveryRetryCount;
+
+ Where = (PCHAR) &SamLogonRequest->UnicodeComputerName;
+
+ NetpLogonPutUnicodeString(
+ NlGlobalUnicodeComputerName,
+ sizeof(SamLogonRequest->UnicodeComputerName),
+ &Where );
+
+ NetpLogonPutUnicodeString(
+ ClientSession->CsAccountName,
+ sizeof(SamLogonRequest->UnicodeUserName),
+ &Where );
+
+ NetpLogonPutOemString(
+ ResponseMailslotName,
+ sizeof(SamLogonRequest->MailslotName),
+ &Where );
+
+ NetpLogonPutBytes(
+ &AllowableAccountControlBits,
+ sizeof(SamLogonRequest->AllowableAccountControlBits),
+ &Where );
+
+ //
+ // place domain SID in the message.
+ //
+
+ NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
+ NetpLogonPutDomainSID( ClientSession->CsDomainId, DomainSidSize, &Where );
+
+ NetpLogonPutNtToken( &Where );
+
+
+ //
+ // Broadcast the message to each Netlogon service in the domain.
+ //
+ // We are sending to the DomainName* name which will be received by
+ // all NT DCs including those DCs on a WAN.
+ //
+ // When doing the discover of the PDC for this domain, send to
+ // DomainName** which only sends to Domain<1B> which is registered
+ // only by the PDC.
+ //
+
+ NetlogonMailslotName[0] = '\\';
+ NetlogonMailslotName[1] = '\\';
+ wcscpy(NetlogonMailslotName+2, ClientSession->CsDomainName.Buffer );
+ wcscat(NetlogonMailslotName, L"*" );
+ if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
+ wcscat(NetlogonMailslotName, L"*" );
+ }
+ wcscat(NetlogonMailslotName, NETLOGON_NT_MAILSLOT_W );
+
+ Status = NlpWriteMailslot(
+ NetlogonMailslotName,
+ SamLogonRequest,
+ Where - (PCHAR)(SamLogonRequest) );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: "
+ "cannot write netlogon mailslot 0x%lx\n",
+ ClientSession->CsDomainName.Buffer,
+ Status));
+
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+
+ //
+ // If this is an asynchronous call and this is the first call,
+ // start the periodic timer.
+ //
+ if ( DiscoveryType == DT_Asynchronous && Action == StartDiscovery ) {
+ if ( NlGlobalDcDiscoveryCount == 0 ) {
+ NlGlobalDcDiscoveryTimer.Period =
+ DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES;
+ (VOID) NtQuerySystemTime( &NlGlobalDcDiscoveryTimer.StartTime );
+
+ //
+ // If netlogon is exitting,
+ // the main thread is already gone.
+ //
+
+ if ( NlGlobalTerminate ) {
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ //
+ // Tell the main thread that I've changed a timer.
+ //
+
+ if ( !SetEvent( NlGlobalTimerEvent ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: SetEvent2 failed %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ GetLastError() ));
+ }
+
+ }
+ NlGlobalDcDiscoveryCount ++;
+ ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_ASYNCHRONOUS;
+
+ //
+ // Don't let the session go away during discovery.
+ //
+ LOCK_TRUST_LIST();
+ NlRefClientSession( ClientSession );
+ UNLOCK_TRUST_LIST();
+
+ //
+ // If this is merely an attempt to revive a "dead" domain,
+ // we just send the single mailslot message above and exit discovery.
+ // If any DC responds, we'll pick up the response even though
+ // discovery isn't in progress.
+ //
+
+ } else if ( DiscoveryType == DT_DeadDomain ) {
+ Status = ClientSession->CsConnectionStatus;
+ goto Cleanup;
+ }
+
+ Status = STATUS_PENDING;
+ goto Ignore;
+
+ }
+
+ //
+ // Handle when a DC claims to be the DC for the requested domain.
+ //
+
+ case DcFoundMessage:
+
+ //
+ // If we already know the name of a DC,
+ // ignore this new name.
+ //
+ // When we implement doing discovery while a session is already up,
+ // we need to handle the case where someone has the ClientSession
+ // write locked. In that case, we should probably just hang the new
+ // DCname somewhere off the ClientSession structure and swap in the
+ // new DCname when the writer drops the write lock. ??
+ //
+
+ if ( ClientSession->CsState != CS_IDLE ) {
+
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: DC %ws ignored."
+ " DC previously found.\n",
+ ClientSession->CsDomainName.Buffer,
+ UncDcName ));
+ Status = STATUS_SUCCESS;
+ goto Ignore;
+ }
+
+
+ //
+ // Install the new DC name in the Client session
+ //
+
+ wcsncpy( ClientSession->CsUncServerName, UncDcName, UNCLEN );
+ ClientSession->CsUncServerName[UNCLEN] = L'\0';
+
+
+
+ //
+ // Save the transport this discovery came in on.
+ //
+ if ( TransportName == NULL ) {
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: Found DC %ws\n",
+ ClientSession->CsDomainName.Buffer,
+ UncDcName ));
+ } else {
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: Found DC %ws on transport %ws\n",
+ ClientSession->CsDomainName.Buffer,
+ UncDcName,
+ TransportName ));
+
+ ClientSession->CsTransportName =
+ NlTransportLookupTransportName( TransportName );
+
+ if ( ClientSession->CsTransportName == NULL ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: " FORMAT_LPWSTR ": Transport not found\n",
+ TransportName ));
+ }
+ }
+
+ //
+ // If this is a BDC discovering it's PDC,
+ // save the PDC name.
+ // Start the replicator and let it figure if it needs to be running.
+ //
+
+ if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
+ NlSetPrimaryName( ClientSession->CsUncServerName+2 );
+ (VOID) NlStartReplicatorThread( 0 );
+ }
+
+
+
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+
+
+ case DcNotFoundMessage:
+
+ //
+ // If we already know the name of a DC,
+ // ignore this new name.
+ //
+
+ if ( ClientSession->CsState != CS_IDLE ) {
+
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: DC %ws ignored."
+ " DC previously found.\n",
+ ClientSession->CsDomainName.Buffer,
+ UncDcName ));
+ Status = STATUS_SUCCESS;
+ goto Ignore;
+ }
+
+ //
+ // If discovery isn't currently going on,
+ // ignore this extraneous message.
+ //
+
+ if ((ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IN_PROGRESS) == 0 ){
+ NlPrint(( NL_SESSION_SETUP,
+ "NlDcDiscoveryMachine: %ws: DC %ws ignored."
+ " Discovery not in progress.\n",
+ ClientSession->CsDomainName.Buffer,
+ UncDcName ));
+ Status = ClientSession->CsConnectionStatus;
+ goto Ignore;
+ }
+
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: "
+ "Received No Such Account message\n",
+ ClientSession->CsDomainName.Buffer));
+
+ Status = STATUS_NO_TRUST_SAM_ACCOUNT;
+ goto Cleanup;
+
+ }
+
+ //
+ // We never reach here.
+ //
+ NlAssert(FALSE);
+
+
+ //
+ // Handle discovery being completed.
+ //
+Cleanup:
+ //
+ // On success,
+ // Indicate that the session setup is allowed to happen immediately.
+ //
+ // Leave CsConnectionStatus with a "failure" status code until the
+ // secure channel is set up. Other, routines simply return
+ // CsConnectionStatus as the state of the secure channel.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ ClientSession->CsLastAuthenticationTry.QuadPart = 0;
+ ClientSession->CsState = CS_DC_PICKED;
+
+ //
+ // On failure,
+ // Indicate that we've recently made the attempt to find a DC.
+ //
+
+ } else {
+ NtQuerySystemTime( &ClientSession->CsLastAuthenticationTry );
+ ClientSession->CsState = CS_IDLE;
+ ClientSession->CsConnectionStatus = Status;
+ }
+
+ NtQuerySystemTime( &ClientSession->CsLastDiscoveryTime );
+
+
+ //
+ // Tell the initiator that discover has completed.
+ //
+
+ ClientSession->CsDiscoveryFlags &= ~CS_DISCOVERY_IN_PROGRESS;
+
+ NlAssert( ClientSession->CsDiscoveryEvent != NULL );
+
+ if ( !SetEvent( ClientSession->CsDiscoveryEvent ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlDcDiscoveryMachine: %ws: SetEvent failed %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ GetLastError() ));
+ }
+
+
+ //
+ // If this was an async discovery,
+ // turn the timer off.
+ //
+
+ if ( ClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS ) {
+ ClientSession->CsDiscoveryFlags &= ~CS_DISCOVERY_ASYNCHRONOUS;
+ NlGlobalDcDiscoveryCount--;
+ if ( NlGlobalDcDiscoveryCount == 0 ) {
+ NlGlobalDcDiscoveryTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+ }
+
+ //
+ // We no longer care about the Client session
+ //
+ LOCK_TRUST_LIST();
+ NlUnrefClientSession( ClientSession );
+ UNLOCK_TRUST_LIST();
+ }
+
+
+ //
+ // Cleanup locally used resources.
+ //
+Ignore:
+
+ //
+ // free log request message.
+ //
+
+ if( SamLogonRequest != NULL ) {
+ NetpMemoryFree( SamLogonRequest );
+ }
+
+ //
+ // Unlock the crit sect and return.
+ //
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ return Status;
+
+}
+
+
+NTSTATUS
+NlDiscoverDc (
+ IN OUT PCLIENT_SESSION ClientSession,
+ IN DISCOVERY_TYPE DiscoveryType
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of a DC in a domain.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The trust list entry must be referenced by the caller.
+ The caller must be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession -- Client session structure whose DC is to be picked.
+ The Client Session structure must be marked for write.
+
+ DiscoveryType -- Indicate synchronous, Asynchronous, or rediscovery of a
+ "Dead domain".
+
+Return Value:
+
+ STATUS_SUCCESS - if DC was found.
+ STATUS_PENDING - Operation is still in progress
+ STATUS_NO_LOGON_SERVERS - if DC was not found.
+ STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
+ an account for this machine.
+
+--*/
+{
+ NTSTATUS Status;
+ HANDLE ResponseMailslotHandle = NULL;
+ CHAR ResponseMailslotName[PATHLEN+1];
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+ NlAssert( ClientSession->CsFlags & CS_WRITER );
+
+
+
+ //
+ // If this is a BDC discovering its own PDC,
+ // and we've already discovered the PDC
+ // (via NetGetDcName or the PDC has spontaneously told us its name),
+ // just use that name.
+ //
+ // If we're our own PDC,
+ // we must have just been demoted to a BDC and haven't found PDC yet,
+ // in that case rediscover.
+ //
+
+ if ( ClientSession->CsSecureChannelType == ServerSecureChannel &&
+ *NlGlobalUnicodePrimaryName != L'\0' &&
+ NlNameCompare( NlGlobalUnicodePrimaryName,
+ NlGlobalUnicodeComputerName,
+ NAMETYPE_COMPUTER) != 0 ) {
+
+
+ //
+ // Just set the PDC name in the Client Session structure.
+ //
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ wcscpy( ClientSession->CsUncServerName, NlGlobalUncPrimaryName );
+ ClientSession->CsLastAuthenticationTry.QuadPart = 0;
+ NtQuerySystemTime( &ClientSession->CsLastDiscoveryTime );
+ ClientSession->CsState = CS_DC_PICKED;
+
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+
+ //
+ // If this is a workstation,
+ // Create a mailslot for the DC's to respond to.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+ NET_API_STATUS NetStatus;
+
+ NlAssert( DiscoveryType == DT_Synchronous );
+ NetStatus = NetpLogonCreateRandomMailslot( ResponseMailslotName,
+ &ResponseMailslotHandle);
+
+ if ( NetStatus != NERR_Success ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDiscoverDc: cannot create temp mailslot %ld\n",
+ NetStatus ));
+
+ Status = NetpApiStatusToNtStatus( NetStatus );
+ goto Cleanup;
+ }
+
+ //
+ // If the mailslot timeout shouldn't be the default 5 seconds,
+ // set it to the right value.
+ //
+
+ if ( NlGlobalExpectedDialupDelayParameter != 0 ) {
+
+ if ( !SetMailslotInfo(
+ ResponseMailslotHandle,
+ DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES ) ) {
+
+ NetStatus = GetLastError();
+
+ NlPrint((NL_CRITICAL,
+ "NlDiscoverDc: cannot change temp mailslot timeout %ld\n",
+ NetStatus ));
+
+ Status = NetpApiStatusToNtStatus( NetStatus );
+ goto Cleanup;
+ }
+
+
+ }
+
+ } else {
+ lstrcpyA( ResponseMailslotName, NETLOGON_NT_MAILSLOT_A );
+ }
+
+
+
+ //
+ // Start discovery.
+ //
+
+ Status = NlDcDiscoveryMachine( ClientSession,
+ StartDiscovery,
+ NULL,
+ NULL,
+ ResponseMailslotName,
+ DiscoveryType );
+
+ if ( !NT_SUCCESS(Status) || DiscoveryType != DT_Synchronous ) {
+ goto Cleanup;
+ }
+
+
+ //
+ // If the discovery machine asked us to call back every DISCOVERY_PERIOD,
+ // loop doing exactly that.
+ //
+
+ if ( Status == STATUS_PENDING ) {
+
+ //
+ // Loop waiting.
+ //
+
+ for (;;) {
+
+ DWORD WaitStatus;
+
+ //
+ // On non-workstations,
+ // the main loop gets the mailslot responses.
+ // (So just do the timeout here).
+ //
+
+ if ( NlGlobalRole != RoleMemberWorkstation ) {
+
+ //
+ // Wait for DISOVERY_PERIOD.
+ //
+
+ WaitStatus =
+ WaitForSingleObject( ClientSession->CsDiscoveryEvent,
+ DISCOVERY_PERIOD + NlGlobalExpectedDialupDelayParameter*1000/MAX_DC_RETRIES );
+
+
+ if ( WaitStatus == 0 ) {
+
+ break;
+
+ } else if ( WaitStatus != WAIT_TIMEOUT ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDiscoverDc: wait error: %ld\n",
+ WaitStatus ));
+ Status = NetpApiStatusToNtStatus( WaitStatus );
+ goto Cleanup;
+ }
+
+ // Drop through to indicate timer expiration
+
+ //
+ // Workstations do the mailslot read directly.
+ //
+
+ } else {
+ CHAR ResponseBuffer[MAX_RANDOM_MAILSLOT_RESPONSE];
+ PNETLOGON_SAM_LOGON_RESPONSE SamLogonResponse;
+ DWORD SamLogonResponseSize;
+
+
+ //
+ // Read the response from the response mailslot
+ // (This mailslot is set up with a 5 second timeout).
+ //
+
+ if ( ReadFile( ResponseMailslotHandle,
+ ResponseBuffer,
+ sizeof(ResponseBuffer),
+ &SamLogonResponseSize,
+ NULL ) ) {
+ DWORD Version;
+ DWORD VersionFlags;
+
+ SamLogonResponse =
+ (PNETLOGON_SAM_LOGON_RESPONSE) ResponseBuffer;
+
+ //
+ // get message version.
+ //
+
+ Version = NetpLogonGetMessageVersion(
+ SamLogonResponse,
+ &SamLogonResponseSize,
+ &VersionFlags );
+
+ //
+ // Handle the incoming message.
+ //
+
+ Status = NlDcDiscoveryHandler ( SamLogonResponse,
+ SamLogonResponseSize,
+ NULL, // Transport name
+ Version );
+
+ if ( Status != STATUS_PENDING ) {
+ goto Cleanup;
+ }
+
+ //
+ // Ignore badly formed responses.
+ //
+
+ continue;
+
+
+ } else {
+ WaitStatus = GetLastError();
+
+ if ( WaitStatus != ERROR_SEM_TIMEOUT ) {
+ NlPrint((NL_CRITICAL,
+ "NlDiscoverDc: "
+ "cannot read response mailslot: %ld\n",
+ WaitStatus ));
+ Status = NetpApiStatusToNtStatus( WaitStatus );
+ goto Cleanup;
+ }
+
+ }
+
+ }
+
+
+ //
+ // If we reach here,
+ // DISCOVERY_PERIOD has expired.
+ //
+
+ Status = NlDcDiscoveryMachine( ClientSession,
+ DcTimerExpired,
+ NULL,
+ NULL,
+ ResponseMailslotName,
+ DiscoveryType );
+
+ if ( Status != STATUS_PENDING ) {
+ goto Cleanup;
+ }
+
+ }
+
+ //
+ // If someone else started the discovery,
+ // just wait for that discovery to finish.
+ //
+
+ } else {
+
+ NlWaitForSingleObject( "Client Session waiting for discovery",
+ ClientSession->CsDiscoveryEvent );
+
+ }
+
+
+ //
+ // Return the status to the caller.
+ //
+
+ if ( ClientSession->CsState == CS_IDLE ) {
+ Status = ClientSession->CsConnectionStatus;
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+Cleanup:
+ if ( ResponseMailslotHandle != NULL ) {
+ CloseHandle(ResponseMailslotHandle);
+ }
+
+ //
+ // If this is a workstation,
+ // get the trusted domain list from the discovered DC.
+ //
+
+ if ( NlGlobalRole == RoleMemberWorkstation && NT_SUCCESS(Status) ) {
+ NTSTATUS TempStatus;
+ LPWSTR TrustedDomainList;
+
+ TempStatus = NlGetTrustedDomainList (
+ ClientSession->CsUncServerName,
+ &TrustedDomainList );
+
+ if ( NT_SUCCESS( TempStatus ) ) {
+
+ NetpMemoryFree( TrustedDomainList );
+ }
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NlUpdateTrustListBySid (
+ IN PSID DomainId,
+ IN PUNICODE_STRING DomainName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ Update a single in-memory trust list entry to match the LSA.
+ Do async discovery on a domain.
+
+Arguments:
+
+ DomainId -- Domain Id of the domain to do the discovery for.
+
+ DomainName -- Specifies the DomainName of the domain. If this parameter
+ isn't specified, the LSA is queried for the name. If this parameter
+ is specified, the LSA is guaranteed to contain this domain.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PLIST_ENTRY ListEntry;
+ PCLIENT_SESSION ClientSession = NULL;
+ PUNICODE_STRING LocalDomainName;
+
+ LSAPR_HANDLE TrustedDomainHandle = NULL;
+ PLSAPR_TRUSTED_DOMAIN_INFO TrustedDomainName = NULL;
+
+
+ //
+ // If the domain name was passed in,
+ // there is no need to query the LSA for the name.
+ //
+
+ if ( DomainName != NULL ) {
+ LocalDomainName = DomainName;
+
+ //
+ // Determine if the TrustedDomain object exists in the LSA.
+ //
+
+ } else {
+
+ Status = LsarOpenTrustedDomain(
+ NlGlobalPolicyHandle,
+ DomainId,
+ TRUSTED_QUERY_DOMAIN_NAME,
+ &TrustedDomainHandle );
+
+ if ( NT_SUCCESS(Status) ) {
+
+ Status = LsarQueryInfoTrustedDomain(
+ TrustedDomainHandle,
+ TrustedDomainNameInformation,
+ &TrustedDomainName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlUpdateTrustListBySid: "
+ "cannot LsarQueryInfoTrustedDomain: %lx\n",
+ Status));
+ TrustedDomainName = NULL;
+ goto Cleanup;
+ }
+
+ LocalDomainName =
+ (PUNICODE_STRING)&TrustedDomainName->TrustedDomainNameInfo.Name;
+
+ } else {
+
+ LocalDomainName = NULL;
+
+ }
+
+ }
+
+ //
+ // Ensure the SID of the trusted domain isn't the domain sid of this
+ // machine.
+ //
+
+ if ( RtlEqualSid( DomainId, NlGlobalPrimaryDomainId )) {
+
+ LPWSTR AlertStrings[3];
+ WCHAR AlertDomainName[DNLEN+1];
+
+ //
+ // alert admin.
+ //
+
+
+ if ( LocalDomainName == NULL ||
+ LocalDomainName->Length > sizeof(AlertDomainName) ) {
+ AlertDomainName[0] = L'\0';
+ } else {
+ RtlCopyMemory( AlertDomainName, LocalDomainName->Buffer, LocalDomainName->Length );
+ AlertDomainName[ LocalDomainName->Length / sizeof(WCHAR) ] = L'\0';
+ }
+
+ AlertStrings[0] = NlGlobalUnicodeDomainName;
+ AlertStrings[1] = AlertDomainName;
+ AlertStrings[2] = NULL;
+
+ RaiseAlert( ALERT_NetLogonSidConflict,
+ AlertStrings );
+
+ //
+ // Save the info in the eventlog
+ //
+
+ NlpWriteEventlog(
+ ALERT_NetLogonSidConflict,
+ EVENTLOG_ERROR_TYPE,
+ DomainId,
+ RtlLengthSid( DomainId ),
+ AlertStrings,
+ 2 );
+
+ }
+
+
+ //
+ // Loop through the trust list finding the right entry.
+ //
+
+ LOCK_TRUST_LIST();
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
+
+ if ( RtlEqualSid( ClientSession->CsDomainId, DomainId ) ) {
+ break;
+ }
+
+ ClientSession = NULL;
+
+ }
+
+
+
+ //
+ // At this point,
+ // LocalDomainName is NULL if the trust relationship doesn't exist in LSA
+ // ClientSession is NULL if the trust relationship doesn't exist in memory
+ //
+
+ //
+ // If the Trust exists in neither place,
+ // ignore this request.
+ //
+
+ if ( LocalDomainName == NULL && ClientSession == NULL ) {
+ UNLOCK_TRUST_LIST();
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+
+
+
+ //
+ // If the trust exists in the LSA but not in memory,
+ // add the trust entry.
+ //
+
+ } else if ( LocalDomainName != NULL && ClientSession == NULL ) {
+
+ ClientSession = NlAllocateClientSession(
+ LocalDomainName,
+ DomainId,
+ TrustedDomainSecureChannel );
+
+ if (ClientSession == NULL) {
+ UNLOCK_TRUST_LIST();
+ Status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // Link this entry onto the tail of the TrustList.
+ //
+
+ InsertTailList( &NlGlobalTrustList, &ClientSession->CsNext );
+ NlGlobalTrustListLength ++;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlUpdateTrustListBySid: " FORMAT_LPWSTR
+ ": Added to local trust list\n",
+ ClientSession->CsDomainName.Buffer ));
+
+
+
+ //
+ // If the trust exists in memory but not in the LSA,
+ // delete the entry.
+ //
+
+ } else if ( LocalDomainName == NULL && ClientSession != NULL ) {
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlUpdateTrustListBySid: " FORMAT_LPWSTR
+ ": Deleted from local trust list\n",
+ ClientSession->CsDomainName.Buffer ));
+ NlFreeClientSession( ClientSession );
+ ClientSession = NULL;
+
+
+ //
+ // If the trust exists in both places,
+ // undo any pending deletion.
+ //
+
+ } else if ( LocalDomainName != NULL && ClientSession != NULL ) {
+
+ ClientSession->CsFlags &= ~CS_DELETE_ON_UNREF;
+ NlRefClientSession( ClientSession );
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlUpdateTrustListBySid: " FORMAT_LPWSTR
+ ": Already in trust list\n",
+ ClientSession->CsDomainName.Buffer ));
+
+ }
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // If we haven't discovered a DC for this domain,
+ // and we haven't tried discovery recently,
+ // start the discovery asynchronously
+ //
+
+ if ( ClientSession != NULL &&
+ ClientSession->CsState == CS_IDLE &&
+ NlTimeToReauthenticate( ClientSession ) ) {
+
+ //
+ // Only wait for 45 seconds. This routine is called by the netlogon main
+ // thread. Another thread may have the ClientSession locked and need the
+ // main thread to finish a discovery.
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ Status = STATUS_SUCCESS;
+ goto Cleanup;
+ }
+
+ Status = NlDiscoverDc ( ClientSession, DT_Asynchronous );
+ NlResetWriterClientSession( ClientSession );
+
+ if ( Status == STATUS_PENDING ) {
+ Status = STATUS_SUCCESS;
+ }
+ goto Cleanup;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Cleanup locally used resources.
+ //
+Cleanup:
+ if ( TrustedDomainName != NULL ) {
+ LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO(
+ TrustedDomainNameInformation,
+ TrustedDomainName );
+ }
+
+ if ( TrustedDomainHandle != NULL ) {
+ NTSTATUS LocalStatus;
+ LocalStatus = LsarClose( &TrustedDomainHandle );
+ NlAssert( NT_SUCCESS( LocalStatus ));
+ }
+
+ if ( ClientSession != NULL ) {
+ NlUnrefClientSession( ClientSession );
+ }
+
+ return Status;
+}
+
+
+
+VOID
+NlDcDiscoveryExpired (
+ IN BOOLEAN Exitting
+ )
+
+/*++
+
+Routine Description:
+
+ Handle expiration of the DC discovery timer.
+
+Arguments:
+
+ NONE
+
+Return Value:
+
+ Exitting: TRUE if the netlogon service is exitting
+
+--*/
+{
+ PLIST_ENTRY ListEntry;
+ PCLIENT_SESSION ClientSession;
+
+
+ NlAssert( NlGlobalRole != RoleMemberWorkstation );
+
+
+ LOCK_TRUST_LIST();
+
+ //
+ // Mark each entry to indicate we've not yet handled the timer expiration
+ //
+
+ if ( !Exitting ) {
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ ClientSession->CsFlags |= CS_HANDLE_TIMER;
+ }
+ }
+
+
+ //
+ // Loop thru the trust list handling timer expiration.
+ //
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ //
+ // If we've already done this entry,
+ // skip this entry.
+ //
+ if ( !Exitting ) {
+ if ( (ClientSession->CsFlags & CS_HANDLE_TIMER) == 0 ) {
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+ ClientSession->CsFlags &= ~CS_HANDLE_TIMER;
+ }
+
+
+ //
+ // If async discovery isn't going on,
+ // skip this entry.
+ //
+
+ if ((ClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS) == 0){
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+
+ //
+ // Call the discovery machine with the trust list unlocked.
+ //
+
+ UNLOCK_TRUST_LIST();
+
+ (VOID) NlDcDiscoveryMachine( ClientSession,
+ DcTimerExpired,
+ NULL,
+ NULL,
+ NETLOGON_NT_MAILSLOT_A,
+ TRUE );
+
+ //
+ // Since we dropped the trust list lock,
+ // we'll start the search from the front of the list.
+ //
+
+ LOCK_TRUST_LIST();
+
+ ListEntry = NlGlobalTrustList.Flink ;
+
+ }
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // Complete the asynchronous discover on the Global client session.
+ //
+
+ if ( NlGlobalClientSession != NULL &&
+ NlGlobalClientSession->CsDiscoveryFlags & CS_DISCOVERY_ASYNCHRONOUS ) {
+ (VOID) NlDcDiscoveryMachine( NlGlobalClientSession,
+ DcTimerExpired,
+ NULL,
+ NULL,
+ NETLOGON_NT_MAILSLOT_A,
+ TRUE );
+
+ }
+
+}
+
+
+NTSTATUS
+NlDcDiscoveryHandler (
+ IN PNETLOGON_SAM_LOGON_RESPONSE Message,
+ IN DWORD MessageSize,
+ IN LPWSTR TransportName,
+ IN DWORD Version
+ )
+
+/*++
+
+Routine Description:
+
+ Handle a mailslot response to a DC Discovery request.
+
+Arguments:
+
+ Message -- The response message
+
+ MessageSize -- The size of the message in bytes.
+
+ TransportName -- Name of the transport the messages arrived on.
+
+ Version -- version info of the message.
+
+Return Value:
+
+ STATUS_SUCCESS - if DC was found.
+ STATUS_PENDING - if discovery is still in progress and the caller should
+ call again in DISCOVERY_PERIOD with the DcTimerExpired action.
+
+ STATUS_NO_LOGON_SERVERS - if DC was not found.
+ STATUS_NO_TRUST_SAM_ACCOUNT - if DC was found but it does not have
+ an account for this machine.
+
+--*/
+{
+ NTSTATUS Status;
+ LPWSTR LocalServerName;
+ LPWSTR LocalUserName;
+ LPWSTR LocalDomainName;
+ PCHAR Where;
+ PCLIENT_SESSION ClientSession = NULL;
+ UNICODE_STRING DomainNameString;
+
+
+ if ( Version != LMNT_MESSAGE ) {
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: version not valid.\n"));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+
+ //
+ // Ignore messages from paused DCs.
+ //
+
+ if ( Message->Opcode != LOGON_SAM_LOGON_RESPONSE &&
+ Message->Opcode != LOGON_SAM_USER_UNKNOWN ) {
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+ //
+ // Pick up the name of the server that responded.
+ //
+
+ Where = (PCHAR) &Message->UnicodeLogonServer;
+ if ( !NetpLogonGetUnicodeString(
+ Message,
+ MessageSize,
+ &Where,
+ sizeof(Message->UnicodeLogonServer),
+ &LocalServerName ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: server name not formatted right\n"));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+ //
+ // Pick up the name of the account the response is for.
+ //
+
+ if ( !NetpLogonGetUnicodeString(
+ Message,
+ MessageSize,
+ &Where,
+ sizeof(Message->UnicodeUserName ),
+ &LocalUserName ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: User name not formatted right\n"));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+ //
+ // If the domain name is not in the message,
+ // ignore the message.
+ //
+
+ if( Where >= ((PCHAR)Message + MessageSize) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: "
+ "Response from %ws doesn't contain domain name\n",
+ LocalServerName ));
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+
+ LocalDomainName = NlGlobalUnicodeDomainName;
+
+ NlPrint((NL_SESSION_SETUP,
+ "NlDcDiscoveryHandler: "
+ "Workstation: Assuming %ws is in domain %ws\n",
+ LocalServerName,
+ LocalDomainName ));
+
+ } else {
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+
+ //
+ // Pick up the name of the domain the response is for.
+ //
+
+ } else {
+ if ( !NetpLogonGetUnicodeString(
+ Message,
+ MessageSize,
+ &Where,
+ sizeof(Message->UnicodeDomainName ),
+ &LocalDomainName ) ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: "
+ " Domain name from %ws not formatted right\n",
+ LocalServerName ));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+ }
+
+ //
+ // On the PDC or BDC,
+ // find the Client session for the domain.
+ // On workstations,
+ // find the primary domain client session.
+ //
+
+
+ RtlInitUnicodeString( &DomainNameString, LocalDomainName );
+
+ ClientSession = NlFindNamedClientSession( &DomainNameString );
+
+ if ( ClientSession == NULL ) {
+ NlPrint((NL_SESSION_SETUP,
+ "NlDcDiscoveryHandler: "
+ " Domain name %ws from %ws has no client session.\n",
+ LocalDomainName,
+ LocalServerName ));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+
+
+
+ //
+ // Ensure the response is for the correct account.
+ //
+
+ if ( NlNameCompare( ClientSession->CsAccountName,
+ LocalUserName,
+ NAMETYPE_USER) != 0 ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlDcDiscoveryHandler: "
+ " Domain name %ws from %ws has invalid account name %ws.\n",
+ LocalDomainName,
+ LocalServerName,
+ LocalUserName ));
+ Status = STATUS_PENDING;
+ goto Cleanup;
+ }
+
+ //
+ // Finally, tell the DC discovery machine what happened.
+ //
+
+
+#ifdef DONT_REQUIRE_ACCOUNT
+ IF_DEBUG( DONT_REQUIRE_ACCOUNT ) {
+ Message->Opcode = LOGON_SAM_LOGON_RESPONSE;
+ }
+#endif // DONT_REQUIRE_ACCOUNT
+
+ Status = NlDcDiscoveryMachine(
+ ClientSession,
+ (Message->Opcode == LOGON_SAM_LOGON_RESPONSE) ?
+ DcFoundMessage :
+ DcNotFoundMessage,
+ LocalServerName,
+ TransportName,
+ NULL,
+ FALSE );
+
+
+ //
+ // Free any locally used resources.
+ //
+Cleanup:
+ if ( ClientSession != NULL ) {
+ NlUnrefClientSession( ClientSession );
+ }
+
+ return Status;
+}
+
+
+
+
+
+NTSTATUS
+NlCaptureServerClientSession (
+ IN PCLIENT_SESSION ClientSession,
+ OUT WCHAR UncServerName[UNCLEN+1]
+ )
+/*++
+
+Routine Description:
+
+ Captures a copy of the UNC server name for the client session.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The trust list entry must be referenced by the caller.
+ The caller must NOT be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession - Specifies a pointer to the trust list entry to use.
+
+ UncServerName - Returns the UNC name of the server for this client session.
+ If there is none, an empty string is returned.
+
+Return Value:
+
+ STATUS_SUCCESS - Server name was successfully copied.
+
+ Otherwise - Status of the secure channel
+--*/
+{
+ NTSTATUS Status;
+
+ NlAssert( ClientSession->CsReferenceCount > 0 );
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+ if ( ClientSession->CsState == CS_IDLE ) {
+ Status = ClientSession->CsConnectionStatus;
+ *UncServerName = L'\0';
+ } else {
+ Status = STATUS_SUCCESS;
+ wcscpy( UncServerName, ClientSession->CsUncServerName );
+ }
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ return Status;
+}
+
+
+PCLIENT_SESSION
+NlPickDomainWithAccount (
+ IN LPWSTR AccountName,
+ IN ULONG AllowableAccountControlBits
+ )
+
+/*++
+
+Routine Description:
+
+ Get the name of a trusted domain that defines a particular account.
+
+Arguments:
+
+ AccountName - Name of our user account to find.
+
+ AllowableAccountControlBits - A mask of allowable SAM account types that
+ are allowed to satisfy this request.
+
+Return Value:
+
+ Pointer to referenced ClientSession structure describing the secure channel
+ to the domain containing the account.
+
+ The returned ClientSession is referenced and should be unreferenced
+ using NlUnrefClientSession.
+
+ NULL - DC was not found.
+
+--*/
+{
+ NTSTATUS Status;
+ NET_API_STATUS NetStatus;
+
+ PCLIENT_SESSION ClientSession;
+ DWORD i;
+ PLIST_ENTRY ListEntry;
+ DWORD ResponsesPending;
+
+ NETLOGON_SAM_LOGON_REQUEST SamLogonRequest;
+ PCHAR Where;
+
+ HANDLE ResponseMailslotHandle = NULL;
+ CHAR ResponseMailslotName[PATHLEN+1];
+ DWORD Opcode;
+ DWORD DomainSidSize;
+
+
+ //
+ // Define a local list of trusted domains.
+ //
+
+ ULONG LocalTrustListLength;
+ ULONG Index;
+ struct _LOCAL_TRUST_LIST {
+
+ //
+ // TRUE if ALL processing is finished on this trusted domain.
+ //
+
+ BOOLEAN Done;
+
+ //
+ // TRUE if at least one discovery has been done on this trusted domain.
+ //
+
+ BOOLEAN DiscoveryDone;
+
+ //
+ // TRUE if discovery is in progress on this trusted domain.
+ //
+
+ BOOLEAN DoingDiscovery;
+
+ //
+ // Number of times we need to repeat the current domain discovery
+ // or finduser datagram for this current domain.
+ //
+
+ DWORD RetriesLeft;
+
+ //
+ // Pointer to referenced ClientSession structure for the domain.
+ //
+
+ PCLIENT_SESSION ClientSession;
+
+ //
+ // Server name for the domain.
+ //
+
+ WCHAR UncServerName[UNCLEN+1];
+
+ //
+ // Second Server name for the domain.
+ //
+
+ WCHAR UncServerName2[UNCLEN+1];
+
+ } *LocalTrustList = NULL;
+
+ //
+ // Be verbose.
+ //
+
+ NlPrint((NL_LOGON,
+ "NlPickDomainWithAccount: %ws: Algorithm entered.\n",
+ AccountName ));
+
+
+ //
+ // Don't allow bogus user names.
+ //
+ // NlReadSamLogonResponse uses NlNameCompare to ensure the response message
+ // is for this user. Since NlNameCompare canonicalizes both names, it will
+ // reject invalid syntax. That causes NlReadSamLogonResponse to ignore ALL
+ // response messages, thus causing multiple retries before failing. We'd
+ // rather fail here.
+ //
+
+ if ( !NetpIsUserNameValid( AccountName ) ){
+ NlPrint((NL_CRITICAL,
+ "NlPickDomainWithAccount: Username " FORMAT_LPWSTR
+ " is invalid syntax.\n",
+ AccountName ));
+ return NULL;
+ }
+
+ //
+ // Allocate a local list of trusted domains.
+ //
+
+ LOCK_TRUST_LIST();
+ LocalTrustListLength = NlGlobalTrustListLength;
+
+ LocalTrustList = (struct _LOCAL_TRUST_LIST *) NetpMemoryAllocate(
+ LocalTrustListLength * sizeof(struct _LOCAL_TRUST_LIST));
+
+ if ( LocalTrustList == NULL ) {
+ UNLOCK_TRUST_LIST();
+ ClientSession = NULL;
+ goto Cleanup;
+ }
+
+
+ //
+ // Build a local list of trusted domains we know DCs for.
+ //
+
+
+ Index = 0;
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
+
+ //
+ // Add this Client Session to the list.
+ //
+
+ NlRefClientSession( ClientSession );
+
+ LocalTrustList[Index].ClientSession = ClientSession;
+ Index++;
+ }
+
+ UNLOCK_TRUST_LIST();
+
+
+ //
+ // Capture the name of the server for each client session.
+ //
+
+ for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
+
+ (VOID) NlCaptureServerClientSession(
+ LocalTrustList[Index].ClientSession,
+ LocalTrustList[Index].UncServerName );
+
+ *LocalTrustList[Index].UncServerName2 = L'\0';
+
+ //
+ // We're not done yet.
+ //
+
+ LocalTrustList[Index].Done = FALSE;
+
+ //
+ // If there is no DC discovered for this domain,
+ // don't try very hard to discover one.
+ // (Indeed, just one discovery datagram is all we need.)
+ //
+
+ if ( *LocalTrustList[Index].UncServerName == L'\0' ) {
+ LocalTrustList[Index].RetriesLeft = 1;
+ LocalTrustList[Index].DoingDiscovery = TRUE;
+ LocalTrustList[Index].DiscoveryDone = TRUE;
+
+ //
+ // If we know the DC for this domain,
+ // try sending to the current DC before discovering a new one.
+ //
+ } else {
+ LocalTrustList[Index].RetriesLeft = 3;
+ LocalTrustList[Index].DoingDiscovery = FALSE;
+ LocalTrustList[Index].DiscoveryDone = FALSE;
+ }
+
+ }
+
+ //
+ // Create a mailslot for the DC's to respond to.
+ //
+
+ if (NetStatus = NetpLogonCreateRandomMailslot( ResponseMailslotName,
+ &ResponseMailslotHandle)){
+ NlPrint((NL_CRITICAL,
+ "NlPickDomainWithAccount: cannot create temp mailslot %ld\n",
+ NetStatus ));
+ ClientSession = NULL;
+ goto Cleanup;
+ }
+
+ //
+ // Build the query message.
+ //
+
+ SamLogonRequest.Opcode = LOGON_SAM_LOGON_REQUEST;
+ SamLogonRequest.RequestCount = 0;
+
+ Where = (PCHAR) &SamLogonRequest.UnicodeComputerName;
+
+ NetpLogonPutUnicodeString(
+ NlGlobalUnicodeComputerName,
+ sizeof(SamLogonRequest.UnicodeComputerName),
+ &Where );
+
+ NetpLogonPutUnicodeString(
+ AccountName,
+ sizeof(SamLogonRequest.UnicodeUserName),
+ &Where );
+
+ NetpLogonPutOemString(
+ ResponseMailslotName,
+ sizeof(SamLogonRequest.MailslotName),
+ &Where );
+
+ NetpLogonPutBytes(
+ &AllowableAccountControlBits,
+ sizeof(SamLogonRequest.AllowableAccountControlBits),
+ &Where );
+
+ //
+ // place domain NULL SID in the message.
+ //
+
+ DomainSidSize = 0;
+ NetpLogonPutBytes( &DomainSidSize, sizeof(DomainSidSize), &Where );
+
+ NetpLogonPutNtToken( &Where );
+
+
+ //
+ // Try multiple times to get a response from each DC.
+ //
+
+ for (;; ) {
+
+ //
+ // Send the mailslot message to each domain that has not yet responded.
+ //
+
+ ResponsesPending = 0;
+
+ for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
+
+ //
+ // If this domain has already responded, ignore it.
+ //
+
+ if ( LocalTrustList[Index].Done ) {
+ continue;
+ }
+
+ //
+ // If we don't currently know the DC name for this domain,
+ // capture any that's been discovered since we started the algorithm.
+ //
+
+ ClientSession = LocalTrustList[Index].ClientSession;
+ if ( *LocalTrustList[Index].UncServerName == L'\0' ) {
+
+ (VOID) NlCaptureServerClientSession(
+ LocalTrustList[Index].ClientSession,
+ LocalTrustList[Index].UncServerName );
+
+ //
+ // Handle the case where we've now discovered a DC.
+ //
+
+ if ( *LocalTrustList[Index].UncServerName != L'\0' ) {
+
+ NlPrint((NL_LOGON,
+ "NlPickDomainWithAccount: %ws: Noticed domain %wZ has discovered a new DC %ws.\n",
+ AccountName,
+ &ClientSession->CsDomainName,
+ LocalTrustList[Index].UncServerName ));
+
+ //
+ // If we did the discovery,
+ //
+
+ if ( LocalTrustList[Index].DoingDiscovery ) {
+ LocalTrustList[Index].DoingDiscovery = FALSE;
+ LocalTrustList[Index].RetriesLeft = 3;
+ }
+ }
+ }
+
+ //
+ // If we're done retrying what we were doing,
+ // try something else.
+ //
+
+ if ( LocalTrustList[Index].RetriesLeft == 0 ) {
+ if ( LocalTrustList[Index].DiscoveryDone ) {
+ LocalTrustList[Index].Done = TRUE;
+
+ NlPrint((NL_LOGON,
+ "NlPickDomainWithAccount: %ws: Can't find DC for domain %wZ (ignore this domain).\n",
+ AccountName,
+ &ClientSession->CsDomainName ));
+
+ continue;
+ } else {
+
+ //
+ // Save the previous DC name since it might just be
+ // very slow in responding. We'll want to be able
+ // to recognize responses from the previous DC.
+ //
+
+ wcscpy( LocalTrustList[Index].UncServerName2,
+ LocalTrustList[Index].UncServerName );
+
+ *LocalTrustList[Index].UncServerName = L'\0';
+
+ LocalTrustList[Index].DoingDiscovery = TRUE;
+ LocalTrustList[Index].RetriesLeft = 3;
+ LocalTrustList[Index].DiscoveryDone = TRUE;
+ }
+ }
+
+ //
+ // Indicate we're trying something.
+ //
+
+ LocalTrustList[Index].RetriesLeft --;
+
+ ResponsesPending ++;
+
+ //
+ // If its time to discover a DC in the domain,
+ // do it.
+ //
+
+
+ if ( LocalTrustList[Index].DoingDiscovery ) {
+
+ //
+ // Discover a new server
+ //
+
+ if ( NlTimeoutSetWriterClientSession( ClientSession, 10*1000 ) ) {
+
+ //
+ // Only tear down an existing secure channel once.
+ //
+
+ if ( LocalTrustList[Index].RetriesLeft == 3 ) {
+ NlSetStatusClientSession( ClientSession,
+ STATUS_NO_LOGON_SERVERS );
+ }
+
+ //
+ // We can't afford to wait so only send a single
+ // discovery datagram.
+ //
+ // Since the discovery is asysnchronous,
+ // it is very unlikely that we'll actually
+ // be able to query the DC on this pass. As such, this
+ // entire iteration of the loop is typically
+ // dedicated to discovery.
+ //
+
+ (VOID) NlDiscoverDc( ClientSession, DT_DeadDomain );
+
+ NlResetWriterClientSession( ClientSession );
+
+ }
+
+ }
+
+ //
+ // Send the message to a DC for the domain.
+ //
+
+ if ( *LocalTrustList[Index].UncServerName != L'\0' ) {
+ CHAR OemServerName[CNLEN+1];
+
+ // Skip over \\ in unc server name
+ NetpCopyWStrToStr( OemServerName,
+ LocalTrustList[Index].UncServerName+2 );
+
+ Status = NlBrowserSendDatagram(
+ OemServerName,
+ ClientSession->CsTransportName,
+ NETLOGON_NT_MAILSLOT_A,
+ &SamLogonRequest,
+ Where - (PCHAR)(&SamLogonRequest) );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlPrint((NL_CRITICAL,
+ "NlPickDomainWithAccount: "
+ " cannot write netlogon mailslot: 0x%lx\n",
+ Status));
+ ClientSession = NULL;
+ goto Cleanup;
+ }
+ }
+
+ }
+
+ //
+ // If all of the domains are done,
+ // leave the loop.
+ //
+
+ if ( ResponsesPending == 0 ) {
+ break;
+ }
+
+ //
+ // See if any DC responds.
+ //
+
+ while ( ResponsesPending > 0 ) {
+ LPWSTR UncLogonServer;
+
+ //
+ // If we timed out,
+ // break out of the loop.
+ //
+
+ if ( !NlReadSamLogonResponse( ResponseMailslotHandle,
+ AccountName,
+ &Opcode,
+ &UncLogonServer ) ) {
+ break;
+ }
+
+ //
+ // Find out which DC responded
+ //
+ // ?? Optimize by converting to uppercase OEM outside of loop
+
+ for ( Index = 0; Index < LocalTrustListLength; Index ++ ) {
+
+ ClientSession = LocalTrustList[Index].ClientSession;
+
+ if ( (*LocalTrustList[Index].UncServerName != L'\0' &&
+ NlNameCompare( LocalTrustList[Index].UncServerName+2,
+ UncLogonServer+2,
+ NAMETYPE_COMPUTER ) == 0 ) ||
+ (*LocalTrustList[Index].UncServerName2 != L'\0' &&
+ NlNameCompare( LocalTrustList[Index].UncServerName2+2,
+ UncLogonServer+2,
+ NAMETYPE_COMPUTER ) == 0 ) ) {
+ break;
+ }
+ }
+
+ NetpMemoryFree( UncLogonServer );
+
+ //
+ // If the response wasn't for one of the DCs we sent to,
+ // ignore the response.
+ //
+
+ if ( Index >= LocalTrustListLength ) {
+ NlPrint((NL_CRITICAL,
+ "NlPickDomainWithAccount: Server %ws responded though we didn't query it for account %ws.",
+ UncLogonServer,
+ AccountName ));
+ continue;
+ }
+
+ //
+ // If the DC recognizes our account,
+ // we've successfully found the DC.
+ //
+
+ if ( Opcode == LOGON_SAM_LOGON_RESPONSE ) {
+ NlPrint((NL_LOGON,
+ "NlPickDomainWithAccount: "
+ "%wZ has account " FORMAT_LPWSTR "\n",
+ &ClientSession->CsDomainName,
+ AccountName ));
+ goto Cleanup;
+ }
+
+ //
+ // If this DC has already responded once,
+ // ignore the response,
+ //
+
+ if ( LocalTrustList[Index].Done ) {
+ continue;
+ }
+
+ //
+ // Mark another DC as having responded negatively.
+ //
+
+ NlPrint((NL_CRITICAL,
+ "NlPickDomainWithAccount: "
+ "%wZ responded negatively for account "
+ FORMAT_LPWSTR " 0x%x\n",
+ &ClientSession->CsDomainName,
+ AccountName,
+ Opcode ));
+
+ LocalTrustList[Index].Done = TRUE;
+ ResponsesPending --;
+
+ }
+ }
+
+ //
+ // No DC has the specified account.
+ //
+
+ ClientSession = NULL;
+
+ //
+ // Cleanup locally used resources.
+ //
+
+Cleanup:
+ if ( ResponseMailslotHandle != NULL ) {
+ CloseHandle(ResponseMailslotHandle);
+ }
+
+
+ //
+ // Unreference each client session structure and free the local trust list.
+ // (Keep the returned ClientSession referenced).
+ //
+
+ if ( LocalTrustList != NULL ) {
+
+ for (i=0; i<LocalTrustListLength; i++ ) {
+ if ( ClientSession != LocalTrustList[i].ClientSession ) {
+ NlUnrefClientSession( LocalTrustList[i].ClientSession );
+ }
+ }
+
+ NetpMemoryFree(LocalTrustList);
+ }
+
+ return ClientSession;
+}
+
+
+NTSTATUS
+NlStartApiClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN BOOLEAN QuickApiCall
+ )
+/*++
+
+Routine Description:
+
+ Enable the timer for timing out an API call on the secure channel.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The caller must be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+
+ QuickApiCall - True if this API call MUST finish in less than 45 seconds
+ and will in reality finish in less than 15 seconds unless something
+ is terribly wrong.
+
+Return Value:
+
+ Status of the RPC binding to the server
+
+--*/
+{
+ NTSTATUS Status;
+ BOOLEAN BindingHandleCached;
+ LARGE_INTEGER TimeNow;
+
+ //
+ // Save the current time.
+ // Start the timer on the API call.
+ //
+
+ LOCK_TRUST_LIST();
+ NtQuerySystemTime( &TimeNow );
+ ClientSession->CsApiTimer.StartTime = TimeNow;
+ ClientSession->CsApiTimer.Period =
+ QuickApiCall ? NlGlobalShortApiCallPeriod : LONG_API_CALL_PERIOD;
+
+ //
+ // If the global timer isn't running,
+ // start it and tell the main thread that I've changed a timer.
+ //
+
+ if ( NlGlobalBindingHandleCount == 0 ) {
+
+ if ( NlGlobalApiTimer.Period != NlGlobalShortApiCallPeriod ) {
+
+ NlGlobalApiTimer.Period = NlGlobalShortApiCallPeriod;
+ NlGlobalApiTimer.StartTime = TimeNow;
+
+ if ( !SetEvent( NlGlobalTimerEvent ) ) {
+ NlPrint(( NL_CRITICAL,
+ "NlStartApiClientSession: %ws: SetEvent failed %ld\n",
+ ClientSession->CsDomainName.Buffer,
+ GetLastError() ));
+ }
+ }
+ }
+
+
+ //
+ // Remember if the binding handle is cached, then mark it as cached.
+ //
+
+ BindingHandleCached = (ClientSession->CsFlags & CS_BINDING_CACHED) != 0;
+ ClientSession->CsFlags |= CS_BINDING_CACHED;
+
+
+ //
+ // Count the number of concurrent binding handles cached
+ //
+
+ if ( !BindingHandleCached ) {
+ NlGlobalBindingHandleCount ++;
+ }
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // If the binding handle isn't already cached,
+ // cache it now.
+ //
+
+ if ( !BindingHandleCached ) {
+
+ NlPrint((NL_SESSION_MORE,
+ "NlStartApiClientSession: %wZ: Bind to server " FORMAT_LPWSTR ".\n",
+ &ClientSession->CsDomainName,
+ ClientSession->CsUncServerName ));
+ NlAssert( ClientSession->CsState != CS_IDLE );
+ Status = NlBindingAddServerToCache ( ClientSession->CsUncServerName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ LOCK_TRUST_LIST();
+ ClientSession->CsFlags &= ~CS_BINDING_CACHED;
+ NlGlobalBindingHandleCount --;
+ UNLOCK_TRUST_LIST();
+ }
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ return Status;
+
+}
+
+
+BOOLEAN
+NlFinishApiClientSession(
+ IN PCLIENT_SESSION ClientSession,
+ IN BOOLEAN OkToKillSession
+ )
+/*++
+
+Routine Description:
+
+ Disable the timer for timing out the API call.
+
+ Also, determine if it is time to pick a new DC since the current DC is
+ reponding so poorly. The decision is made from the number of
+ timeouts that happened during the last reauthentication time. If
+ timeoutcount is more than the limit, it sets the connection status
+ to CS_IDLE so that new DC will be picked up and new session will be
+ established.
+
+ On Entry,
+ The trust list must NOT be locked.
+ The caller must be a writer of the trust list entry.
+
+Arguments:
+
+ ClientSession - Structure used to define the session.
+
+ OkToKillSession - TRUE if it's OK to actually drop the secure channel.
+ Otherwise, this routine will simply return FALSE upon timeout and
+ depend on the caller to drop the secure channel.
+
+Return Value:
+
+ TRUE - API finished normally
+ FALSE - API timed out AND the ClientSession structure was torn down.
+ The caller shouldn't use the ClientSession structure without first
+ setting up another session. FALSE will only be return for a "quick"
+ API call.
+
+ FALSE does not imply that the API call failed. It should only be used
+ as an indication that the secure channel was torn down.
+
+--*/
+{
+ BOOLEAN SessionOk = TRUE;
+ TIMER ApiTimer;
+
+ //
+ // Grab a copy of the ApiTimer.
+ //
+ // Only a copy is needed and we don't want to keep the trust list locked
+ // while locking NlGlobalDcDiscoveryCritSect (wrong locking order) nor while
+ // freeing the session.
+ //
+
+ LOCK_TRUST_LIST();
+ ApiTimer = ClientSession->CsApiTimer;
+
+ //
+ // Turn off the timer for this API call.
+ //
+
+ ClientSession->CsApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+
+ UNLOCK_TRUST_LIST();
+
+
+
+ //
+ // If this was a "quick" API call,
+ // and the API took too long,
+ // increment the count of times it timed out.
+ //
+
+ if ( ApiTimer.Period == NlGlobalShortApiCallPeriod ) {
+ if( NlTimeHasElapsed(
+ ApiTimer.StartTime,
+ ( ClientSession->CsSecureChannelType ==
+ WorkstationSecureChannel ?
+ MAX_WKSTA_API_TIMEOUT :
+ MAX_DC_API_TIMEOUT) + NlGlobalExpectedDialupDelayParameter*1000 ) ) {
+
+ //
+ // API timeout.
+ //
+
+ ClientSession->CsTimeoutCount++;
+
+ NlPrint((NL_CRITICAL,
+ "NlFinishApiClientSession: "
+ "timeout call to " FORMAT_LPWSTR ". Count: %lu \n",
+ ClientSession->CsUncServerName,
+ ClientSession->CsTimeoutCount));
+ }
+
+ //
+ // did we hit the limit ?
+ //
+
+ if( ClientSession->CsTimeoutCount >=
+ (DWORD)( ClientSession->CsSecureChannelType ==
+ WorkstationSecureChannel ?
+ MAX_WKSTA_TIMEOUT_COUNT :
+ MAX_DC_TIMEOUT_COUNT ) ) {
+
+ BOOL IsTimeHasElapsed;
+
+ //
+ // block CsLastAuthenticationTry access
+ //
+
+ EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ IsTimeHasElapsed =
+ NlTimeHasElapsed(
+ ClientSession->CsLastAuthenticationTry,
+ ( ClientSession->CsSecureChannelType ==
+ WorkstationSecureChannel ?
+ MAX_WKSTA_REAUTHENTICATION_WAIT :
+ MAX_DC_REAUTHENTICATION_WAIT) );
+
+ LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
+
+ if( IsTimeHasElapsed ) {
+
+ NlPrint((NL_CRITICAL,
+ "NlFinishApiClientSession: "
+ "dropping the session to " FORMAT_LPWSTR "\n",
+ ClientSession->CsUncServerName ));
+
+ //
+ // timeoutcount limit exceeded and it is time to reauth.
+ //
+
+ SessionOk = FALSE;
+
+ //
+ // Only drop the secure channel if the caller requested it.
+ //
+
+ if ( OkToKillSession ) {
+ NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
+
+ //
+ // Start asynchronous DC discovery if this is not a workstation.
+ //
+
+ if ( NlGlobalRole != RoleMemberWorkstation ) {
+ (VOID) NlDiscoverDc( ClientSession, DT_Asynchronous );
+ }
+ }
+
+ }
+ }
+ }
+
+ return SessionOk;
+}
+
+
+
+BOOLEAN
+NlTimeoutOneApiClientSession (
+ PCLIENT_SESSION ClientSession
+ )
+
+/*++
+
+Routine Description:
+
+ Timeout any API calls active specified client session structure
+
+Arguments:
+
+ ClientSession: Pointer to client session to time out
+
+ Enter with global trust list locked.
+
+Return Value:
+
+ TRUE - iff this routine temporarily dropped the global trust list lock.
+
+--*/
+{
+#define SHARE_TO_KILL L"\\IPC$"
+#define SHARE_TO_KILL_LENGTH 5
+
+ NET_API_STATUS NetStatus;
+ WCHAR ShareToKill[UNCLEN+SHARE_TO_KILL_LENGTH+1];
+ WCHAR UncServerName[UNCLEN+1];
+ BOOLEAN TrustListUnlocked = FALSE;
+
+ //
+ // Ignore non-existent sessions.
+ //
+
+ if ( ClientSession == NULL ) {
+ return FALSE;
+ }
+
+ //
+ // If an API call is in progress and has taken too long,
+ // Timeout the API call.
+ //
+
+ if ( NlTimeHasElapsed( ClientSession->CsApiTimer.StartTime,
+ ClientSession->CsApiTimer.Period ) ) {
+
+
+ //
+ // Save the server name but drop all our locks.
+ //
+
+ NlRefClientSession( ClientSession );
+ UNLOCK_TRUST_LIST();
+ (VOID) NlCaptureServerClientSession( ClientSession, ShareToKill );
+ NlUnrefClientSession( ClientSession );
+ TrustListUnlocked = TRUE;
+
+ //
+ // Now that we've unlocked the trust list,
+ // Drop the session to the server we've identified.
+ //
+
+ wcscat( ShareToKill, SHARE_TO_KILL );
+
+ NlPrint(( NL_CRITICAL,
+ "NlTimeoutApiClientSession: Start NetUseDel on "
+ FORMAT_LPWSTR "\n",
+ ShareToKill ));
+
+ IF_DEBUG( INHIBIT_CANCEL ) {
+ NlPrint(( NL_INHIBIT_CANCEL,
+ "NlimeoutApiClientSession: NetUseDel bypassed due to "
+ "INHIBIT_CANCEL Dbflag on " FORMAT_LPWSTR "\n",
+ ShareToKill ));
+ } else {
+ NetStatus = NetUseDel( NULL, ShareToKill, USE_LOTS_OF_FORCE );
+ }
+
+
+ NlPrint(( NL_CRITICAL,
+ "NlTimeoutApiClientSession: Completed NetUseDel on "
+ FORMAT_LPWSTR " (%ld)\n",
+ ShareToKill,
+ NetStatus ));
+
+
+ //
+ // If we have an RPC binding handle cached,
+ // and it has outlived its usefulness,
+ // purge it from the cache.
+ //
+
+ } else if ( (ClientSession->CsFlags & CS_BINDING_CACHED) != 0 &&
+ NlTimeHasElapsed( ClientSession->CsApiTimer.StartTime,
+ BINDING_CACHE_PERIOD ) ) {
+
+
+ //
+ // We must be a writer of the Client Session to unbind the RPC binding
+ // handle.
+ //
+ // Don't wait to become the writer because:
+ // A) We've violated the locking order by trying to become the writer
+ // with the trust list locked.
+ // B) The writer might be doing a long API call like replication and
+ // we're not willing to wait.
+ //
+
+ NlRefClientSession( ClientSession );
+ if ( NlTimeoutSetWriterClientSession( ClientSession, 0 ) ) {
+
+ //
+ // Indicate the handle is no longer cached.
+ //
+
+ ClientSession->CsFlags &= ~CS_BINDING_CACHED;
+ NlGlobalBindingHandleCount --;
+
+ //
+ // Save the server name but drop all our locks.
+ //
+
+ UNLOCK_TRUST_LIST();
+ (VOID) NlCaptureServerClientSession( ClientSession, UncServerName );
+ TrustListUnlocked = TRUE;
+
+
+ //
+ // Unbind this server.
+ //
+
+ NlPrint((NL_SESSION_MORE,
+ "NlTimeoutApiClientSession: %wZ: Unbind from server " FORMAT_LPWSTR ".\n",
+ &ClientSession->CsDomainName,
+ UncServerName ));
+ (VOID) NlBindingRemoveServerFromCache( UncServerName );
+
+ //
+ // Done being writer of the client session.
+ //
+
+ NlResetWriterClientSession( ClientSession );
+ }
+ NlUnrefClientSession( ClientSession );
+ }
+
+ if ( TrustListUnlocked ) {
+ LOCK_TRUST_LIST();
+ }
+ return TrustListUnlocked;
+}
+
+
+VOID
+NlTimeoutApiClientSession (
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ Timeout any API calls active on any of the client session structures
+
+Arguments:
+
+ NONE.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ PCLIENT_SESSION ClientSession;
+ PLIST_ENTRY ListEntry;
+
+ //
+ // If there are no API calls outstanding,
+ // just reset the global timer.
+ //
+
+ NlPrint(( NL_SESSION_MORE, "NlTimeoutApiClientSession Called\n"));
+
+ LOCK_TRUST_LIST();
+ if ( NlGlobalBindingHandleCount == 0 ) {
+ NlGlobalApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
+
+
+ //
+ // If there are API calls outstanding,
+ // Loop through the trust list making a list of Servers to kill
+ //
+
+ } else {
+
+
+ //
+ // Mark each trust list entry indicating it needs to be handled
+ //
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ ClientSession->CsFlags |= CS_HANDLE_API_TIMER;
+ }
+
+
+ //
+ // Loop thru the trust list handling API timeout
+ //
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ) {
+
+ ClientSession = CONTAINING_RECORD( ListEntry,
+ CLIENT_SESSION,
+ CsNext );
+
+ //
+ // If we've already done this entry,
+ // skip this entry.
+ //
+
+ if ( (ClientSession->CsFlags & CS_HANDLE_API_TIMER) == 0 ) {
+ ListEntry = ListEntry->Flink;
+ continue;
+ }
+ ClientSession->CsFlags &= ~CS_HANDLE_API_TIMER;
+
+
+ //
+ // Handle timing out the API call and the RPC binding handle.
+ //
+ // If the routine had to drop the TrustList crit sect,
+ // start at the very beginning of the list.
+
+ if ( NlTimeoutOneApiClientSession ( ClientSession ) ) {
+ ListEntry = NlGlobalTrustList.Flink;
+ } else {
+ ListEntry = ListEntry->Flink;
+ }
+
+ }
+
+ //
+ // Do the global client session, too.
+ //
+
+ (VOID) NlTimeoutOneApiClientSession ( NlGlobalClientSession );
+
+ }
+
+ UNLOCK_TRUST_LIST();
+
+
+ return;
+}
+
+
+NTSTATUS
+NetrEnumerateTrustedDomains (
+ IN LPWSTR ServerName OPTIONAL,
+ OUT PDOMAIN_NAME_BUFFER DomainNameBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This API returns the names of the domains trusted by the domain ServerName is a member of.
+
+ The returned list does not include the domain ServerName is directly a member of.
+
+ Netlogon implements this API by calling LsaEnumerateTrustedDomains on a DC in the
+ domain ServerName is a member of. However, Netlogon returns cached information if
+ it has been less than 5 minutes since the last call was made or if no DC is available.
+ Netlogon's cache of Trusted domain names is maintained in the registry across reboots.
+ As such, the list is available upon boot even if no DC is available.
+
+
+Arguments:
+
+ ServerName - name of remote server (null for local). ServerName must be an NT workstation
+ or NT non-DC server.
+
+ DomainNameBuffer->DomainNames - Returns an allocated buffer containing the list of trusted domains in
+ MULTI-SZ format (i.e., each string is terminated by a zero character, the next string
+ immediately follows, the sequence is terminated by zero length domain name). The
+ buffer should be freed using NetApiBufferFree.
+
+ DomainNameBuffer->DomainNameByteCount - Number of bytes returned in DomainNames
+
+Return Value:
+
+
+ ERROR_SUCCESS - Success.
+
+ STATUS_NOT_SUPPORTED - This machine is not an NT workstation or NT non-DC server.
+
+ STATUS_NO_LOGON_SERVERS - No DC could be found and no cached information is available.
+
+ STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
+ broken and no cached information is available.
+
+ STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
+ broken or the password is broken and no cached information is available.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PCLIENT_SESSION ClientSession = NlGlobalClientSession;
+ ULONG DiscoveryDone = FALSE;
+
+ LPWSTR TrustedDomainList = NULL;
+ BOOL TrustedDomainListKnown;
+
+ UNICODE_STRING UncDcNameString;
+ WCHAR UncDcName[UNCLEN+1];
+
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: Called.\n" ));
+
+ if ( NlGlobalRole == RoleMemberWorkstation ) {
+
+
+ //
+ // Don't give up unless we've done discovery.
+ //
+
+ do {
+
+ //
+ // If we don't currently know the name of the server,
+ // discover one.
+ //
+
+ if ( ClientSession->CsState == CS_IDLE ) {
+
+ //
+ // If we've tried to authenticate recently,
+ // don't bother trying again.
+ //
+
+ if ( !NlTimeToReauthenticate( ClientSession ) ) {
+ Status = ClientSession->CsConnectionStatus;
+ goto Cleanup;
+
+ }
+
+ //
+ // Discover a DC
+ //
+
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomains: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+
+ // Check again now that we're the writer
+ if ( ClientSession->CsState == CS_IDLE ) {
+ Status = NlDiscoverDc( ClientSession, DT_Synchronous );
+
+ if ( !NT_SUCCESS(Status) ) {
+ NlResetWriterClientSession( ClientSession );
+
+ NlPrint((NL_CRITICAL,
+ "NetrEnumerateTrustedDomains: Discovery failed %lx\n",
+ Status ));
+ goto Cleanup;
+ }
+
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: Discovery succeeded\n" ));
+ DiscoveryDone = TRUE;
+ }
+
+ NlResetWriterClientSession( ClientSession );
+
+ }
+
+
+
+ //
+ // Capture a copy of the DC the session is to.
+ //
+
+ Status = NlCaptureServerClientSession( ClientSession, UncDcName );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Status = STATUS_NO_LOGON_SERVERS;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomainsGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ //
+ // If we don't have DCs in our cache or
+ // if it has been more than 5 minutes since we've refreshed our cache,
+ // get a new list from our primary domain.
+ //
+
+ if ( !NlGlobalTrustedDomainListKnown ||
+ NlTimeHasElapsed( NlGlobalTrustedDomainListTime, 5 * 60 * 1000 ) ) {
+
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: Domain List collected from %ws\n", UncDcName ));
+
+ Status = NlGetTrustedDomainList (
+ UncDcName,
+ &TrustedDomainList );
+
+ if ( !NT_SUCCESS(Status) ) {
+ Status = STATUS_NO_LOGON_SERVERS;
+ if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
+ NlPrint((NL_CRITICAL, "NetrEnumerateTrustedDomainsGetAnyDcName: Can't become writer of client session.\n" ));
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+ NlSetStatusClientSession( ClientSession, Status );
+ NlResetWriterClientSession( ClientSession );
+ continue;
+ }
+
+ continue;
+
+
+ //
+ // Otherwise just use the cached information.
+ //
+ } else {
+ Status = STATUS_NO_LOGON_SERVERS;
+ goto Cleanup;
+ }
+
+ } while ( !NT_SUCCESS(Status) && !DiscoveryDone );
+
+
+
+ //
+ // Free any locally used resources.
+ //
+ Cleanup:
+
+ //
+ // Don't divulge too much to the caller.
+ //
+
+ if ( Status == STATUS_ACCESS_DENIED ) {
+ Status = STATUS_NO_TRUST_SAM_ACCOUNT;
+ }
+
+ //
+ // If we simply can't access a DC,
+ // return the cached information.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+ NET_API_STATUS NetStatus;
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: Domain List returned from cache.\n" ));
+
+ NetStatus = NlReadRegTrustedDomainList (
+ NULL,
+ FALSE, // Don't delete registry key
+ &TrustedDomainList,
+ &TrustedDomainListKnown );
+
+ // Leave 'Status' alone if we can't read from the cache.
+ if (NetStatus == NO_ERROR ) {
+ if ( TrustedDomainListKnown ) {
+ Status = STATUS_SUCCESS;
+ }
+ } else {
+ NlPrint((NL_CRITICAL,
+ "NetrEnumerateTrustedDomains: Can't get Domain List from cache: 0x%lX\n",
+ NetStatus ));
+ }
+ }
+
+ #ifdef notdef // We're using NlGlobalClientSession
+ if ( ClientSession != NULL ) {
+ NlUnrefClientSession( ClientSession );
+ }
+ #endif // notdef // We're using NlGlobalClientSession
+
+ //
+ // Return the DCName to the caller.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
+ DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
+ } else {
+ if ( TrustedDomainList != NULL ) {
+ NetApiBufferFree( TrustedDomainList );
+ }
+ DomainNameBuffer->DomainNameByteCount = 0;
+ DomainNameBuffer->DomainNames = NULL;
+ }
+
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: returns: 0x%lX\n",
+ Status ));
+ return Status;
+ } else {
+
+ //
+ // NlGlobalRole != RoleMemberWorksation
+ //
+
+#ifndef notdef
+ Status = NlGetTrustedDomainList (
+ NULL, // we want to query our own trusted domain list
+ &TrustedDomainList );
+
+ //
+ // Return the DCName to the caller.
+ //
+
+ if ( NT_SUCCESS(Status) ) {
+ DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
+ DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
+ } else {
+ if ( TrustedDomainList != NULL ) {
+ NetApiBufferFree( TrustedDomainList );
+ }
+ DomainNameBuffer->DomainNameByteCount = 0;
+ DomainNameBuffer->DomainNames = NULL;
+ }
+
+ NlPrint((NL_MISC,
+ "NetrEnumerateTrustedDomains: returns: 0x%lX\n",
+ Status ));
+ return Status;
+#else
+
+ //
+ // BUGBUG: this code does not work because the list of
+ // client sessions does not accurately reflect the list of
+ // trusted domains. See bug 34234
+ //
+
+ PLIST_ENTRY ListEntry;
+ ULONG BufferLength;
+ LPWSTR CurrentLoc;
+
+ //
+ // Loop through the client sessions add first calculate the
+ // size required
+ //
+
+ BufferLength = sizeof(WCHAR);
+ LOCK_TRUST_LIST();
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession =
+ CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
+
+ BufferLength += ClientSession->CsDomainName.Length + sizeof(WCHAR);
+
+ }
+
+ TrustedDomainList = (LPWSTR) NetpMemoryAllocate( BufferLength );
+
+ if (TrustedDomainList == NULL) {
+ Status = STATUS_NO_MEMORY;
+
+ } else {
+
+ Status = STATUS_SUCCESS;
+ *TrustedDomainList = L'\0';
+ CurrentLoc = TrustedDomainList;
+
+ //
+ // Now add all the trusted domains onto the string we
+ // allocated
+ //
+
+ for ( ListEntry = NlGlobalTrustList.Flink ;
+ ListEntry != &NlGlobalTrustList ;
+ ListEntry = ListEntry->Flink) {
+
+ ClientSession =
+ CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext );
+
+ RtlCopyMemory(
+ CurrentLoc,
+ ClientSession->CsDomainName.Buffer,
+ ClientSession->CsDomainName.Length
+ );
+ CurrentLoc += ClientSession->CsDomainName.Length / sizeof(WCHAR);
+
+ *(CurrentLoc++) = L'\0';
+ *CurrentLoc = L'\0'; // Place double terminator each time
+
+ }
+ }
+
+ UNLOCK_TRUST_LIST();
+
+ //
+ // Return the list of domains to the caller.
+ //
+
+
+ if ( NT_SUCCESS(Status) ) {
+ DomainNameBuffer->DomainNameByteCount = NetpTStrArraySize( TrustedDomainList );
+ DomainNameBuffer->DomainNames = (LPBYTE) TrustedDomainList;
+ } else {
+ if ( TrustedDomainList != NULL ) {
+ NetApiBufferFree( TrustedDomainList );
+ }
+ DomainNameBuffer->DomainNameByteCount = 0;
+ DomainNameBuffer->DomainNames = NULL;
+ }
+ return Status;
+#endif
+ }
+
+ UNREFERENCED_PARAMETER( ServerName );
+}