diff options
Diffstat (limited to 'private/net/svcdlls/logonsrv')
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 ); +} |