mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4700 lines
121 KiB
4700 lines
121 KiB
/*++
|
|
|
|
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;
|
|
|
|
}
|