6850 lines
200 KiB
C
6850 lines
200 KiB
C
/*--
|
||
|
||
|
||
Copyright (c) 1987-1996 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
netlogon.c
|
||
|
||
Abstract:
|
||
|
||
Entry point and main thread of Netlogon service.
|
||
|
||
Author:
|
||
|
||
Ported from Lan Man 2.0
|
||
|
||
Environment:
|
||
|
||
User mode only.
|
||
Contains NT-specific code.
|
||
Requires ANSI C extensions: slash-slash comments, long external names.
|
||
|
||
Revision History:
|
||
|
||
21-Nov-1990 (madana)
|
||
added code for update (reverse replication) and lockout support.
|
||
|
||
21-Nov-1990 (madana)
|
||
server type support.
|
||
|
||
21-May-1991 (cliffv)
|
||
Ported to NT. Converted to NT style.
|
||
|
||
--*/
|
||
|
||
|
||
//
|
||
// Common include files.
|
||
//
|
||
#include "logonsrv.h" // Include files common to entire service
|
||
#pragma hdrstop
|
||
|
||
//
|
||
// Include lsrvdata.h again allocating the actual variables
|
||
// this time around.
|
||
//
|
||
|
||
#define LSRVDATA_ALLOCATE
|
||
#include "lsrvdata.h"
|
||
#undef LSRVDATA_ALLOCATE
|
||
|
||
|
||
//
|
||
// Include files specific to this .c file
|
||
//
|
||
|
||
#include <ctype.h> // C library type functions
|
||
#include <lmwksta.h> // WKSTA API defines and prototypes
|
||
#include <w32timep.h> // W32TimeGetNetlogonServiceBits
|
||
extern BOOLEAN SampUsingDsData();
|
||
|
||
//
|
||
// Globals
|
||
//
|
||
|
||
#define INTERROGATE_RESP_DELAY 2000 // may want to tune it
|
||
#define MAX_PRIMARY_TRACK_FAIL 3 // Primary pulse slips
|
||
|
||
//
|
||
// RpcInit workitem
|
||
WORKER_ITEM NlGlobalRpcInitWorkItem;
|
||
|
||
|
||
|
||
BOOLEAN
|
||
NetlogonDllInit (
|
||
IN PVOID DllHandle,
|
||
IN ULONG Reason,
|
||
IN PCONTEXT Context OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the DLL initialization routine for netlogon.dll.
|
||
|
||
Arguments:
|
||
|
||
Standard.
|
||
|
||
Return Value:
|
||
|
||
TRUE iff initialization succeeded.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
UNREFERENCED_PARAMETER(DllHandle); // avoid compiler warnings
|
||
UNREFERENCED_PARAMETER(Context); // avoid compiler warnings
|
||
|
||
|
||
//
|
||
// Handle attaching netlogon.dll to a new process.
|
||
//
|
||
|
||
if (Reason == DLL_PROCESS_ATTACH) {
|
||
|
||
NlGlobalMsvEnabled = FALSE;
|
||
NlGlobalMsvThreadCount = 0;
|
||
NlGlobalMsvTerminateEvent = NULL;
|
||
|
||
if ( !DisableThreadLibraryCalls( DllHandle ) ) {
|
||
KdPrint(("NETLOGON.DLL: DisableThreadLibraryCalls failed: %ld\n",
|
||
GetLastError() ));
|
||
}
|
||
Status = NlInitChangeLog();
|
||
|
||
#if NETLOGONDBG
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
|
||
Status ));
|
||
}
|
||
#endif // NETLOGONDBG
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
//
|
||
// Initialize the Critical Section used to serialize access to
|
||
// variables shared by MSV threads and netlogon threads.
|
||
//
|
||
|
||
try {
|
||
InitializeCriticalSection( &NlGlobalMsvCritSect );
|
||
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
||
KdPrint(("NETLOGON.DLL: Cannot initialize NlGlobalMsvCritSect\n"));
|
||
Status = STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// Initialize the cache of discovered domains.
|
||
//
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
NetStatus = NetpDcInitializeCache();
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
KdPrint(("NETLOGON.DLL: Cannot NetpDcinitializeCache\n"));
|
||
Status = STATUS_NO_MEMORY;
|
||
}
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
DeleteCriticalSection( &NlGlobalMsvCritSect );
|
||
}
|
||
}
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlCloseChangeLog();
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Handle detaching netlogon.dll from a process.
|
||
//
|
||
|
||
} else if (Reason == DLL_PROCESS_DETACH) {
|
||
Status = NlCloseChangeLog();
|
||
#if NETLOGONDBG
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
|
||
Status ));
|
||
}
|
||
#endif // NETLOGONDBG
|
||
|
||
//
|
||
// Delete the Critical Section used to serialize access to
|
||
// variables shared by MSV threads and netlogon threads.
|
||
//
|
||
|
||
DeleteCriticalSection( &NlGlobalMsvCritSect );
|
||
|
||
//
|
||
// Free the cache of discovered DCs.
|
||
//
|
||
|
||
NetpDcUninitializeCache();
|
||
|
||
} else {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
return (BOOLEAN)(NT_SUCCESS(Status));
|
||
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
NlInitDbSerialNumber(
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN OUT PLARGE_INTEGER SerialNumber,
|
||
IN OUT PLARGE_INTEGER CreationTime,
|
||
IN DWORD DBIndex
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Set the SerialNumber and CreationTime in the NlGlobalDBInfoArray data
|
||
structure.
|
||
|
||
On the PDC,
|
||
Validate that it matches the value found in the change log.
|
||
Ensure the values are non-zero.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain this database is for.
|
||
|
||
SerialNumber - Specifies the serial number found in the database.
|
||
On return, specifies the serial number to write to the database
|
||
|
||
CreationTime - Specifies the creation time found in the database.
|
||
On return, specifies the creation time to write to the database
|
||
|
||
DBIndex -- DB Index of the database being initialized
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff the serial number and creation time need to be written back
|
||
to the database.
|
||
|
||
--*/
|
||
|
||
{
|
||
BOOLEAN ReturnValue = FALSE;
|
||
|
||
|
||
//
|
||
// If we're running as the primary,
|
||
// check to see if we are a newly promoted primary that was in
|
||
// the middle of a full sync before we were promoted.
|
||
//
|
||
|
||
NlAssert( IsPrimaryDomain( DomainInfo ) );
|
||
if ( NlGlobalPdcDoReplication ) {
|
||
|
||
if ( SerialNumber->QuadPart == 0 || CreationTime->QuadPart == 0 ) {
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlInitDbSerialNumber: %ws"
|
||
": Pdc has bogus Serial number %lx %lx or Creation Time %lx %lx (reset).\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
SerialNumber->HighPart,
|
||
SerialNumber->LowPart,
|
||
CreationTime->HighPart,
|
||
CreationTime->LowPart ));
|
||
|
||
//
|
||
// This is the primary,
|
||
// we probably shouldn't be replicating from a partial database,
|
||
// but at least set the replication information to something
|
||
// reasonable.
|
||
//
|
||
// This will FORCE a full sync on every BDC since the CreationTime has
|
||
// changed. That's the right thing to do since we can't possibly know
|
||
// what state this database is in.
|
||
//
|
||
|
||
NlQuerySystemTime( CreationTime );
|
||
SerialNumber->QuadPart = 1;
|
||
ReturnValue = TRUE;
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// The global serial number array has already been initialized
|
||
// from the changelog. If that information is wrong, just reset the
|
||
// changelog now.
|
||
//
|
||
|
||
|
||
LOCK_CHANGELOG();
|
||
|
||
//
|
||
// If there was no serial number in the changelog for this database,
|
||
// set it now.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart == 0 ) {
|
||
|
||
NlPrint((NL_SYNC, "NlInitDbSerialNumber: %ws"
|
||
": No serial number in change log (set to %lx %lx)\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
SerialNumber->HighPart,
|
||
SerialNumber->LowPart ));
|
||
|
||
|
||
NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
|
||
|
||
//
|
||
// If the serial number in the changelog is greater than the
|
||
// serial number in the database, this is caused by the changelog
|
||
// being flushed to disk and the SAM database not being flushed.
|
||
//
|
||
// Cure this problem by deleting the superfluous changelog entries.
|
||
//
|
||
|
||
} else if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart !=
|
||
SerialNumber->QuadPart ) {
|
||
|
||
NlPrint((NL_SYNC, "NlInitDbSerialNumber: %ws"
|
||
": Changelog serial number different than database: "
|
||
"ChangeLog = %lx %lx DB = %lx %lx\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
NlGlobalChangeLogDesc.SerialNumber[DBIndex].HighPart,
|
||
NlGlobalChangeLogDesc.SerialNumber[DBIndex].LowPart,
|
||
SerialNumber->HighPart,
|
||
SerialNumber->LowPart ));
|
||
|
||
(VOID) NlFixChangeLog( &NlGlobalChangeLogDesc, DBIndex, *SerialNumber );
|
||
|
||
} else {
|
||
|
||
NlPrint((NL_SYNC, "NlInitDbSerialNumber: %ws"
|
||
": Serial number is %lx %lx\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
SerialNumber->HighPart,
|
||
SerialNumber->LowPart ));
|
||
}
|
||
|
||
//
|
||
// In all cases,
|
||
// set the globals to match the database.
|
||
//
|
||
|
||
NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
|
||
NlGlobalDBInfoArray[DBIndex].CreationTime = *CreationTime;
|
||
|
||
UNLOCK_CHANGELOG();
|
||
|
||
|
||
return ReturnValue;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlInitLsaDBInfo(
|
||
PDOMAIN_INFO DomainInfo,
|
||
DWORD DBIndex
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize NlGlobalDBInfoArray data structure. Some of the LSA
|
||
database info is already determined in ValidateStartup functions, so
|
||
those values are used here.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain this database is for.
|
||
|
||
DBIndex -- DB Index of the database being initialized
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// Initialize LSA database info.
|
||
//
|
||
|
||
NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
|
||
NlGlobalDBInfoArray[DBIndex].DBName = L"LSA";
|
||
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_LSA_REPL_NEEDED;
|
||
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle = DomainInfo->DomLsaPolicyHandle;
|
||
|
||
//
|
||
// Forgo this initialization on a workstation.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
LARGE_INTEGER SerialNumber;
|
||
LARGE_INTEGER CreationTime;
|
||
|
||
|
||
//
|
||
// Get the LSA Modified information.
|
||
//
|
||
|
||
Status = LsaIGetSerialNumberPolicy(
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
||
&SerialNumber,
|
||
&CreationTime );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlInitLsaDbInfo: LsaIGetSerialNumberPolicy failed %lx\n",
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Set the SerialNumber and CreationTime in the globals.
|
||
//
|
||
|
||
if ( NlInitDbSerialNumber(
|
||
DomainInfo,
|
||
&SerialNumber,
|
||
&CreationTime,
|
||
DBIndex ) ) {
|
||
|
||
|
||
Status = LsaISetSerialNumberPolicy(
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
||
&SerialNumber,
|
||
&CreationTime,
|
||
(BOOLEAN) FALSE );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
Cleanup:
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlInitSamDBInfo(
|
||
PDOMAIN_INFO DomainInfo,
|
||
DWORD DBIndex
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize NlGlobalDBInfoArray data structure. Some of the SAM database
|
||
info is already determined in ValidateStartup functions, so those
|
||
values are used here. For BUILTIN database, the database is opened,
|
||
database handle is obtained and other DB info
|
||
queried and initialized in this function.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain this database is for.
|
||
|
||
DBIndex -- DB Index of the database being initialized
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS Status;
|
||
PSAMPR_DOMAIN_INFO_BUFFER DomainModified = NULL;
|
||
PSAMPR_DOMAIN_INFO_BUFFER DomainReplica = NULL;
|
||
|
||
|
||
|
||
//
|
||
// Initialize SAM database info.
|
||
//
|
||
|
||
NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
|
||
if ( DBIndex == SAM_DB ) {
|
||
NlGlobalDBInfoArray[DBIndex].DBName = L"SAM";
|
||
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_ACCOUNT_REPL_NEEDED;
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle = DomainInfo->DomSamAccountDomainHandle;
|
||
} else {
|
||
NlGlobalDBInfoArray[DBIndex].DBName = L"BUILTIN";
|
||
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_BUILTIN_REPL_NEEDED;
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle = DomainInfo->DomSamBuiltinDomainHandle;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Forgo this initialization on a workstation.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
|
||
//
|
||
// Get the replica source name.
|
||
//
|
||
|
||
Status = SamrQueryInformationDomain(
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
||
DomainReplicationInformation,
|
||
&DomainReplica );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlInitSamDbInfo: %ws: Cannot SamrQueryInformationDomain (Replica): %lx\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
Status ));
|
||
DomainReplica = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the Domain Modified information.
|
||
//
|
||
|
||
Status = SamrQueryInformationDomain(
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
||
DomainModifiedInformation,
|
||
&DomainModified );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlInitSamDbInfo: %ws: Cannot SamrQueryInformationDomain (Modified): %lx\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
Status ));
|
||
DomainModified = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Set the SerialNumber and CreationTime in the globals.
|
||
//
|
||
|
||
if ( NlInitDbSerialNumber(
|
||
DomainInfo,
|
||
&DomainModified->Modified.DomainModifiedCount,
|
||
&DomainModified->Modified.CreationTime,
|
||
DBIndex ) ) {
|
||
|
||
Status = SamISetSerialNumberDomain(
|
||
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
||
&DomainModified->Modified.DomainModifiedCount,
|
||
&DomainModified->Modified.CreationTime,
|
||
(BOOLEAN) FALSE );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlInitSamDbInfo: %ws: Cannot SamISetSerialNumberDomain: %lx\n",
|
||
NlGlobalDBInfoArray[DBIndex].DBName,
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Free locally used resources.
|
||
//
|
||
if ( DomainModified != NULL ) {
|
||
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainModified,
|
||
DomainModifiedInformation );
|
||
}
|
||
|
||
if ( DomainReplica != NULL ) {
|
||
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainReplica,
|
||
DomainReplicationInformation );
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
BOOL
|
||
NlCreateSysvolShares(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Create the Sysvol and Netlogon shares.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff initialization is successful.
|
||
|
||
--*/
|
||
{
|
||
BOOL RetVal = TRUE;
|
||
BOOL NetlogonShareRelatedToSysvolShare = FALSE;
|
||
|
||
NET_API_STATUS NetStatus;
|
||
LPWSTR AllocatedPath = NULL;
|
||
LPWSTR PathToShare = NULL;
|
||
|
||
LPWSTR DomDnsDomainNameAlias = NULL;
|
||
LPWSTR AllocatedPathAlias = NULL;
|
||
//
|
||
// Create the sysvol share.
|
||
//
|
||
if ( NlGlobalParameters.SysVolReady ) {
|
||
|
||
NetStatus = NlCreateShare( NlGlobalParameters.UnicodeSysvolPath,
|
||
NETLOGON_SYSVOL_SHARE,
|
||
TRUE ) ;
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
NlPrint((NL_CRITICAL, "NlCreateShare %lu\n", NetStatus ));
|
||
|
||
MsgStrings[0] = NlGlobalParameters.UnicodeSysvolPath;
|
||
MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog (NELOG_NetlogonFailedToCreateShare,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) &NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
|
||
/* This isn't fatal. Just continue */
|
||
}
|
||
} else {
|
||
NetStatus = NetShareDel( NULL, NETLOGON_SYSVOL_SHARE, 0);
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
|
||
if ( NetStatus != NERR_NetNameNotFound ) {
|
||
NlPrint((NL_CRITICAL, "NetShareDel SYSVOL failed %lu\n", NetStatus ));
|
||
}
|
||
|
||
/* This isn't fatal. Just continue */
|
||
}
|
||
}
|
||
|
||
//
|
||
// Create NETLOGON share.
|
||
//
|
||
|
||
//
|
||
// Build the default netlogon share path
|
||
//
|
||
if ( NlGlobalParameters.UnicodeScriptPath == NULL &&
|
||
NlGlobalParameters.UnicodeSysvolPath != NULL ) {
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
ULONG Size;
|
||
ULONG SysVolSize;
|
||
PUCHAR Where;
|
||
|
||
//
|
||
// Get pointer to global domain info.
|
||
//
|
||
|
||
DomainInfo = NlFindNetbiosDomain( NULL, TRUE ); // Primary domain
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NlPrint((NL_CRITICAL, "NlCreateSysvolShares: Cannot find primary domain.\n" ));
|
||
// This can't happen
|
||
RetVal = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Allocate a buffer for the real path
|
||
// Avoid this if we have no DNS domain
|
||
// name which is the case when we are
|
||
// in teh middle of dcpromo and somebody
|
||
// started us manually.
|
||
//
|
||
EnterCriticalSection(&NlGlobalDomainCritSect);
|
||
if ( DomainInfo->DomUnicodeDnsDomainNameString.Length > 0 ) {
|
||
SysVolSize = wcslen( NlGlobalParameters.UnicodeSysvolPath ) * sizeof(WCHAR);
|
||
Size = SysVolSize +
|
||
sizeof(WCHAR) +
|
||
DomainInfo->DomUnicodeDnsDomainNameString.Length +
|
||
sizeof(DEFAULT_SCRIPTS);
|
||
|
||
AllocatedPath = LocalAlloc( LMEM_ZEROINIT, Size );
|
||
|
||
if ( AllocatedPath == NULL ) {
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
NlDereferenceDomain( DomainInfo );
|
||
RetVal = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
PathToShare = AllocatedPath;
|
||
|
||
//
|
||
// Build the real path
|
||
//
|
||
|
||
Where = (PUCHAR)PathToShare;
|
||
RtlCopyMemory( Where, NlGlobalParameters.UnicodeSysvolPath, SysVolSize );
|
||
Where += SysVolSize;
|
||
|
||
*((WCHAR *)Where) = L'\\';
|
||
Where += sizeof(WCHAR);
|
||
|
||
// Ignore the trailing . on the DNS domain name
|
||
RtlCopyMemory( Where,
|
||
DomainInfo->DomUnicodeDnsDomainNameString.Buffer,
|
||
DomainInfo->DomUnicodeDnsDomainNameString.Length - sizeof(WCHAR) );
|
||
Where += DomainInfo->DomUnicodeDnsDomainNameString.Length - sizeof(WCHAR);
|
||
|
||
//
|
||
// At this point the path has the form "...\SYSVOL\SYSVOL\DnsDomainName".
|
||
// This is the name of the junction point that points to the actual
|
||
// sysvol root directory "...\SYSVOL\domain" (where "domain" is literal).
|
||
// On the domain rename, we need to rename the junction point to correspond
|
||
// to the current DNS domain name. The old name is stored in the domain
|
||
// name alias, so we can rename from "...\SYSVOL\SYSVOL\DnsDomainNameAlias"
|
||
// to "...\SYSVOL\SYSVOL\DnsDomainName". Note that if the rename hasn't yet
|
||
// happened, DNS domain name alias is actually the future domain name. This
|
||
// is OK as the junction named "...\SYSVOL\SYSVOL\DnsDomainNameAlias" will
|
||
// not exist and the junction rename will fail properly.
|
||
//
|
||
|
||
if ( DomainInfo->DomUtf8DnsDomainNameAlias != NULL &&
|
||
!NlEqualDnsNameUtf8(DomainInfo->DomUtf8DnsDomainName,
|
||
DomainInfo->DomUtf8DnsDomainNameAlias) ) {
|
||
|
||
|
||
//
|
||
// Get the Unicode alias name
|
||
//
|
||
DomDnsDomainNameAlias = NetpAllocWStrFromUtf8Str( DomainInfo->DomUtf8DnsDomainNameAlias );
|
||
if ( DomDnsDomainNameAlias == NULL ) {
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
NlDereferenceDomain( DomainInfo );
|
||
RetVal = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Allocate storage for the path corresponding to the alias
|
||
//
|
||
AllocatedPathAlias = LocalAlloc( LMEM_ZEROINIT,
|
||
SysVolSize + // sysvol part of the path
|
||
sizeof(WCHAR) + // path separator
|
||
wcslen(DomDnsDomainNameAlias)*sizeof(WCHAR) + // domain name part
|
||
sizeof(WCHAR) ); // string terminator
|
||
|
||
if ( AllocatedPathAlias == NULL ) {
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
NlDereferenceDomain( DomainInfo );
|
||
RetVal = FALSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Fill in the path corresponding to the alias
|
||
//
|
||
swprintf( AllocatedPathAlias,
|
||
L"%ws\\%ws",
|
||
NlGlobalParameters.UnicodeSysvolPath,
|
||
DomDnsDomainNameAlias );
|
||
|
||
//
|
||
// Rename the junction. Ignore any failure.
|
||
//
|
||
if ( !MoveFile(AllocatedPathAlias, PathToShare) ) {
|
||
NetStatus = GetLastError();
|
||
if ( NetStatus != ERROR_FILE_NOT_FOUND ) {
|
||
NlPrint(( NL_CRITICAL, "NlCreateSysvolShares: Failed to rename junction: %ws %ws 0x%lx\n",
|
||
AllocatedPathAlias,
|
||
PathToShare,
|
||
NetStatus ));
|
||
}
|
||
} else {
|
||
NlPrint(( NL_INIT, "Renamed SysVol junction from %ws to %ws\n",
|
||
AllocatedPathAlias,
|
||
PathToShare ));
|
||
}
|
||
}
|
||
|
||
//
|
||
// Now finish building the share path
|
||
//
|
||
RtlCopyMemory( Where, DEFAULT_SCRIPTS, sizeof(DEFAULT_SCRIPTS) );
|
||
}
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
|
||
NlDereferenceDomain( DomainInfo );
|
||
NetlogonShareRelatedToSysvolShare = TRUE;
|
||
} else {
|
||
PathToShare = NlGlobalParameters.UnicodeScriptPath;
|
||
}
|
||
|
||
|
||
if ( NlGlobalParameters.SysVolReady ||
|
||
!NetlogonShareRelatedToSysvolShare ) {
|
||
|
||
if ( PathToShare != NULL ) {
|
||
NetStatus = NlCreateShare( PathToShare,
|
||
NETLOGON_SCRIPTS_SHARE,
|
||
FALSE ) ;
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
NlPrint((NL_CRITICAL, "NlCreateShare %lu\n", NetStatus ));
|
||
|
||
MsgStrings[0] = PathToShare;
|
||
MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog (NELOG_NetlogonFailedToCreateShare,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) &NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
|
||
/* This isn't fatal. Just continue */
|
||
}
|
||
}
|
||
} else {
|
||
NetStatus = NetShareDel( NULL, NETLOGON_SCRIPTS_SHARE, 0);
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
|
||
if ( NetStatus != NERR_NetNameNotFound ) {
|
||
NlPrint((NL_CRITICAL, "NetShareDel NETLOGON failed %lu\n", NetStatus ));
|
||
}
|
||
|
||
/* This isn't fatal. Just continue */
|
||
}
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
if ( AllocatedPath != NULL ) {
|
||
LocalFree( AllocatedPath );
|
||
}
|
||
if ( AllocatedPathAlias != NULL ) {
|
||
LocalFree( AllocatedPathAlias );
|
||
}
|
||
if ( DomDnsDomainNameAlias != NULL ) {
|
||
NetApiBufferFree( DomDnsDomainNameAlias );
|
||
}
|
||
|
||
return RetVal;
|
||
}
|
||
|
||
|
||
|
||
#ifdef _DC_NETLOGON
|
||
|
||
BOOL
|
||
NlInitDomainController(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Do Domain Controller specific initialization.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff initialization is successful.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
WCHAR ChangeLogFile[MAX_PATH+1];
|
||
|
||
//
|
||
// Ensure the browser doesn't have extra Hosted domains.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
NlBrowserSyncHostedDomains();
|
||
|
||
|
||
|
||
//
|
||
// Check that the server is installed or install pending
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( !NetpIsServiceStarted( SERVICE_SERVER ) ){
|
||
NlExit( NERR_ServerNotStarted, ERROR_SERVICE_DEPENDENCY_FAIL, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Create SYSVOL and Netlogon shares.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( !NlCreateSysvolShares() ) {
|
||
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Delete the key Netlogon\FullSyncKey
|
||
// (This key was used on a BDC in releases prior to NT 5.0 to keep
|
||
// synchronization state.)
|
||
//
|
||
|
||
NetStatus = RegDeleteKeyA(
|
||
HKEY_LOCAL_MACHINE,
|
||
NL_FULL_SYNC_KEY );
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
|
||
if ( NetStatus != ERROR_FILE_NOT_FOUND ) {
|
||
NlPrint((NL_CRITICAL, "Cannot delete Netlogon\\FullSyncKey %lu\n", NetStatus ));
|
||
}
|
||
|
||
/* This isn't fatal. Just continue */
|
||
}
|
||
|
||
//
|
||
// Tell LSA whether we emulate NT4.0
|
||
//
|
||
|
||
LsaINotifyNetlogonParametersChangeW(
|
||
LsaEmulateNT4,
|
||
REG_DWORD,
|
||
(PWSTR)&NlGlobalParameters.Nt4Emulator,
|
||
sizeof(NlGlobalParameters.Nt4Emulator) );
|
||
|
||
#ifdef notdef
|
||
//
|
||
// Initialize any Hosted domains.
|
||
//
|
||
|
||
Status = NlInitializeHostedDomains();
|
||
if (!NT_SUCCESS(Status)){
|
||
NET_API_STATUS NetStatus = NetpNtStatusToApiStatus(Status);
|
||
|
||
NlPrint((NL_CRITICAL, "Failed to initialize Hosted domains: 0x%x\n",Status));
|
||
NlExit( SERVICE_UIC_M_DATABASE_ERROR, NetStatus, LogErrorAndNtStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
#endif // notdef
|
||
|
||
|
||
//
|
||
// Successful initialization.
|
||
//
|
||
|
||
return TRUE;
|
||
}
|
||
#endif // _DC_NETLOGON
|
||
|
||
|
||
NET_API_STATUS
|
||
NlReadPersitantTrustedDomainList(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Read the persistant trusted domain list
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff initialization is successful.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
PDS_DOMAIN_TRUSTSW ForestTrustList = NULL;
|
||
ULONG ForestTrustListCount;
|
||
ULONG ForestTrustListSize;
|
||
|
||
PDS_DOMAIN_TRUSTSW RegForestTrustList = NULL;
|
||
ULONG RegForestTrustListCount;
|
||
ULONG RegForestTrustListSize;
|
||
|
||
|
||
|
||
//
|
||
// Get pointer to global domain info.
|
||
//
|
||
|
||
DomainInfo = NlFindNetbiosDomain( NULL, TRUE ); // Primary domain
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the cached trusted domain list from the registry.
|
||
// (Do this even if the data isn't used to force deletion of the registry entry.)
|
||
//
|
||
// The TDL was kept in the registry for NT 4. NT 5 keeps it in a binary file.
|
||
//
|
||
|
||
NetStatus = NlReadRegTrustedDomainList (
|
||
DomainInfo,
|
||
TRUE, // Delete this registry key since we no longer need it.
|
||
&RegForestTrustList,
|
||
&RegForestTrustListSize,
|
||
&RegForestTrustListCount );
|
||
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If NCPA has just joined a domain,
|
||
// and has pre-determined the trusted domain list for us,
|
||
// pick up that list.
|
||
//
|
||
// When this machine joins a domain,
|
||
// NCPA caches the trusted domain list where we can find it. That ensures the
|
||
// trusted domain list is available upon reboot even before we dial via RAS. Winlogon
|
||
// can therefore get the trusted domain list from us under those circumstances.
|
||
//
|
||
|
||
(VOID) NlReadFileTrustedDomainList (
|
||
DomainInfo,
|
||
NL_FOREST_BINARY_LOG_FILE_JOIN,
|
||
TRUE, // Delete this file since we no longer need it.
|
||
DS_DOMAIN_VALID_FLAGS, // Read everything
|
||
&ForestTrustList,
|
||
&ForestTrustListSize,
|
||
&ForestTrustListCount );
|
||
|
||
|
||
|
||
//
|
||
// If there is a cached list,
|
||
// Save it back in the primary file for future starts.
|
||
//
|
||
|
||
if ( ForestTrustListCount ) {
|
||
NlPrint(( NL_INIT,
|
||
"Replacing trusted domain list with one for newly joined %ws domain.\n",
|
||
DomainInfo->DomUnicodeDomainName));
|
||
|
||
NetStatus = NlWriteFileForestTrustList (
|
||
NL_FOREST_BINARY_LOG_FILE,
|
||
ForestTrustList,
|
||
ForestTrustListCount );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
MsgStrings[0] = NL_FOREST_BINARY_LOG_FILE;
|
||
MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog (NELOG_NetlogonFailedFileCreate,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) &NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
}
|
||
|
||
//
|
||
// Indicate that we no longer know what site we're in.
|
||
//
|
||
NlSetDynamicSiteName( NULL );
|
||
|
||
//
|
||
// Otherwise, read the current one from the binary file.
|
||
//
|
||
|
||
} else {
|
||
NlPrint(( NL_INIT, "Getting cached trusted domain list from binary file.\n" ));
|
||
|
||
(VOID) NlReadFileTrustedDomainList (
|
||
DomainInfo,
|
||
NL_FOREST_BINARY_LOG_FILE,
|
||
FALSE, // Don't delete (Save it for the next boot)
|
||
DS_DOMAIN_VALID_FLAGS, // Read everything
|
||
&ForestTrustList,
|
||
&ForestTrustListSize,
|
||
&ForestTrustListCount );
|
||
|
||
//
|
||
// If there is no information in the file,
|
||
// use the information from the registry.
|
||
//
|
||
|
||
if ( ForestTrustListCount == 0 ) {
|
||
NlPrint(( NL_INIT, "There is no binary file (use registry).\n" ));
|
||
ForestTrustList = RegForestTrustList;
|
||
RegForestTrustList = NULL;
|
||
ForestTrustListSize = RegForestTrustListSize;
|
||
ForestTrustListCount = RegForestTrustListCount;
|
||
|
||
//
|
||
// Save the collected information to the binary file.
|
||
//
|
||
|
||
NetStatus = NlWriteFileForestTrustList (
|
||
NL_FOREST_BINARY_LOG_FILE,
|
||
ForestTrustList,
|
||
ForestTrustListCount );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
MsgStrings[0] = NL_FOREST_BINARY_LOG_FILE;
|
||
MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog (NELOG_NetlogonFailedFileCreate,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) &NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// In all cases, set the trusted domain list into globals.
|
||
//
|
||
|
||
(VOID) NlSetForestTrustList( DomainInfo,
|
||
&ForestTrustList,
|
||
ForestTrustListSize,
|
||
ForestTrustListCount );
|
||
|
||
NetStatus = NO_ERROR;
|
||
|
||
|
||
//
|
||
// Return
|
||
//
|
||
Cleanup:
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
if ( ForestTrustList != NULL ) {
|
||
NetApiBufferFree( ForestTrustList );
|
||
}
|
||
|
||
if ( RegForestTrustList != NULL ) {
|
||
NetApiBufferFree( RegForestTrustList );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
BOOL
|
||
NlInitWorkstation(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Do workstation specific initialization.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff initialization is successful.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
|
||
|
||
//
|
||
// Get the persistant trusted domain list.
|
||
//
|
||
NetStatus = NlReadPersitantTrustedDomainList();
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlExit( SERVICE_UIC_RESOURCE, NetStatus, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlWaitForService(
|
||
LPWSTR ServiceName,
|
||
ULONG Timeout,
|
||
BOOLEAN RequireAutoStart
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Wait up to Timeout seconds for the a service to start.
|
||
|
||
Arguments:
|
||
|
||
Timeout - Timeout for event (in seconds).
|
||
|
||
RequireAutoStart - TRUE if the service start needs to be automatic.
|
||
|
||
Return Status:
|
||
|
||
STATUS_SUCCESS - Indicates service successfully initialized.
|
||
|
||
STATUS_TIMEOUT - Timeout occurred.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
SC_HANDLE ScManagerHandle = NULL;
|
||
SC_HANDLE ServiceHandle = NULL;
|
||
SERVICE_STATUS ServiceStatus;
|
||
LPQUERY_SERVICE_CONFIG ServiceConfig;
|
||
LPQUERY_SERVICE_CONFIG AllocServiceConfig = NULL;
|
||
QUERY_SERVICE_CONFIG DummyServiceConfig;
|
||
DWORD ServiceConfigSize;
|
||
|
||
|
||
|
||
//
|
||
// Open a handle to the Service.
|
||
//
|
||
|
||
ScManagerHandle = OpenSCManager(
|
||
NULL,
|
||
NULL,
|
||
SC_MANAGER_CONNECT );
|
||
|
||
if (ScManagerHandle == NULL) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: OpenSCManager failed: %lu\n",
|
||
ServiceName,
|
||
GetLastError()));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ServiceHandle = OpenService(
|
||
ScManagerHandle,
|
||
ServiceName,
|
||
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG );
|
||
|
||
if ( ServiceHandle == NULL ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: OpenService failed: %lu\n",
|
||
ServiceName,
|
||
GetLastError()));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// If need to have automatic service start up and
|
||
// If the service isn't configured to be automatically started
|
||
// by the service controller, don't bother waiting for it to start.
|
||
// Also don't wait if the service is disabled.
|
||
//
|
||
// ?? Pass "DummyServiceConfig" and "sizeof(..)" since QueryService config
|
||
// won't allow a null pointer, yet.
|
||
|
||
if ( QueryServiceConfig(
|
||
ServiceHandle,
|
||
&DummyServiceConfig,
|
||
sizeof(DummyServiceConfig),
|
||
&ServiceConfigSize )) {
|
||
|
||
ServiceConfig = &DummyServiceConfig;
|
||
|
||
} else {
|
||
|
||
NetStatus = GetLastError();
|
||
if ( NetStatus != ERROR_INSUFFICIENT_BUFFER ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: QueryServiceConfig failed: %lu\n",
|
||
ServiceName,
|
||
NetStatus));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
AllocServiceConfig = LocalAlloc( 0, ServiceConfigSize );
|
||
ServiceConfig = AllocServiceConfig;
|
||
|
||
if ( AllocServiceConfig == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !QueryServiceConfig(
|
||
ServiceHandle,
|
||
ServiceConfig,
|
||
ServiceConfigSize,
|
||
&ServiceConfigSize )) {
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: QueryServiceConfig failed again: %lu\n",
|
||
ServiceName,
|
||
GetLastError()));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
if ( (RequireAutoStart && ServiceConfig->dwStartType != SERVICE_AUTO_START) ||
|
||
(ServiceConfig->dwStartType == SERVICE_DISABLED) ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws Service start type invalid: %lu\n",
|
||
ServiceName,
|
||
ServiceConfig->dwStartType ));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Loop waiting for the service to start.
|
||
// (Convert Timeout to a number of 5 second iterations)
|
||
//
|
||
|
||
Timeout = (Timeout+5)/5;
|
||
for (;;) {
|
||
|
||
|
||
//
|
||
// Query the status of the service.
|
||
//
|
||
|
||
if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) {
|
||
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: QueryServiceStatus failed: %lu\n",
|
||
ServiceName,
|
||
GetLastError() ));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Return or continue waiting depending on the state of
|
||
// the service.
|
||
//
|
||
|
||
switch( ServiceStatus.dwCurrentState) {
|
||
case SERVICE_RUNNING:
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
|
||
case SERVICE_STOPPED:
|
||
|
||
//
|
||
// If service failed to start,
|
||
// error out now. The caller has waited long enough to start.
|
||
//
|
||
if ( ServiceStatus.dwWin32ExitCode != ERROR_SERVICE_NEVER_STARTED ){
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: service couldn't start: %lu %lx\n",
|
||
ServiceName,
|
||
ServiceStatus.dwWin32ExitCode,
|
||
ServiceStatus.dwWin32ExitCode ));
|
||
if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) {
|
||
NlPrint(( NL_CRITICAL, " Service specific error code: %lu %lx\n",
|
||
ServiceStatus.dwServiceSpecificExitCode,
|
||
ServiceStatus.dwServiceSpecificExitCode ));
|
||
}
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If service has never been started on this boot,
|
||
// continue waiting for it to start.
|
||
//
|
||
|
||
break;
|
||
|
||
//
|
||
// If service is trying to start up now,
|
||
// continue waiting for it to start.
|
||
//
|
||
case SERVICE_START_PENDING:
|
||
break;
|
||
|
||
//
|
||
// Any other state is bogus.
|
||
//
|
||
default:
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlWaitForService: %ws: Invalid service state: %lu\n",
|
||
ServiceName,
|
||
ServiceStatus.dwCurrentState ));
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Wait five seconds for the service to start.
|
||
// If it has successfully started, just return now.
|
||
//
|
||
|
||
NlPrint(( NL_INIT,
|
||
"NlWaitForService: %ws: wait for service to start\n",
|
||
ServiceName ));
|
||
(VOID) WaitForSingleObject( NlGlobalTerminateEvent, 5 * 1000 );
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If we've waited long enough for the service to start,
|
||
// time out now.
|
||
//
|
||
|
||
if ( (--Timeout) == 0 ) {
|
||
Status = STATUS_TIMEOUT;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/* NOT REACHED */
|
||
|
||
Cleanup:
|
||
if ( ScManagerHandle != NULL ) {
|
||
(VOID) CloseServiceHandle(ScManagerHandle);
|
||
}
|
||
if ( ServiceHandle != NULL ) {
|
||
(VOID) CloseServiceHandle(ServiceHandle);
|
||
}
|
||
if ( AllocServiceConfig != NULL ) {
|
||
LocalFree( AllocServiceConfig );
|
||
}
|
||
return Status;
|
||
}
|
||
|
||
VOID
|
||
NlInitTcpRpc(
|
||
IN LPVOID ThreadParam
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes TCP RPC for Netlogon. It runs in a separate thread
|
||
so that Netlogon need not depend on RPCSS.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
ULONG RetryCount;
|
||
RPC_POLICY RpcPolicy;
|
||
// #define NL_TOTAL_RPC_SLEEP_TIME (5*60*1000) // 5 minutes
|
||
// #define NL_RPC_SLEEP_TIME (10 * 1000) // 10 seconds
|
||
// #define NL_RPC_RETRY_COUNT (NL_TOTAL_RPC_SLEEP_TIME/NL_RPC_SLEEP_TIME)
|
||
|
||
//
|
||
// Set up TCP/IP as a non-authenticated transport.
|
||
//
|
||
// The named pipe transport is authenticated. Since Netlogon runs as Local
|
||
// System, Kerberos will authenticate using the machine account. On the BDC/PDC
|
||
// connection, this requires both the PDC and BDC
|
||
// machine account to be in sync. However, netlogon is responsible for making the
|
||
// BDC account in sync by trying the old and new passwords in NlSessionSetup. And
|
||
// Netlogon (or DS replication in the future) is responsible for keeping the PDC password
|
||
// in sync. Thus, it is better to remove Netlogon's dependency on Kerberos authentication.
|
||
//
|
||
|
||
|
||
//
|
||
// Wait up to 15 minutes for the RPCSS service to start
|
||
//
|
||
|
||
Status = NlWaitForService( L"RPCSS", 15 * 60, TRUE );
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
return;
|
||
}
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Tell RPC to not fail. That'll ensure RPC uses TCP when it gets added.
|
||
//
|
||
RtlZeroMemory( &RpcPolicy, sizeof(RpcPolicy) );
|
||
|
||
RpcPolicy.Length = sizeof(RpcPolicy);
|
||
RpcPolicy.EndpointFlags = RPC_C_DONT_FAIL;
|
||
RpcPolicy.NICFlags = RPC_C_BIND_TO_ALL_NICS;
|
||
|
||
NetStatus = RpcServerUseProtseqExW(
|
||
L"ncacn_ip_tcp",
|
||
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
|
||
NULL, // no security descriptor
|
||
&RpcPolicy );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint((NL_CRITICAL, "Can't RpcServerUseProtseq %ld (giving up)\n", NetStatus ));
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
{
|
||
|
||
|
||
RPC_BINDING_VECTOR *BindingVector = NULL;
|
||
NetStatus = RpcServerInqBindings(&BindingVector);
|
||
|
||
if ( NetStatus != NO_ERROR) {
|
||
NlPrint((NL_CRITICAL, "Can't RpcServerInqBindings %ld\n", NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Some early versions of NT 5 still haven't started RPCSS by the time
|
||
// we get here.
|
||
//
|
||
// for ( RetryCount = NL_RPC_RETRY_COUNT; RetryCount != 0; RetryCount-- ) {
|
||
NetStatus = RpcEpRegister(
|
||
logon_ServerIfHandle,
|
||
BindingVector,
|
||
NULL, // no uuid vector
|
||
L"" // no annotation
|
||
);
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint((NL_CRITICAL, "Can't RpcEpRegister %ld (giving up)\n", NetStatus ));
|
||
}
|
||
|
||
// if (RetryCount == 1 ) {
|
||
// } else {
|
||
// NlPrint((NL_CRITICAL, "Can't RpcEpRegister %ld (trying again)\n", NetStatus ));
|
||
/// (VOID) WaitForSingleObject( NlGlobalTerminateEvent, NL_RPC_SLEEP_TIME );
|
||
// }
|
||
// }
|
||
|
||
RpcBindingVectorFree(&BindingVector);
|
||
|
||
if ( NetStatus != NO_ERROR) {
|
||
NlPrint((NL_CRITICAL, "Can't RpcEpRegister %ld\n", NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlGlobalTcpIpRpcServerStarted = TRUE;
|
||
}
|
||
|
||
|
||
//
|
||
// Finish enabling Netlogon functionality.
|
||
//
|
||
|
||
Cleanup:
|
||
NlGlobalPartialDisable = FALSE;
|
||
|
||
//
|
||
// NlMainLoop avoided doing an immediate announcement when first starting up.
|
||
// To do so would have the BDC call us (the PDC) prior to having TCP/IP RPC enabled.
|
||
// Thus, we do an announcement now to ensure the BDCs do call us as soon as possible
|
||
// after the PDC boots.
|
||
//
|
||
|
||
if ( !NlGlobalTerminate && NlGlobalPdcDoReplication ) {
|
||
LOCK_CHANGELOG();
|
||
NlGlobalChangeLogReplicateImmediately = TRUE;
|
||
UNLOCK_CHANGELOG();
|
||
|
||
if ( !SetEvent( NlGlobalChangeLogEvent ) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"Cannot set ChangeLog event: %lu\n",
|
||
GetLastError() ));
|
||
}
|
||
}
|
||
|
||
NlPrint((NL_INIT, "NlInitTcpRpc thread finished.\n" ));
|
||
UNREFERENCED_PARAMETER( ThreadParam );
|
||
}
|
||
|
||
|
||
VOID
|
||
NlpDsNotPaused(
|
||
IN PVOID Context,
|
||
IN BOOLEAN TimedOut
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Worker routine that gets called when the DS is no longer paused.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
NlGlobalDsPaused = FALSE;
|
||
|
||
NlPrint((NL_INIT, "DS is no longer paused.\n" ));
|
||
|
||
|
||
UNREFERENCED_PARAMETER( Context );
|
||
UNREFERENCED_PARAMETER( TimedOut );
|
||
}
|
||
|
||
|
||
|
||
|
||
BOOL
|
||
NlInit(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize NETLOGON service related data structs after verfiying that
|
||
all conditions for startup have been satisfied. Will also create a
|
||
mailslot to listen to requests from clients and create two shares to
|
||
allow execution of logon scripts.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE -- iff initialization is successful.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
LONG RegStatus;
|
||
|
||
OBJECT_ATTRIBUTES EventAttributes;
|
||
UNICODE_STRING EventName;
|
||
|
||
NT_PRODUCT_TYPE NtProductType;
|
||
|
||
WORD wVersionRequested;
|
||
WSADATA wsaData;
|
||
int err;
|
||
|
||
HANDLE WmiInitThreadHandle = NULL;
|
||
DWORD ThreadId;
|
||
|
||
|
||
//
|
||
// Initialize CryptoAPI provider.
|
||
//
|
||
|
||
if ( !CryptAcquireContext(
|
||
&NlGlobalCryptProvider,
|
||
NULL,
|
||
NULL,
|
||
PROV_RSA_FULL,
|
||
CRYPT_VERIFYCONTEXT
|
||
))
|
||
{
|
||
NlGlobalCryptProvider = (HCRYPTPROV)NULL;
|
||
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Let the ChangeLog routines know that Netlogon is started.
|
||
//
|
||
|
||
NlGlobalChangeLogNetlogonState = NetlogonStarting;
|
||
|
||
//
|
||
// Enable detection of duplicate event log messages
|
||
//
|
||
NetpEventlogSetTimeout ( NlGlobalEventlogHandle,
|
||
NlGlobalParameters.DuplicateEventlogTimeout*1000 );
|
||
|
||
//
|
||
// Don't let MaxConcurrentApi dynamically change
|
||
//
|
||
|
||
if ( !RtlGetNtProductType( &NtProductType ) ) {
|
||
NtProductType = NtProductWinNt;
|
||
}
|
||
|
||
NlGlobalMaxConcurrentApi = NlGlobalParameters.MaxConcurrentApi;
|
||
if ( NlGlobalMaxConcurrentApi == 0 ) {
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
|
||
// Default to 1 concurrent API on a member workstation
|
||
if ( NtProductType == NtProductWinNt ) {
|
||
NlGlobalMaxConcurrentApi = 1;
|
||
|
||
// Default to 2 concurrent API on a member server
|
||
} else {
|
||
NlGlobalMaxConcurrentApi = 2;
|
||
}
|
||
|
||
} else {
|
||
// Default to 1 concurrent API on a DC
|
||
NlGlobalMaxConcurrentApi = 1;
|
||
}
|
||
}
|
||
|
||
if ( NlGlobalMaxConcurrentApi != 1 ) {
|
||
// One for the original binding and one for each concurrent logon api
|
||
NlGlobalMaxConcurrentApi += 1;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize worker threads.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
NetStatus = NlWorkerInitialization();
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlExit( SERVICE_UIC_RESOURCE, NetStatus, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Check that the redirector is installed, will exit on error.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( !NetpIsServiceStarted( SERVICE_WORKSTATION ) ){
|
||
NlExit( NERR_WkstaNotStarted, ERROR_SERVICE_DEPENDENCY_FAIL, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Create well know SID for netlogon.dll
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
Status = NetpCreateWellKnownSids( NULL );
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
NlExit( SERVICE_UIC_RESOURCE, NetStatus, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Create the security descriptors we'll use for the APIs
|
||
//
|
||
|
||
Status = NlCreateNetlogonObjects();
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NET_API_STATUS NetStatus = NetpNtStatusToApiStatus(Status);
|
||
|
||
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNtStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Create Timer event
|
||
//
|
||
|
||
NlGlobalTimerEvent = CreateEvent(
|
||
NULL, // No special security
|
||
FALSE, // Auto Reset
|
||
FALSE, // No Timers need no attention
|
||
NULL ); // No name
|
||
|
||
if ( NlGlobalTimerEvent == NULL ) {
|
||
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
#if DBG
|
||
|
||
//
|
||
// create debug share. Ignore error.
|
||
//
|
||
|
||
if( NlCreateShare(
|
||
NlGlobalDebugSharePath,
|
||
DEBUG_SHARE_NAME,
|
||
FALSE ) != NERR_Success ) {
|
||
NlPrint((NL_CRITICAL, "Can't create Debug share (%ws, %ws).\n",
|
||
NlGlobalDebugSharePath, DEBUG_SHARE_NAME ));
|
||
}
|
||
|
||
#endif
|
||
|
||
//
|
||
// Initialize winsock. We need it for all DNS support.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
wVersionRequested = MAKEWORD( 1, 1 );
|
||
|
||
err = WSAStartup( wVersionRequested, &wsaData );
|
||
if ( err == 0 ) {
|
||
if ( LOBYTE( wsaData.wVersion ) != 1 ||
|
||
HIBYTE( wsaData.wVersion ) != 1 ) {
|
||
WSACleanup();
|
||
NlPrint((NL_CRITICAL, "Wrong winsock version (continuing) %ld.\n", wsaData.wVersion ));
|
||
} else {
|
||
NlGlobalWinSockInitialized = TRUE;
|
||
}
|
||
} else {
|
||
NlPrint((NL_CRITICAL, "Can't initialize winsock (continuing) %ld.\n", err ));
|
||
}
|
||
|
||
|
||
//
|
||
// Open the browser so we can send and receive mailslot messages.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( !NlBrowserOpen() ) {
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Wait for SAM/LSA to start
|
||
// Do this before the first access to SAM/LSA/DS.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( !NlWaitForSamService( TRUE ) ) {
|
||
NlExit( SERVICE_UIC_M_DATABASE_ERROR, ERROR_INVALID_SERVER_STATE, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Re-initialize after netlogon.dll unload
|
||
// See if the DS is running.
|
||
// I only need this since nltest /unload loses all dll state.
|
||
|
||
if ( NlGlobalNetlogonUnloaded ) {
|
||
if ( SampUsingDsData() ) {
|
||
NlPrint((NL_INIT,
|
||
"Set DS-running bit after netlogon.dll unload\n" ));
|
||
I_NetLogonSetServiceBits( DS_DS_FLAG, DS_DS_FLAG );
|
||
}
|
||
if ( NetpIsServiceStarted( SERVICE_KDC ) ){
|
||
NlPrint((NL_INIT,
|
||
"Set KDC-running bit after netlogon.dll unload\n" ));
|
||
I_NetLogonSetServiceBits( DS_KDC_FLAG, DS_KDC_FLAG );
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Initialize the Site lookup code
|
||
//
|
||
|
||
NetStatus = NlSiteInitialize();
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
if ( NetStatus == NELOG_NetlogonBadSiteName ) {
|
||
// Error already logged
|
||
NlExit( NetStatus, NetStatus, DontLogError, NULL);
|
||
} else {
|
||
NlExit( NELOG_NetlogonGetSubnetToSite, NetStatus, LogErrorAndNetStatus, NULL);
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Build a list of transports for later reference
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
NetStatus = NlTransportOpen();
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize the Dynamic Dns code
|
||
//
|
||
|
||
NetStatus = NlDnsInitialize();
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize the Hosted domain module and the primary domain.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
NetStatus = NlInitializeDomains();
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
// NlExit already called
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Initialize WMI tracing in a separate thread.
|
||
// Ignore any failure.
|
||
//
|
||
|
||
WmiInitThreadHandle =
|
||
CreateThread(
|
||
NULL, // No security attributes
|
||
0,
|
||
(LPTHREAD_START_ROUTINE)
|
||
NlpInitializeTrace,
|
||
NULL,
|
||
0, // No special creation flags
|
||
&ThreadId );
|
||
|
||
if ( WmiInitThreadHandle == NULL ) {
|
||
NlPrint(( NL_CRITICAL, "Can't create WMI init thread %ld\n", GetLastError() ));
|
||
} else {
|
||
CloseHandle( WmiInitThreadHandle );
|
||
}
|
||
|
||
//
|
||
// Do Workstation or Domain Controller specific initialization
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
if ( !NlInitWorkstation() ) {
|
||
return FALSE;
|
||
}
|
||
} else {
|
||
if ( !NlInitDomainController() ) {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Create an event that is signalled when the last MSV thread leaves
|
||
// a netlogon routine.
|
||
//
|
||
|
||
NlGlobalMsvTerminateEvent = CreateEvent( NULL, // No security attributes
|
||
TRUE, // Must be manually reset
|
||
FALSE, // Initially not signaled
|
||
NULL ); // No name
|
||
|
||
if ( NlGlobalMsvTerminateEvent == NULL ) {
|
||
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
NlGlobalMsvEnabled = TRUE;
|
||
|
||
//
|
||
// We are now ready to act as a Netlogon service
|
||
// Enable RPC
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
NlPrint((NL_INIT,"Starting RPC server.\n"));
|
||
|
||
//
|
||
// Tell RPC that Netlogon support the Netlogon security package.
|
||
//
|
||
|
||
#ifdef ENABLE_AUTH_RPC
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
NetStatus = RpcServerRegisterAuthInfo(
|
||
NL_PACKAGE_NAME,
|
||
RPC_C_AUTHN_NETLOGON,
|
||
NULL,
|
||
NULL );
|
||
|
||
if ( NetStatus == RPC_S_UNKNOWN_AUTHN_SERVICE ) {
|
||
NlGlobalServerSupportsAuthRpc = FALSE;
|
||
NlPrint((NL_CRITICAL, "Rpc doesn't support Netlogon Authentication service. Disable it.\n" ));
|
||
|
||
} else if (NetStatus != NERR_Success) {
|
||
NlExit( NELOG_NetlogonFailedToAddRpcInterface, NetStatus, LogErrorAndNetStatus, NULL );
|
||
return FALSE;
|
||
}
|
||
}
|
||
#endif // ENABLE_AUTH_RPC
|
||
|
||
//
|
||
// NOTE: Now all RPC servers in lsass.exe (now winlogon) share the same
|
||
// pipe name. However, in order to support communication with
|
||
// version 1.0 of WinNt, it is necessary for the Client Pipe name
|
||
// to remain the same as it was in version 1.0. Mapping to the new
|
||
// name is performed in the Named Pipe File System code.
|
||
//
|
||
NetStatus = RpcpAddInterface ( L"lsass", logon_ServerIfHandle );
|
||
|
||
if (NetStatus != NERR_Success) {
|
||
NlExit( NELOG_NetlogonFailedToAddRpcInterface, NetStatus, LogErrorAndNetStatus, NULL );
|
||
return FALSE;
|
||
}
|
||
|
||
NlGlobalRpcServerStarted = TRUE;
|
||
|
||
|
||
|
||
//
|
||
// Start TCP/IP transport in another thread to avoid dependency on RPCSS.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
HANDLE LocalThreadHandle;
|
||
DWORD ThreadId;
|
||
|
||
NlGlobalPartialDisable = TRUE;
|
||
|
||
//
|
||
// Queue the TCP/IP initialization to a high priority worker thread.
|
||
// (We'd rather not wait for discovery on 100's of trusted domains.)
|
||
//
|
||
|
||
NlInitializeWorkItem( &NlGlobalRpcInitWorkItem, NlInitTcpRpc, NULL );
|
||
if ( !NlQueueWorkItem( &NlGlobalRpcInitWorkItem, TRUE, TRUE ) ) {
|
||
|
||
NlGlobalPartialDisable = FALSE;
|
||
|
||
NlPrint((NL_CRITICAL, "Can't create TcpRpc Thread\n" ));
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// If the DS isn't backsyncing,
|
||
// avoid overhead of finding out when it is done.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NlGlobalDsPaused = FALSE;
|
||
} else {
|
||
NlGlobalDsPaused = LsaIIsDsPaused();
|
||
|
||
if ( NlGlobalDsPaused ) {
|
||
NlPrint((NL_INIT, "NlInit: DS is paused.\n" ));
|
||
|
||
//
|
||
// Open the event that the DS triggers after it is no longer paused
|
||
//
|
||
|
||
NlGlobalDsPausedEvent = OpenEvent( SYNCHRONIZE,
|
||
FALSE,
|
||
DS_SYNCED_EVENT_NAME_W );
|
||
|
||
if ( NlGlobalDsPausedEvent == NULL ) {
|
||
NetStatus = GetLastError();
|
||
|
||
NlPrint((NL_CRITICAL, "NlInit: Cannot open DS paused event. %ld\n", NetStatus ));
|
||
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Register to wait on the event.
|
||
//
|
||
|
||
if ( !RegisterWaitForSingleObject(
|
||
&NlGlobalDsPausedWaitHandle,
|
||
NlGlobalDsPausedEvent,
|
||
NlpDsNotPaused, // Callback routine
|
||
NULL, // No context
|
||
-1, // Wait forever
|
||
WT_EXECUTEINWAITTHREAD | // We're quick so reduce the overhead
|
||
WT_EXECUTEONLYONCE ) ) { // Once the DS triggers, we're done
|
||
|
||
NetStatus = GetLastError();
|
||
|
||
NlPrint((NL_CRITICAL, "NlInit: Cannot register DS paused wait routine. %ld\n", NetStatus ));
|
||
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Let the ChangeLog routines know that Netlogon is started.
|
||
//
|
||
|
||
NlGlobalChangeLogNetlogonState = NetlogonStarted;
|
||
|
||
|
||
// Set an event telling anyone wanting to call NETLOGON that we're
|
||
// initialized.
|
||
//
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
RtlInitUnicodeString( &EventName, L"\\NETLOGON_SERVICE_STARTED");
|
||
InitializeObjectAttributes( &EventAttributes, &EventName, 0, 0, NULL );
|
||
|
||
Status = NtCreateEvent(
|
||
&NlGlobalStartedEvent,
|
||
SYNCHRONIZE|EVENT_MODIFY_STATE,
|
||
&EventAttributes,
|
||
NotificationEvent,
|
||
(BOOLEAN) FALSE // The event is initially not signaled
|
||
);
|
||
|
||
if ( !NT_SUCCESS(Status)) {
|
||
|
||
//
|
||
// If the event already exists, a waiting thread beat us to
|
||
// creating it. Just open it.
|
||
//
|
||
|
||
if( Status == STATUS_OBJECT_NAME_EXISTS ||
|
||
Status == STATUS_OBJECT_NAME_COLLISION ) {
|
||
|
||
Status = NtOpenEvent( &NlGlobalStartedEvent,
|
||
SYNCHRONIZE|EVENT_MODIFY_STATE,
|
||
&EventAttributes );
|
||
|
||
}
|
||
if ( !NT_SUCCESS(Status)) {
|
||
NET_API_STATUS NetStatus = NetpNtStatusToApiStatus(Status);
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
" Failed to open NETLOGON_SERVICE_STARTED event. %lX\n",
|
||
Status ));
|
||
NlPrint((NL_CRITICAL,
|
||
" Failing to initialize SAM Server.\n"));
|
||
NlExit( SERVICE_UIC_SYSTEM, NetStatus, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
Status = NtSetEvent( NlGlobalStartedEvent, NULL );
|
||
if ( !NT_SUCCESS(Status)) {
|
||
NET_API_STATUS NetStatus = NetpNtStatusToApiStatus(Status);
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"Failed to set NETLOGON_SERVICE_STARTED event. %lX\n",
|
||
Status ));
|
||
NlPrint((NL_CRITICAL, " Failing to initialize SAM Server.\n"));
|
||
|
||
NtClose(NlGlobalStartedEvent);
|
||
NlExit( SERVICE_UIC_SYSTEM, NetStatus, LogError, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Don't close the event handle. Closing it would delete the event and
|
||
// a future waiter would never see it be set.
|
||
//
|
||
|
||
|
||
//
|
||
// Query the Windows Time service to determine if this machine
|
||
// is the server of the Time service and if it is a good time server.
|
||
//
|
||
// We need to make this call after we've started RPC to avoid race
|
||
// conditions between netlogon and w32time. Both services will first
|
||
// start RPC and only then will try to set the service bits in netlogon.
|
||
// The last one up will correctly set the bits through calling
|
||
// W32TimeGetNetlogonServiceBits (in the case of netlogon) or
|
||
// I_NetLogonSetServiceBits (in the case of w32time).
|
||
//
|
||
// ???: Relink to the w32tclnt.lib when w32time folks move it to a
|
||
// public location.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
ULONG TimeServiceBits;
|
||
|
||
NetStatus = W32TimeGetNetlogonServiceBits( NULL, &TimeServiceBits );
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
Status = I_NetLogonSetServiceBits( DS_TIMESERV_FLAG | DS_GOOD_TIMESERV_FLAG,
|
||
TimeServiceBits );
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint(( NL_CRITICAL, "Cannot I_NetLogonSetServiceBits %ld\n", Status ));
|
||
}
|
||
} else {
|
||
NlPrint(( NL_CRITICAL, "Cannot W32TimeGetNetlogonServiceBits 0x%lx\n", NetStatus ));
|
||
}
|
||
}
|
||
|
||
//
|
||
// we are just about done, this will be final hint
|
||
//
|
||
|
||
if ( !GiveInstallHints( TRUE ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Successful initialization.
|
||
//
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
ULONG
|
||
NlGetDomainFlags(
|
||
IN PDOMAIN_INFO DomainInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns the flags describing what capabilities this Domain has.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Domain whose flags are to be returned.
|
||
If NULL, only non-domain specific flags are returned.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
{
|
||
ULONG Flags=0;
|
||
|
||
//
|
||
// Grab the global flags.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
Flags |= NlGlobalChangeLogServiceBits;
|
||
UNLOCK_CHANGELOG();
|
||
|
||
//
|
||
// A machine that supports the DS also supports an LDAP server
|
||
//
|
||
|
||
if ( Flags & DS_DS_FLAG ) {
|
||
Flags |= DS_LDAP_FLAG;
|
||
|
||
// NT 5 DCs are always writable
|
||
Flags |= DS_WRITABLE_FLAG;
|
||
}
|
||
|
||
//
|
||
// Grab the domain specific flags.
|
||
//
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
|
||
if ( DomainInfo->DomRole == RolePrimary ) {
|
||
Flags |= DS_PDC_FLAG;
|
||
}
|
||
|
||
//
|
||
// If this is NDNC, we are only an LDAP server servicing it.
|
||
// So, set only those two flags and only if the DS is running.
|
||
//
|
||
if ( (DomainInfo->DomFlags & DOM_NON_DOMAIN_NC) != 0 &&
|
||
(Flags & DS_DS_FLAG) != 0 ) {
|
||
Flags = DS_NDNC_FLAG | DS_LDAP_FLAG | DS_WRITABLE_FLAG;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// If we're emulating AD/UNIX,
|
||
// turn off all of the bits they're not allowed to set.
|
||
//
|
||
#ifdef EMULATE_AD_UNIX
|
||
Flags &= ~(DS_DS_FLAG|DS_PDC_FLAG);
|
||
#endif // EMULATE_AD_UNIX
|
||
|
||
return Flags;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
BuildSamLogonResponse(
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN BOOLEAN UseNameAliases,
|
||
IN USHORT Opcode,
|
||
IN LPCWSTR UnicodeUserName OPTIONAL,
|
||
IN LPCWSTR TransportName,
|
||
IN LPCWSTR UnicodeWorkstationName,
|
||
IN BOOL IsNt5,
|
||
IN DWORD OurIpAddress,
|
||
OUT BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE],
|
||
OUT LPDWORD ResponseBufferSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Build the response message to a SAM Logon request.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain message came from
|
||
|
||
UseNameAliases - TRUE if domain and forest name aliases (not active names)
|
||
should be returned in the response message.
|
||
|
||
Opcode - Opcode for the response message
|
||
|
||
UnicodeUserName - The name of the user logging on.
|
||
|
||
TransportName - Name of transport the request came in on
|
||
|
||
UnicodeWorkstationName - Name of the machine the request is from
|
||
|
||
IsNt5 - True if this is a response to an NT 5 query.
|
||
|
||
OurIpAddress - IP Address of the transport this message was received on.
|
||
0: Not an IP transport
|
||
|
||
ResponseBuffer - Buffer to build the response in
|
||
|
||
ResponseBufferSize - Size (in bytes) of the returned message.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
PCHAR Where;
|
||
PNETLOGON_SAM_LOGON_RESPONSE SamResponse = (PNETLOGON_SAM_LOGON_RESPONSE) ResponseBuffer;
|
||
ULONG ResponseNtVersion = 0;
|
||
|
||
//
|
||
// Pack the pre-NT 5.0 information.
|
||
//
|
||
|
||
SamResponse->Opcode = Opcode;
|
||
|
||
Where = (PCHAR) SamResponse->UnicodeLogonServer;
|
||
NetpLogonPutUnicodeString( DomainInfo->DomUncUnicodeComputerName,
|
||
sizeof(SamResponse->UnicodeLogonServer),
|
||
&Where );
|
||
NetpLogonPutUnicodeString( (LPWSTR) UnicodeUserName,
|
||
sizeof(SamResponse->UnicodeUserName),
|
||
&Where );
|
||
NetpLogonPutUnicodeString( DomainInfo->DomUnicodeDomainName,
|
||
sizeof(SamResponse->UnicodeDomainName),
|
||
&Where );
|
||
|
||
//
|
||
// Append GUID and DNS info if this is NT 5.0 asking,
|
||
//
|
||
|
||
if ( IsNt5 ) {
|
||
WORD CompressOffset[3]; // One per compressessed String
|
||
CHAR *CompressUtf8String[3];
|
||
ULONG CompressCount;
|
||
|
||
ULONG Utf8StringSize;
|
||
ULONG Flags = 0;
|
||
UCHAR ZeroByte = 0;
|
||
|
||
NetpLogonPutGuid( &DomainInfo->DomDomainGuidBuffer,
|
||
&Where );
|
||
|
||
// We don't handle the site GUID.
|
||
NetpLogonPutGuid( &NlGlobalZeroGuid,
|
||
&Where );
|
||
|
||
//
|
||
// If we're not responding to a message on an IP transport,
|
||
// don't include DNS naming information in the response.
|
||
//
|
||
|
||
if ( OurIpAddress == 0 ) {
|
||
//
|
||
// This routine is only called if the original caller used a Netbios domain name.
|
||
// Such a caller shouldn't be returned the DNS domain information. We have
|
||
// no reason to believe he has a DNS server.
|
||
// (This problem is also "fixed" on the client side such that the client ignores
|
||
// the DNS info. We're fixing it here to avoid putting the extra bytes on the wire.)
|
||
//
|
||
// Copy NULL Dns Tree name, dns domain name, and dns host name
|
||
//
|
||
NetpLogonPutBytes( &ZeroByte, 1, &Where );
|
||
NetpLogonPutBytes( &ZeroByte, 1, &Where );
|
||
NetpLogonPutBytes( &ZeroByte, 1, &Where );
|
||
} else {
|
||
|
||
//
|
||
// Initialize for copying Cutf-8 strings.
|
||
//
|
||
|
||
Utf8StringSize = sizeof(SamResponse->DnsForestName) +
|
||
sizeof(SamResponse->DnsDomainName) +
|
||
sizeof(SamResponse->DnsHostName);
|
||
|
||
CompressCount = 0; // No strings compressed yet.
|
||
|
||
|
||
//
|
||
// Copy the DnsTree name into the message.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
NetStatus = NlpUtf8ToCutf8( ResponseBuffer,
|
||
UseNameAliases ?
|
||
NlGlobalUtf8DnsForestNameAlias :
|
||
NlGlobalUtf8DnsForestName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
LeaveCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack DnsForestName into message %ld\n",
|
||
NetStatus ));
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
//
|
||
// Copy the Dns Domain Name after the Tree name.
|
||
//
|
||
|
||
|
||
EnterCriticalSection(&NlGlobalDomainCritSect);
|
||
NetStatus = NlpUtf8ToCutf8(
|
||
ResponseBuffer,
|
||
UseNameAliases ?
|
||
DomainInfo->DomUtf8DnsDomainNameAlias :
|
||
DomainInfo->DomUtf8DnsDomainName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack DomainName into message %ld\n",
|
||
NetStatus ));
|
||
return NetStatus;
|
||
}
|
||
|
||
//
|
||
// Copy the Dns Host Name after the domain name.
|
||
//
|
||
|
||
NetStatus = NlpUtf8ToCutf8(
|
||
ResponseBuffer,
|
||
DomainInfo->DomUtf8DnsHostName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack HostName into message %ld\n",
|
||
NetStatus ));
|
||
return NetStatus;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Output the IP address of the transport we received the message on.
|
||
//
|
||
|
||
SmbPutUlong( Where, ntohl(OurIpAddress));
|
||
Where += sizeof(ULONG);
|
||
|
||
//
|
||
// Finally output the flags describing this machine.
|
||
//
|
||
|
||
SmbPutUlong( Where, NlGetDomainFlags(DomainInfo) );
|
||
Where += sizeof(ULONG);
|
||
|
||
|
||
//
|
||
// Tell the caller additional information is present.
|
||
//
|
||
ResponseNtVersion |= NETLOGON_NT_VERSION_5;
|
||
}
|
||
|
||
NetpLogonPutNtToken( &Where, ResponseNtVersion );
|
||
|
||
*ResponseBufferSize = (DWORD)(Where - (PCHAR)SamResponse);
|
||
|
||
//
|
||
// Always good to debug
|
||
//
|
||
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"Ping response '%s' %ws to \\\\%ws on %ws\n",
|
||
NlMailslotOpcode(Opcode),
|
||
UnicodeUserName,
|
||
UnicodeWorkstationName,
|
||
TransportName ));
|
||
|
||
|
||
return NO_ERROR;
|
||
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
BuildSamLogonResponseEx(
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN BOOLEAN UseNameAliases,
|
||
IN USHORT Opcode,
|
||
IN LPCWSTR UnicodeUserName OPTIONAL,
|
||
IN BOOL IsDnsDomainTrustAccount,
|
||
IN LPCWSTR TransportName,
|
||
IN LPCWSTR UnicodeWorkstationName,
|
||
IN PSOCKADDR ClientSockAddr OPTIONAL,
|
||
IN ULONG VersionFlags,
|
||
IN DWORD OurIpAddress,
|
||
OUT BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE],
|
||
OUT LPDWORD ResponseBufferSize
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Build the extended response message to a SAM Logon request.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain message came from
|
||
|
||
UseNameAliases - TRUE if domain and forest name aliases (not active names)
|
||
should be returned in the response message.
|
||
|
||
Opcode - Opcode for the response message
|
||
This is the non-EX version of the opcode.
|
||
|
||
UnicodeUserName - The name of the user logging on.
|
||
|
||
IsDnsDomainTrustAccount - If TRUE, UnicodeUserName is the
|
||
name of a DNS domain trust account.
|
||
|
||
TransportName - Name of transport the request came in on
|
||
|
||
UnicodeWorkstationName - The name of the workstation we're responding to.
|
||
|
||
ClientSockAddr - Socket Address of the client this request came in on.
|
||
If NULL, the client is this machine.
|
||
|
||
VersionFlags - Version flags from the caller.
|
||
|
||
OurIpAddress - IP Address of the transport this message was received on.
|
||
0: Not an IP transport
|
||
|
||
ResponseBuffer - Buffer to build the response in
|
||
|
||
ResponseBufferSize - Size (in bytes) of the returned message.
|
||
|
||
Return Value:
|
||
|
||
Status of the operation.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
PCHAR Where;
|
||
PNETLOGON_SAM_LOGON_RESPONSE_EX SamResponse = (PNETLOGON_SAM_LOGON_RESPONSE_EX) ResponseBuffer;
|
||
ULONG ResponseNtVersion = 0;
|
||
WORD CompressOffset[10];
|
||
CHAR *CompressUtf8String[10];
|
||
ULONG CompressCount = 0;
|
||
ULONG Utf8StringSize;
|
||
ULONG LocalFlags = 0;
|
||
ULONG LocalVersion = 0;
|
||
PNL_SITE_ENTRY ClientSiteEntry = NULL;
|
||
LPWSTR ClientSiteName = NULL;
|
||
WCHAR CapturedSiteName[NL_MAX_DNS_LABEL_LENGTH+1];
|
||
LPSTR LocalUtf8UserName = NULL;
|
||
|
||
//
|
||
// Compute the name of the site the client machine is in.
|
||
//
|
||
|
||
if ( ClientSockAddr != NULL ) {
|
||
|
||
ClientSiteEntry = NlFindSiteEntryBySockAddr( ClientSockAddr );
|
||
|
||
if ( ClientSiteEntry == NULL ) {
|
||
WCHAR IpAddressString[NL_SOCK_ADDRESS_LENGTH+1];
|
||
|
||
NetpSockAddrToWStr( ClientSockAddr,
|
||
sizeof(SOCKADDR_IN),
|
||
IpAddressString );
|
||
|
||
//
|
||
// Passing 0 as the bit mask will force the
|
||
// log output even if DbFlag == 0. We point to
|
||
// this output from the event log written at
|
||
// scavenging time, so don't change the format
|
||
// of the output here.
|
||
//
|
||
NlPrintDom(( 0, DomainInfo,
|
||
"NO_CLIENT_SITE: %ws %ws\n",
|
||
UnicodeWorkstationName,
|
||
IpAddressString ));
|
||
|
||
//
|
||
// If this is the first no site client,
|
||
// set the timestamp for this observation window
|
||
//
|
||
EnterCriticalSection( &NlGlobalSiteCritSect );
|
||
if ( NlGlobalNoClientSiteCount == 0 ) {
|
||
NlQuerySystemTime( &NlGlobalNoClientSiteEventTime );
|
||
}
|
||
|
||
//
|
||
// Increment the number of clients with no site
|
||
// we hit during this timeout period
|
||
//
|
||
NlGlobalNoClientSiteCount ++;
|
||
LeaveCriticalSection( &NlGlobalSiteCritSect );
|
||
|
||
} else {
|
||
ULONG SiteIndex;
|
||
|
||
ClientSiteName = ClientSiteEntry->SiteName;
|
||
|
||
EnterCriticalSection( &NlGlobalSiteCritSect );
|
||
if ( VersionFlags & NETLOGON_NT_VERSION_GC ) {
|
||
for ( SiteIndex = 0; SiteIndex < DomainInfo->GcCoveredSitesCount; SiteIndex++ ) {
|
||
if ( (DomainInfo->GcCoveredSites)[SiteIndex].CoveredSite == ClientSiteEntry ) {
|
||
LocalFlags |= DS_CLOSEST_FLAG;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
for ( SiteIndex = 0; SiteIndex < DomainInfo->CoveredSitesCount; SiteIndex++ ) {
|
||
if ( (DomainInfo->CoveredSites)[SiteIndex].CoveredSite == ClientSiteEntry ) {
|
||
LocalFlags |= DS_CLOSEST_FLAG;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
LeaveCriticalSection( &NlGlobalSiteCritSect );
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// If this is a loopback call,
|
||
// we already know our site name.
|
||
// (And it is the closest site.)
|
||
//
|
||
|
||
if ( VersionFlags & NETLOGON_NT_VERSION_LOCAL ) {
|
||
if ( NlCaptureSiteName( CapturedSiteName ) ) {
|
||
ClientSiteName = CapturedSiteName;
|
||
LocalFlags |= DS_CLOSEST_FLAG;
|
||
}
|
||
} else {
|
||
NlPrintDom((NL_SITE, DomainInfo,
|
||
"Client didn't pass us his IP Address. (No site returned)\n" ));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Pack the opcode converting it to the _EX version
|
||
//
|
||
|
||
switch ( Opcode ) {
|
||
case LOGON_SAM_LOGON_RESPONSE:
|
||
Opcode = LOGON_SAM_LOGON_RESPONSE_EX; break;
|
||
case LOGON_SAM_PAUSE_RESPONSE:
|
||
Opcode = LOGON_SAM_PAUSE_RESPONSE_EX; break;
|
||
case LOGON_SAM_USER_UNKNOWN:
|
||
Opcode = LOGON_SAM_USER_UNKNOWN_EX; break;
|
||
}
|
||
|
||
SamResponse->Opcode = Opcode;
|
||
SamResponse->Sbz = 0;
|
||
|
||
//
|
||
// Output the flags describing this machine.
|
||
//
|
||
|
||
SamResponse->Flags = LocalFlags | NlGetDomainFlags(DomainInfo);
|
||
|
||
//
|
||
// Output the GUID of this domain.
|
||
//
|
||
|
||
Where = (PCHAR) &SamResponse->DomainGuid;
|
||
NetpLogonPutGuid( &DomainInfo->DomDomainGuidBuffer,
|
||
&Where );
|
||
|
||
//
|
||
// Initialize for copying Cutf-8 strings.
|
||
//
|
||
|
||
Utf8StringSize = sizeof(SamResponse->DnsForestName) +
|
||
sizeof(SamResponse->DnsDomainName) +
|
||
sizeof(SamResponse->DnsHostName) +
|
||
sizeof(SamResponse->NetbiosDomainName) +
|
||
sizeof(SamResponse->NetbiosComputerName) +
|
||
sizeof(SamResponse->UserName) +
|
||
sizeof(SamResponse->DcSiteName) +
|
||
sizeof(SamResponse->ClientSiteName);
|
||
|
||
CompressCount = 0; // No strings compressed yet.
|
||
|
||
|
||
//
|
||
// Copy the DnsTree name into the message.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
NetStatus = NlpUtf8ToCutf8( ResponseBuffer,
|
||
UseNameAliases ?
|
||
NlGlobalUtf8DnsForestNameAlias :
|
||
NlGlobalUtf8DnsForestName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
LeaveCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack DnsForestName into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Copy the Dns Domain Name after the Tree name.
|
||
//
|
||
|
||
EnterCriticalSection(&NlGlobalDomainCritSect);
|
||
NetStatus = NlpUtf8ToCutf8(
|
||
ResponseBuffer,
|
||
UseNameAliases ?
|
||
DomainInfo->DomUtf8DnsDomainNameAlias :
|
||
DomainInfo->DomUtf8DnsDomainName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack DomainName into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Copy the Dns Host Name after the domain name.
|
||
//
|
||
|
||
NetStatus = NlpUtf8ToCutf8(
|
||
ResponseBuffer,
|
||
DomainInfo->DomUtf8DnsHostName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack HostName into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Copy the Netbios domain name
|
||
//
|
||
|
||
NetStatus = NlpUnicodeToCutf8(
|
||
ResponseBuffer,
|
||
DomainInfo->DomUnicodeDomainName,
|
||
TRUE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack Netbios Domain Name into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Copy the Netbios computer name
|
||
//
|
||
|
||
NetStatus = NlpUnicodeToCutf8(
|
||
ResponseBuffer,
|
||
DomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
TRUE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack Netbios computername into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Copy the UserName
|
||
//
|
||
|
||
if ( UnicodeUserName != NULL && *UnicodeUserName != UNICODE_NULL ) {
|
||
|
||
LocalUtf8UserName = NetpAllocUtf8StrFromWStr( UnicodeUserName );
|
||
if ( LocalUtf8UserName == NULL ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// For SAM account names we are going to truncate the name
|
||
// to 63 bytes max to fit it into the space allowed for a
|
||
// single label by RFC 1035. Note that this should be fine
|
||
// because valid SAM account names are limited to 20 characters
|
||
// (which is at most 60 bytes for UTF-8 character storage).
|
||
// Therefore our response for long (truncated) SAM names is
|
||
// going to be SAM_USER_UNKNOWN in which case the client will
|
||
// skip the verification of the returned (truncated) account name.
|
||
//
|
||
|
||
if ( !IsDnsDomainTrustAccount && // => SAM account name
|
||
strlen(LocalUtf8UserName) > NL_MAX_DNS_LABEL_LENGTH ) {
|
||
|
||
NlAssert( Opcode == LOGON_SAM_USER_UNKNOWN_EX );
|
||
NlPrintDom(( (Opcode == LOGON_SAM_USER_UNKNOWN_EX) ? NL_MISC : NL_CRITICAL,
|
||
DomainInfo,
|
||
"BuildSamLogonResponseEx: Truncating SAM account name %ws for Opcode %lu\n",
|
||
UnicodeUserName,
|
||
Opcode ));
|
||
LocalUtf8UserName[ NL_MAX_DNS_LABEL_LENGTH ] = '\0';
|
||
}
|
||
}
|
||
|
||
//
|
||
// Always ignore dots for user name (even if this is a DNS domain name)
|
||
// to preserve the last period in the DNS domain trust name.
|
||
//
|
||
|
||
NetStatus = NlpUtf8ToCutf8(
|
||
ResponseBuffer,
|
||
LocalUtf8UserName,
|
||
TRUE, // Ignore dots
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack User Name into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Copy the Name of the site this DC is in.
|
||
//
|
||
|
||
NetStatus = NlpUtf8ToCutf8( ResponseBuffer,
|
||
NlGlobalUtf8SiteName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack DcSiteName into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Copy the Name of the site the client machine is in.
|
||
//
|
||
|
||
NetStatus = NlpUnicodeToCutf8( ResponseBuffer,
|
||
ClientSiteName,
|
||
FALSE,
|
||
&Where,
|
||
&Utf8StringSize,
|
||
&CompressCount,
|
||
CompressOffset,
|
||
CompressUtf8String );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cannot pack ClientSiteName into message %ld\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the caller wants it,
|
||
// output the IP address of the transport we received the message on.
|
||
//
|
||
|
||
if ( OurIpAddress &&
|
||
(VersionFlags & NETLOGON_NT_VERSION_5EX_WITH_IP) != 0 ) {
|
||
SOCKADDR_IN DcSockAddrIn;
|
||
CHAR DcSockAddrSize;
|
||
|
||
//
|
||
// Convert the IP address to a SockAddr.
|
||
//
|
||
RtlZeroMemory( &DcSockAddrIn, sizeof(DcSockAddrIn) );
|
||
DcSockAddrIn.sin_family = AF_INET;
|
||
DcSockAddrIn.sin_port = 0;
|
||
DcSockAddrIn.sin_addr.S_un.S_addr = OurIpAddress;
|
||
|
||
DcSockAddrSize = sizeof(SOCKADDR_IN);
|
||
|
||
//
|
||
// Put the size of the SockAddr into the message
|
||
//
|
||
NetpLogonPutBytes( &DcSockAddrSize, 1, &Where );
|
||
|
||
//
|
||
// Put the SockAddr itself into the message
|
||
//
|
||
NetpLogonPutBytes( &DcSockAddrIn, sizeof(DcSockAddrIn), &Where );
|
||
|
||
//
|
||
// Tell the caller that the size field is there.
|
||
//
|
||
LocalVersion |= NETLOGON_NT_VERSION_5EX_WITH_IP;
|
||
}
|
||
|
||
//
|
||
// Set the version of this message.
|
||
//
|
||
|
||
NetpLogonPutNtToken( &Where, NETLOGON_NT_VERSION_5EX | LocalVersion );
|
||
|
||
*ResponseBufferSize = (DWORD)(Where - (PCHAR)SamResponse);
|
||
|
||
NetStatus = NO_ERROR;
|
||
|
||
//
|
||
// Free locally used resources;
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Always good to debug
|
||
//
|
||
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"Ping response '%s' %ws to \\\\%ws Site: %ws on %ws\n",
|
||
NlMailslotOpcode(Opcode),
|
||
UnicodeUserName,
|
||
UnicodeWorkstationName,
|
||
ClientSiteName,
|
||
TransportName ));
|
||
|
||
if ( LocalUtf8UserName != NULL ) {
|
||
NetpMemoryFree( LocalUtf8UserName );
|
||
}
|
||
|
||
if ( ClientSiteEntry != NULL ) {
|
||
NlDerefSiteEntry( ClientSiteEntry );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
#ifdef _DC_NETLOGON
|
||
NTSTATUS
|
||
NlSamVerifyUserAccountEnabled(
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN LPCWSTR AccountName,
|
||
IN ULONG AllowableAccountControlBits,
|
||
IN BOOL CheckAccountDisabled
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verify whether the user account exists and is enabled.
|
||
This function uses efficient version of SAM account lookup,
|
||
namely SamINetLogonPing (as opposed to SamIOpenNamedUser).
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - Hosted Domain
|
||
|
||
AccountName - The name of the user account to check
|
||
|
||
AllowableAccountControlBits - A mask of allowable SAM account types that
|
||
are allowed to satisfy this request.
|
||
|
||
CheckAccountDisabled - TRUE if we should return an error if the account
|
||
is disabled.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The account has been verified
|
||
STATUS_NO_SUCH_USER -- The account has failed to verify
|
||
Otherwise, an error returned by SamINetLogonPing
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
UNICODE_STRING UserNameString;
|
||
BOOLEAN AccountExists;
|
||
ULONG UserAccountControl;
|
||
ULONG Length;
|
||
|
||
//
|
||
// Ensure the account name has the correct postfix.
|
||
//
|
||
|
||
if ( AllowableAccountControlBits == USER_SERVER_TRUST_ACCOUNT ||
|
||
AllowableAccountControlBits == USER_WORKSTATION_TRUST_ACCOUNT ) {
|
||
|
||
Length = wcslen( AccountName );
|
||
|
||
if ( Length <= SSI_ACCOUNT_NAME_POSTFIX_LENGTH ) {
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
|
||
if ( _wcsicmp(&AccountName[Length - SSI_ACCOUNT_NAME_POSTFIX_LENGTH],
|
||
SSI_ACCOUNT_NAME_POSTFIX) != 0 ) {
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
}
|
||
|
||
//
|
||
// User accounts exist only in real domains
|
||
//
|
||
|
||
if ( (DomainInfo->DomFlags & DOM_REAL_DOMAIN) == 0 ) {
|
||
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlSamVerifyUserAccountEnabled: Domain is not real 0x%lx\n",
|
||
DomainInfo->DomFlags ));
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
|
||
|
||
RtlInitUnicodeString( &UserNameString, AccountName );
|
||
|
||
//
|
||
// Call the expedite version of SAM user lookup
|
||
//
|
||
|
||
Status = SamINetLogonPing( DomainInfo->DomSamAccountDomainHandle,
|
||
&UserNameString,
|
||
&AccountExists,
|
||
&UserAccountControl );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlSamVerifyUserAccountEnabled: SamINetLogonPing failed 0x%lx\n",
|
||
Status ));
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// If the account doesn't exist,
|
||
// return now
|
||
//
|
||
|
||
if ( !AccountExists ) {
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
|
||
//
|
||
// Ensure the Account type matches the account type on the account.
|
||
//
|
||
|
||
if ( (UserAccountControl & USER_ACCOUNT_TYPE_MASK & AllowableAccountControlBits) == 0 ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlSamVerifyUserAccountEnabled: Invalid account type (0x%lx) instead of 0x%lx for %ws\n",
|
||
UserAccountControl & USER_ACCOUNT_TYPE_MASK,
|
||
AllowableAccountControlBits,
|
||
AccountName ));
|
||
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
|
||
//
|
||
// Check if the account is disabled if requested
|
||
//
|
||
|
||
if ( CheckAccountDisabled ) {
|
||
if ( UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
||
NlPrintDom(( NL_MISC, DomainInfo,
|
||
"NlSamVerifyUserAccountEnabled: %ws account is disabled\n",
|
||
AccountName ));
|
||
return STATUS_NO_SUCH_USER;
|
||
}
|
||
}
|
||
|
||
//
|
||
// All checks succeeded
|
||
//
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
LogonRequestHandler(
|
||
IN LPCWSTR TransportName,
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN BOOLEAN UseNameAliases,
|
||
IN PSID DomainSid OPTIONAL,
|
||
IN DWORD Version,
|
||
IN DWORD VersionFlags,
|
||
IN LPCWSTR UnicodeUserName,
|
||
IN DWORD RequestCount,
|
||
IN LPCWSTR UnicodeWorkstationName,
|
||
IN ULONG AllowableAccountControlBits,
|
||
IN DWORD OurIpAddress,
|
||
IN PSOCKADDR ClientSockAddr OPTIONAL,
|
||
OUT BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE],
|
||
OUT LPDWORD ResponseBufferSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Respond appropriate to an LM 2.0 or NT 3.x logon request.
|
||
|
||
Arguments:
|
||
|
||
TransportName - Name of the transport the request came in on
|
||
|
||
DomainInfo - Hosted Domain message came from
|
||
|
||
UseNameAliases - TRUE if domain and forest name aliases (not active names)
|
||
should be returned in the response message.
|
||
|
||
DomainSid - If specified, must match the DomainSid of the sid specified by
|
||
DomainInfo.
|
||
|
||
Version - The version of the input message. This parameter determine
|
||
the version of the response.
|
||
|
||
VersionFlags - The version flag bit from the input messge
|
||
|
||
UnicodeUserName - The name of the user logging on.
|
||
|
||
RequestCount - The number of times this user has repeated the logon request.
|
||
|
||
UnicodeWorkstationName - The name of the workstation where the user is
|
||
logging onto.
|
||
|
||
AllowableAccountControlBits - A mask of allowable SAM account types that
|
||
are allowed to satisfy this request.
|
||
|
||
OurIpAddress - IP Address of the transport this message was received on.
|
||
0: Not an IP transport
|
||
|
||
ClientSockAddr - Socket Address of the client this request came in on.
|
||
If NULL, the client is this machine.
|
||
|
||
ResponseBuffer - Buffer to build the response in
|
||
|
||
ResponseBufferSize - Size (in bytes) of the returned message.
|
||
|
||
Return Value:
|
||
|
||
TRUE if this query should be responded to (the ResponseBuffer was filled in)
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
USHORT Response = 0;
|
||
|
||
PCHAR Where;
|
||
ULONG AccountType;
|
||
BOOLEAN MessageBuilt = FALSE;
|
||
BOOLEAN NetlogonPaused = FALSE;
|
||
LPSTR NetlogonPausedReason;
|
||
|
||
ULONG ResponseNtVersion = 0;
|
||
BOOL IsDnsDomainTrustAccount = FALSE;
|
||
|
||
//
|
||
// If we are emulating NT4.0 domain and the client
|
||
// didn't indicate to neutralize the emulation,
|
||
// treat the client as NT4.0 client. That way we
|
||
// won't leak NT5.0 specific info to the client.
|
||
//
|
||
|
||
if ( NlGlobalParameters.Nt4Emulator &&
|
||
(VersionFlags & NETLOGON_NT_VERSION_AVOID_NT4EMUL) == 0 ) {
|
||
|
||
//
|
||
// Pick up the only bit that existed in NT4.0
|
||
//
|
||
VersionFlags &= NETLOGON_NT_VERSION_1;
|
||
}
|
||
|
||
|
||
//
|
||
// Compare the domain SID specified with the one for this domain.
|
||
//
|
||
|
||
if( DomainSid != NULL &&
|
||
!RtlEqualSid( DomainInfo->DomAccountDomainId, DomainSid ) ) {
|
||
|
||
LPWSTR AlertStrings[4];
|
||
|
||
//
|
||
// alert admin.
|
||
//
|
||
|
||
AlertStrings[0] = (LPWSTR)UnicodeWorkstationName;
|
||
AlertStrings[1] = DomainInfo->DomUncUnicodeComputerName;
|
||
AlertStrings[2] = DomainInfo->DomUnicodeDomainName;
|
||
AlertStrings[3] = NULL; // Needed for RAISE_ALERT_TOO
|
||
|
||
//
|
||
// Save the info in the eventlog
|
||
//
|
||
|
||
NlpWriteEventlog(
|
||
ALERT_NetLogonUntrustedClient,
|
||
EVENTLOG_ERROR_TYPE,
|
||
NULL,
|
||
0,
|
||
AlertStrings,
|
||
3 | NETP_RAISE_ALERT_TOO );
|
||
|
||
return FALSE;
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Logons are not processed if the service is paused
|
||
//
|
||
// Even though we're "PartialDisabled",
|
||
// we'll respond to queries that originated from this machine.
|
||
// That ensures apps on this machine find this machine even though we're booting.
|
||
//
|
||
// Also, if this a PDC discovery and we are the PDC,
|
||
// respond to it even if netlogon is paused since we are the only one who can respond.
|
||
//
|
||
|
||
if ( NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED &&
|
||
!((VersionFlags & NETLOGON_NT_VERSION_PDC) != 0 && DomainInfo->DomRole == RolePrimary) ) {
|
||
NetlogonPaused = TRUE;
|
||
NetlogonPausedReason = "Netlogon Service Paused";
|
||
|
||
} else if ( NlGlobalDsPaused ) {
|
||
NetlogonPaused = TRUE;
|
||
NetlogonPausedReason = "DS paused";
|
||
|
||
} else if ( NlGlobalPartialDisable && (VersionFlags & NETLOGON_NT_VERSION_LOCAL) == 0 ) {
|
||
NetlogonPaused = TRUE;
|
||
NetlogonPausedReason = "Waiting for RPCSS";
|
||
|
||
} else if ( !NlGlobalParameters.SysVolReady ) {
|
||
NetlogonPaused = TRUE;
|
||
NetlogonPausedReason = "SysVol not ready";
|
||
|
||
}
|
||
|
||
if ( NetlogonPaused ) {
|
||
|
||
if ( Version == LMNT_MESSAGE ) {
|
||
Response = LOGON_SAM_PAUSE_RESPONSE;
|
||
} else {
|
||
|
||
//
|
||
// Don't respond immediately to non-nt clients. They treat
|
||
// "paused" responses as fatal. That's just not so.
|
||
// There may be many other DCs that are able to process the logon.
|
||
//
|
||
if ( RequestCount >= MAX_LOGONREQ_COUNT &&
|
||
NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED ) {
|
||
Response = LOGON_PAUSE_RESPONSE;
|
||
}
|
||
}
|
||
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"Returning paused to '%ws' since: %s\n",
|
||
UnicodeWorkstationName,
|
||
NetlogonPausedReason ));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// NT 5 does queries with a null account name.
|
||
// Bypass the SAM lookup for efficiencies sake.
|
||
//
|
||
|
||
if ( (UnicodeUserName == NULL || *UnicodeUserName == L'\0') &&
|
||
Version == LMNT_MESSAGE ) {
|
||
Response = LOGON_SAM_LOGON_RESPONSE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// If this user does not have an account in SAM,
|
||
// immediately return a response indicating so.
|
||
//
|
||
// All we are trying to do here is ensuring that this guy
|
||
// has a valid account except that we are not checking the
|
||
// password
|
||
//
|
||
// This is done so that STANDALONE logons for non existent
|
||
// users can be done in very first try, speeding up the response
|
||
// to user and reducing processing on DCs/BCs.
|
||
//
|
||
//
|
||
// Disallow use of disabled accounts.
|
||
//
|
||
// We use this message to determine if a trusted domain has a
|
||
// particular account. Since the UI recommends disabling an account
|
||
// rather than deleting it (conservation of rids and all that),
|
||
// we shouldn't respond that we have the account if we really don't.
|
||
//
|
||
// We don't check the disabled bit in the Lanmax 2.x/WFW/WIN 95 case. Downlevel
|
||
// interactive logons are directed at a single particular domain.
|
||
// It is better here that we indicate we have the account so later
|
||
// he'll get a better error code indicating that the account is
|
||
// disabled, rather than allowing him to logon standalone.
|
||
//
|
||
|
||
//
|
||
// If the account is interdomain trust account,
|
||
// we need to look it up in LSA
|
||
//
|
||
if ( AllowableAccountControlBits == USER_DNS_DOMAIN_TRUST_ACCOUNT ||
|
||
AllowableAccountControlBits == USER_INTERDOMAIN_TRUST_ACCOUNT ) {
|
||
|
||
Status = NlGetIncomingPassword(
|
||
DomainInfo,
|
||
UnicodeUserName,
|
||
NullSecureChannel, // Don't know the secure channel type
|
||
AllowableAccountControlBits,
|
||
Version == LMNT_MESSAGE,
|
||
NULL, // Don't return the password
|
||
NULL, // Don't return the previous password
|
||
NULL, // Don't return the account RID
|
||
NULL, // Don't return the trust attributes
|
||
&IsDnsDomainTrustAccount );
|
||
|
||
//
|
||
// Otherwise the account is a SAM user account and
|
||
// we can use a quick SAM lookup
|
||
//
|
||
} else {
|
||
Status = NlSamVerifyUserAccountEnabled( DomainInfo,
|
||
UnicodeUserName,
|
||
AllowableAccountControlBits,
|
||
Version == LMNT_MESSAGE );
|
||
}
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
if ( Status == STATUS_NO_SUCH_USER ) {
|
||
|
||
if ( Version == LMNT_MESSAGE ) {
|
||
Response = LOGON_SAM_USER_UNKNOWN;
|
||
} else if ( Version == LM20_MESSAGE ) {
|
||
Response = LOGON_USER_UNKNOWN;
|
||
}
|
||
}
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// For SAM clients, respond immediately.
|
||
//
|
||
|
||
if ( Version == LMNT_MESSAGE ) {
|
||
Response = LOGON_SAM_LOGON_RESPONSE;
|
||
goto Cleanup;
|
||
|
||
//
|
||
// For LM 2.0 clients, respond immediately.
|
||
//
|
||
|
||
} else if ( Version == LM20_MESSAGE ) {
|
||
Response = LOGON_RESPONSE2;
|
||
goto Cleanup;
|
||
|
||
//
|
||
// For LM 1.0 clients,
|
||
// don't support the request.
|
||
//
|
||
|
||
} else {
|
||
Response = LOGON_USER_UNKNOWN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
Cleanup:
|
||
//
|
||
// If we should respond to the caller, do so now.
|
||
//
|
||
|
||
switch (Response) {
|
||
case LOGON_SAM_PAUSE_RESPONSE:
|
||
case LOGON_SAM_USER_UNKNOWN:
|
||
case LOGON_SAM_LOGON_RESPONSE:
|
||
|
||
if (VersionFlags & (NETLOGON_NT_VERSION_5EX|NETLOGON_NT_VERSION_5EX_WITH_IP)) {
|
||
NetStatus = BuildSamLogonResponseEx(
|
||
DomainInfo,
|
||
UseNameAliases,
|
||
Response,
|
||
UnicodeUserName,
|
||
IsDnsDomainTrustAccount,
|
||
TransportName,
|
||
UnicodeWorkstationName,
|
||
ClientSockAddr,
|
||
VersionFlags,
|
||
OurIpAddress,
|
||
ResponseBuffer,
|
||
ResponseBufferSize );
|
||
} else {
|
||
NetStatus = BuildSamLogonResponse(
|
||
DomainInfo,
|
||
UseNameAliases,
|
||
Response,
|
||
UnicodeUserName,
|
||
TransportName,
|
||
UnicodeWorkstationName,
|
||
(VersionFlags & NETLOGON_NT_VERSION_5) != 0,
|
||
OurIpAddress,
|
||
ResponseBuffer,
|
||
ResponseBufferSize );
|
||
}
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Done;
|
||
}
|
||
|
||
MessageBuilt = TRUE;
|
||
break;
|
||
|
||
|
||
case LOGON_RESPONSE2:
|
||
case LOGON_USER_UNKNOWN:
|
||
case LOGON_PAUSE_RESPONSE: {
|
||
PNETLOGON_LOGON_RESPONSE2 Response2 = (PNETLOGON_LOGON_RESPONSE2)ResponseBuffer;
|
||
|
||
Response2->Opcode = Response;
|
||
|
||
Where = Response2->LogonServer;
|
||
(VOID) strcpy( Where, "\\\\");
|
||
Where += 2;
|
||
NetpLogonPutOemString( DomainInfo->DomOemComputerName,
|
||
sizeof(Response2->LogonServer) - 2,
|
||
&Where );
|
||
NetpLogonPutLM20Token( &Where );
|
||
|
||
*ResponseBufferSize = (DWORD)(Where - (PCHAR)Response2);
|
||
MessageBuilt = TRUE;
|
||
|
||
//
|
||
// Always good to debug
|
||
//
|
||
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"%s logon mailslot message for %ws from \\\\%ws. Response '%s' on %ws\n",
|
||
Version == LMNT_MESSAGE ? "Sam" : "Uas",
|
||
UnicodeUserName,
|
||
UnicodeWorkstationName,
|
||
NlMailslotOpcode(Response),
|
||
TransportName ));
|
||
|
||
break;
|
||
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free up any locally used resources.
|
||
//
|
||
|
||
Done:
|
||
|
||
return MessageBuilt;
|
||
|
||
}
|
||
|
||
|
||
VOID
|
||
I_NetLogonFree(
|
||
IN PVOID Buffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Free any buffer allocated by Netlogon and returned to an in-process caller.
|
||
|
||
Arguments:
|
||
|
||
Buffer - Buffer to deallocate.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
NetpMemoryFree( Buffer );
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
PrimaryQueryHandler(
|
||
IN LPCWSTR TransportName,
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN BOOLEAN UseNameAliases,
|
||
IN DWORD Version,
|
||
IN DWORD VersionFlags,
|
||
IN LPCWSTR UnicodeWorkstationName,
|
||
IN DWORD OurIpAddress,
|
||
IN PSOCKADDR ClientSockAddr OPTIONAL,
|
||
OUT BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE],
|
||
OUT LPDWORD ResponseBufferSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Respond appropriately to a primary query request.
|
||
|
||
Arguments:
|
||
|
||
TransportName - Name of the tranport the request came in on
|
||
|
||
DomainInfo - Hosted Domain message came from
|
||
|
||
UseNameAliases - TRUE if domain and forest name aliases (not active names)
|
||
should be returned in the response message.
|
||
|
||
Version - The version of the input message.
|
||
|
||
VersionFlags - The version flag bit from the input messge
|
||
|
||
UnicodeWorkstationName - The name of the workstation doing the query.
|
||
|
||
OurIpAddress - IP Address of the transport this message was received on.
|
||
0: Not an IP transport
|
||
|
||
ClientSockAddr - Socket Address of the client this request came in on.
|
||
If NULL, the client is this machine.
|
||
|
||
ResponseBuffer - Buffer to build the response in
|
||
|
||
ResponseBufferSize - Size (in bytes) of the returned message.
|
||
|
||
Return Value:
|
||
|
||
TRUE if this primary query should be responded to (the ResponseBuffer was filled in)
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// If we are emulating NT4.0 domain and the client
|
||
// didn't indicate to neutralize the emulation,
|
||
// treat the client as NT4.0 client. That way we
|
||
// won't leak NT5.0 specific info to the client.
|
||
//
|
||
|
||
if ( NlGlobalParameters.Nt4Emulator &&
|
||
(VersionFlags & NETLOGON_NT_VERSION_AVOID_NT4EMUL) == 0 ) {
|
||
|
||
//
|
||
// Pick up the only bit that existed in NT4.0
|
||
//
|
||
VersionFlags &= NETLOGON_NT_VERSION_1;
|
||
}
|
||
|
||
|
||
//
|
||
// Don't respond if the TCP transport isn't yet enabled.
|
||
//
|
||
// This might be a BDC wanting to find its PDC to setup a secure channel.
|
||
// We don't want it to fall back to named pipes.
|
||
//
|
||
|
||
if ( NlGlobalDsPaused || NlGlobalPartialDisable ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Only respond if we're a PDC.
|
||
//
|
||
|
||
if ( DomainInfo->DomRole != RolePrimary ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Respond to the query
|
||
//
|
||
|
||
//
|
||
// If the caller is an NT5.0 client,
|
||
// respond with a SamLogonResponse.
|
||
//
|
||
if (VersionFlags & (NETLOGON_NT_VERSION_5EX|NETLOGON_NT_VERSION_5EX_WITH_IP)) {
|
||
NET_API_STATUS NetStatus;
|
||
|
||
NetStatus = BuildSamLogonResponseEx(
|
||
DomainInfo,
|
||
UseNameAliases,
|
||
LOGON_SAM_LOGON_RESPONSE_EX,
|
||
NULL, // No user name in response
|
||
FALSE, // Not a DNS trust account name
|
||
TransportName,
|
||
UnicodeWorkstationName,
|
||
ClientSockAddr,
|
||
VersionFlags,
|
||
OurIpAddress,
|
||
ResponseBuffer,
|
||
ResponseBufferSize );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
} else if ( VersionFlags & NETLOGON_NT_VERSION_5 ) {
|
||
NET_API_STATUS NetStatus;
|
||
|
||
NetStatus = BuildSamLogonResponse(
|
||
DomainInfo,
|
||
UseNameAliases,
|
||
LOGON_SAM_LOGON_RESPONSE,
|
||
NULL, // No user name in response
|
||
TransportName,
|
||
UnicodeWorkstationName,
|
||
TRUE, // Supply NT 5.0 specific response
|
||
OurIpAddress,
|
||
ResponseBuffer,
|
||
ResponseBufferSize );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
} else {
|
||
PNETLOGON_PRIMARY Response = (PNETLOGON_PRIMARY)ResponseBuffer;
|
||
PCHAR Where;
|
||
|
||
//
|
||
// Build the response
|
||
//
|
||
// If we are the Primary DC, tell the caller our computername.
|
||
// If we are a backup DC,
|
||
// tell the downlevel PDC who we think the primary is.
|
||
//
|
||
|
||
Response->Opcode = LOGON_PRIMARY_RESPONSE;
|
||
|
||
Where = Response->PrimaryDCName;
|
||
NetpLogonPutOemString(
|
||
DomainInfo->DomOemComputerName,
|
||
sizeof( Response->PrimaryDCName),
|
||
&Where );
|
||
|
||
//
|
||
// If this is an NT query,
|
||
// add the NT specific response.
|
||
//
|
||
if ( Version == LMNT_MESSAGE ) {
|
||
NetpLogonPutUnicodeString(
|
||
DomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
sizeof(Response->UnicodePrimaryDCName),
|
||
&Where );
|
||
|
||
NetpLogonPutUnicodeString(
|
||
DomainInfo->DomUnicodeDomainName,
|
||
sizeof(Response->UnicodeDomainName),
|
||
&Where );
|
||
|
||
NetpLogonPutNtToken( &Where, 0 );
|
||
}
|
||
|
||
*ResponseBufferSize = (DWORD)(Where - (PCHAR)Response);
|
||
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"%s Primary Query mailslot message from %ws. Response %ws on %ws\n",
|
||
Version == LMNT_MESSAGE ? "Sam" : "Uas",
|
||
UnicodeWorkstationName,
|
||
DomainInfo->DomUncUnicodeComputerName,
|
||
TransportName ));
|
||
|
||
}
|
||
|
||
return TRUE;
|
||
|
||
//
|
||
// Free Locally used resources
|
||
//
|
||
Cleanup:
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NlGetLocalPingResponse(
|
||
IN LPCWSTR TransportName,
|
||
IN BOOL LdapPing,
|
||
IN LPCWSTR NetbiosDomainName OPTIONAL,
|
||
IN LPCSTR DnsDomainName OPTIONAL,
|
||
IN GUID *DomainGuid OPTIONAL,
|
||
IN PSID DomainSid OPTIONAL,
|
||
IN BOOL PdcOnly,
|
||
IN LPCWSTR UnicodeComputerName,
|
||
IN LPCWSTR UnicodeUserName OPTIONAL,
|
||
IN ULONG AllowableAccountControlBits,
|
||
IN ULONG NtVersion,
|
||
IN ULONG NtVersionFlags,
|
||
IN PSOCKADDR ClientSockAddr OPTIONAL,
|
||
OUT PVOID *Message,
|
||
OUT PULONG MessageSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Build the message response message to for a DC ping.
|
||
|
||
Arguments:
|
||
|
||
TransportName - Name of the transport the message came in on
|
||
|
||
LdapPing - TRUE iff the ping from client came over LDAP
|
||
|
||
NetbiosDomainName - Netbios Domain Name of the domain to query.
|
||
|
||
DnsDomainName - UTF-8 DNS Domain Name of the domain to query.
|
||
|
||
DomainGuid - GUID of the domain being located.
|
||
|
||
If all three of the above are NULL, the primary domain is used.
|
||
|
||
DomainSid - If specified, must match the DomainSid of the domain referenced.
|
||
|
||
PdcOnly - True if only the PDC should respond.
|
||
|
||
UnicodeComputerName - Netbios computer name of the machine to respond to.
|
||
|
||
UnicodeUserName - Account name of the user being pinged.
|
||
If NULL, DC will always respond affirmatively.
|
||
|
||
AllowableAccountControlBits - Mask of allowable account types for UnicodeUserName.
|
||
|
||
NtVersion - Version of the message
|
||
|
||
NtVersionFlags - Version of the message.
|
||
0: For backward compatibility.
|
||
NETLOGON_NT_VERSION_5: for NT 5.0 message.
|
||
|
||
ClientSockAddr - Socket Address of the client this request came in on.
|
||
If NULL, the client is this machine.
|
||
|
||
Message - Returns the message to be sent to the DC in question.
|
||
Buffer must be free using NetpMemoryFree().
|
||
|
||
MessageSize - Returns the size (in bytes) of the returned message
|
||
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - Operation completed successfully;
|
||
|
||
ERROR_NO_SUCH_DOMAIN - If the machine isn't a DC for the requested domain.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - The message could not be allocated.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
DWORD ResponseBufferSize;
|
||
BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE]; // Buffer to build response in
|
||
DWORD OurIpAddress;
|
||
PLIST_ENTRY ListEntry;
|
||
BOOLEAN AliasNameMatched = FALSE;
|
||
|
||
//
|
||
// Ignore this call on a workstation.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return ERROR_NO_SUCH_DOMAIN;
|
||
}
|
||
|
||
//
|
||
// If we are emulating NT4.0 domain and this ping came from LDAP
|
||
// and the client didn't indicate to neutralize the emulation,
|
||
// ignore this ping
|
||
//
|
||
|
||
if ( NlGlobalParameters.Nt4Emulator &&
|
||
LdapPing &&
|
||
(NtVersionFlags & NETLOGON_NT_VERSION_AVOID_NT4EMUL) == 0 ) {
|
||
|
||
return ERROR_NO_SUCH_DOMAIN;
|
||
}
|
||
|
||
//
|
||
// Be Verbose
|
||
//
|
||
|
||
NlPrint((NL_MAILSLOT,
|
||
"Received ping from %ws %s %ws on %ws\n",
|
||
UnicodeComputerName,
|
||
DnsDomainName,
|
||
UnicodeUserName,
|
||
TransportName ));
|
||
|
||
//
|
||
// The first time this is called, wait for the DS service to start.
|
||
//
|
||
|
||
if ( NlGlobalDsRunningUnknown ) {
|
||
DWORD WaitStatus;
|
||
#define NL_NTDS_HANDLE 0
|
||
#define NL_SHUTDOWN_HANDLE 1
|
||
#define NL_DS_HANDLE_COUNT 2
|
||
HANDLE EventHandles[NL_DS_HANDLE_COUNT];
|
||
|
||
//
|
||
// Create an event to wait on.
|
||
//
|
||
|
||
EventHandles[NL_NTDS_HANDLE] = OpenEvent(
|
||
SYNCHRONIZE,
|
||
FALSE,
|
||
NTDS_DELAYED_STARTUP_COMPLETED_EVENT );
|
||
|
||
if ( EventHandles[NL_NTDS_HANDLE] == NULL ) {
|
||
NetStatus = GetLastError();
|
||
NlPrint((NL_CRITICAL,
|
||
"NlGetLocalPingResponse: Cannot OpenEvent %ws %ld\n",
|
||
NTDS_DELAYED_STARTUP_COMPLETED_EVENT,
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
EventHandles[NL_SHUTDOWN_HANDLE] = NlGlobalTerminateEvent;
|
||
|
||
//
|
||
// Wait for the DS to start
|
||
//
|
||
|
||
WaitStatus = WaitForMultipleObjects( NL_DS_HANDLE_COUNT,
|
||
EventHandles,
|
||
FALSE,
|
||
20*60*1000 ); // Twenty minutes maximum
|
||
|
||
CloseHandle( EventHandles[NL_NTDS_HANDLE] );
|
||
|
||
switch ( WaitStatus ) {
|
||
case WAIT_OBJECT_0 + NL_NTDS_HANDLE:
|
||
break;
|
||
|
||
case WAIT_OBJECT_0 + NL_SHUTDOWN_HANDLE:
|
||
NlPrint((NL_CRITICAL,
|
||
"NlGetLocalPingResponse: Netlogon shut down.\n" ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
|
||
case WAIT_TIMEOUT:
|
||
NlPrint((NL_CRITICAL,
|
||
"NlGetLocalPingResponse: DS took too long to start.\n" ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
|
||
case WAIT_FAILED:
|
||
NetStatus = GetLastError();
|
||
NlPrint((NL_CRITICAL,
|
||
"NlGetLocalPingResponse: Wait for DS failed %ld.\n", NetStatus ));
|
||
goto Cleanup;
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"NlGetLocalPingResponse: Unknown status from Wait %ld.\n", WaitStatus ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Never wait again.
|
||
//
|
||
NlGlobalDsRunningUnknown = FALSE;
|
||
|
||
}
|
||
|
||
//
|
||
// If no specific domain is needed,
|
||
// use the default.
|
||
//
|
||
|
||
if ( DnsDomainName == NULL && DomainGuid == NULL && NetbiosDomainName == NULL ) {
|
||
DomainInfo = NlFindNetbiosDomain(
|
||
NULL,
|
||
TRUE );
|
||
|
||
//
|
||
// See if the requested domain/NDNC is supported.
|
||
//
|
||
} else if ( DnsDomainName != NULL || DomainGuid != NULL ) {
|
||
|
||
//
|
||
// Lookup an emulated domain/NDNC using the passed DNS name.
|
||
//
|
||
// If the DNS domain name alias matches the query, the alias
|
||
// may change by the time we build the response. That's OK,
|
||
// the client will disregard our response which is proper
|
||
// since we will no longer have that alias.
|
||
//
|
||
|
||
DomainInfo = NlFindDnsDomain(
|
||
DnsDomainName,
|
||
DomainGuid,
|
||
TRUE, // look up NDNCs too
|
||
TRUE, // check domain name aliase
|
||
&AliasNameMatched );
|
||
|
||
//
|
||
// If that didn't find the emulated domain,
|
||
// and the caller is looking for a GC,
|
||
// and this is a query that doesn't need a specific domain,
|
||
// check if the DNS domain name specified is that of our tree,
|
||
// we can respond to this request.
|
||
//
|
||
// Simply use the primary emulated domain.
|
||
//
|
||
|
||
if ( DomainInfo == NULL &&
|
||
( NtVersionFlags & NETLOGON_NT_VERSION_GC ) != 0 &&
|
||
DomainSid == NULL &&
|
||
UnicodeUserName == NULL &&
|
||
AllowableAccountControlBits == 0 &&
|
||
DnsDomainName != NULL ) {
|
||
|
||
BOOL ForestNameSame = FALSE;
|
||
|
||
EnterCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
if ( NlGlobalUtf8DnsForestName != NULL &&
|
||
NlEqualDnsNameUtf8( DnsDomainName, NlGlobalUtf8DnsForestName ) ) {
|
||
ForestNameSame = TRUE;
|
||
}
|
||
|
||
//
|
||
// If this didn't match, check if the forest name alias does
|
||
//
|
||
if ( !ForestNameSame &&
|
||
NlGlobalUtf8DnsForestNameAlias != NULL &&
|
||
NlEqualDnsNameUtf8( DnsDomainName, NlGlobalUtf8DnsForestNameAlias ) ) {
|
||
ForestNameSame = TRUE;
|
||
AliasNameMatched = TRUE;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
|
||
if ( ForestNameSame ) {
|
||
DomainInfo = NlFindNetbiosDomain( NULL, TRUE );
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( DomainInfo == NULL && NetbiosDomainName != NULL ) {
|
||
DomainInfo = NlFindNetbiosDomain(
|
||
NetbiosDomainName,
|
||
FALSE );
|
||
}
|
||
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"Ping from %ws for domain %s %ws for %ws on %ws is invalid since we don't host the named domain.\n",
|
||
UnicodeComputerName,
|
||
DnsDomainName,
|
||
NetbiosDomainName,
|
||
UnicodeUserName,
|
||
TransportName ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Get the IP address of this machine (any IP address)
|
||
// Loop through the list of addresses learned via winsock.
|
||
//
|
||
// Default to the loopback address (127.0.0.1). Since all DCs require IP
|
||
// to be installed, make sure we always have an IP address even though
|
||
// the net card is currently unplugged.
|
||
//
|
||
|
||
OurIpAddress = htonl(0x7f000001);
|
||
EnterCriticalSection( &NlGlobalTransportCritSect );
|
||
if ( NlGlobalWinsockPnpAddresses != NULL ) {
|
||
int i;
|
||
for ( i=0; i<NlGlobalWinsockPnpAddresses->iAddressCount; i++ ) {
|
||
PSOCKADDR SockAddr;
|
||
|
||
SockAddr = NlGlobalWinsockPnpAddresses->Address[i].lpSockaddr;
|
||
if ( SockAddr->sa_family == AF_INET ) {
|
||
OurIpAddress = ((PSOCKADDR_IN)SockAddr)->sin_addr.S_un.S_addr;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
LeaveCriticalSection( &NlGlobalTransportCritSect );
|
||
|
||
|
||
|
||
//
|
||
// If this is a primary query,
|
||
// handle it.
|
||
//
|
||
|
||
if ( PdcOnly ) {
|
||
|
||
//
|
||
// If we don't have a response,
|
||
// just tell the caller this DC doesn't match.
|
||
//
|
||
|
||
if ( !PrimaryQueryHandler(
|
||
TransportName,
|
||
DomainInfo,
|
||
AliasNameMatched,
|
||
NtVersion,
|
||
NtVersionFlags,
|
||
UnicodeComputerName,
|
||
OurIpAddress,
|
||
ClientSockAddr,
|
||
ResponseBuffer,
|
||
&ResponseBufferSize ) ) {
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If this isn't a primary query,
|
||
// handle it.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// If we don't have a response,
|
||
// just tell the caller this DC doesn't match.
|
||
//
|
||
if ( !LogonRequestHandler(
|
||
TransportName,
|
||
DomainInfo,
|
||
AliasNameMatched,
|
||
DomainSid,
|
||
NtVersion,
|
||
NtVersionFlags,
|
||
UnicodeUserName,
|
||
0, // RequestCount
|
||
UnicodeComputerName,
|
||
AllowableAccountControlBits,
|
||
OurIpAddress,
|
||
ClientSockAddr,
|
||
ResponseBuffer,
|
||
&ResponseBufferSize ) ) {
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Actually allocate a buffer for the response.
|
||
//
|
||
|
||
*Message = NetpMemoryAllocate( ResponseBufferSize );
|
||
|
||
if ( *Message == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlCopyMemory( *Message, ResponseBuffer, ResponseBufferSize );
|
||
*MessageSize = ResponseBufferSize;
|
||
|
||
NetStatus = NO_ERROR;
|
||
|
||
|
||
Cleanup:
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
#endif // _DC_NETLOGON
|
||
|
||
|
||
BOOL
|
||
TimerExpired(
|
||
IN PTIMER Timer,
|
||
IN PLARGE_INTEGER TimeNow,
|
||
IN OUT LPDWORD Timeout
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determine whether a timer has expired. If not, adjust the passed in
|
||
timeout value to take this timer into account.
|
||
|
||
Arguments:
|
||
|
||
Timer - Specifies the timer to check.
|
||
|
||
TimeNow - Specifies the current time of day in NT standard time.
|
||
|
||
Timeout - Specifies the current amount of time (in milliseconds)
|
||
that the caller intends to wait for a timer to expire.
|
||
If this timer has not expired, this value is adjusted to the
|
||
smaller of the current value and the amount of time remaining
|
||
on the passed in timer.
|
||
|
||
Return Value:
|
||
|
||
TRUE - if the timer has expired.
|
||
|
||
--*/
|
||
|
||
{
|
||
LARGE_INTEGER Period;
|
||
LARGE_INTEGER ExpirationTime;
|
||
LARGE_INTEGER ElapsedTime;
|
||
LARGE_INTEGER TimeRemaining;
|
||
LARGE_INTEGER MillisecondsRemaining;
|
||
|
||
/*lint -e569 */ /* don't complain about 32-bit to 31-bit initialize */
|
||
LARGE_INTEGER BaseGetTickMagicDivisor = { 0xe219652c, 0xd1b71758 };
|
||
/*lint +e569 */ /* don't complain about 32-bit to 31-bit initialize */
|
||
CCHAR BaseGetTickMagicShiftCount = 13;
|
||
|
||
//
|
||
// If the period to too large to handle (i.e., 0xffffffff is forever),
|
||
// just indicate that the timer has not expired.
|
||
//
|
||
|
||
if ( Timer->Period > TIMER_MAX_PERIOD ) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// If time has gone backwards (someone changed the clock),
|
||
// just start the timer over again.
|
||
//
|
||
// The kernel automatically updates the system time to the CMOS clock
|
||
// periodically. If we just expired the timer when time went backwards,
|
||
// we'd risk periodically falsely triggering the timeout.
|
||
//
|
||
|
||
ElapsedTime.QuadPart = TimeNow->QuadPart - Timer->StartTime.QuadPart;
|
||
|
||
if ( ElapsedTime.QuadPart < 0 ) {
|
||
Timer->StartTime = *TimeNow;
|
||
}
|
||
|
||
//
|
||
// Convert the period from milliseconds to 100ns units.
|
||
//
|
||
|
||
Period.QuadPart = UInt32x32To64( (LONG) Timer->Period, 10000 );
|
||
|
||
//
|
||
// Compute the expiration time.
|
||
//
|
||
|
||
ExpirationTime.QuadPart = Timer->StartTime.QuadPart + Period.QuadPart;
|
||
|
||
//
|
||
// Compute the Time remaining on the timer.
|
||
//
|
||
|
||
TimeRemaining.QuadPart = ExpirationTime.QuadPart - TimeNow->QuadPart;
|
||
|
||
//
|
||
// If the timer has expired, tell the caller so.
|
||
//
|
||
|
||
if ( TimeRemaining.QuadPart <= 0 ) {
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// If the timer hasn't expired, compute the number of milliseconds
|
||
// remaining.
|
||
//
|
||
|
||
MillisecondsRemaining = RtlExtendedMagicDivide(
|
||
TimeRemaining,
|
||
BaseGetTickMagicDivisor,
|
||
BaseGetTickMagicShiftCount );
|
||
|
||
NlAssert( MillisecondsRemaining.HighPart == 0 );
|
||
NlAssert( MillisecondsRemaining.LowPart <= TIMER_MAX_PERIOD );
|
||
|
||
//
|
||
// Adjust the running timeout to be the smaller of the current value
|
||
// and the value computed for this timer.
|
||
//
|
||
|
||
if ( *Timeout > MillisecondsRemaining.LowPart ) {
|
||
*Timeout = MillisecondsRemaining.LowPart;
|
||
}
|
||
|
||
return FALSE;
|
||
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlDomainScavenger(
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Perform the per-domain scavenging.
|
||
|
||
Arguments:
|
||
|
||
DomainInfo - The domain being scavenged.
|
||
|
||
Context - Not Used
|
||
|
||
Return Value:
|
||
|
||
Success (not used).
|
||
|
||
--*/
|
||
{
|
||
DWORD DomFlags;
|
||
|
||
//
|
||
// Change password if neccessary
|
||
//
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
if ( !NlGlobalParameters.DisablePasswordChange ) {
|
||
PCLIENT_SESSION ClientSession;
|
||
|
||
ClientSession = NlRefDomClientSession( DomainInfo );
|
||
|
||
if ( ClientSession != NULL ) {
|
||
(VOID) NlChangePassword( ClientSession, FALSE, NULL );
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
}
|
||
|
||
|
||
|
||
#ifdef _DC_NETLOGON
|
||
//
|
||
// Change the password on each entry in the trust list.
|
||
//
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
if ( DomainInfo->DomRole == RolePrimary ) {
|
||
PLIST_ENTRY ListEntry;
|
||
PCLIENT_SESSION ClientSession;
|
||
|
||
//
|
||
// Reset all the flags indicating we need to check the password
|
||
//
|
||
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
for ( ListEntry = DomainInfo->DomTrustList.Flink ;
|
||
ListEntry != &DomainInfo->DomTrustList ;
|
||
ListEntry = ListEntry->Flink) {
|
||
|
||
ClientSession = CONTAINING_RECORD( ListEntry,
|
||
CLIENT_SESSION,
|
||
CsNext );
|
||
|
||
//
|
||
// Only check the password if there is a direct trust to the domain.
|
||
//
|
||
if ( ClientSession->CsFlags & CS_DIRECT_TRUST ) {
|
||
ClientSession->CsFlags |= CS_CHECK_PASSWORD;
|
||
}
|
||
}
|
||
|
||
for ( ListEntry = DomainInfo->DomTrustList.Flink ;
|
||
ListEntry != &DomainInfo->DomTrustList ;
|
||
) {
|
||
|
||
ClientSession = CONTAINING_RECORD( ListEntry,
|
||
CLIENT_SESSION,
|
||
CsNext );
|
||
|
||
if ( (ClientSession->CsFlags & CS_CHECK_PASSWORD) == 0 ) {
|
||
ListEntry = ListEntry->Flink;
|
||
continue;
|
||
}
|
||
ClientSession->CsFlags &= ~CS_CHECK_PASSWORD;
|
||
|
||
NlRefClientSession( ClientSession );
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
|
||
|
||
//
|
||
// Change the password for this trusted domain.
|
||
//
|
||
|
||
(VOID) NlChangePassword( ClientSession, FALSE, NULL );
|
||
|
||
NlUnrefClientSession( ClientSession );
|
||
|
||
//
|
||
// check to see if we have been asked to leave.
|
||
//
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
|
||
// Start again at the beginning.
|
||
ListEntry = DomainInfo->DomTrustList.Flink;
|
||
|
||
}
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
|
||
}
|
||
|
||
//
|
||
// Scavenge the list of failed forwarded user logons
|
||
//
|
||
|
||
if ( DomainInfo->DomRole == RoleBackup ) {
|
||
NlScavengeOldFailedLogons( DomainInfo );
|
||
}
|
||
|
||
//
|
||
// Scavenge through the server session table.
|
||
//
|
||
|
||
if ( DomainInfo->DomRole == RolePrimary || DomainInfo->DomRole == RoleBackup ) {
|
||
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
NlServerSessionScavenger( DomainInfo );
|
||
|
||
//
|
||
// Pick a DC for each non-authenicated entry in the trust list.
|
||
//
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
return NERR_Success;
|
||
}
|
||
|
||
NlPickTrustedDcForEntireTrustList( DomainInfo, FALSE );
|
||
|
||
}
|
||
|
||
//
|
||
// If the role of this machine isn't known,
|
||
// the role update failed (so schedule another one).
|
||
//
|
||
|
||
if ( DomainInfo->DomRole == RoleInvalid ) {
|
||
NlPrintDom((NL_MISC, DomainInfo,
|
||
"DomainScavenger: Try again to update the role.\n" ));
|
||
|
||
DomFlags = DOM_ROLE_UPDATE_NEEDED;
|
||
NlStartDomainThread( DomainInfo, &DomFlags );
|
||
}
|
||
|
||
//
|
||
// If this is a primary domain and the trust info is not up to date,
|
||
// schedule the trust info update now.
|
||
//
|
||
|
||
if ( DomainInfo->DomFlags & DOM_PRIMARY_DOMAIN ) {
|
||
|
||
if ( WaitForSingleObject( NlGlobalTrustInfoUpToDateEvent, 0 ) == WAIT_TIMEOUT ) {
|
||
NlPrintDom((NL_MISC, DomainInfo,
|
||
"DomainScavenger: Try again to update the trusted domain list.\n" ));
|
||
|
||
DomFlags = DOM_TRUST_UPDATE_NEEDED;
|
||
NlStartDomainThread( DomainInfo, &DomFlags );
|
||
}
|
||
|
||
}
|
||
|
||
#endif // _DC_NETLOGON
|
||
|
||
return NERR_Success;
|
||
UNREFERENCED_PARAMETER( Context );
|
||
}
|
||
|
||
VOID
|
||
NlDcScavenger(
|
||
IN LPVOID ScavengerParam
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function performs the scavenger operation. This function is
|
||
called every 15 mins interval. This function is
|
||
executed on the scavenger thread, thus leaving the main thread to
|
||
process the mailslot messages better.
|
||
|
||
This function is specific to domain controllers.
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
LPWSTR MsgStrings[4];
|
||
ULONG TimePassed = 0;
|
||
LARGE_INTEGER DuplicateEventlogTimeout_100ns;
|
||
|
||
//
|
||
// Reset the scavenger timer to run at the normal interval.
|
||
// Other places (challenge request/response handling) which
|
||
// need more expedient scavenging will reschedule the timer
|
||
// as needed.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
NlGlobalScavengerTimer.Period = NlGlobalParameters.ScavengeInterval * 1000L;
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
//
|
||
// Scavenge one domain at a time
|
||
//
|
||
|
||
if ( NlGlobalTerminate ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
(VOID) NlEnumerateDomains( FALSE, NlDomainScavenger, NULL );
|
||
|
||
//
|
||
// Scavenge expired challenge entries in the
|
||
// global list of outstanding challenges
|
||
//
|
||
|
||
NlScavengeOldChallenges();
|
||
|
||
//
|
||
// If there were clients with no site, see if it's time
|
||
// to log an event -- avoid poluting the event log.
|
||
//
|
||
// Note that we don't use the duplicate event log mechanism
|
||
// as the message we are logging is likely to be different
|
||
// from previous ones due to the count parameter.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalSiteCritSect );
|
||
DuplicateEventlogTimeout_100ns.QuadPart =
|
||
Int32x32To64( NlGlobalParameters.DuplicateEventlogTimeout, 10000000 );
|
||
|
||
if ( NlGlobalNoClientSiteCount > 0 &&
|
||
NlTimeHasElapsedEx(&NlGlobalNoClientSiteEventTime,
|
||
&DuplicateEventlogTimeout_100ns,
|
||
&TimePassed) ) {
|
||
|
||
// Max ULONG is 4294967295 => 11 chars to store it
|
||
WCHAR ConnectionCountStr[11];
|
||
WCHAR DefaultLogMaxSizeStr[11];
|
||
WCHAR LogMaxSizeStr[11];
|
||
|
||
// 20 chars is more than enough: 0xffffffff/3600 = 1193046.47
|
||
WCHAR TimeoutStr[20];
|
||
|
||
//
|
||
// Get the time passed since we logged
|
||
// the event last time
|
||
//
|
||
swprintf( TimeoutStr,
|
||
L"%.2f",
|
||
(double) (NlGlobalParameters.DuplicateEventlogTimeout + TimePassed/1000) / 3600 );
|
||
|
||
swprintf( ConnectionCountStr, L"%lu", NlGlobalNoClientSiteCount );
|
||
swprintf( DefaultLogMaxSizeStr, L"%lu", DEFAULT_MAXIMUM_LOGFILE_SIZE );
|
||
swprintf( LogMaxSizeStr, L"%lu", NlGlobalParameters.LogFileMaxSize );
|
||
|
||
MsgStrings[0] = TimeoutStr;
|
||
MsgStrings[1] = ConnectionCountStr;
|
||
MsgStrings[2] = DefaultLogMaxSizeStr;
|
||
MsgStrings[3] = LogMaxSizeStr;
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonNoSiteForClient,
|
||
EVENTLOG_WARNING_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
4 );
|
||
|
||
//
|
||
// Reset the count
|
||
//
|
||
NlGlobalNoClientSiteCount = 0;
|
||
NlQuerySystemTime( &NlGlobalNoClientSiteEventTime );
|
||
}
|
||
LeaveCriticalSection( &NlGlobalSiteCritSect );
|
||
|
||
//
|
||
// It's OK to run the scavenger again.
|
||
//
|
||
Cleanup:
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
NlGlobalDcScavengerIsRunning = FALSE;
|
||
|
||
|
||
// Reset the StartTime in case this routine takes a long time to process.
|
||
NlQuerySystemTime( &NlGlobalScavengerTimer.StartTime );
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
UNREFERENCED_PARAMETER( ScavengerParam );
|
||
|
||
}
|
||
|
||
VOID
|
||
NlWksScavenger(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function performs the scavenger operation. This function is
|
||
called every 15 mins interval. This function is executed on the main thread.
|
||
|
||
This function is specific to member workstations and member servers
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
ULONG CallAgainPeriod = MAILSLOT_WAIT_FOREVER; // Default to not scavenging again.
|
||
ULONG TempPeriod;
|
||
|
||
|
||
//
|
||
// Change password if neccessary
|
||
//
|
||
|
||
if ( !NlGlobalParameters.DisablePasswordChange ) {
|
||
PCLIENT_SESSION ClientSession;
|
||
|
||
ClientSession = NlRefDomClientSession( NlGlobalDomainInfo );
|
||
|
||
if ( ClientSession != NULL ) {
|
||
(VOID) NlChangePassword( ClientSession, FALSE, &CallAgainPeriod );
|
||
NlUnrefClientSession( ClientSession );
|
||
} else {
|
||
// This can't happen (but try again periodically)
|
||
CallAgainPeriod = 0;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Never scavenge more frequently than the configured rate.
|
||
//
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
NlGlobalScavengerTimer.Period = max( (NlGlobalParameters.ScavengeInterval * 1000L),
|
||
CallAgainPeriod );
|
||
|
||
NlpDumpPeriod( NL_MISC,
|
||
"NlWksScavenger: Can be called again in",
|
||
NlGlobalScavengerTimer.Period );
|
||
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
}
|
||
|
||
|
||
VOID
|
||
NlMainLoop(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Waits for a logon request to arrive at the NETLOGON mailslot.
|
||
|
||
This routine, also, processes several periodic events. These events
|
||
are timed by computing a timeout value on the mailslot read which is the
|
||
time needed before the nearest periodic event needs to be processed.
|
||
After such a timeout, this routine processes the event.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Return iff the service is to exit.
|
||
|
||
mail slot error occurred, eg if someone deleted the NETLOGON
|
||
mail slot explicitly or if the logon server share has been deleted
|
||
and cannot be re-shared.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
DWORD WaitStatus;
|
||
BOOLEAN IgnoreDuplicatesOfThisMessage;
|
||
|
||
BOOLEAN RegNotifyNeeded = TRUE;
|
||
HKEY ParmHandle = NULL;
|
||
HANDLE ParmEventHandle = NULL;
|
||
|
||
BOOLEAN GpRegNotifyNeeded = TRUE;
|
||
HKEY GpParmHandle = NULL;
|
||
HANDLE GpParmEventHandle = NULL;
|
||
|
||
//
|
||
// Variables controlling mailslot read timeout
|
||
//
|
||
|
||
DWORD MainLoopTimeout = 0;
|
||
LARGE_INTEGER TimeNow;
|
||
|
||
TIMER AnnouncerTimer;
|
||
|
||
#define NL_WAIT_TERMINATE 0
|
||
#define NL_WAIT_TIMER 1
|
||
#define NL_WAIT_MAILSLOT 2
|
||
// Optional entries should be at the end.
|
||
ULONG NlWaitWinsock = 0; // 3
|
||
ULONG NlWaitNotify = 0; // 4
|
||
ULONG NlWaitParameters = 0; // 5
|
||
ULONG NlWaitGpParameters = 0; // 6
|
||
#define NL_WAIT_COUNT 7
|
||
|
||
HANDLE WaitHandles[ NL_WAIT_COUNT ];
|
||
DWORD WaitCount = 0;
|
||
|
||
//
|
||
// Initialize handles to wait on.
|
||
//
|
||
|
||
WaitHandles[NL_WAIT_TERMINATE] = NlGlobalTerminateEvent;
|
||
WaitCount++;
|
||
WaitHandles[NL_WAIT_TIMER] = NlGlobalTimerEvent;
|
||
WaitCount++;
|
||
WaitHandles[NL_WAIT_MAILSLOT] = NlGlobalMailslotHandle;
|
||
WaitCount++;
|
||
|
||
//
|
||
// In IP-less environments the Winsock event doesn't exist.
|
||
//
|
||
if ( NlGlobalWinsockPnpEvent != NULL ) {
|
||
NlWaitWinsock = WaitCount;
|
||
WaitHandles[NlWaitWinsock] = NlGlobalWinsockPnpEvent;
|
||
WaitCount++;
|
||
}
|
||
|
||
//
|
||
// When netlogon is run during retail setup
|
||
// (in an attempt to replicate the databases to a BDC),
|
||
// the role is Workstation at the instant netlogon.dll is loaded,
|
||
// therefore, the ChangeLogEvent won't have been initialized.
|
||
//
|
||
|
||
if ( NlGlobalChangeLogEvent != NULL ) {
|
||
NlWaitNotify = WaitCount;
|
||
WaitHandles[NlWaitNotify] = NlGlobalChangeLogEvent;
|
||
WaitCount++;
|
||
}
|
||
|
||
|
||
//
|
||
// Set up a secure channel to any DC in the domain.
|
||
// Don't fail if setup is impossible.
|
||
//
|
||
// We wait until now since this is a potentially lengthy operation.
|
||
// If the user on the workstation is trying to logon immediately after
|
||
// reboot, we'd rather have him wait in netlogon (where we have more
|
||
// control) than have him waiting in MSV.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
PDOMAIN_INFO DomainInfo;
|
||
PCLIENT_SESSION ClientSession;
|
||
|
||
DomainInfo = NlFindNetbiosDomain( NULL, TRUE ); // Primary domain
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
|
||
ClientSession = NlRefDomClientSession(DomainInfo);
|
||
|
||
if ( ClientSession != NULL ) {
|
||
|
||
//
|
||
// Set up a client session if it hasn't been already done
|
||
//
|
||
(VOID) NlTimeoutSetWriterClientSession( ClientSession, 0xFFFFFFFF );
|
||
if ( ClientSession->CsState == CS_IDLE ) {
|
||
(VOID) NlSessionSetup( ClientSession );
|
||
}
|
||
NlResetWriterClientSession( ClientSession );
|
||
|
||
NlUnrefClientSession( ClientSession );
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlMainLoop: Cannot NlRefDomClientSession\n" ));
|
||
}
|
||
|
||
NlDereferenceDomain( DomainInfo );
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlMainLoop: Cannot NlFindNetbiosDomain\n" ));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Force the announce to happen immediately.
|
||
//
|
||
// Actually, wait the announcement period. NlInitTcpRpc will force an "immediate"
|
||
// announcement as soon as TCP RPC is enabled.
|
||
//
|
||
|
||
NlQuerySystemTime( &TimeNow );
|
||
|
||
AnnouncerTimer.StartTime = TimeNow;
|
||
AnnouncerTimer.Period = NlGlobalParameters.Pulse * 1000L;
|
||
|
||
|
||
|
||
NlGlobalApiTimer.StartTime = TimeNow;
|
||
|
||
//
|
||
// It is possible that we missed service notifications to update DNS
|
||
// records on boot because we were not ready to process notifications
|
||
// at that time. So if any of the DNS service bits is set, schedule
|
||
// the DNS scavenger to run immediately to update DNS if it indeed
|
||
// hasn't been done already.
|
||
//
|
||
|
||
if ( !NlGlobalMemberWorkstation &&
|
||
(NlGetDomainFlags(NULL) & DS_DNS_SERVICE_BITS) != 0 ) {
|
||
NlGlobalDnsScavengerTimer.StartTime.QuadPart = 0;
|
||
NlGlobalDnsScavengerTimer.Period = 0;
|
||
}
|
||
|
||
NlPrint((NL_INIT, "Started successfully\n" ));
|
||
|
||
//
|
||
// Loop reading from the Netlogon mailslot
|
||
//
|
||
|
||
IgnoreDuplicatesOfThisMessage = FALSE;
|
||
for ( ;; ) {
|
||
DWORD Timeout;
|
||
|
||
//
|
||
// Issue a mailslot read request if we are domain controller and
|
||
// there is no outstanding read request pending.
|
||
//
|
||
|
||
NlMailslotPostRead( IgnoreDuplicatesOfThisMessage );
|
||
IgnoreDuplicatesOfThisMessage = FALSE;
|
||
|
||
|
||
//
|
||
// Register for registry change notification
|
||
//
|
||
|
||
if ( RegNotifyNeeded || GpRegNotifyNeeded ) {
|
||
ULONG TryCount;
|
||
|
||
//
|
||
// Try couple of times to post the registry
|
||
// notification requests
|
||
//
|
||
for ( TryCount = 0; TryCount < 2; TryCount++ ) {
|
||
NetStatus = NO_ERROR;
|
||
|
||
// Retry the Netlogon Parameters registration on each iteration for resiliency
|
||
if ( ParmHandle == NULL ) {
|
||
ParmHandle = NlOpenNetlogonKey( NL_PARAM_KEY );
|
||
|
||
if (ParmHandle == NULL) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot NlOpenNetlogonKey (ignored)\n" ));
|
||
}
|
||
}
|
||
|
||
if ( ParmEventHandle == NULL ) {
|
||
ParmEventHandle = CreateEvent( NULL, // No security attributes
|
||
TRUE, // Must be manually reset
|
||
FALSE, // Initially not signaled
|
||
NULL ); // No name
|
||
|
||
if ( ParmEventHandle == NULL ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot Create parameter key event %ld (ignored)\n",
|
||
GetLastError() ));
|
||
} else {
|
||
NlWaitParameters = WaitCount;
|
||
WaitHandles[NlWaitParameters] = ParmEventHandle;
|
||
WaitCount++;
|
||
NlAssert( WaitCount <= NL_WAIT_COUNT );
|
||
}
|
||
}
|
||
|
||
if ( RegNotifyNeeded && ParmHandle != NULL && ParmEventHandle != NULL ) {
|
||
NetStatus = RegNotifyChangeKeyValue(
|
||
ParmHandle,
|
||
FALSE, // don't watch subtree
|
||
REG_NOTIFY_CHANGE_LAST_SET,
|
||
ParmEventHandle,
|
||
TRUE ); // Async
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
RegNotifyNeeded = FALSE;
|
||
|
||
// If the key has been manually deleted,
|
||
// recover from it by just closing ParmHandle
|
||
// to reopen it on the second try
|
||
} else if ( NetStatus == ERROR_KEY_DELETED ) {
|
||
NlPrint(( NL_CRITICAL, "Netlogon Parameters key deleted (recover)\n" ));
|
||
RegCloseKey( ParmHandle );
|
||
ParmHandle = NULL;
|
||
ResetEvent( ParmEventHandle );
|
||
} else {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot RegNotifyChangeKeyValue 0x%lx (ignored)\n",
|
||
NetStatus ));
|
||
}
|
||
}
|
||
|
||
// Retry the GP Parameters registration on each iteration for resiliency
|
||
// Note that here we open the Netlogon key (not Netlogon\Parameters key)
|
||
// and we watch for the subtree. We do this for debugging purposes to
|
||
// see whether GP is enabled for Netlogon by checking if the GP created
|
||
// Parameters section exists. See nlparse.c.
|
||
if ( GpParmHandle == NULL ) {
|
||
GpParmHandle = NlOpenNetlogonKey( NL_GP_KEY );
|
||
|
||
if (GpParmHandle == NULL) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot NlOpenNetlogonKey for GP (ignored)\n" ));
|
||
}
|
||
}
|
||
|
||
if ( GpParmEventHandle == NULL ) {
|
||
GpParmEventHandle = CreateEvent( NULL, // No security attributes
|
||
TRUE, // Must be manually reset
|
||
FALSE, // Initially not signaled
|
||
NULL ); // No name
|
||
|
||
if ( GpParmEventHandle == NULL ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot Create GP parameter key event %ld (ignored)\n",
|
||
GetLastError() ));
|
||
} else {
|
||
NlWaitGpParameters = WaitCount;
|
||
WaitHandles[NlWaitGpParameters] = GpParmEventHandle;
|
||
WaitCount++;
|
||
NlAssert( WaitCount <= NL_WAIT_COUNT );
|
||
}
|
||
}
|
||
|
||
if ( GpRegNotifyNeeded && GpParmHandle != NULL && GpParmEventHandle != NULL ) {
|
||
NetStatus = RegNotifyChangeKeyValue(
|
||
GpParmHandle,
|
||
TRUE, // watch subtree
|
||
REG_NOTIFY_CHANGE_LAST_SET,
|
||
GpParmEventHandle,
|
||
TRUE ); // Async
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
GpRegNotifyNeeded = FALSE;
|
||
|
||
// If GP has deleted the key,
|
||
// recover from it by just closing GpParmHandle
|
||
// to reopen it on the second try
|
||
} else if ( NetStatus == ERROR_KEY_DELETED ) {
|
||
NlPrint(( NL_CRITICAL, "Netlogon GP Parameters key deleted (recover)\n" ));
|
||
RegCloseKey( GpParmHandle );
|
||
GpParmHandle = NULL;
|
||
ResetEvent( GpParmEventHandle );
|
||
} else {
|
||
NlPrint(( NL_CRITICAL,
|
||
"Cannot RegNotifyChangeKeyValue for GP 0x%lx (ignored)\n",
|
||
NetStatus ));
|
||
}
|
||
}
|
||
|
||
//
|
||
// If no error occured, no need to retry
|
||
//
|
||
if ( NetStatus == NO_ERROR ) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
NlReparse();
|
||
|
||
//
|
||
// Grab any changed parameters that affect this routine.
|
||
//
|
||
AnnouncerTimer.Period = NlGlobalParameters.Pulse * 1000L;
|
||
}
|
||
|
||
|
||
//
|
||
// Wait for the next interesting event.
|
||
//
|
||
// On each iteration of the loop,
|
||
// we do an "extra" wait with a timeout of 0 to force mailslot
|
||
// processing to be more important that timeout processing.
|
||
//
|
||
// Since we can only compute a non-zero timeout by processing the
|
||
// timeout events, using a constant 0 allows us to process all
|
||
// non-timeout events before we compute the next true timeout value.
|
||
//
|
||
// This is especially important for handling async discovery.
|
||
// Our mailslot may be full of responses to discovery queries and
|
||
// we only have a 5 second timer before we ask for more responses.
|
||
// We want to avoid asking for additional responses until we finish
|
||
// processing those we have.
|
||
//
|
||
|
||
if ( MainLoopTimeout != 0 ) {
|
||
NlPrint((NL_MAILSLOT_TEXT,
|
||
"Going to wait on mailslot. (Timeout: %ld)\n",
|
||
MainLoopTimeout));
|
||
}
|
||
|
||
WaitStatus = WaitForMultipleObjects( WaitCount,
|
||
WaitHandles,
|
||
FALSE, // Wait for ANY handle
|
||
MainLoopTimeout );
|
||
|
||
MainLoopTimeout = 0; // Set default timeout
|
||
|
||
|
||
//
|
||
// If we've been asked to terminate,
|
||
// do so immediately
|
||
//
|
||
|
||
if ( WaitStatus == NL_WAIT_TERMINATE ) { // service termination
|
||
goto Cleanup;
|
||
|
||
|
||
//
|
||
// Process timeouts and determine the timeout for the next iteration
|
||
//
|
||
|
||
} else if ( WaitStatus == WAIT_TIMEOUT || // timeout
|
||
WaitStatus == NL_WAIT_TIMER ) { // someone changed a timer
|
||
|
||
//
|
||
// Assume there is no timeout to do.
|
||
//
|
||
// On each iteration of the loop we only process a single timer.
|
||
// That ensures other events are more important than timers.
|
||
//
|
||
|
||
Timeout = (DWORD) -1;
|
||
NlQuerySystemTime( &TimeNow );
|
||
|
||
|
||
//
|
||
// On the primary, timeout announcements to BDCs
|
||
//
|
||
|
||
if ( NlGlobalPdcDoReplication &&
|
||
TimerExpired( &NlGlobalPendingBdcTimer, &TimeNow, &Timeout )) {
|
||
|
||
NlPrimaryAnnouncementTimeout();
|
||
NlGlobalPendingBdcTimer.StartTime = TimeNow;
|
||
|
||
|
||
//
|
||
// Check the scavenger timer
|
||
//
|
||
|
||
} else if ( TimerExpired( &NlGlobalScavengerTimer, &TimeNow, &Timeout ) ) {
|
||
|
||
//
|
||
// On workstation run the scavenger on main thread.
|
||
//
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
NlWksScavenger();
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
//
|
||
// On domain controller, start scavenger thread if it is not
|
||
// running already.
|
||
//
|
||
} else {
|
||
|
||
if ( !NlGlobalDcScavengerIsRunning ) {
|
||
|
||
if ( NlQueueWorkItem( &NlGlobalDcScavengerWorkItem, TRUE, FALSE ) ) {
|
||
NlGlobalDcScavengerIsRunning = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// NlDcScavenger sets the StartTime,too
|
||
// (But we have to reset the timer here to prevent it from
|
||
// going off immediately again. We need to reset the period
|
||
// (as well as the start time) since the period is set in
|
||
// the registry notification processing to zero.)
|
||
//
|
||
|
||
NlGlobalScavengerTimer.StartTime = TimeNow;
|
||
NlGlobalScavengerTimer.Period = NlGlobalParameters.ScavengeInterval * 1000L;
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
//
|
||
// Check the API timer
|
||
//
|
||
|
||
} else if ( TimerExpired( &NlGlobalApiTimer, &TimeNow, &Timeout)) {
|
||
|
||
//
|
||
// On worktstation, do the work in the main loop
|
||
//
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NlTimeoutApiClientSession( NlGlobalDomainInfo );
|
||
|
||
//
|
||
// On DC, timout APIs on all Hosted domains.
|
||
// Do this in domain threads so that not to block
|
||
// the main thread (which is critical for a DC) as
|
||
// the API timeout involves RPC.
|
||
//
|
||
} else {
|
||
DWORD DomFlags = DOM_API_TIMEOUT_NEEDED;
|
||
NlEnumerateDomains( FALSE, NlStartDomainThread, &DomFlags );
|
||
}
|
||
|
||
NlGlobalApiTimer.StartTime = TimeNow;
|
||
|
||
//
|
||
// Check the DNS Scavenger timer
|
||
//
|
||
|
||
} else if ( TimerExpired( &NlGlobalDnsScavengerTimer, &TimeNow, &Timeout)) {
|
||
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
if ( !NlGlobalDnsScavengerIsRunning ) {
|
||
|
||
if ( NlQueueWorkItem( &NlGlobalDnsScavengerWorkItem, TRUE, FALSE ) ) {
|
||
// Let the scavenger thread set the Period
|
||
NlGlobalDnsScavengerTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
||
NlGlobalDnsScavengerIsRunning = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// DnsScavenger sets the StartTime,too
|
||
// (But we have to reset the timer here to prevent it from
|
||
// going off immediately again.)
|
||
NlGlobalDnsScavengerTimer.StartTime = TimeNow;
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
|
||
|
||
|
||
//
|
||
// If we're the primary,
|
||
// periodically do announcements
|
||
//
|
||
|
||
} else if (NlGlobalPdcDoReplication &&
|
||
TimerExpired( &AnnouncerTimer, &TimeNow, &Timeout ) ) {
|
||
|
||
NlPrimaryAnnouncement( 0 );
|
||
AnnouncerTimer.StartTime = TimeNow;
|
||
|
||
//
|
||
// If we've gotten this far,
|
||
// we know the only thing left to do is to wait for the next event.
|
||
//
|
||
|
||
} else {
|
||
MainLoopTimeout = Timeout;
|
||
}
|
||
|
||
|
||
//
|
||
// Process interesting changelog events.
|
||
//
|
||
|
||
} else if ( WaitStatus == NlWaitNotify ) {
|
||
|
||
|
||
//
|
||
// If a "replicate immediately" event has happened,
|
||
// send a primary announcement.
|
||
//
|
||
LOCK_CHANGELOG();
|
||
if ( NlGlobalChangeLogReplicateImmediately ) {
|
||
|
||
NlGlobalChangeLogReplicateImmediately = FALSE;
|
||
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification to replicate immediately\n" ));
|
||
|
||
UNLOCK_CHANGELOG();
|
||
|
||
//
|
||
// Ignore this event on BDCs.
|
||
//
|
||
// This event is never set on a BDC. It may have been set
|
||
// prior to the role change while this machine was a PDC.
|
||
//
|
||
|
||
if ( NlGlobalPdcDoReplication ) {
|
||
NlPrimaryAnnouncement( ANNOUNCE_IMMEDIATE );
|
||
}
|
||
LOCK_CHANGELOG();
|
||
}
|
||
|
||
//
|
||
// Process any notifications that need processing
|
||
//
|
||
|
||
while ( !IsListEmpty( &NlGlobalChangeLogNotifications ) ) {
|
||
PLIST_ENTRY ListEntry;
|
||
PCHANGELOG_NOTIFICATION Notification;
|
||
DWORD DomFlags;
|
||
|
||
ListEntry = RemoveHeadList( &NlGlobalChangeLogNotifications );
|
||
UNLOCK_CHANGELOG();
|
||
|
||
Notification = CONTAINING_RECORD(
|
||
ListEntry,
|
||
CHANGELOG_NOTIFICATION,
|
||
Next );
|
||
|
||
switch ( Notification->EntryType ) {
|
||
case ChangeLogTrustAccountAdded: {
|
||
NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType =
|
||
*(NETLOGON_SECURE_CHANNEL_TYPE*)&Notification->ObjectGuid;
|
||
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that trust account added (or changed) %wZ 0x%lx %lx\n",
|
||
&Notification->ObjectName,
|
||
Notification->ObjectRid,
|
||
SecureChannelType ));
|
||
|
||
// This event happens on both a PDC and BDC
|
||
(VOID) NlCheckServerSession( Notification->ObjectRid,
|
||
&Notification->ObjectName,
|
||
SecureChannelType );
|
||
|
||
break;
|
||
}
|
||
|
||
case ChangeLogTrustAccountDeleted:
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that trust account deleted\n" ));
|
||
// This event happens on both a PDC and BDC
|
||
NlFreeServerSessionForAccount( &Notification->ObjectName );
|
||
break;
|
||
|
||
case ChangeLogTrustDeleted:
|
||
case ChangeLogTrustAdded:
|
||
|
||
//
|
||
// When a TrustedDomainObject is deleted,
|
||
// don't just delete the ClientSession.
|
||
// There still might be an XREF object stating an indirect trust.
|
||
//
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that TDO added or deleted.\n" ));
|
||
DomFlags = DOM_TRUST_UPDATE_NEEDED;
|
||
NlStartDomainThread( NlGlobalDomainInfo, &DomFlags );
|
||
break;
|
||
|
||
case ChangeLogRoleChanged:
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that role changed\n" ));
|
||
DomFlags = DOM_ROLE_UPDATE_NEEDED;
|
||
NlStartDomainThread( NlGlobalDomainInfo, &DomFlags );
|
||
break;
|
||
|
||
case ChangeDnsNames:
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that registered DNS names should change\n" ));
|
||
//
|
||
// Register any names that need it.
|
||
// (The caller passed TRUE or FALSE in ObjectRid to indicate whether
|
||
// or not to force re-registration.)
|
||
//
|
||
NlDnsPnp( Notification->ObjectRid );
|
||
break;
|
||
|
||
case ChangeLogDsChanged: {
|
||
NL_DS_CHANGE_TYPE DsChangeType = (NL_DS_CHANGE_TYPE) Notification->ObjectRid;
|
||
|
||
switch ( DsChangeType ) {
|
||
case NlSubnetObjectChanged:
|
||
case NlSiteObjectChanged:
|
||
case NlSiteChanged:
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
|
||
BOOLEAN SiteNameChanged;
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that DS site info changed\n" ));
|
||
(VOID) NlSitesAddSubnetFromDs( &SiteNameChanged );
|
||
|
||
//
|
||
// If the Site Name changed,
|
||
// tell DNS to re-register its names.
|
||
//
|
||
if ( SiteNameChanged || NlGlobalParameters.AutoSiteCoverage ) {
|
||
|
||
//
|
||
// We have no way to sync with ISM. So
|
||
// wait awhile and let ISM find out about the changes.
|
||
//
|
||
if ( NlGlobalParameters.AutoSiteCoverage ) {
|
||
Sleep( 2000 );
|
||
}
|
||
NlDnsPnp( FALSE );
|
||
}
|
||
}
|
||
break;
|
||
|
||
case NlNdncChanged:
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
BOOLEAN ServicedNdncChanged = FALSE;
|
||
NlPrint(( NL_MISC, "NlMainLoop: Notification that NDNC changed\n" ));
|
||
|
||
NetStatus = NlUpdateServicedNdncs(
|
||
NlGlobalDomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
NlGlobalDomainInfo->DomUnicodeDnsHostNameString.Buffer,
|
||
FALSE, // Don't call NlExit on failure
|
||
&ServicedNdncChanged );
|
||
|
||
if ( NetStatus == NO_ERROR && ServicedNdncChanged ) {
|
||
NlDnsPnp( FALSE );
|
||
}
|
||
}
|
||
break;
|
||
|
||
case NlDnsRootAliasChanged:
|
||
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
NTSTATUS Status;
|
||
BOOL AliasNamesChanged = FALSE;
|
||
NlPrint(( NL_MISC, "NlMainLoop: Notification that DnsRootAlias changed\n" ));
|
||
|
||
Status = NlUpdateDnsRootAlias( NlGlobalDomainInfo,
|
||
&AliasNamesChanged );
|
||
|
||
if ( NT_SUCCESS(Status) && AliasNamesChanged ) {
|
||
NlDnsPnp( FALSE );
|
||
}
|
||
}
|
||
break;
|
||
|
||
case NlOrgChanged:
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that ORG tree changed\n" ));
|
||
DomFlags = DOM_TRUST_UPDATE_NEEDED;
|
||
NlStartDomainThread( NlGlobalDomainInfo, &DomFlags );
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"Invalid DsChangeType: %ld\n",
|
||
DsChangeType ));
|
||
|
||
}
|
||
break;
|
||
}
|
||
|
||
case ChangeLogLsaPolicyChanged: {
|
||
POLICY_NOTIFICATION_INFORMATION_CLASS LsaPolicyChangeType =
|
||
(POLICY_NOTIFICATION_INFORMATION_CLASS) Notification->ObjectRid;
|
||
NlPrint((NL_MISC,
|
||
"NlMainLoop: Notification that LSA Policy changed\n" ));
|
||
|
||
switch ( LsaPolicyChangeType ) {
|
||
case PolicyNotifyDnsDomainInformation: {
|
||
LPWSTR DomainName = NULL;
|
||
LPWSTR DnsDomainName = NULL;
|
||
PSID AccountDomainSid = NULL;
|
||
PSID PrimaryDomainSid = NULL;
|
||
GUID *PrimaryDomainGuid = NULL;
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
BOOLEAN DnsForestNameChanged;
|
||
BOOLEAN DnsDomainNameChanged;
|
||
BOOLEAN NetbiosDomainNameChanged;
|
||
BOOLEAN DomainGuidChanged;
|
||
NTSTATUS Status;
|
||
|
||
|
||
//
|
||
// Get the updated information from the LSA.
|
||
//
|
||
// (Update the TreeName as a side effect.)
|
||
//
|
||
//
|
||
NetStatus = NlGetDomainName(
|
||
&DomainName,
|
||
&DnsDomainName,
|
||
&AccountDomainSid,
|
||
&PrimaryDomainSid,
|
||
&PrimaryDomainGuid,
|
||
&DnsForestNameChanged );
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
PDOMAIN_INFO DomainInfo;
|
||
|
||
DomainInfo = NlFindNetbiosDomain( NULL, TRUE ); // Primary domain
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
//
|
||
// Set the DomainNames on the domain.
|
||
//
|
||
|
||
// ???: retry later on failure
|
||
(VOID) NlSetDomainNameInDomainInfo(
|
||
DomainInfo,
|
||
DnsDomainName,
|
||
DomainName,
|
||
PrimaryDomainGuid,
|
||
&DnsDomainNameChanged,
|
||
&NetbiosDomainNameChanged,
|
||
&DomainGuidChanged );
|
||
|
||
//
|
||
// If the Netbios domain name has changed,
|
||
// re-register the <DomainName>[1B] name.
|
||
//
|
||
// Merely flag the fact here that it needs to be renamed.
|
||
// Wait to do the actual rename after the bowser
|
||
// knows about the new emulated domain.
|
||
//
|
||
|
||
EnterCriticalSection(&NlGlobalDomainCritSect);
|
||
if ( NetbiosDomainNameChanged && DomainInfo->DomRole == RolePrimary ) {
|
||
DomainInfo->DomFlags |= DOM_RENAMED_1B_NAME;
|
||
}
|
||
LeaveCriticalSection(&NlGlobalDomainCritSect);
|
||
|
||
//
|
||
// If there is a client session associated with this domain,
|
||
// set the information there, too.
|
||
//
|
||
|
||
ClientSession = NlRefDomClientSession( DomainInfo );
|
||
|
||
if ( ClientSession != NULL) {
|
||
|
||
//
|
||
// Must be a writer to change
|
||
if ( NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
|
||
UNICODE_STRING NetbiosDomainNameString;
|
||
UNICODE_STRING DnsDomainNameString;
|
||
|
||
|
||
//
|
||
// Update any names that are on the ClientSession structure.
|
||
//
|
||
// ???: The routine below interprets a NULL parameter as
|
||
// a lack of interest in changing the name. We're calling it
|
||
// as specifying that the name no longer exists.
|
||
// (This only applies to the GUID since the other fields
|
||
// are never passed in as NULL.)
|
||
// But that means this is a NT 4 domain and the GUID won't be used.
|
||
//
|
||
|
||
RtlInitUnicodeString( &NetbiosDomainNameString, DomainName );
|
||
RtlInitUnicodeString( &DnsDomainNameString, DnsDomainName );
|
||
|
||
// ???: retry later on failure
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
(VOID ) NlSetNamesClientSession( DomainInfo->DomClientSession,
|
||
&NetbiosDomainNameString,
|
||
&DnsDomainNameString,
|
||
PrimaryDomainSid,
|
||
PrimaryDomainGuid );
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
|
||
//
|
||
// If the domain changed,
|
||
// Drop the secure channel since it is to the wrong DC.
|
||
//
|
||
|
||
if ( DnsDomainNameChanged ||
|
||
NetbiosDomainNameChanged ||
|
||
DomainGuidChanged ) {
|
||
|
||
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
|
||
|
||
//
|
||
// Indicate that we no longer know what site we're in.
|
||
//
|
||
NlSetDynamicSiteName( NULL );
|
||
|
||
//
|
||
// Grab the trusted domain list from where join left it.
|
||
//
|
||
|
||
(VOID) NlReadPersitantTrustedDomainList();
|
||
|
||
}
|
||
|
||
NlResetWriterClientSession( ClientSession );
|
||
}
|
||
|
||
NlUnrefClientSession( ClientSession );
|
||
|
||
}
|
||
|
||
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
//
|
||
// If one of the names that changed is one of the
|
||
// names registered in DNS,
|
||
// update any DNS names
|
||
//
|
||
|
||
if ( (DnsForestNameChanged ||
|
||
DnsDomainNameChanged ||
|
||
DomainGuidChanged ) &&
|
||
!NlGlobalMemberWorkstation ) {
|
||
NlDnsPnp( FALSE );
|
||
}
|
||
|
||
//
|
||
// Tell the browser about the domain rename
|
||
//
|
||
|
||
Status = NlBrowserRenameDomain( NULL, DomainName );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"Browser cannot rename domain to: %ws 0x%lx\n",
|
||
DomainName,
|
||
Status ));
|
||
}
|
||
|
||
}
|
||
|
||
|
||
if ( DomainName != NULL ) {
|
||
(VOID)LocalFree( DomainName );
|
||
}
|
||
if ( DnsDomainName != NULL ) {
|
||
(VOID)LocalFree( DnsDomainName );
|
||
}
|
||
if ( AccountDomainSid != NULL ) {
|
||
(VOID)LocalFree( AccountDomainSid );
|
||
}
|
||
if ( PrimaryDomainSid != NULL ) {
|
||
(VOID)LocalFree( PrimaryDomainSid );
|
||
}
|
||
if ( PrimaryDomainGuid != NULL ) {
|
||
(VOID)LocalFree( PrimaryDomainGuid );
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"Invalid LsaPolicyChangeType: %ld\n",
|
||
LsaPolicyChangeType ));
|
||
|
||
}
|
||
break;
|
||
}
|
||
|
||
//
|
||
// NTDS-DSA object deleted
|
||
//
|
||
|
||
case ChangeLogNtdsDsaDeleted:
|
||
(VOID) NlDnsNtdsDsaDeletion (
|
||
Notification->DomainName.Buffer,
|
||
&Notification->DomainGuid,
|
||
&Notification->ObjectGuid,
|
||
Notification->ObjectName.Buffer );
|
||
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"Invalid ChangeLogNotification: %ld %wZ\n",
|
||
Notification->EntryType,
|
||
&Notification->ObjectName ));
|
||
|
||
}
|
||
|
||
NetpMemoryFree( Notification );
|
||
LOCK_CHANGELOG();
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
|
||
//
|
||
// Process WINSOCK PNP events.
|
||
//
|
||
|
||
} else if ( WaitStatus == NlWaitWinsock ) {
|
||
|
||
//
|
||
// Get the new list of IP addresses
|
||
//
|
||
|
||
if ( NlHandleWsaPnp() ) {
|
||
//
|
||
// The list changed.
|
||
//
|
||
if ( !NlGlobalMemberWorkstation ) {
|
||
NlDnsPnp( TRUE );
|
||
|
||
//
|
||
// Flush any caches that aren't valid any more since there
|
||
// is now a new transport
|
||
//
|
||
// ?? Differentiate between adding a transport and removing one
|
||
//
|
||
NlFlushCacheOnPnp();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Process mailslot messages.
|
||
//
|
||
|
||
} else if ( WaitStatus == NL_WAIT_MAILSLOT ) {
|
||
PDOMAIN_INFO DomainInfo;
|
||
DWORD Version;
|
||
DWORD VersionFlags;
|
||
DWORD BytesRead;
|
||
|
||
LPBYTE Message;
|
||
LPWSTR TransportName;
|
||
PSOCKADDR ClientSockAddr;
|
||
PNL_TRANSPORT Transport;
|
||
LPWSTR ServerOrDomainName;
|
||
NETLOGON_PNP_OPCODE NlPnpOpcode;
|
||
|
||
//
|
||
// Variables for unmarshalling the message read.
|
||
//
|
||
|
||
PCHAR Where;
|
||
LPSTR OemWorkstationName;
|
||
LPSTR OemUserName;
|
||
LPSTR OemMailslotName;
|
||
|
||
LPWSTR UnicodeWorkstationName;
|
||
LPWSTR UnicodeUserName;
|
||
|
||
LPSTR OemTemp;
|
||
|
||
LPWSTR UnicodeTemp;
|
||
|
||
DWORD ResponseBufferSize;
|
||
BYTE ResponseBuffer[NETLOGON_MAX_MS_SIZE]; // Buffer to build response in
|
||
|
||
|
||
if ( !NlMailslotOverlappedResult( &Message,
|
||
&BytesRead,
|
||
&TransportName,
|
||
&Transport,
|
||
&ClientSockAddr,
|
||
&ServerOrDomainName,
|
||
&IgnoreDuplicatesOfThisMessage,
|
||
&NlPnpOpcode )){
|
||
// Just continue if there really isn't a message
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// If this is a PNP notification,
|
||
// process it.
|
||
//
|
||
|
||
if ( NlPnpOpcode != NlPnpMailslotMessage ) {
|
||
BOOLEAN IpTransportChanged = FALSE;
|
||
|
||
switch ( NlPnpOpcode ) {
|
||
case NlPnpTransportBind:
|
||
case NlPnpNewIpAddress:
|
||
if (!NlTransportAddTransportName(TransportName, &IpTransportChanged )) {
|
||
NlPrint((NL_CRITICAL,
|
||
"PNP: %ws: cannot add transport.\n",
|
||
TransportName ));
|
||
}
|
||
|
||
//
|
||
// Flush any caches that aren't valid any more since there
|
||
// is now a new transport
|
||
//
|
||
NlFlushCacheOnPnp();
|
||
|
||
break;
|
||
|
||
case NlPnpTransportUnbind:
|
||
IpTransportChanged = NlTransportDisableTransportName( TransportName );
|
||
break;
|
||
|
||
case NlPnpDomainRename:
|
||
NlPrint((NL_DOMAIN,
|
||
"PNP: Bowser says the domain has been renamed\n" ));
|
||
|
||
//
|
||
// Now that the hosted domain name in the bowser
|
||
// matches the one in netlogon,
|
||
// Ensure the DomainName<1B> names are properly registered.
|
||
//
|
||
|
||
(VOID) NlEnumerateDomains( FALSE, NlBrowserFixAllNames, NULL );
|
||
break;
|
||
|
||
case NlPnpNewRole:
|
||
// We don't care that the browser has a new role.
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"Unknown PNP opcode 0x%x\n",
|
||
NlPnpOpcode ));
|
||
break;
|
||
}
|
||
|
||
#ifdef notdef
|
||
// This is now done based on winsock PNP events.
|
||
//
|
||
// If any IP transport has been added,
|
||
// update any DNS names
|
||
//
|
||
|
||
if ( IpTransportChanged && !NlGlobalMemberWorkstation ) {
|
||
NlDnsPnp( TRUE );
|
||
}
|
||
#endif // notdef
|
||
|
||
|
||
// Just continue if there really isn't a message
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Ignore mailslot messages to NETLOGON mailslot on workstation.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NlPrint((NL_CRITICAL,"NETLOGON mailslot on workstation (ignored)\n" ));
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// ASSERT: Message and BytesRead describe a newly read message
|
||
//
|
||
//
|
||
// Got a message. Check for bad length just in case.
|
||
//
|
||
|
||
if (BytesRead < sizeof(unsigned short) ) {
|
||
NlPrint((NL_CRITICAL,"message size bad %ld\n", BytesRead ));
|
||
continue; // Need at least an opcode
|
||
}
|
||
|
||
//
|
||
// Here with a request to process in the Message.
|
||
//
|
||
|
||
Version = NetpLogonGetMessageVersion( Message, &BytesRead, &VersionFlags );
|
||
|
||
if (Version == LMUNKNOWNNT_MESSAGE) {
|
||
|
||
//
|
||
// received a non-supported NT message.
|
||
//
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"Received a non-supported NT message, Opcode is 0x%x\n",
|
||
((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
|
||
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// Determine which domain this message came in for.
|
||
//
|
||
|
||
DomainInfo = NlFindNetbiosDomain( ServerOrDomainName, FALSE );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
DomainInfo = NlFindDomainByServerName( ServerOrDomainName );
|
||
if ( DomainInfo == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"%ws: Received message for this unsupported domain\n",
|
||
ServerOrDomainName ));
|
||
continue;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Handle a logon request from a UAS client
|
||
//
|
||
|
||
switch ( ((PNETLOGON_LOGON_QUERY)Message)->Opcode) {
|
||
case LOGON_REQUEST: {
|
||
USHORT RequestCount;
|
||
|
||
//
|
||
// Unmarshall the incoming message.
|
||
//
|
||
|
||
if ( Version == LMNT_MESSAGE ) {
|
||
break;
|
||
}
|
||
|
||
Where = ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName;
|
||
if ( !NetpLogonGetOemString(
|
||
(PNETLOGON_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName),
|
||
&OemWorkstationName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetOemString(
|
||
(PNETLOGON_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->UserName),
|
||
&OemUserName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetOemString(
|
||
(PNETLOGON_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->MailslotName),
|
||
&OemMailslotName )) {
|
||
break;
|
||
}
|
||
|
||
// LM 2.x puts request count right before token
|
||
Where = Message + BytesRead - 2;
|
||
if ( !NetpLogonGetBytes(
|
||
(PNETLOGON_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->RequestCount),
|
||
&RequestCount )) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Handle the logon request
|
||
//
|
||
|
||
UnicodeUserName = NetpLogonOemToUnicode( OemUserName );
|
||
if ( UnicodeUserName == NULL ) {
|
||
break;
|
||
}
|
||
|
||
UnicodeWorkstationName = NetpLogonOemToUnicode( OemWorkstationName );
|
||
if( UnicodeWorkstationName == NULL ) {
|
||
NetpMemoryFree( UnicodeUserName );
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Handle the primary query request
|
||
//
|
||
|
||
if ( LogonRequestHandler(
|
||
Transport->TransportName,
|
||
DomainInfo,
|
||
FALSE, // don't use name aliases
|
||
NULL, // Domain Sid not known
|
||
Version,
|
||
VersionFlags,
|
||
UnicodeUserName,
|
||
RequestCount,
|
||
UnicodeWorkstationName,
|
||
USER_NORMAL_ACCOUNT,
|
||
Transport->IpAddress,
|
||
ClientSockAddr,
|
||
ResponseBuffer,
|
||
&ResponseBufferSize ) ) {
|
||
|
||
NTSTATUS Status;
|
||
|
||
Status = NlBrowserSendDatagram( DomainInfo,
|
||
0,
|
||
UnicodeWorkstationName,
|
||
ComputerName,
|
||
Transport->TransportName,
|
||
OemMailslotName,
|
||
ResponseBuffer,
|
||
ResponseBufferSize,
|
||
NULL ); // Don't flush Netbios cache
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
IgnoreDuplicatesOfThisMessage = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
NetpMemoryFree( UnicodeWorkstationName );
|
||
NetpMemoryFree( UnicodeUserName );
|
||
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Handle a logon request from a SAM client
|
||
//
|
||
|
||
case LOGON_SAM_LOGON_REQUEST: {
|
||
USHORT RequestCount;
|
||
ULONG AllowableAccountControlBits;
|
||
DWORD DomainSidSize;
|
||
PCHAR DomainSid = NULL;
|
||
|
||
//
|
||
// Unmarshall the incoming message.
|
||
//
|
||
|
||
|
||
if ( Version != LMNT_MESSAGE ) {
|
||
break;
|
||
}
|
||
|
||
RequestCount = ((PNETLOGON_SAM_LOGON_REQUEST)Message)->RequestCount;
|
||
|
||
Where = (PCHAR)
|
||
(((PNETLOGON_SAM_LOGON_REQUEST)Message)->UnicodeComputerName);
|
||
|
||
if ( !NetpLogonGetUnicodeString(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
||
UnicodeComputerName),
|
||
&UnicodeWorkstationName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetUnicodeString(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
||
UnicodeUserName),
|
||
&UnicodeUserName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetOemString(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
||
MailslotName),
|
||
&OemMailslotName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetBytes(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
||
AllowableAccountControlBits),
|
||
&AllowableAccountControlBits )) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Get the domain SID.
|
||
//
|
||
// Don't make the following check mandatory. Chicago
|
||
// uses this message type without the SID present. Oct 1993.
|
||
//
|
||
|
||
|
||
if( Where < ((PCHAR)Message + BytesRead ) ) {
|
||
|
||
//
|
||
// Read Domain SID Length
|
||
//
|
||
|
||
if ( !NetpLogonGetBytes(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
||
DomainSidSize),
|
||
&DomainSidSize )) {
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
//
|
||
// Read the SID itself.
|
||
//
|
||
|
||
if( DomainSidSize > 0 ) {
|
||
|
||
|
||
if ( !NetpLogonGetDomainSID(
|
||
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
||
BytesRead,
|
||
&Where,
|
||
DomainSidSize,
|
||
&DomainSid )) {
|
||
|
||
break;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Handle the logon request
|
||
//
|
||
|
||
if ( LogonRequestHandler(
|
||
Transport->TransportName,
|
||
DomainInfo,
|
||
FALSE, // don't use name aliases
|
||
DomainSid,
|
||
Version,
|
||
VersionFlags,
|
||
UnicodeUserName,
|
||
RequestCount,
|
||
UnicodeWorkstationName,
|
||
AllowableAccountControlBits,
|
||
Transport->IpAddress,
|
||
ClientSockAddr,
|
||
ResponseBuffer,
|
||
&ResponseBufferSize ) ) {
|
||
NTSTATUS Status;
|
||
|
||
Status = NlBrowserSendDatagram( DomainInfo,
|
||
0,
|
||
UnicodeWorkstationName,
|
||
ComputerName,
|
||
Transport->TransportName,
|
||
OemMailslotName,
|
||
ResponseBuffer,
|
||
ResponseBufferSize,
|
||
NULL ); // Don't flush Netbios cache
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
IgnoreDuplicatesOfThisMessage = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Handle Logon Central query.
|
||
//
|
||
// This query could be sent by either LM1.0, LM 2.0 or LM NT Netlogon
|
||
// services. We ignore LM 2.0 and LM NT queries since they are merely
|
||
// trying
|
||
// to find out if there are any LM1.0 netlogon services in the domain.
|
||
// For LM 1.0 we respond with a LOGON_CENTRAL_RESPONSE to prevent the
|
||
// starting LM1.0 netlogon service from starting.
|
||
//
|
||
|
||
case LOGON_CENTRAL_QUERY:
|
||
|
||
if ( Version != LMUNKNOWN_MESSAGE ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Drop on through to LOGON_DISTRIB_QUERY to send the response
|
||
//
|
||
|
||
|
||
//
|
||
// Handle a Logon Disrib query
|
||
//
|
||
// LM2.0 NETLOGON server never sends this query hence it
|
||
// must be another LM1.0 NETLOGON server trying to start up
|
||
// in non-centralized mode. LM2.0 NETLOGON server will respond
|
||
// with LOGON_CENTRAL_RESPONSE to prevent this.
|
||
//
|
||
|
||
case LOGON_DISTRIB_QUERY:
|
||
|
||
|
||
//
|
||
// Unmarshall the incoming message.
|
||
//
|
||
|
||
Where = ((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
|
||
if ( !NetpLogonGetOemString(
|
||
Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
|
||
&OemWorkstationName )) {
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetOemString(
|
||
Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
|
||
&OemMailslotName )) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Build the response
|
||
//
|
||
|
||
((PNETLOGON_LOGON_QUERY)ResponseBuffer)->Opcode = LOGON_CENTRAL_RESPONSE;
|
||
ResponseBufferSize = sizeof( unsigned short); // opcode only
|
||
|
||
#if NETLOGONDBG
|
||
NlPrintDom((NL_MAILSLOT, DomainInfo,
|
||
"Sent '%s' message to %s[%s] on %ws.\n",
|
||
NlMailslotOpcode(((PNETLOGON_LOGON_QUERY)ResponseBuffer)->Opcode),
|
||
OemWorkstationName,
|
||
NlDgrNameType(ComputerName),
|
||
TransportName ));
|
||
#endif // NETLOGONDBG
|
||
|
||
(VOID) NlBrowserSendDatagramA( DomainInfo,
|
||
0,
|
||
OemWorkstationName,
|
||
ComputerName,
|
||
TransportName,
|
||
OemMailslotName,
|
||
ResponseBuffer,
|
||
ResponseBufferSize );
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// Handle LOGON_PRIMARY_QUERY
|
||
//
|
||
// If we're the PDC, always respond to this message
|
||
// identifying ourselves.
|
||
//
|
||
// Otherwise, only respond to the message if it is from a Lanman 2.x
|
||
// netlogon trying to see if it can start up as a PDC. In that
|
||
// case, pretend we are a PDC to prevent the Lanman 2.x PDC from
|
||
// starting.
|
||
//
|
||
//
|
||
|
||
case LOGON_PRIMARY_QUERY:
|
||
|
||
|
||
//
|
||
// Unmarshall the incoming message.
|
||
//
|
||
|
||
|
||
Where =((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
|
||
if ( !NetpLogonGetOemString(
|
||
Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
|
||
&OemWorkstationName )) {
|
||
|
||
break;
|
||
}
|
||
if ( !NetpLogonGetOemString(
|
||
Message,
|
||
BytesRead,
|
||
&Where,
|
||
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
|
||
&OemMailslotName )) {
|
||
break;
|
||
}
|
||
|
||
UnicodeWorkstationName =
|
||
NetpLogonOemToUnicode( OemWorkstationName );
|
||
|
||
if( UnicodeWorkstationName == NULL ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Out of memory to send logon response\n"));
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Handle the primary query request
|
||
//
|
||
|
||
if ( PrimaryQueryHandler(Transport->TransportName,
|
||
DomainInfo,
|
||
FALSE, // don't use name aliases
|
||
Version,
|
||
VersionFlags,
|
||
UnicodeWorkstationName,
|
||
Transport->IpAddress,
|
||
ClientSockAddr,
|
||
ResponseBuffer,
|
||
&ResponseBufferSize ) ) {
|
||
NTSTATUS Status;
|
||
|
||
Status = NlBrowserSendDatagram( DomainInfo,
|
||
0,
|
||
UnicodeWorkstationName,
|
||
ComputerName,
|
||
Transport->TransportName,
|
||
OemMailslotName,
|
||
ResponseBuffer,
|
||
ResponseBufferSize,
|
||
NULL ); // Don't flush Netbios cache
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
IgnoreDuplicatesOfThisMessage = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
NetpMemoryFree( UnicodeWorkstationName );
|
||
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// Handle LOGON_FAIL_PRIMARY
|
||
//
|
||
|
||
case LOGON_FAIL_PRIMARY:
|
||
|
||
//
|
||
// If we are the primary,
|
||
// let everyone know we are really alive.
|
||
//
|
||
|
||
if ( NlGlobalPdcDoReplication ) {
|
||
// Send a UAS_CHANGE to everyone.
|
||
NlPrimaryAnnouncement( 0 );
|
||
break;
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// Handle LOGON_UAS_CHANGE
|
||
//
|
||
|
||
case LOGON_UAS_CHANGE:
|
||
|
||
|
||
//
|
||
// Only accept messages from an NT PDC.
|
||
//
|
||
|
||
if ( Version != LMNT_MESSAGE ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Only accepts messages if we're doing replication.
|
||
//
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"UAS Change message ignored since replication not enabled on this BDC.\n" ));
|
||
|
||
break;
|
||
|
||
|
||
|
||
|
||
//
|
||
// Message not sent since NT3.1.
|
||
// We ingnore this message and wait for the announcement.
|
||
//
|
||
case LOGON_START_PRIMARY:
|
||
break;
|
||
|
||
|
||
|
||
//
|
||
// Messages used for NetLogonEnum support.
|
||
//
|
||
// Simply ignore the messages
|
||
//
|
||
|
||
case LOGON_NO_USER:
|
||
case LOGON_RELOGON_RESPONSE:
|
||
case LOGON_WKSTINFO_RESPONSE:
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// Handle unidentified opcodes
|
||
//
|
||
|
||
default:
|
||
|
||
//
|
||
// Unknown request, continue for re-issue of read.
|
||
//
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Unknown op-code in mailslot message 0x%x\n",
|
||
((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Dereference the domain.
|
||
//
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
|
||
//
|
||
// Process registry change notifications
|
||
//
|
||
|
||
} else if ( WaitStatus == NlWaitParameters ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlMainLoop: Registry changed\n" ));
|
||
RegNotifyNeeded = TRUE;
|
||
|
||
//
|
||
// Process GP registry change notifications
|
||
//
|
||
|
||
} else if ( WaitStatus == NlWaitGpParameters ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlMainLoop: GP Registry changed\n" ));
|
||
GpRegNotifyNeeded = TRUE;
|
||
|
||
//
|
||
// Handle all other reasons of waking up
|
||
//
|
||
|
||
} else {
|
||
NetStatus = GetLastError();
|
||
NlPrint((NL_CRITICAL,
|
||
"NlMainLoop: Invalid wait status %ld %ld\n",
|
||
WaitStatus, NetStatus ));
|
||
NlExit(NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
Cleanup:
|
||
if ( ParmEventHandle != NULL ) {
|
||
CloseHandle( ParmEventHandle );
|
||
}
|
||
if ( ParmHandle != NULL ) {
|
||
RegCloseKey( ParmHandle );
|
||
}
|
||
if ( GpParmEventHandle != NULL ) {
|
||
CloseHandle( GpParmEventHandle );
|
||
}
|
||
if ( GpParmHandle != NULL ) {
|
||
RegCloseKey( GpParmHandle );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
int
|
||
NlNetlogonMain(
|
||
IN DWORD argc,
|
||
IN LPWSTR *argv
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Main routine for Netlogon service.
|
||
|
||
This routine initializes the netlogon service. This thread becomes
|
||
the thread that reads logon mailslot messages.
|
||
|
||
Arguments:
|
||
|
||
argc, argv - Command line arguments for the service.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
PDB_INFO DBInfo;
|
||
DWORD i;
|
||
LARGE_INTEGER TimeNow;
|
||
|
||
|
||
|
||
//
|
||
// Initialize all global variable.
|
||
//
|
||
// We can't rely on this happening at load time since this address
|
||
// space is shared by other services.
|
||
//
|
||
|
||
RtlZeroMemory( &NlGlobalParameters, sizeof(NlGlobalParameters) );
|
||
NlGlobalMailslotHandle = NULL;
|
||
NlGlobalNtDsaHandle = NULL;
|
||
NlGlobalDsApiDllHandle = NULL;
|
||
NlGlobalIsmDllHandle = NULL;
|
||
NlGlobalRpcServerStarted = FALSE;
|
||
NlGlobalServerSupportsAuthRpc = TRUE;
|
||
NlGlobalTcpIpRpcServerStarted = FALSE;
|
||
NlGlobalUnicodeComputerName = NULL;
|
||
NlGlobalNetlogonSecurityDescriptor = NULL;
|
||
|
||
try {
|
||
InitializeCriticalSection( &NlGlobalChallengeCritSect );
|
||
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
||
NlPrint(( NL_CRITICAL, "Cannot initialize NlGlobalChallengeCritSect\n" ));
|
||
return (int) ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
InitializeListHead( &NlGlobalChallengeList );
|
||
NlGlobalChallengeCount = 0;
|
||
|
||
InitializeListHead( &NlGlobalBdcServerSessionList );
|
||
NlGlobalBdcServerSessionCount = 0;
|
||
|
||
NlGlobalPdcDoReplication = FALSE;
|
||
|
||
NlGlobalWinSockInitialized = FALSE;
|
||
|
||
NlGlobalIpTransportCount = 0;
|
||
InitializeListHead( &NlGlobalTransportList );
|
||
InitializeListHead( &NlGlobalDnsList );
|
||
NlGlobalUnicodeDnsForestName = NULL;
|
||
NlGlobalUnicodeDnsForestNameLen = 0;
|
||
RtlInitUnicodeString( &NlGlobalUnicodeDnsForestNameString, NULL );
|
||
NlGlobalUtf8DnsForestName = NULL;
|
||
NlGlobalUtf8DnsForestNameAlias = NULL;
|
||
NlGlobalWinsockPnpSocket = INVALID_SOCKET;
|
||
NlGlobalWinsockPnpEvent = NULL;
|
||
NlGlobalWinsockPnpAddresses = NULL;
|
||
NlGlobalWinsockPnpAddressSize = 0;
|
||
|
||
InitializeListHead( &NlGlobalPendingBdcList );
|
||
NlGlobalPendingBdcCount = 0;
|
||
NlGlobalPendingBdcTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
||
|
||
NlGlobalTerminateEvent = NULL;
|
||
NlGlobalStartedEvent = NULL;
|
||
NlGlobalTimerEvent = NULL;
|
||
|
||
NlGlobalServiceHandle = 0;
|
||
|
||
NlGlobalServiceStatus.dwServiceType = SERVICE_WIN32;
|
||
NlGlobalServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||
NlGlobalServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
||
SERVICE_ACCEPT_PAUSE_CONTINUE;
|
||
NlGlobalServiceStatus.dwCheckPoint = 1;
|
||
NlGlobalServiceStatus.dwWaitHint = NETLOGON_INSTALL_WAIT;
|
||
|
||
SET_SERVICE_EXITCODE(
|
||
NO_ERROR,
|
||
NlGlobalServiceStatus.dwWin32ExitCode,
|
||
NlGlobalServiceStatus.dwServiceSpecificExitCode
|
||
);
|
||
|
||
NlGlobalClientSession = NULL;
|
||
NlGlobalDomainInfo = NULL;
|
||
NlGlobalServicedDomainCount = 0;
|
||
NlGlobalTrustedDomainList = NULL;
|
||
NlGlobalParameters.SiteName = NULL;
|
||
NlGlobalTrustedDomainCount = 0;
|
||
NlGlobalTrustedDomainListTime.QuadPart = 0;
|
||
NlGlobalSiteNameSetTime.QuadPart = 0;
|
||
NlGlobalNoClientSiteCount = 0;
|
||
NlQuerySystemTime( &NlGlobalNoClientSiteEventTime );
|
||
NlGlobalBindingHandleCount = 0;
|
||
NlGlobalApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
||
NlGlobalDnsScavengerTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
||
NlGlobalNetlogonUnloaded = NlGlobalChangeLogDllUnloaded;
|
||
NlGlobalDsRunningUnknown = TRUE;
|
||
RtlZeroMemory( &NlGlobalZeroGuid, sizeof(NlGlobalZeroGuid) );
|
||
NlGlobalJoinLogicDone = FALSE;
|
||
|
||
|
||
//
|
||
// Force the scavenger to start immediately.
|
||
//
|
||
// We want the password on the trust account to change immediately
|
||
// on the very first boot.
|
||
//
|
||
|
||
NlGlobalScavengerTimer.StartTime.QuadPart = 0;
|
||
NlGlobalScavengerTimer.Period = NlGlobalParameters.ScavengeInterval * 1000L;
|
||
|
||
#if NETLOGONDBG
|
||
NlGlobalParameters.DbFlag = 0;
|
||
NlGlobalLogFile = INVALID_HANDLE_VALUE;
|
||
NlGlobalDebugSharePath = NULL;
|
||
#endif // NETLOGONDBG
|
||
|
||
|
||
for( i = 0, DBInfo = &NlGlobalDBInfoArray[0];
|
||
i < NUM_DBS;
|
||
i++, DBInfo++ ) {
|
||
|
||
RtlZeroMemory( DBInfo, sizeof(*DBInfo) );
|
||
}
|
||
NlGlobalPartialDisable = FALSE;
|
||
NlGlobalDsPaused = TRUE;
|
||
NlGlobalDsPausedEvent = NULL;
|
||
NlGlobalDsPausedWaitHandle = NULL;
|
||
NlGlobalDcDemotionInProgress = FALSE;
|
||
|
||
NlInitializeWorkItem( &NlGlobalDcScavengerWorkItem, NlDcScavenger, NULL );
|
||
NlInitializeWorkItem( &NlGlobalDnsScavengerWorkItem, NlDnsScavenge, NULL );
|
||
|
||
|
||
|
||
|
||
//
|
||
// Setup things needed before NlExit can be called
|
||
//
|
||
|
||
NlGlobalTerminate = FALSE;
|
||
#if NETLOGONDBG
|
||
NlGlobalUnloadNetlogon = FALSE;
|
||
#endif // NETLOGONDBG
|
||
|
||
NlGlobalTerminateEvent = CreateEvent( NULL, // No security attributes
|
||
TRUE, // Must be manually reset
|
||
FALSE, // Initially not signaled
|
||
NULL ); // No name
|
||
|
||
if ( NlGlobalTerminateEvent == NULL ) {
|
||
NetStatus = GetLastError();
|
||
NlPrint((NL_CRITICAL, "Cannot create termination Event %lu\n",
|
||
NetStatus ));
|
||
return (int) NetStatus;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize global crit sects
|
||
//
|
||
|
||
try {
|
||
InitializeCriticalSection( &NlGlobalReplicatorCritSect );
|
||
InitializeCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
InitializeCriticalSection( &NlGlobalScavengerCritSect );
|
||
InitializeCriticalSection( &NlGlobalTransportCritSect );
|
||
InitializeCriticalSection( &NlGlobalDnsCritSect );
|
||
InitializeCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
||
NlPrint((NL_CRITICAL, "Cannot InitializeCriticalSection\n" ));
|
||
return (int) ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
try {
|
||
InitializeCriticalSection( &NlGlobalParametersCritSect );
|
||
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
||
NlPrint((NL_CRITICAL, "Cannot initialize NlGlobalParametersCritSect\n" ));
|
||
return (int) ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
|
||
//
|
||
// seed the pseudo random number generator
|
||
//
|
||
|
||
NlQuerySystemTime( &TimeNow );
|
||
srand( TimeNow.LowPart );
|
||
|
||
|
||
//
|
||
// Tell the service controller we've started.
|
||
//
|
||
// ?? - Need to set up security descriptor.
|
||
//
|
||
|
||
NlPrint((NL_INIT,"Calling RegisterServiceCtrlHandler\n"));
|
||
|
||
NlGlobalServiceHandle =
|
||
RegisterServiceCtrlHandler( SERVICE_NETLOGON, NlControlHandler);
|
||
|
||
if (NlGlobalServiceHandle == 0) {
|
||
LPWSTR MsgStrings[1];
|
||
|
||
NetStatus = GetLastError();
|
||
|
||
NlPrint((NL_CRITICAL, "RegisterServiceCtrlHandler failed %lu\n",
|
||
NetStatus ));
|
||
|
||
MsgStrings[0] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog (NELOG_NetlogonFailedToRegisterSC,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) &NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
1 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
|
||
return (int) NetStatus;
|
||
}
|
||
|
||
if ( !GiveInstallHints( FALSE ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Nlparse the command line (.ini) arguments
|
||
// it will set globals reflecting switch settings
|
||
//
|
||
|
||
NlOpenDebugFile( FALSE );
|
||
if (! NlparseAllSections( &NlGlobalParameters, FALSE ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_INIT,"Command line parsed successfully ...\n"));
|
||
if ( NlGlobalNetlogonUnloaded ) {
|
||
NlPrint((NL_INIT,"Netlogon.dll has been unloaded (recover from it).\n"));
|
||
}
|
||
|
||
|
||
|
||
|
||
#if DBG
|
||
//
|
||
// Enter the debugger.
|
||
//
|
||
// Wait 'til now since we don't want the service controller to time us out.
|
||
//
|
||
|
||
|
||
IF_NL_DEBUG( BREAKPOINT ) {
|
||
DbgBreakPoint( );
|
||
}
|
||
#endif // DBG
|
||
|
||
|
||
|
||
//
|
||
// Do startup checks, initialize data structs and do prelim setups
|
||
//
|
||
|
||
if ( !NlInit() ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Loop till the service is to exit.
|
||
//
|
||
|
||
NlGlobalNetlogonUnloaded = FALSE;
|
||
NlMainLoop();
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Cleanup and return to our caller.
|
||
//
|
||
|
||
return (int) NlCleanup();
|
||
UNREFERENCED_PARAMETER( argc );
|
||
UNREFERENCED_PARAMETER( argv );
|
||
|
||
}
|