mirror of https://github.com/lianthony/NT4.0
4427 lines
113 KiB
4427 lines
113 KiB
/*--
|
|
|
|
|
|
Copyright (c) 1987-1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
netlogon.c
|
|
|
|
Abstract:
|
|
|
|
Entry point and main thread of Netlogon service.
|
|
|
|
Author:
|
|
|
|
Ported from Lan Man 2.0
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
|
|
21-Nov-1990 (madana)
|
|
added code for update (reverse replication) and lockout support.
|
|
|
|
21-Nov-1990 (madana)
|
|
server type support.
|
|
|
|
21-May-1991 (cliffv)
|
|
Ported to NT. Converted to NT style.
|
|
|
|
--*/
|
|
|
|
|
|
//
|
|
// Common include files.
|
|
//
|
|
|
|
#define LSRVDATA_ALLOCATE // Allocate data from lsrvdata.h
|
|
#include <logonsrv.h> // Include files common to entire service
|
|
#undef LSRVDATA_ALLOCATE
|
|
|
|
//
|
|
// Include files specific to this .c file
|
|
//
|
|
|
|
#include <alertmsg.h> // Alert message text.
|
|
#include <ctype.h> // C library type functions
|
|
#include <iniparm.h> // initial values of global variables
|
|
#include <lmapibuf.h> // NetApiBufferFree
|
|
#include <lmbrowsr.h> // I_BrowserResetNetlogonState
|
|
#include <lmerr.h> // System Error Log definitions
|
|
#include <lmserver.h> // Server API defines and prototypes
|
|
#include <lmwksta.h> // WKSTA API defines and prototypes
|
|
#include <lmsvc.h> // SERVICE_UIC codes are defined here
|
|
#include <nlsecure.h> // NlCreateNetlogonObjects
|
|
#include <ntlsa.h> // Defines policy database
|
|
#include <ntrpcp.h> // Rpcp routines
|
|
#include <replutil.h>
|
|
#include <samisrv.h> // SamIConnect
|
|
#include <srvann.h> // Service announcement
|
|
#include <stddef.h> // offsetof
|
|
#include <stdlib.h> // C library functions: rand()
|
|
#include <string.h> // strnicmp ...
|
|
#include <tstring.h> // IS_PATH_SEPARATOR ...
|
|
#include <secobj.h> // BuiltinDomainSID defined here ..
|
|
|
|
|
|
#define INTERROGATE_RESP_DELAY 2000 // may want to tune it
|
|
#define MAX_PRIMARY_TRACK_FAIL 3 // Primary pulse slips
|
|
|
|
|
|
|
|
BOOLEAN
|
|
NetlogonDllInit (
|
|
IN PVOID DllHandle,
|
|
IN ULONG Reason,
|
|
IN PCONTEXT Context OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the DLL initialization routine for netlogon.dll.
|
|
|
|
Arguments:
|
|
|
|
Standard.
|
|
|
|
Return Value:
|
|
|
|
TRUE iff initialization succeeded.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
UNREFERENCED_PARAMETER(DllHandle); // avoid compiler warnings
|
|
UNREFERENCED_PARAMETER(Context); // avoid compiler warnings
|
|
|
|
|
|
//
|
|
// Handle attaching netlogon.dll to a new process.
|
|
//
|
|
|
|
if (Reason == DLL_PROCESS_ATTACH) {
|
|
|
|
if ( !DisableThreadLibraryCalls( DllHandle ) ) {
|
|
KdPrint(("NETLOGON.DLL: DisableThreadLibraryCalls failed: %ld\n",
|
|
GetLastError() ));
|
|
}
|
|
Status = NlInitChangeLog();
|
|
#if DBG
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
|
|
Status ));
|
|
}
|
|
#endif // DBG
|
|
|
|
//
|
|
// Initialize the Critical Section used to serialize access to
|
|
// variables shared by MSV threads and netlogon threads.
|
|
//
|
|
|
|
InitializeCriticalSection( &NlGlobalMsvCritSect );
|
|
NlGlobalMsvEnabled = FALSE;
|
|
NlGlobalMsvThreadCount = 0;
|
|
NlGlobalMsvTerminateEvent = NULL;
|
|
|
|
|
|
//
|
|
// Handle detaching netlogon.dll from a process.
|
|
//
|
|
|
|
//
|
|
// netlogon.dll never detaches
|
|
//
|
|
#ifdef NETLOGON_PROCESS_DETACH
|
|
|
|
} else if (Reason == DLL_PROCESS_DETACH) {
|
|
Status = NlCloseChangeLog();
|
|
#if DBG
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
KdPrint(("NETLOGON.DLL: Changelog initialization failed: %lx\n",
|
|
Status ));
|
|
}
|
|
#endif // DBG
|
|
|
|
//
|
|
// Delete the Critical Section used to serialize access to
|
|
// variables shared by MSV threads and netlogon threads.
|
|
//
|
|
|
|
DeleteCriticalSection( &NlGlobalMsvCritSect );
|
|
#endif // NETLOGON_PROCESS_DETACH
|
|
|
|
} else {
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return (BOOLEAN)(NT_SUCCESS(Status));
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
NlInitDBSerialNumber(
|
|
IN OUT PLARGE_INTEGER SerialNumber,
|
|
IN OUT PLARGE_INTEGER CreationTime,
|
|
IN PUNICODE_STRING ReplicaSource,
|
|
IN DWORD DBIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the SerialNumber and CreationTime in the NlGlobalDBInfoArray data
|
|
structure.
|
|
|
|
On the PDC,
|
|
Validate that it matches the value found in the change log.
|
|
Ensure the values are non-zero.
|
|
|
|
Arguments:
|
|
|
|
SerialNumber - Specifies the serial number found in the database.
|
|
On return, specifies the serial number to write to the database
|
|
|
|
CreationTime - Specifies the creation time found in the database.
|
|
On return, specifies the creation time to write to the database
|
|
|
|
ReplicaSource - Specifies the replica source for the datbase.
|
|
|
|
DBIndex -- DB Index of the database being initialized
|
|
|
|
Return Value:
|
|
|
|
TRUE -- iff the serial number and creation time need to be written back
|
|
to the database.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN ReturnValue = FALSE;
|
|
|
|
//
|
|
// Save the name of the Replica source.
|
|
//
|
|
|
|
wcsncpy( NlGlobalDBInfoArray[DBIndex].PrimaryName,
|
|
ReplicaSource->Buffer,
|
|
ReplicaSource->Length / sizeof(WCHAR) );
|
|
|
|
NlGlobalDBInfoArray[DBIndex].PrimaryName[
|
|
ReplicaSource->Length / sizeof(WCHAR) ] = L'\0';
|
|
|
|
|
|
//
|
|
// If we're running as the primary,
|
|
// check to see if we are a newly promoted primary that was in
|
|
// the middle of a full sync before we were promoted.
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary) {
|
|
|
|
if ( SerialNumber->QuadPart == 0 || CreationTime->QuadPart == 0 ) {
|
|
|
|
NlPrint(( NL_CRITICAL,
|
|
"NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": Pdc has bogus Serial number %lx %lx or Creation Time %lx %lx (reset).\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
SerialNumber->HighPart,
|
|
SerialNumber->LowPart,
|
|
CreationTime->HighPart,
|
|
CreationTime->LowPart ));
|
|
|
|
//
|
|
// This is the primary,
|
|
// we probably shouldn't be replicating from a partial database,
|
|
// but at least set the replication information to something
|
|
// reasonable.
|
|
//
|
|
|
|
(VOID) NtQuerySystemTime( CreationTime );
|
|
SerialNumber->QuadPart = 1;
|
|
ReturnValue = TRUE;
|
|
|
|
}
|
|
|
|
NlGlobalDBInfoArray[DBIndex].UpdateRqd = FALSE;
|
|
|
|
|
|
//
|
|
// If we aren't the primary flag that an update is required,
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we've never had a full sync on this database,
|
|
// force one now.
|
|
//
|
|
|
|
if ( ReplicaSource->Length == 0 ) {
|
|
|
|
//
|
|
// Set this flag so that we can pause the netlogon service
|
|
// when we do the full sync.
|
|
//
|
|
NlGlobalFirstTimeFullSync = TRUE;
|
|
|
|
NlGlobalDBInfoArray[DBIndex].UpdateRqd = TRUE;
|
|
NlGlobalDBInfoArray[DBIndex].FullSyncRequired = TRUE;
|
|
|
|
NlPrint(( NL_CRITICAL,
|
|
"NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": Force FULL SYNC because first sync after install.\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName ));
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we were in the middle of a full sync when we stopped,
|
|
// continue it now.
|
|
//
|
|
if ( SerialNumber->QuadPart == 0 || CreationTime->QuadPart == 0 ) {
|
|
|
|
NlGlobalDBInfoArray[DBIndex].UpdateRqd = TRUE;
|
|
NlGlobalDBInfoArray[DBIndex].FullSyncRequired = TRUE;
|
|
|
|
NlPrint(( NL_CRITICAL,
|
|
"NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
" is marked as needing FULL SYNC.\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName ));
|
|
|
|
}
|
|
|
|
NlPrint(( NL_SYNC,
|
|
"NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": Last sync done from \\\\" FORMAT_LPWSTR "\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
NlGlobalDBInfoArray[DBIndex].PrimaryName ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// The global serial number array has already been initialized
|
|
// from the changelog. If that information is wrong, just reset the
|
|
// changelog now.
|
|
//
|
|
|
|
|
|
LOCK_CHANGELOG();
|
|
|
|
//
|
|
// If there was no serial number in the changelog for this database,
|
|
// set it now.
|
|
//
|
|
|
|
if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart == 0 ) {
|
|
|
|
NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": No serial number in change log (set to %lx %lx)\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
SerialNumber->HighPart,
|
|
SerialNumber->LowPart ));
|
|
|
|
|
|
NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
|
|
|
|
//
|
|
// If the serial number in the changelog is greater than the
|
|
// serial number in the database, this is caused by the changelog
|
|
// being flushed to disk and the SAM database not being flushed.
|
|
//
|
|
// Cure this problem by deleting the superfluous changelog entries.
|
|
//
|
|
|
|
} else if ( NlGlobalChangeLogDesc.SerialNumber[DBIndex].QuadPart !=
|
|
SerialNumber->QuadPart ) {
|
|
|
|
NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": Changelog serial number different than database: "
|
|
"ChangeLog = %lx %lx DB = %lx %lx\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
NlGlobalChangeLogDesc.SerialNumber[DBIndex].HighPart,
|
|
NlGlobalChangeLogDesc.SerialNumber[DBIndex].LowPart,
|
|
SerialNumber->HighPart,
|
|
SerialNumber->LowPart ));
|
|
|
|
(VOID) NlFixChangeLog( &NlGlobalChangeLogDesc, DBIndex, *SerialNumber, FALSE );
|
|
|
|
} else {
|
|
|
|
NlPrint((NL_SYNC, "NlInitDbSerialNumber: " FORMAT_LPWSTR
|
|
": Serial number is %lx %lx\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
SerialNumber->HighPart,
|
|
SerialNumber->LowPart ));
|
|
}
|
|
|
|
//
|
|
// In all cases,
|
|
// set the globals to match the database.
|
|
//
|
|
|
|
NlGlobalChangeLogDesc.SerialNumber[DBIndex] = *SerialNumber;
|
|
NlGlobalDBInfoArray[DBIndex].CreationTime = *CreationTime;
|
|
|
|
UNLOCK_CHANGELOG();
|
|
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NlInitLsaDBInfo(
|
|
DWORD DBIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize NlGlobalDBInfoArray data structure. Some of the LSA
|
|
database info is already determined in ValidateStartup functions, so
|
|
those values are used here.
|
|
|
|
Arguments:
|
|
|
|
DBIndex -- DB Index of the database being initialized
|
|
|
|
Return Value:
|
|
|
|
NT status code.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
NTSTATUS Status;
|
|
PLSAPR_POLICY_INFORMATION PolicyInfo = NULL;
|
|
|
|
//
|
|
// Initialize LSA database info.
|
|
//
|
|
|
|
NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
|
|
NlGlobalDBInfoArray[DBIndex].DBName = L"LSA";
|
|
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_LSA_REPL_NEEDED;
|
|
|
|
//
|
|
// Database ID field contains nothing for LSA database since
|
|
// there will be only one LSA database on the system.
|
|
//
|
|
|
|
NlGlobalDBInfoArray[DBIndex].DBId = NULL;
|
|
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle = NlGlobalPolicyHandle;
|
|
|
|
//
|
|
// Forgo this initialization on a workstation.
|
|
//
|
|
|
|
if ( NlGlobalRole != RoleMemberWorkstation ) {
|
|
LARGE_INTEGER SerialNumber;
|
|
LARGE_INTEGER CreationTime;
|
|
|
|
//
|
|
// Get the replica source name
|
|
//
|
|
|
|
Status = LsarQueryInformationPolicy(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
PolicyReplicaSourceInformation,
|
|
&PolicyInfo );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
PolicyInfo = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the LSA Modified information.
|
|
//
|
|
|
|
Status = LsaIGetSerialNumberPolicy(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
&SerialNumber,
|
|
&CreationTime );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the SerialNumber and CreationTime in the globals.
|
|
//
|
|
|
|
if ( NlInitDBSerialNumber(
|
|
&SerialNumber,
|
|
&CreationTime,
|
|
(PUNICODE_STRING)&PolicyInfo->PolicyReplicaSourceInfo.ReplicaSource,
|
|
DBIndex ) ) {
|
|
|
|
|
|
Status = LsaISetSerialNumberPolicy(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
&SerialNumber,
|
|
&CreationTime,
|
|
(BOOLEAN) FALSE );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( PolicyInfo != NULL ) {
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyReplicaSourceInformation,
|
|
PolicyInfo );
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NlInitSamDBInfo(
|
|
DWORD DBIndex,
|
|
PSID DomainId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize NlGlobalDBInfoArray data structure. Some of the SAM database
|
|
info is already determined in ValidateStartup functions, so those
|
|
values are used here. For BUILTIN database, the database is opened,
|
|
database handle is obtained and other DB info
|
|
queried and initialized in this function.
|
|
|
|
Arguments:
|
|
|
|
DBIndex -- DB Index of the database being initialized
|
|
|
|
DomainId -- Domain Sid of the database to open/initialize.
|
|
|
|
Return Value:
|
|
|
|
NT status code.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
NTSTATUS Status;
|
|
PSAMPR_DOMAIN_INFO_BUFFER DomainModified = NULL;
|
|
PSAMPR_DOMAIN_INFO_BUFFER DomainServerRole = NULL;
|
|
PSAMPR_DOMAIN_INFO_BUFFER DomainReplica = NULL;
|
|
|
|
BOOLEAN FixRole = FALSE;
|
|
DOMAIN_SERVER_ROLE DesiredRole;
|
|
|
|
|
|
|
|
//
|
|
// Initialize SAM database info.
|
|
//
|
|
|
|
NlGlobalDBInfoArray[DBIndex].DBIndex = DBIndex;
|
|
if ( DBIndex == SAM_DB ) {
|
|
NlGlobalDBInfoArray[DBIndex].DBName = L"SAM";
|
|
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_ACCOUNT_REPL_NEEDED;
|
|
} else {
|
|
NlGlobalDBInfoArray[DBIndex].DBName = L"BUILTIN";
|
|
NlGlobalDBInfoArray[DBIndex].DBSessionFlag = SS_BUILTIN_REPL_NEEDED;
|
|
}
|
|
|
|
NlGlobalDBInfoArray[DBIndex].DBId = NetpMemoryAllocate( RtlLengthSid( DomainId ));
|
|
|
|
if ( NlGlobalDBInfoArray[DBIndex].DBId == NULL ) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlCopyMemory( NlGlobalDBInfoArray[DBIndex].DBId,
|
|
DomainId,
|
|
RtlLengthSid( DomainId ));
|
|
|
|
//
|
|
// Open the domain.
|
|
//
|
|
|
|
Status = SamrOpenDomain( NlGlobalSamServerHandle,
|
|
DOMAIN_ALL_ACCESS,
|
|
NlGlobalDBInfoArray[DBIndex].DBId,
|
|
&NlGlobalDBInfoArray[DBIndex].DBHandle );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Ensure the role in SAM is compatible with Netlogon's role
|
|
//
|
|
|
|
Status = SamrQueryInformationDomain( NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
DomainServerRoleInformation,
|
|
&DomainServerRole );
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
DomainServerRole = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
switch ( NlGlobalRole ) {
|
|
case RolePrimary:
|
|
case RoleMemberWorkstation:
|
|
if ( DomainServerRole->Role.DomainServerRole != DomainServerRolePrimary ) {
|
|
FixRole = TRUE;
|
|
DesiredRole = DomainServerRolePrimary;
|
|
}
|
|
break;
|
|
case RoleBackup:
|
|
if ( DomainServerRole->Role.DomainServerRole != DomainServerRoleBackup ) {
|
|
FixRole = TRUE;
|
|
DesiredRole = DomainServerRoleBackup;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Status = STATUS_INVALID_DOMAIN_ROLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( FixRole) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"NlInitSamDbInfo: " FORMAT_LPWSTR
|
|
": Role is %ld which doesn't match LSA's role. (Fixed)\n",
|
|
NlGlobalDBInfoArray[DBIndex].DBName,
|
|
DomainServerRole->Role.DomainServerRole ));
|
|
|
|
DomainServerRole->Role.DomainServerRole = DesiredRole;
|
|
|
|
Status = SamrSetInformationDomain(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
DomainServerRoleInformation,
|
|
DomainServerRole );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
DomainServerRole = NULL;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Forgo this initialization on a workstation.
|
|
//
|
|
|
|
if ( NlGlobalRole != RoleMemberWorkstation ) {
|
|
|
|
//
|
|
// Get the replica source name.
|
|
//
|
|
|
|
Status = SamrQueryInformationDomain(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
DomainReplicationInformation,
|
|
&DomainReplica );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
DomainReplica = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the Domain Modified information.
|
|
//
|
|
|
|
Status = SamrQueryInformationDomain(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
DomainModifiedInformation2,
|
|
&DomainModified );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
DomainModified = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the SerialNumber and CreationTime in the globals.
|
|
//
|
|
|
|
if ( NlInitDBSerialNumber(
|
|
&DomainModified->Modified2.DomainModifiedCount,
|
|
&DomainModified->Modified2.CreationTime,
|
|
(PUNICODE_STRING)&DomainReplica->Replication.ReplicaSourceNodeName,
|
|
DBIndex ) ) {
|
|
|
|
Status = SamISetSerialNumberDomain(
|
|
NlGlobalDBInfoArray[DBIndex].DBHandle,
|
|
&DomainModified->Modified2.DomainModifiedCount,
|
|
&DomainModified->Modified2.CreationTime,
|
|
(BOOLEAN) FALSE );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Free locally used resources.
|
|
//
|
|
if ( DomainModified != NULL ) {
|
|
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainModified,
|
|
DomainModifiedInformation2 );
|
|
}
|
|
|
|
if ( DomainServerRole != NULL ) {
|
|
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainServerRole,
|
|
DomainServerRoleInformation);
|
|
}
|
|
|
|
if ( DomainReplica != NULL ) {
|
|
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainReplica,
|
|
DomainReplicationInformation );
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
NlSetDomainName(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine gets the primary domain name from the LSA and stores
|
|
that name in global variables.
|
|
|
|
Arguments:
|
|
|
|
NONE.
|
|
|
|
Return Value:
|
|
|
|
TRUE -- Iff the domain name can be saved.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PLSAPR_POLICY_INFORMATION PolicyInfo;
|
|
|
|
|
|
//
|
|
// Get the Primary Domain Name from the LSA.
|
|
//
|
|
|
|
Status = LsarQueryInformationPolicy(
|
|
NlGlobalPolicyHandle,
|
|
PolicyPrimaryDomainInformation,
|
|
&PolicyInfo );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( PolicyInfo->PolicyPrimaryDomainInfo.Name.Length == 0 ||
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Name.Length >
|
|
DNLEN * sizeof(WCHAR) ||
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Sid == NULL ) {
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyPrimaryDomainInformation,
|
|
PolicyInfo );
|
|
|
|
NlPrint((NL_CRITICAL, "Primary domain info from LSA invalid.\n"));
|
|
NlExit( SERVICE_UIC_M_UAS_INVALID_ROLE, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy name to the globals.
|
|
//
|
|
|
|
RtlCopyMemory( NlGlobalUnicodeDomainName,
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Name.Buffer,
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Name.Length );
|
|
|
|
NlGlobalUnicodeDomainName[
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Name.Length /
|
|
sizeof(WCHAR)] = L'\0';
|
|
|
|
RtlInitUnicodeString( &NlGlobalUnicodeDomainNameString,
|
|
NlGlobalUnicodeDomainName);
|
|
|
|
//
|
|
// This routine is only called once during initialization so previous
|
|
// storage need not be freed.
|
|
//
|
|
|
|
NlGlobalAnsiDomainName =
|
|
NetpLogonUnicodeToOem( NlGlobalUnicodeDomainName);
|
|
|
|
if ( NlGlobalAnsiDomainName == NULL ) {
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyPrimaryDomainInformation,
|
|
PolicyInfo );
|
|
|
|
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Save the SID in a global
|
|
//
|
|
|
|
NlGlobalPrimaryDomainId = NetpMemoryAllocate(
|
|
RtlLengthSid( (PSID)PolicyInfo->PolicyPrimaryDomainInfo.Sid ));
|
|
|
|
if ( NlGlobalPrimaryDomainId == NULL ) {
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyPrimaryDomainInformation,
|
|
PolicyInfo );
|
|
|
|
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlCopyMemory( NlGlobalPrimaryDomainId,
|
|
PolicyInfo->PolicyPrimaryDomainInfo.Sid,
|
|
RtlLengthSid( PolicyInfo->PolicyPrimaryDomainInfo.Sid ));
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyPrimaryDomainInformation,
|
|
PolicyInfo );
|
|
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
NlInitWorkstation(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do workstation specific initialization.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE -- iff initialization is successful.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Ensure the primary and account domain ID are different.
|
|
//
|
|
|
|
if ( RtlEqualSid( NlGlobalDBInfoArray[SAM_DB].DBId, NlGlobalPrimaryDomainId ) ) {
|
|
|
|
LPWSTR AlertStrings[3];
|
|
|
|
//
|
|
// alert admin.
|
|
//
|
|
|
|
AlertStrings[0] = NlGlobalUnicodeComputerName;
|
|
AlertStrings[1] = NlGlobalUnicodeDomainName;
|
|
AlertStrings[2] = NULL;
|
|
|
|
//
|
|
// Save the info in the eventlog
|
|
//
|
|
|
|
NlpWriteEventlog(
|
|
ALERT_NetLogonSidConflict,
|
|
EVENTLOG_ERROR_TYPE,
|
|
NlGlobalPrimaryDomainId,
|
|
RtlLengthSid( NlGlobalPrimaryDomainId ),
|
|
AlertStrings,
|
|
2 );
|
|
|
|
//
|
|
// This isn't fatal. (Just drop through)
|
|
//
|
|
}
|
|
|
|
|
|
//
|
|
// Set up the Client Session structure to identify the domain and
|
|
// account used to talk to the DC.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalClientSession = NlAllocateClientSession(
|
|
&NlGlobalUnicodeDomainNameString,
|
|
NlGlobalPrimaryDomainId,
|
|
WorkstationSecureChannel );
|
|
|
|
if ( NlGlobalClientSession == NULL ) {
|
|
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
NlInitDomainController(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do Domain Controller specific initialization.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE -- iff initialization is successful.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
NET_API_STATUS NetStatus;
|
|
WCHAR ChangeLogFile[PATHLEN+1];
|
|
|
|
LPWSTR DCName;
|
|
DWORD Version;
|
|
|
|
BOOL DeferAuth = FALSE;
|
|
|
|
|
|
|
|
//
|
|
// Handle if there is no other PDC running in this domain.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
NetStatus = NetpLogonGetDCName( NlGlobalUnicodeComputerName,
|
|
NlGlobalUnicodeDomainName,
|
|
0,
|
|
&DCName,
|
|
&Version );
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success) {
|
|
|
|
//
|
|
// If we are the first primary in the domain,
|
|
// Remember that we are the primary.
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary) {
|
|
|
|
if ( !NlSetPrimaryName( NlGlobalUnicodeComputerName ) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Handle starting a BDC when there is not current primary in
|
|
// this domain.
|
|
//
|
|
|
|
} else if ( NlGlobalRole == RoleBackup ) {
|
|
|
|
NlpWriteEventlog( SERVICE_UIC_M_NETLOGON_NO_DC,
|
|
EVENTLOG_ERROR_TYPE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0 );
|
|
|
|
//
|
|
// Start normally but defer authentication with the
|
|
// primary until it starts.
|
|
//
|
|
|
|
DeferAuth = TRUE;
|
|
|
|
}
|
|
|
|
//
|
|
// There is a primary dc running in this domain
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Since there already is a primary in the domain,
|
|
// we cannot become the primary.
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary) {
|
|
|
|
//
|
|
// Don't worry if this is a BDC telling us that we're the PDC.
|
|
//
|
|
|
|
if ( NlNameCompare( NlGlobalUnicodeComputerName,
|
|
DCName + 2,
|
|
NAMETYPE_COMPUTER) != 0 ){
|
|
NlExit(SERVICE_UIC_M_NETLOGON_DC_CFLCT, 0, LogError, NULL);
|
|
(VOID) NetApiBufferFree( DCName );
|
|
return FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the primary is NOT an NT primary,
|
|
// refuse to start.
|
|
//
|
|
// An NT BDC or member server cannot replicate from a downlevel PDC.
|
|
//
|
|
|
|
if ( Version != LMNT_MESSAGE ) {
|
|
PSERVER_INFO_101 ServerInfo101 = NULL;
|
|
|
|
//
|
|
// This might just be a LM 2.1A (or newer) BDC responding on
|
|
// behalf of an NT PDC.
|
|
//
|
|
// Ask the PDC if it is NT.
|
|
//
|
|
|
|
NetStatus = NetServerGetInfo( DCName,
|
|
101,
|
|
(LPBYTE *)&ServerInfo101 );
|
|
if ( NetStatus != NERR_Success ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"can't NetServerGetInfo on primary " FORMAT_LPWSTR
|
|
" %ld.\n",
|
|
DCName,
|
|
NetStatus ));
|
|
(VOID) NetApiBufferFree( DCName );
|
|
NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, NetStatus, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( (ServerInfo101->sv101_type & SV_TYPE_DOMAIN_CTRL) == 0 ) {
|
|
NetApiBufferFree( ServerInfo101 );
|
|
NlPrint((NL_CRITICAL, "PDC " FORMAT_LPWSTR " really isn't a PDC\n",
|
|
DCName ));
|
|
(VOID) NetApiBufferFree( DCName );
|
|
NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( (ServerInfo101->sv101_type & SV_TYPE_NT) == 0 ) {
|
|
NetApiBufferFree( ServerInfo101 );
|
|
NlPrint((NL_CRITICAL, "PDC " FORMAT_LPWSTR
|
|
" really isn't an NT PDC\n", DCName ));
|
|
(VOID) NetApiBufferFree( DCName );
|
|
NlExit(SERVICE_UIC_M_NETLOGON_NO_DC, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
NetApiBufferFree( ServerInfo101 );
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Remember this primary name.
|
|
//
|
|
|
|
if ( !NlSetPrimaryName( DCName + 2 ) ) {
|
|
NlExit(SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
|
|
(VOID) NetApiBufferFree( DCName );
|
|
return FALSE;
|
|
}
|
|
|
|
(VOID) NetApiBufferFree( DCName );
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Open the browser so we can send and receive mailslot messages.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !NlBrowserOpen() ) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Here ensure that the Secret Password exists.
|
|
// (If the secret password doesn't exist, we'll never be able
|
|
// to establish a session to the PDC and netlogon shouldn't be
|
|
// running).
|
|
//
|
|
|
|
if ( NlGlobalRole == RoleBackup ) {
|
|
|
|
LSAPR_HANDLE SecretHandle;
|
|
|
|
//
|
|
// Set up the Client Session structure to identify the domain and
|
|
// account used to talk to the PDC.
|
|
//
|
|
|
|
NlGlobalClientSession = NlAllocateClientSession(
|
|
&NlGlobalUnicodeDomainNameString,
|
|
NlGlobalPrimaryDomainId,
|
|
ServerSecureChannel );
|
|
|
|
if ( NlGlobalClientSession == NULL ) {
|
|
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
Status = NlOpenSecret( NlGlobalClientSession,
|
|
SECRET_QUERY_VALUE | SECRET_SET_VALUE,
|
|
&SecretHandle );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_LSA_MACHINE_ACCT, Status, LogError, NULL );
|
|
return FALSE;
|
|
}
|
|
|
|
(VOID) LsarClose( &SecretHandle );
|
|
|
|
}
|
|
|
|
//
|
|
// Check that the server is installed or install pending
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !NetpIsServiceStarted( SERVICE_SERVER ) ){
|
|
NlExit( NERR_ServerNotStarted, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Allocate & initialize the data structs and storage for replication
|
|
//
|
|
|
|
|
|
Status = NlInitSSI();
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( NELOG_NetlogonSSIInitError, Status, LogErrorAndNtStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Create the event the replicator thread waits on to terminate.
|
|
//
|
|
|
|
NlGlobalReplicatorTerminateEvent = CreateEvent(
|
|
NULL, // No security attributes
|
|
TRUE, // Must be manually reset
|
|
FALSE, // Initially not signaled
|
|
NULL ); // No name
|
|
|
|
if ( NlGlobalReplicatorTerminateEvent == NULL ) {
|
|
NetStatus = GetLastError();
|
|
NlPrint((NL_CRITICAL, "Cannot create replicator termination Event %lu\n",
|
|
NetStatus ));
|
|
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// On a BDC, set up a session to the PDC now.
|
|
//
|
|
// If the PDC was previously found,
|
|
// require now that we successfully establish a session
|
|
// else
|
|
// wait till the PDC identifies itself
|
|
//
|
|
|
|
if (NlGlobalRole == RoleBackup ) {
|
|
|
|
if ( !DeferAuth ) {
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
(VOID) NlTimeoutSetWriterClientSession( NlGlobalClientSession, 0xFFFFFFFF );
|
|
Status = NlSessionSetup( NlGlobalClientSession );
|
|
NlResetWriterClientSession( NlGlobalClientSession );
|
|
|
|
//
|
|
// Treat it as fatal if the PDC explicitly denies us access.
|
|
//
|
|
|
|
if ( Status == STATUS_NO_TRUST_SAM_ACCOUNT ||
|
|
Status == STATUS_ACCESS_DENIED ) {
|
|
|
|
// NlSessionSetup already logged the error
|
|
NlExit( NetpNtStatusToApiStatus(Status), 0, DontLogError, NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Determine the trust list from the LSA.
|
|
//
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
Status = NlInitTrustList();
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( NELOG_NetlogonFailedToUpdateTrustList, Status, LogErrorAndNtStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Create NETLOGON share.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
NetStatus = NlCreateShare( NlGlobalUnicodeScriptPath,
|
|
NETLOGON_SCRIPTS_SHARE) ;
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
LPWSTR MsgStrings[2];
|
|
|
|
NlPrint((NL_CRITICAL, "NlCreateShare %lu\n", NetStatus ));
|
|
|
|
MsgStrings[0] = NlGlobalUnicodeScriptPath;
|
|
MsgStrings[1] = (LPWSTR) NetStatus;
|
|
|
|
NlpWriteEventlog (NELOG_NetlogonFailedToCreateShare,
|
|
EVENTLOG_ERROR_TYPE,
|
|
(LPBYTE) &NetStatus,
|
|
sizeof(NetStatus),
|
|
MsgStrings,
|
|
2 | LAST_MESSAGE_IS_NETSTATUS );
|
|
|
|
/* This isn't fatal. Just continue */
|
|
}
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// create debug share. Ignore error.
|
|
//
|
|
|
|
if( NlCreateShare(
|
|
NlGlobalDebugSharePath,
|
|
DEBUG_SHARE_NAME ) != NERR_Success ) {
|
|
NlPrint((NL_CRITICAL, "Can't create Debug share (%ws, %ws).\n",
|
|
NlGlobalDebugSharePath, DEBUG_SHARE_NAME ));
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// If a redo log exists,
|
|
// open it on a BDC,
|
|
// delete it on a PDC.
|
|
//
|
|
|
|
wcscpy( ChangeLogFile, NlGlobalChangeLogFilePrefix );
|
|
wcscat( ChangeLogFile, REDO_FILE_POSTFIX );
|
|
|
|
if (NlGlobalRole == RoleBackup ) {
|
|
|
|
//
|
|
// Read in the existing redo log file.
|
|
//
|
|
// It's OK if the file simply doesn't exist,
|
|
// we'll create it when we need it.
|
|
//
|
|
|
|
Status = NlOpenChangeLogFile( ChangeLogFile, &NlGlobalRedoLogDesc, FALSE );
|
|
|
|
if ( !NT_SUCCESS(Status) && Status != STATUS_NO_SUCH_FILE ) {
|
|
|
|
NlpWriteEventlog (
|
|
NELOG_NetlogonChangeLogCorrupt,
|
|
EVENTLOG_ERROR_TYPE,
|
|
(LPBYTE)&Status,
|
|
sizeof(Status),
|
|
NULL,
|
|
0 );
|
|
|
|
NlPrint((NL_CRITICAL, "Deleting broken redo log\n" ));
|
|
|
|
(VOID) DeleteFileW( ChangeLogFile );
|
|
|
|
}
|
|
|
|
} else {
|
|
(VOID) DeleteFileW( ChangeLogFile );
|
|
}
|
|
|
|
//
|
|
// Successful initialization.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
ULONG
|
|
NlServerType(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines server type, that is used to set in service table.
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
SV_TYPE_DOMAIN_CTRL if role is primary domain controller
|
|
SV_TYPE_DOMAIN_BAKCTRL if backup
|
|
0 if none of the above
|
|
|
|
|
|
--*/
|
|
{
|
|
switch (NlGlobalRole) {
|
|
case RolePrimary:
|
|
return SV_TYPE_DOMAIN_CTRL;
|
|
case RoleBackup:
|
|
return SV_TYPE_DOMAIN_BAKCTRL;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
NlInit(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize NETLOGON service related data structs after verfiying that
|
|
all conditions for startup have been satisfied. Will also create a
|
|
mailslot to listen to requests from clients and create two shares to
|
|
allow execution of logon scripts.
|
|
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE -- iff initialization is successful.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
NET_API_STATUS NetStatus;
|
|
|
|
LARGE_INTEGER TimeNow;
|
|
OBJECT_ATTRIBUTES EventAttributes;
|
|
UNICODE_STRING EventName;
|
|
|
|
PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
|
|
|
|
PLSAPR_POLICY_INFORMATION PolicyLsaServerRole = NULL;
|
|
PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL;
|
|
|
|
NT_PRODUCT_TYPE NtProductType;
|
|
DWORD ComputerNameLength;
|
|
|
|
|
|
|
|
//
|
|
// Let the ChangeLog routines know that Netlogon is started.
|
|
//
|
|
|
|
NlGlobalChangeLogNetlogonState = NetlogonStarting;
|
|
|
|
|
|
//
|
|
// seed the pseudo random number generator
|
|
//
|
|
|
|
(VOID) NtQuerySystemTime( &TimeNow );
|
|
srand( TimeNow.LowPart );
|
|
|
|
|
|
//
|
|
// Check that the redirector is installed, will exit on error.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !NetpIsServiceStarted( SERVICE_WORKSTATION ) ){
|
|
NlExit( NERR_WkstaNotStarted, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the local computer name.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalUncUnicodeComputerName[0] = '\\';
|
|
NlGlobalUncUnicodeComputerName[1] = '\\';
|
|
|
|
ComputerNameLength =
|
|
(sizeof(NlGlobalUncUnicodeComputerName)/sizeof(WCHAR)) - 2;
|
|
|
|
if ( !GetComputerNameW( NlGlobalUncUnicodeComputerName+2,
|
|
&ComputerNameLength ) ) {
|
|
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalUnicodeComputerName = NlGlobalUncUnicodeComputerName + 2;
|
|
|
|
RtlInitUnicodeString( &NlGlobalUnicodeComputerNameString,
|
|
NlGlobalUnicodeComputerName );
|
|
|
|
NlGlobalAnsiComputerName =
|
|
NetpLogonUnicodeToOem( NlGlobalUnicodeComputerName );
|
|
if ( NlGlobalAnsiComputerName == NULL ) {
|
|
NlExit( SERVICE_UIC_RESOURCE, ERROR_NOT_ENOUGH_MEMORY, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the LSA.
|
|
//
|
|
|
|
Status = LsaIOpenPolicyTrusted( &NlGlobalPolicyHandle );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Determine whether the Role of the local LSA is primary or backup.
|
|
//
|
|
|
|
Status = LsarQueryInformationPolicy(
|
|
NlGlobalPolicyHandle,
|
|
PolicyLsaServerRoleInformation,
|
|
&PolicyLsaServerRole );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (PolicyLsaServerRole->
|
|
PolicyServerRoleInfo.LsaServerRole) {
|
|
case PolicyServerRolePrimary:
|
|
NlGlobalRole = RolePrimary;
|
|
break;
|
|
|
|
case PolicyServerRoleBackup:
|
|
NlGlobalRole = RoleBackup;
|
|
break;
|
|
|
|
default:
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyLsaServerRoleInformation,
|
|
PolicyLsaServerRole );
|
|
|
|
NlExit( SERVICE_UIC_M_UAS_INVALID_ROLE, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyLsaServerRoleInformation,
|
|
PolicyLsaServerRole );
|
|
|
|
|
|
|
|
//
|
|
// If this is Windows-NT running,
|
|
// change the role to RoleMemberWorkstation.
|
|
//
|
|
// All error conditions default to RoleMemberWorkstation.
|
|
//
|
|
|
|
if ( RtlGetNtProductType( &NtProductType ) ) {
|
|
if ( NtProductType != NtProductLanManNt ) {
|
|
NlGlobalRole = RoleMemberWorkstation;
|
|
}
|
|
} else {
|
|
NlGlobalRole = RoleMemberWorkstation;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Get the Primary Domain name from LSA and save it in globals.
|
|
//
|
|
|
|
if ( !NlSetDomainName() ) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If this is a workstation,
|
|
// get the cached trusted domain list from the registry.
|
|
//
|
|
|
|
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
|
LPWSTR TrustedDomainList = NULL;
|
|
BOOL TrustedDomainListKnown;
|
|
|
|
//
|
|
// If NCPA has just joined a domain,
|
|
// and has pre-determined the trusted domain list for us,
|
|
// pick up that list.
|
|
//
|
|
// When this machine joins a domain,
|
|
// NCPA caches the trusted domain list where we can find it. That ensures the
|
|
// trusted domain list is available upon reboot even before we dial via RAS. Winlogon
|
|
// can therefore get the trusted domain list from us under those circumstances.
|
|
//
|
|
|
|
NetStatus = NlReadRegTrustedDomainList (
|
|
NlGlobalUnicodeDomainName,
|
|
TRUE, // Delete this registry key since we no longer need it.
|
|
&TrustedDomainList,
|
|
&TrustedDomainListKnown );
|
|
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, NetStatus, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If there is a cached list,
|
|
// Save it back in the registry for future starts.
|
|
//
|
|
|
|
if ( TrustedDomainListKnown ) {
|
|
NlPrint(( NL_INIT,
|
|
"Replacing trusted domain list with one for newly joined %ws domain.\n",
|
|
NlGlobalUnicodeDomainName));
|
|
NlSaveTrustedDomainList ( TrustedDomainList );
|
|
|
|
//
|
|
// Otherwise, read the current one from the registry.
|
|
//
|
|
|
|
} else {
|
|
NlPrint(( NL_INIT, "Getting cached trusted domain list from registry.\n" ));
|
|
NetStatus = NlReadRegTrustedDomainList (
|
|
NULL,
|
|
FALSE,
|
|
&TrustedDomainList,
|
|
&TrustedDomainListKnown );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, NetStatus, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// In all cases, set the trusted domain list into globals.
|
|
//
|
|
|
|
(VOID) NlSetTrustedDomainList( TrustedDomainList, TrustedDomainListKnown );
|
|
|
|
if ( TrustedDomainList != NULL ) {
|
|
NetApiBufferFree( TrustedDomainList );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Determine the name of the local Sam Account database as the
|
|
// user reference it when logging on.
|
|
//
|
|
// On a workstation, it is the workstation name.
|
|
// On a DC, it is the primary domain name.
|
|
//
|
|
|
|
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
|
RtlInitUnicodeString( &NlGlobalAccountDomainName,
|
|
NlGlobalUnicodeComputerName );
|
|
} else {
|
|
RtlInitUnicodeString( &NlGlobalAccountDomainName,
|
|
NlGlobalUnicodeDomainName );
|
|
}
|
|
|
|
//
|
|
// Initialize LSA database info.
|
|
//
|
|
|
|
Status = NlInitLsaDBInfo( LSA_DB );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Compute the Domain ID of the SAM Account domain.
|
|
//
|
|
|
|
Status = LsarQueryInformationPolicy(
|
|
NlGlobalPolicyHandle,
|
|
PolicyAccountDomainInformation,
|
|
&PolicyAccountDomainInfo );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ) {
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyAccountDomainInformation,
|
|
PolicyAccountDomainInfo );
|
|
|
|
NlPrint((NL_CRITICAL, "Account domain info from LSA invalid.\n"));
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Wait for SAM to start
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !NlWaitForSamService( TRUE ) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, 0, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Open our connection with SAM
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
Status = SamIConnect( NULL, // No server name
|
|
&NlGlobalSamServerHandle,
|
|
0, // Ignore desired access
|
|
(BOOLEAN) TRUE ); // Indicate we are privileged
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlGlobalSamServerHandle = NULL;
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the Sam Account domain.
|
|
//
|
|
|
|
Status = NlInitSamDBInfo(
|
|
SAM_DB,
|
|
PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid );
|
|
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyAccountDomainInformation,
|
|
PolicyAccountDomainInfo );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Create well know SID for netlogon.dll
|
|
//
|
|
|
|
Status = NetpCreateWellKnownSids( NULL );
|
|
|
|
if( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
NlExit( SERVICE_UIC_RESOURCE, NetStatus, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Create the security descriptors we'll use for the APIs
|
|
//
|
|
|
|
Status = NlCreateNetlogonObjects();
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( NELOG_NetlogonSystemError, Status, LogErrorAndNtStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Open the SAM Builtin domain.
|
|
//
|
|
|
|
Status = NlInitSamDBInfo( BUILTIN_DB, BuiltinDomainSid );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Get our UAS compatibility mode from SAM
|
|
//
|
|
|
|
Status = SamrQueryInformationDomain(
|
|
NlGlobalDBInfoArray[SAM_DB].DBHandle,
|
|
DomainGeneralInformation,
|
|
&DomainInfo );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
DomainInfo = NULL;
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalUasCompatibilityMode = DomainInfo->General.UasCompatibilityRequired;
|
|
IF_DEBUG( CRITICAL ) {
|
|
if ( !NlGlobalUasCompatibilityMode ) {
|
|
NlPrint((NL_CRITICAL, "ERROR: UasCompatibility mode is off.\n"));
|
|
}
|
|
}
|
|
|
|
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation );
|
|
|
|
|
|
|
|
//
|
|
// Create Timer event
|
|
//
|
|
|
|
NlGlobalTimerEvent = CreateEvent(
|
|
NULL, // No special security
|
|
FALSE, // Auto Reset
|
|
FALSE, // No Timers need no attention
|
|
NULL ); // No name
|
|
|
|
if ( NlGlobalTimerEvent == NULL ) {
|
|
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Do Workstation or Domain Controller specific initialization
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
|
if ( !NlInitWorkstation() ) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
if ( !NlInitDomainController() ) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create an event that is signalled when the last MSV thread leaves
|
|
// a netlogon routine.
|
|
//
|
|
|
|
NlGlobalMsvTerminateEvent = CreateEvent( NULL, // No security attributes
|
|
TRUE, // Must be manually reset
|
|
FALSE, // Initially not signaled
|
|
NULL ); // No name
|
|
|
|
if ( NlGlobalMsvTerminateEvent == NULL ) {
|
|
NlExit( NELOG_NetlogonSystemError, GetLastError(), LogErrorAndNetStatus, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalMsvEnabled = TRUE;
|
|
|
|
//
|
|
// We are now ready to act as a Netlogon service
|
|
// Enable RPC
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
NlPrint((NL_INIT,"Starting RPC server.\n"));
|
|
|
|
//
|
|
// NOTE: Now all RPC servers in lsass.exe (now winlogon) share the same
|
|
// pipe name. However, in order to support communication with
|
|
// version 1.0 of WinNt, it is necessary for the Client Pipe name
|
|
// to remain the same as it was in version 1.0. Mapping to the new
|
|
// name is performed in the Named Pipe File System code.
|
|
//
|
|
NetStatus = RpcpAddInterface ( L"lsass", logon_ServerIfHandle );
|
|
|
|
if (NetStatus != NERR_Success) {
|
|
NlExit( NELOG_NetlogonFailedToAddRpcInterface, NetStatus, LogErrorAndNetStatus, NULL );
|
|
return FALSE;
|
|
}
|
|
|
|
NlGlobalRpcServerStarted = TRUE;
|
|
|
|
|
|
|
|
//
|
|
// Tell the ServiceController what services we provide.
|
|
//
|
|
|
|
if ( !I_ScSetServiceBits( NlGlobalServiceHandle,
|
|
NlServerType(),
|
|
TRUE, // Set bits on
|
|
TRUE, // Force immediate announcement
|
|
NULL)) { // All transports
|
|
|
|
NetStatus = GetLastError();
|
|
|
|
NlPrint((NL_CRITICAL,"Couldn't I_ScSetServiceBits %ld 0x%lx.\n",
|
|
NetStatus, NetStatus ));
|
|
NlExit( NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Tell the browser that the role may have changed
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
NetStatus = I_BrowserResetNetlogonState( NULL );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
NlPrint((NL_INIT,"Couldn't I_BrowserResetNetlogonState %ld 0x%lx.\n",
|
|
NetStatus, NetStatus ));
|
|
// This isn't fatal
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Let the ChangeLog routines know that Netlogon is started.
|
|
//
|
|
|
|
NlGlobalChangeLogNetlogonState = NetlogonStarted;
|
|
|
|
|
|
|
|
|
|
//
|
|
// Set an event telling anyone wanting to call NETLOGON that we're
|
|
// initialized.
|
|
//
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
RtlInitUnicodeString( &EventName, L"\\NETLOGON_SERVICE_STARTED");
|
|
InitializeObjectAttributes( &EventAttributes, &EventName, 0, 0, NULL );
|
|
|
|
Status = NtCreateEvent(
|
|
&NlGlobalStartedEvent,
|
|
SYNCHRONIZE|EVENT_MODIFY_STATE,
|
|
&EventAttributes,
|
|
NotificationEvent,
|
|
(BOOLEAN) FALSE // The event is initially not signaled
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// If the event already exists, a waiting thread beat us to
|
|
// creating it. Just open it.
|
|
//
|
|
|
|
if( Status == STATUS_OBJECT_NAME_EXISTS ||
|
|
Status == STATUS_OBJECT_NAME_COLLISION ) {
|
|
|
|
Status = NtOpenEvent( &NlGlobalStartedEvent,
|
|
SYNCHRONIZE|EVENT_MODIFY_STATE,
|
|
&EventAttributes );
|
|
|
|
}
|
|
if ( !NT_SUCCESS(Status)) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
" Failed to open NETLOGON_SERVICE_STARTED event. %lX\n",
|
|
Status ));
|
|
NlPrint((NL_CRITICAL,
|
|
" Failing to initialize SAM Server.\n"));
|
|
NlExit( SERVICE_UIC_SYSTEM, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
Status = NtSetEvent( NlGlobalStartedEvent, NULL );
|
|
if ( !NT_SUCCESS(Status)) {
|
|
NlPrint((NL_CRITICAL,
|
|
"Failed to set NETLOGON_SERVICE_STARTED event. %lX\n",
|
|
Status ));
|
|
NlPrint((NL_CRITICAL, " Failing to initialize SAM Server.\n"));
|
|
|
|
NtClose(NlGlobalStartedEvent);
|
|
NlExit( SERVICE_UIC_SYSTEM, Status, LogError, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Don't close the event handle. Closing it would delete the event and
|
|
// a future waiter would never see it be set.
|
|
//
|
|
|
|
|
|
//
|
|
// Announce that we're started
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary) {
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
return FALSE;
|
|
}
|
|
NlAnnouncePrimaryStart();
|
|
}
|
|
|
|
|
|
//
|
|
// we are just about done, this will be final hint
|
|
//
|
|
|
|
if ( !GiveInstallHints( TRUE ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// If we're not the primary,
|
|
// sync the SAM database as requested.
|
|
//
|
|
|
|
if ( NlGlobalRole == RoleBackup ) {
|
|
DWORD i;
|
|
|
|
//
|
|
// Give the BDC a chance to change its password before the replicator
|
|
// starts.
|
|
//
|
|
|
|
NlChangePassword( NlGlobalClientSession );
|
|
|
|
//
|
|
// Handle each database separately.
|
|
//
|
|
|
|
for( i = 0; i < NUM_DBS; i++ ) {
|
|
|
|
//
|
|
// if /UPDATE:YES has been specified then FORCE replication
|
|
//
|
|
|
|
if ( NlGlobalSynchronizeParameter ) {
|
|
|
|
NlPrint(( NL_SYNC,
|
|
FORMAT_LPWSTR ": Force FULL SYNC because UPDATE was specified.\n",
|
|
NlGlobalDBInfoArray[i].DBName ));
|
|
|
|
|
|
//
|
|
// Do a complete full sync (don't restart it).
|
|
//
|
|
|
|
NlSetFullSyncKey( i, NULL );
|
|
|
|
Status = NlForceStartupSync( &NlGlobalDBInfoArray[i] );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NlExit( SERVICE_UIC_M_DATABASE_ERROR,
|
|
NetpNtStatusToApiStatus(Status),
|
|
LogError,
|
|
NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// See if there is any reason to start replication
|
|
//
|
|
|
|
for ( i = 0; i < NUM_DBS; i++ ) {
|
|
|
|
if ( NlGlobalDBInfoArray[i].UpdateRqd ||
|
|
NlGlobalRedoLogDesc.EntryCount[i] != 0 ) {
|
|
|
|
if ( NlGlobalClientSession->CsState != CS_IDLE ) {
|
|
NlPrint((NL_SYNC, "Starting replicator on startup.\n"));
|
|
(VOID) NlStartReplicatorThread( 0 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clear the full sync key on the primary to avoid confusion if we ever
|
|
// demote to a BDC.
|
|
//
|
|
} else if ( NlGlobalRole == RolePrimary ) {
|
|
|
|
DWORD i;
|
|
|
|
for( i = 0; i < NUM_DBS; i++ ) {
|
|
NlSetFullSyncKey( i, NULL );
|
|
(VOID) NlResetFirstTimeFullSync( i );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Successful initialization.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
LogonRequestHandler(
|
|
IN DWORD Version,
|
|
IN LPWSTR UnicodeUserName,
|
|
IN DWORD RequestCount,
|
|
IN LPSTR OemWorkstationName,
|
|
IN LPSTR OemMailslotName,
|
|
IN LPWSTR TransportName,
|
|
IN ULONG AllowableAccountControlBits
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Respond appropriate to a LM 1.0, LM 2.0 or SAM logon request.
|
|
|
|
Requests from LM1.0 clients to be handled differently
|
|
since response_buffer size has changed due to PATHLEN.
|
|
|
|
Arguments:
|
|
|
|
Version - The version of the input message. This parameter determine
|
|
the version of the response.
|
|
|
|
UnicodeUserName - The name of the user logging on.
|
|
|
|
RequestCount - The number of times this user has repeated the logon request.
|
|
|
|
OemWorkstationName - The name of the workstation where the user is
|
|
logging onto.
|
|
|
|
OemMailslotName - The name of the mailslot to respond to.
|
|
|
|
AllowableAccountControlBits - A mask of allowable SAM account types that
|
|
are allowed to satisfy this request.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the any duplicates of this message should be ignored.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
USHORT Response = 0;
|
|
PSAMPR_USER_INFO_BUFFER UserControl = NULL;
|
|
|
|
NETLOGON_LOGON_RESPONSE2 Response2;
|
|
NETLOGON_SAM_LOGON_RESPONSE SamResponse;
|
|
PCHAR Where;
|
|
BOOLEAN IgnoreDuplicatesOfThisMessage = FALSE;
|
|
|
|
SAMPR_HANDLE UserHandle = NULL;
|
|
|
|
//
|
|
// Logons are not processed if the service is paused
|
|
//
|
|
|
|
if ( (NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED) ||
|
|
( NlGlobalFirstTimeFullSync == TRUE ) ) {
|
|
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
Response = LOGON_SAM_PAUSE_RESPONSE;
|
|
} else {
|
|
|
|
//
|
|
// Don't respond to immediately to non-nt clients. They treat
|
|
// "paused" responses as fatal. That's just not so.
|
|
// There may be many other DCs that are able to process the logon.
|
|
//
|
|
if ( RequestCount >= MAX_LOGONREQ_COUNT &&
|
|
NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED ) {
|
|
Response = LOGON_PAUSE_RESPONSE;
|
|
}
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If this user does not have an account in SAM,
|
|
// immediately return a response indicating so.
|
|
//
|
|
// All we are trying to do here is ensuring that this guy
|
|
// has a valid account except that we are not checking the
|
|
// password
|
|
//
|
|
// This is done so that STANDALONE logons for non existent
|
|
// users can be done in very first try, speeding up the response
|
|
// to user and reducing processing on DCs/BCs.
|
|
//
|
|
|
|
Status = NlSamOpenNamedUser( UnicodeUserName, &UserHandle, NULL );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
|
|
if ( Status == STATUS_NO_SUCH_USER ) {
|
|
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
Response = LOGON_SAM_USER_UNKNOWN;
|
|
} else if ( Version == LM20_MESSAGE ) {
|
|
Response = LOGON_USER_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the account control information.
|
|
//
|
|
|
|
Status = SamrQueryInformationUser(
|
|
UserHandle,
|
|
UserControlInformation,
|
|
&UserControl );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Disallow use of disabled accounts.
|
|
//
|
|
// We use this message to determine if a trusted domain has a
|
|
// particular account. Since the UI recommend disabling an account
|
|
// rather than deleting it (conservation of rids and all that),
|
|
// we shouldn't respond that we have the account if we really don't.
|
|
//
|
|
// We don't check the disabled bit in the downlevel case. Downlevel
|
|
// interactive logons are directed at a single particular domain.
|
|
// It is better here that we indicate we have the account so later
|
|
// he'll get a better error code indicating that the account is
|
|
// disabled, rather than allowing him to logon standalone.
|
|
//
|
|
|
|
if ( Version == LMNT_MESSAGE &&
|
|
(UserControl->Control.UserAccountControl & USER_ACCOUNT_DISABLED) ) {
|
|
Response = LOGON_SAM_USER_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Ensure the Account type matches those valid for logons.
|
|
//
|
|
if ( (UserControl->Control.UserAccountControl &
|
|
USER_ACCOUNT_TYPE_MASK &
|
|
AllowableAccountControlBits )
|
|
== 0 ) {
|
|
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
Response = LOGON_SAM_USER_UNKNOWN;
|
|
} else if ( Version == LM20_MESSAGE ) {
|
|
Response = LOGON_USER_UNKNOWN;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// For SAM clients, respond immediately.
|
|
//
|
|
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
Response = LOGON_SAM_LOGON_RESPONSE;
|
|
goto Cleanup;
|
|
|
|
//
|
|
// For LM 2.0 clients, respond immediately.
|
|
//
|
|
|
|
} else if ( Version == LM20_MESSAGE ) {
|
|
Response = LOGON_RESPONSE2;
|
|
goto Cleanup;
|
|
|
|
//
|
|
// For LM 1.0 clients,
|
|
// don't support the request.
|
|
//
|
|
|
|
} else {
|
|
Response = LOGON_USER_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Always good to debug
|
|
//
|
|
|
|
NlPrint((NL_LOGON,
|
|
"%s logon mailslot message for " FORMAT_LPWSTR " from \\\\%s"
|
|
". Response 0x%lx\n",
|
|
Version == LMNT_MESSAGE ? "Sam" : "Uas",
|
|
UnicodeUserName,
|
|
OemWorkstationName,
|
|
Response ));
|
|
|
|
//
|
|
// If we should respond to the caller, do so now.
|
|
//
|
|
|
|
switch (Response) {
|
|
case LOGON_SAM_PAUSE_RESPONSE:
|
|
case LOGON_SAM_USER_UNKNOWN:
|
|
case LOGON_SAM_LOGON_RESPONSE:
|
|
SamResponse.Opcode = Response;
|
|
|
|
Where = (PCHAR) SamResponse.UnicodeLogonServer;
|
|
NetpLogonPutUnicodeString( NlGlobalUncUnicodeComputerName,
|
|
sizeof(SamResponse.UnicodeLogonServer),
|
|
&Where );
|
|
NetpLogonPutUnicodeString( UnicodeUserName,
|
|
sizeof(SamResponse.UnicodeUserName),
|
|
&Where );
|
|
NetpLogonPutUnicodeString( NlGlobalUnicodeDomainName,
|
|
sizeof(SamResponse.UnicodeDomainName),
|
|
&Where );
|
|
NetpLogonPutNtToken( &Where );
|
|
|
|
Status = NlBrowserSendDatagram( OemWorkstationName,
|
|
TransportName,
|
|
OemMailslotName,
|
|
&SamResponse,
|
|
Where - (PCHAR)&SamResponse );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
}
|
|
break;
|
|
|
|
case LOGON_RESPONSE2:
|
|
case LOGON_USER_UNKNOWN:
|
|
case LOGON_PAUSE_RESPONSE:
|
|
|
|
Response2.Opcode = Response;
|
|
|
|
Where = Response2.LogonServer;
|
|
(VOID) lstrcpyA( Where, "\\\\");
|
|
Where += 2;
|
|
NetpLogonPutOemString( NlGlobalAnsiComputerName,
|
|
sizeof(Response2.LogonServer) - 2,
|
|
&Where );
|
|
NetpLogonPutLM20Token( &Where );
|
|
|
|
|
|
Status = NlBrowserSendDatagram( OemWorkstationName,
|
|
TransportName,
|
|
OemMailslotName,
|
|
&Response2,
|
|
Where - (PCHAR)&Response2 );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Free up any locally used resources.
|
|
//
|
|
|
|
if ( UserControl != NULL ) {
|
|
SamIFree_SAMPR_USER_INFO_BUFFER( UserControl, UserControlInformation );
|
|
}
|
|
|
|
if ( UserHandle != NULL ) {
|
|
(VOID) SamrCloseHandle( &UserHandle );
|
|
}
|
|
|
|
return IgnoreDuplicatesOfThisMessage;
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
PrimaryQueryHandler(
|
|
IN DWORD Version,
|
|
IN LPSTR OemWorkstationName,
|
|
IN LPSTR OemMailslotName,
|
|
IN LPWSTR TransportName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Respond appropriately to a primary query request.
|
|
|
|
Arguments:
|
|
|
|
Version - The version of the input message.
|
|
|
|
OemWorkstationName - The name of the workstation where the user is
|
|
logging onto.
|
|
|
|
OemMailslotName - The name of the mailslot to respond to.
|
|
|
|
TransportName - The name of the transport to respond on.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the any duplicates of this message should be ignored.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
NETLOGON_PRIMARY Response;
|
|
PCHAR Where;
|
|
BOOLEAN IgnoreDuplicatesOfThisMessage = FALSE;
|
|
|
|
//
|
|
// If we're a PDC,
|
|
// always respond.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
goto Respond;
|
|
}
|
|
|
|
|
|
//
|
|
// ASSERT: This machine is a BDC
|
|
//
|
|
|
|
NlAssert( NlGlobalRole == RoleBackup );
|
|
|
|
|
|
//
|
|
// Always respond to this message if this query is from a downlevel
|
|
// PDC trying to start up.
|
|
//
|
|
//
|
|
// If this request is from NetGetDCName, ignore the request.
|
|
// We know the NetGetDCName uses a mailslot name that is
|
|
// randomly generated and that LM2.0 netlogon uses the
|
|
// standard netlogon mailslot name to find out if another PDC
|
|
// is already running.
|
|
//
|
|
|
|
if ( Version != LMNT_MESSAGE &&
|
|
lstrcmpiA( OemMailslotName, NETLOGON_LM_MAILSLOT_A ) == 0 ) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"PrimaryQueryHandler: Preventing Lanman PDC from starting "
|
|
"in this NT domain. \\\\%s.\n",
|
|
OemWorkstationName ));
|
|
goto Respond;
|
|
}
|
|
|
|
//
|
|
// If the caller is an NT machine,
|
|
// don't respond to this request.
|
|
//
|
|
// NT 3.1 clients have a sophisticated TCP/IP stack which ensures that the
|
|
// request reaches the real PDC so we don't need to respond.
|
|
//
|
|
// NT 3.5 clients, Chicago clients and newer (8/8/94) WFW clients send
|
|
// directly to the Domain<1B> address which is registered by the PDC.
|
|
//
|
|
|
|
if ( Version == LMNT_MESSAGE || Version == LMWFW_MESSAGE ) {
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If this machine is on a WAN,
|
|
// this primary query message may have reached us but not the PDC.
|
|
// Therefore, we'll respond to the request if we know who the PDC is.
|
|
//
|
|
|
|
if ( *NlGlobalUnicodePrimaryName == L'\0' ) {
|
|
NlPrint((NL_MAILSLOT,
|
|
"PrimaryQueryHandler: This BDC doesn't know who primary is." ));
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Ensure we have a session up to the PDC.
|
|
// This is our evidence that the machine we think is the PDC really
|
|
// is the PDC.
|
|
//
|
|
|
|
if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
|
|
NlPrint((NL_MAILSLOT,
|
|
"PrimaryQueryHandler: This BDC doesn't have a session to the PDC.\n" ));
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Respond to the query
|
|
//
|
|
Respond:
|
|
|
|
NlPrint((NL_MAILSLOT,
|
|
"%s Primary Query mailslot message from %s. "
|
|
"Response " FORMAT_LPWSTR "\n",
|
|
Version == LMNT_MESSAGE ? "Sam" : "Uas",
|
|
OemWorkstationName,
|
|
NlGlobalUncPrimaryName ));
|
|
|
|
//
|
|
// Build the response
|
|
//
|
|
// If we are the Primary DC, tell the caller our computername.
|
|
// If we are a backup DC,
|
|
// tell the downlevel PDC who we think the primary is.
|
|
//
|
|
|
|
Response.Opcode = LOGON_PRIMARY_RESPONSE;
|
|
|
|
Where = Response.PrimaryDCName;
|
|
NetpLogonPutOemString(
|
|
NlGlobalAnsiPrimaryName,
|
|
sizeof( Response.PrimaryDCName),
|
|
&Where );
|
|
|
|
//
|
|
// If this is an NT query,
|
|
// add the NT specific response.
|
|
//
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
NetpLogonPutUnicodeString(
|
|
NlGlobalUnicodePrimaryName,
|
|
sizeof(Response.UnicodePrimaryDCName),
|
|
&Where );
|
|
|
|
NetpLogonPutUnicodeString(
|
|
NlGlobalUnicodeDomainName,
|
|
sizeof(Response.UnicodeDomainName),
|
|
&Where );
|
|
|
|
NetpLogonPutNtToken( &Where );
|
|
}
|
|
|
|
|
|
Status = NlBrowserSendDatagram( OemWorkstationName,
|
|
TransportName,
|
|
OemMailslotName,
|
|
&Response,
|
|
(DWORD)(Where - (PCHAR)&Response) );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
IgnoreDuplicatesOfThisMessage = TRUE;
|
|
}
|
|
|
|
//
|
|
// Free Locally used resources
|
|
//
|
|
Cleanup:
|
|
|
|
return IgnoreDuplicatesOfThisMessage;
|
|
}
|
|
|
|
BOOL
|
|
TimerExpired(
|
|
IN PTIMER Timer,
|
|
IN PLARGE_INTEGER TimeNow,
|
|
IN OUT LPDWORD Timeout
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine whether a timer has expired. If not, adjust the passed in
|
|
timeout value to take this timer into account.
|
|
|
|
Arguments:
|
|
|
|
Timer - Specifies the timer to check.
|
|
|
|
TimeNow - Specifies the current time of day in NT standard time.
|
|
|
|
Timeout - Specifies the current amount of time (in milliseconds)
|
|
that the caller intends to wait for a timer to expire.
|
|
If this timer has not expired, this value is adjusted to the
|
|
smaller of the current value and the amount of time remaining
|
|
on the passed in timer.
|
|
|
|
Return Value:
|
|
|
|
TRUE - if the timer has expired.
|
|
|
|
--*/
|
|
|
|
{
|
|
LARGE_INTEGER Period;
|
|
LARGE_INTEGER ExpirationTime;
|
|
LARGE_INTEGER ElapsedTime;
|
|
LARGE_INTEGER TimeRemaining;
|
|
LARGE_INTEGER MillisecondsRemaining;
|
|
|
|
/*lint -e569 */ /* don't complain about 32-bit to 31-bit initialize */
|
|
LARGE_INTEGER BaseGetTickMagicDivisor = { 0xe219652c, 0xd1b71758 };
|
|
/*lint +e569 */ /* don't complain about 32-bit to 31-bit initialize */
|
|
CCHAR BaseGetTickMagicShiftCount = 13;
|
|
|
|
//
|
|
// If the period to too large to handle (i.e., 0xffffffff is forever),
|
|
// just indicate that the timer has not expired.
|
|
//
|
|
|
|
if ( Timer->Period > 0x7fffffff ) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If time has gone backwards (someone changed the clock),
|
|
// just start the timer over again.
|
|
//
|
|
// The kernel automatically updates the system time to the CMOS clock
|
|
// periodically. If we just expired the timer when time went backwards,
|
|
// we'd risk periodically falsely triggering the timeout.
|
|
//
|
|
|
|
ElapsedTime.QuadPart = TimeNow->QuadPart - Timer->StartTime.QuadPart;
|
|
|
|
if ( ElapsedTime.QuadPart < 0 ) {
|
|
Timer->StartTime = *TimeNow;
|
|
}
|
|
|
|
//
|
|
// Convert the period from milliseconds to 100ns units.
|
|
//
|
|
|
|
Period = RtlEnlargedIntegerMultiply( (LONG) Timer->Period, 10000 );
|
|
|
|
//
|
|
// Compute the expiration time.
|
|
//
|
|
|
|
ExpirationTime.QuadPart = Timer->StartTime.QuadPart + Period.QuadPart;
|
|
|
|
//
|
|
// Compute the Time remaining on the timer.
|
|
//
|
|
|
|
TimeRemaining.QuadPart = ExpirationTime.QuadPart - TimeNow->QuadPart;
|
|
|
|
//
|
|
// If the timer has expired, tell the caller so.
|
|
//
|
|
|
|
if ( TimeRemaining.QuadPart <= 0 ) {
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If the timer hasn't expired, compute the number of milliseconds
|
|
// remaining.
|
|
//
|
|
|
|
MillisecondsRemaining = RtlExtendedMagicDivide(
|
|
TimeRemaining,
|
|
BaseGetTickMagicDivisor,
|
|
BaseGetTickMagicShiftCount );
|
|
|
|
NlAssert( MillisecondsRemaining.HighPart == 0 );
|
|
NlAssert( MillisecondsRemaining.LowPart < 0x7fffffff );
|
|
|
|
//
|
|
// Adjust the running timeout to be the smaller of the current value
|
|
// and the value computed for this timer.
|
|
//
|
|
|
|
if ( *Timeout > MillisecondsRemaining.LowPart ) {
|
|
*Timeout = MillisecondsRemaining.LowPart;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
VOID
|
|
NlScavenger(
|
|
IN LPVOID ScavengerParam
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function performs the scavenger operation. This function is
|
|
called every 15 mins interval. On workstation this function is
|
|
executed in the main netlogon thread, but on server this function is
|
|
executed on the scavenger thread, thus making the main thread to
|
|
process the mailslot messages better.
|
|
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Return iff the service is to exit or all scavenger operations
|
|
for this turn are completed.
|
|
|
|
--*/
|
|
{
|
|
|
|
|
|
//
|
|
// Change password if neccessary
|
|
//
|
|
|
|
if ( (NlGlobalRole == RoleMemberWorkstation ||
|
|
NlGlobalRole == RoleBackup) &&
|
|
!NlGlobalDisablePasswordChangeParameter ) {
|
|
|
|
(VOID) NlChangePassword( NlGlobalClientSession );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Change the password on each entry in the trust list.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
PLIST_ENTRY ListEntry;
|
|
PCLIENT_SESSION ClientSession;
|
|
|
|
//
|
|
// Reset all the flags indicating we need to check the password
|
|
//
|
|
|
|
LOCK_TRUST_LIST();
|
|
for ( ListEntry = NlGlobalTrustList.Flink ;
|
|
ListEntry != &NlGlobalTrustList ;
|
|
ListEntry = ListEntry->Flink) {
|
|
|
|
ClientSession = CONTAINING_RECORD( ListEntry,
|
|
CLIENT_SESSION,
|
|
CsNext );
|
|
|
|
ClientSession->CsFlags |= CS_CHECK_PASSWORD;
|
|
}
|
|
|
|
for ( ListEntry = NlGlobalTrustList.Flink ;
|
|
ListEntry != &NlGlobalTrustList ;
|
|
) {
|
|
|
|
ClientSession = CONTAINING_RECORD( ListEntry,
|
|
CLIENT_SESSION,
|
|
CsNext );
|
|
|
|
if ( (ClientSession->CsFlags & CS_CHECK_PASSWORD) == 0 ) {
|
|
ListEntry = ListEntry->Flink;
|
|
continue;
|
|
}
|
|
ClientSession->CsFlags &= ~CS_CHECK_PASSWORD;
|
|
|
|
NlRefClientSession( ClientSession );
|
|
UNLOCK_TRUST_LIST();
|
|
|
|
|
|
//
|
|
// Change the password for this trusted domain.
|
|
//
|
|
|
|
(VOID) NlChangePassword( ClientSession );
|
|
|
|
NlUnrefClientSession( ClientSession );
|
|
|
|
//
|
|
// check to see iff we have been asked to leave.
|
|
//
|
|
|
|
if( NlGlobalScavengerTerminate == TRUE ) {
|
|
|
|
return;
|
|
}
|
|
|
|
LOCK_TRUST_LIST();
|
|
|
|
// Start again at the beginning.
|
|
ListEntry = NlGlobalTrustList.Flink;
|
|
|
|
}
|
|
UNLOCK_TRUST_LIST();
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Scavenge through the server session table.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup ) {
|
|
NlServerSessionScavenger();
|
|
|
|
//
|
|
// Pick a DC for each non-authenicated entry in the trust list.
|
|
//
|
|
|
|
NlPickTrustedDcForEntireTrustList();
|
|
|
|
}
|
|
|
|
//
|
|
// Ensure our Domain<1B> name is registered.
|
|
//
|
|
|
|
NlBrowserAddName();
|
|
|
|
UNREFERENCED_PARAMETER( ScavengerParam );
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsScavengerRunning(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Test if the scavenger thread is running
|
|
|
|
Enter with NlGlobalScavengerCritSect locked.
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
TRUE - The scavenger thread is running
|
|
|
|
FALSE - The scavenger thread is not running.
|
|
|
|
--*/
|
|
{
|
|
DWORD WaitStatus;
|
|
|
|
//
|
|
// Determine if the scavenger thread is already running.
|
|
//
|
|
|
|
if ( NlGlobalScavengerThreadHandle != NULL ) {
|
|
|
|
//
|
|
// Time out immediately if the scavenger is still running.
|
|
//
|
|
|
|
WaitStatus = WaitForSingleObject(
|
|
NlGlobalScavengerThreadHandle,
|
|
0 );
|
|
|
|
if ( WaitStatus == WAIT_TIMEOUT ) {
|
|
return TRUE;
|
|
|
|
} else if ( WaitStatus == 0 ) {
|
|
CloseHandle( NlGlobalScavengerThreadHandle );
|
|
NlGlobalScavengerThreadHandle = NULL;
|
|
return FALSE;
|
|
|
|
} else {
|
|
NlPrint((NL_CRITICAL,
|
|
"Cannot WaitFor scavenger thread: %ld\n",
|
|
WaitStatus ));
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
NlStopScavenger(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Stops the scavenger thread if it is running and waits for it to
|
|
stop.
|
|
|
|
Enter with NlGlobalScavengerCritSect locked.
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Ask the scavenger to stop running.
|
|
//
|
|
|
|
NlGlobalScavengerTerminate = TRUE;
|
|
|
|
//
|
|
// Determine if the scavenger thread is already running.
|
|
//
|
|
|
|
if ( NlGlobalScavengerThreadHandle != NULL ) {
|
|
|
|
//
|
|
// We've asked the scavenger to stop. It should do so soon.
|
|
// Wait for it to stop.
|
|
//
|
|
|
|
NlWaitForSingleObject( "Wait for scavenger to stop",
|
|
NlGlobalScavengerThreadHandle );
|
|
|
|
|
|
CloseHandle( NlGlobalScavengerThreadHandle );
|
|
NlGlobalScavengerThreadHandle = NULL;
|
|
|
|
}
|
|
|
|
NlGlobalScavengerTerminate = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOL
|
|
NlStartScavengerThread(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Start the scavenger thread if it is not already running.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
DWORD ThreadHandle;
|
|
|
|
//
|
|
// If the scavenger thread is already running, do nothing.
|
|
//
|
|
|
|
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
|
if ( IsScavengerRunning() ) {
|
|
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Initialize the scavenger parameters
|
|
//
|
|
|
|
NlGlobalScavengerTerminate = FALSE;
|
|
|
|
NlGlobalScavengerThreadHandle = CreateThread(
|
|
NULL, // No security attributes
|
|
THREAD_STACKSIZE,
|
|
(LPTHREAD_START_ROUTINE)
|
|
NlScavenger,
|
|
NULL,
|
|
0, // No special creation flags
|
|
&ThreadHandle );
|
|
|
|
if ( NlGlobalScavengerThreadHandle == NULL ) {
|
|
|
|
//
|
|
// ?? Shouldn't we do something in non-debug case
|
|
//
|
|
|
|
NlPrint((NL_CRITICAL, "Can't create scavenger Thread %lu\n",
|
|
GetLastError() ));
|
|
|
|
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
|
return FALSE;
|
|
}
|
|
|
|
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
NlMainLoop(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Waits for a logon request to arrive at the NETLOGON mailslot.
|
|
|
|
This routine, also, processes several periodic events. These events
|
|
are timed by computing a timeout value on the mailslot read which is the
|
|
time needed before the nearest periodic event needs to be processed.
|
|
After such a timeout, this routine processes the event.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Return iff the service is to exit.
|
|
|
|
mail slot error occurred, eg if someone deleted the NETLOGON
|
|
mail slot explicitly or if the logon server share has been deleted
|
|
and cannot be re-shared.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
DWORD WaitStatus;
|
|
|
|
DWORD BytesRead;
|
|
LPBYTE Message;
|
|
LPWSTR TransportName;
|
|
|
|
DWORD ResponseBufferSize;
|
|
// ?? Get these off the workstation stack
|
|
BYTE resp_buf[NETLOGON_MAX_MS_SIZE]; // Buffer to build response in
|
|
|
|
//
|
|
// Variables for unmarshalling the message read.
|
|
//
|
|
|
|
DWORD Version;
|
|
DWORD VersionFlags;
|
|
PCHAR Where;
|
|
LPSTR OemWorkstationName;
|
|
LPSTR AnsiUserName;
|
|
LPSTR OemMailslotName;
|
|
|
|
LPWSTR UnicodeWorkstationName;
|
|
LPWSTR UnicodeUserName;
|
|
|
|
LPSTR AnsiTemp;
|
|
|
|
LPWSTR UnicodeTemp;
|
|
BOOLEAN IgnoreDuplicatesOfThisMessage;
|
|
|
|
|
|
|
|
//
|
|
// Variables controlling mailslot read timeout
|
|
//
|
|
|
|
DWORD MainLoopTimeout = 0;
|
|
LARGE_INTEGER TimeNow;
|
|
|
|
TIMER ScavengerTimer;
|
|
TIMER AnnouncerTimer;
|
|
|
|
#define NL_WAIT_TERMINATE 0
|
|
#define NL_WAIT_TIMER 1
|
|
#define NL_WAIT_MAILSLOT 2
|
|
#define NL_WAIT_NOTIFY 3
|
|
|
|
#define NL_WAIT_COUNT 4
|
|
|
|
HANDLE WaitHandles[ NL_WAIT_COUNT ];
|
|
DWORD WaitCount = 0;
|
|
|
|
|
|
|
|
//
|
|
// Initialize handles to wait on.
|
|
//
|
|
|
|
WaitHandles[NL_WAIT_TERMINATE] = NlGlobalTerminateEvent;
|
|
WaitCount++;
|
|
WaitHandles[NL_WAIT_TIMER] = NlGlobalTimerEvent;
|
|
WaitCount++;
|
|
|
|
if ( NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup ) {
|
|
WaitHandles[NL_WAIT_MAILSLOT] = NlGlobalMailslotHandle;
|
|
WaitCount++;
|
|
|
|
//
|
|
// When netlogon is run during retail setup
|
|
// (in an attempt to replicate the databases to a BDC),
|
|
// the role is Workstation at the instant netlogon.dll is loaded,
|
|
// therefore, the ChangeLogEvent won't have been initialized.
|
|
//
|
|
|
|
if ( NlGlobalChangeLogEvent != NULL ) {
|
|
WaitHandles[NL_WAIT_NOTIFY] = NlGlobalChangeLogEvent;
|
|
WaitCount++;
|
|
}
|
|
}
|
|
|
|
NlAssert( WaitCount <= NL_WAIT_COUNT );
|
|
|
|
|
|
//
|
|
// Set up a secure channel to any DC in the domain.
|
|
// Don't fail if setup is impossible.
|
|
//
|
|
// We wait until now since this is a potentially lengthy operation.
|
|
// If the user on the workstation is trying to logon immediately after
|
|
// reboot, we'd rather have him wait in netlogon (where we have more
|
|
// control) than have him waiting in MSV.
|
|
//
|
|
|
|
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
|
(VOID) NlTimeoutSetWriterClientSession( NlGlobalClientSession, 0xFFFFFFFF );
|
|
(VOID) NlSessionSetup( NlGlobalClientSession );
|
|
NlResetWriterClientSession( NlGlobalClientSession );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Force the scavenger to start immediately.
|
|
//
|
|
// We want the password on the trust account to change immediately
|
|
// on the very first boot.
|
|
//
|
|
|
|
(VOID) NtQuerySystemTime( &TimeNow );
|
|
|
|
ScavengerTimer.StartTime.QuadPart = 0;
|
|
ScavengerTimer.Period = NlGlobalScavengeIntervalParameter * 1000L;
|
|
|
|
|
|
|
|
//
|
|
// Force the announce to happen immediately.
|
|
//
|
|
// We use this initial announcement in case the "Primary Start"
|
|
// message was lost and this is the first boot of a new PDC.
|
|
// This ensures that all BDCs receive the name of the new PDC quickly
|
|
// so they respond correctly to "Primary Query" requests.
|
|
//
|
|
|
|
AnnouncerTimer.StartTime.QuadPart = 0;
|
|
AnnouncerTimer.Period = NlGlobalPulseParameter * 1000L;
|
|
|
|
|
|
|
|
//
|
|
// Ensure we don't immediately time out the discovery timer.
|
|
//
|
|
|
|
NlGlobalDcDiscoveryTimer.StartTime = TimeNow;
|
|
NlGlobalApiTimer.StartTime = TimeNow;
|
|
|
|
|
|
|
|
NlPrint((NL_INIT, "Started successfully\n" ));
|
|
|
|
//
|
|
// Loop reading from the Netlogon mailslot
|
|
//
|
|
|
|
IgnoreDuplicatesOfThisMessage = FALSE;
|
|
for ( ;; ) {
|
|
DWORD Timeout;
|
|
|
|
|
|
|
|
//
|
|
// Issue a mailslot read request if we are domain controller and
|
|
// there is no outstanding read request pending.
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary || NlGlobalRole == RoleBackup) {
|
|
NlMailslotPostRead( IgnoreDuplicatesOfThisMessage );
|
|
IgnoreDuplicatesOfThisMessage = FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Wait for the next interesting event.
|
|
//
|
|
// On each iteration of the loop,
|
|
// we do an "extra" wait with a timeout of 0 to force mailslot
|
|
// processing to be more important that timeout processing.
|
|
//
|
|
// Since we can only compute a non-zero timeout by processing the
|
|
// timeout events, using a constant 0 allows us to process all
|
|
// non-timeout events before we compute the next true timeout value.
|
|
//
|
|
// This is especially important for handling async discovery.
|
|
// Our mailslot may be full of responses to discovery queries and
|
|
// we only have a 5 second timer before we ask for more responses.
|
|
// We want to avoid asking for additional responses until we finish
|
|
// processing those we have.
|
|
//
|
|
|
|
if ( MainLoopTimeout != 0 ) {
|
|
NlPrint((NL_MAILSLOT,
|
|
"Going to wait on mailslot. (Timeout: %ld)\n",
|
|
MainLoopTimeout));
|
|
}
|
|
|
|
WaitStatus = WaitForMultipleObjects( WaitCount,
|
|
WaitHandles,
|
|
FALSE, // Wait for ANY handle
|
|
MainLoopTimeout );
|
|
|
|
MainLoopTimeout = 0; // Set default timeout
|
|
|
|
|
|
//
|
|
// If we've been asked to terminate,
|
|
// do so immediately
|
|
//
|
|
|
|
switch ( WaitStatus ) {
|
|
case NL_WAIT_TERMINATE: // service termination
|
|
goto Cleanup;
|
|
|
|
|
|
//
|
|
// Process timeouts and determine the timeout for the next iteration
|
|
//
|
|
|
|
case WAIT_TIMEOUT: // timeout
|
|
case NL_WAIT_TIMER: // someone changed a timer
|
|
|
|
//
|
|
// Assume there is no timeout to do.
|
|
//
|
|
|
|
Timeout = (DWORD) -1;
|
|
(VOID) NtQuerySystemTime( &TimeNow );
|
|
|
|
|
|
//
|
|
// On the primary, timeout announcements to BDCs
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
if ( TimerExpired( &NlGlobalPendingBdcTimer, &TimeNow, &Timeout )) {
|
|
NlPrimaryAnnouncementTimeout();
|
|
NlGlobalPendingBdcTimer.StartTime = TimeNow;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Check the scavenger timer
|
|
//
|
|
|
|
if ( TimerExpired( &ScavengerTimer, &TimeNow, &Timeout ) ) {
|
|
|
|
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
|
|
|
//
|
|
// On workstation run the scavenger on main thread.
|
|
//
|
|
|
|
NlScavenger(NULL);
|
|
|
|
} else {
|
|
//
|
|
// On server, start scavenger thread if it is not
|
|
// running already.
|
|
//
|
|
|
|
(VOID)NlStartScavengerThread();
|
|
}
|
|
|
|
ScavengerTimer.StartTime = TimeNow;
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Check the DC discovery timer.
|
|
//
|
|
|
|
if ( TimerExpired( &NlGlobalDcDiscoveryTimer, &TimeNow, &Timeout)) {
|
|
|
|
NlDcDiscoveryExpired( FALSE );
|
|
|
|
//
|
|
// The above operation might have taken a significant fraction
|
|
// of DISCOVERY_PERIOD. So, set the timer to the current time
|
|
// rather than TimeNow to allow time for responses to come in.
|
|
//
|
|
(VOID) NtQuerySystemTime( &NlGlobalDcDiscoveryTimer.StartTime );
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// Check the API timer
|
|
//
|
|
|
|
if ( TimerExpired( &NlGlobalApiTimer, &TimeNow, &Timeout)) {
|
|
|
|
NlTimeoutApiClientSession();
|
|
NlGlobalApiTimer.StartTime = TimeNow;
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// If we're the primary,
|
|
// periodically do announcements
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary &&
|
|
TimerExpired( &AnnouncerTimer, &TimeNow, &Timeout ) ) {
|
|
|
|
NlPrimaryAnnouncement( 0 );
|
|
AnnouncerTimer.StartTime = TimeNow;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If we've gotten this far,
|
|
// we know the only thing left to do is to wait for the next event.
|
|
//
|
|
|
|
MainLoopTimeout = Timeout;
|
|
continue;
|
|
|
|
|
|
|
|
|
|
//
|
|
// Process mailslot messages.
|
|
//
|
|
|
|
case NL_WAIT_MAILSLOT: // mailslot message
|
|
|
|
if ( !NlMailslotOverlappedResult( &Message,
|
|
&BytesRead,
|
|
&TransportName,
|
|
&IgnoreDuplicatesOfThisMessage )){
|
|
// Just continue if there really isn't a message
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
//
|
|
// Process interesting changelog events.
|
|
//
|
|
|
|
case NL_WAIT_NOTIFY: // Something interesting Logged to change log
|
|
|
|
|
|
|
|
|
|
//
|
|
// If a "replicate immediately" event has happened,
|
|
// send a primary announcement.
|
|
//
|
|
LOCK_CHANGELOG();
|
|
if ( NlGlobalChangeLogReplicateImmediately ) {
|
|
|
|
NlGlobalChangeLogReplicateImmediately = FALSE;
|
|
NlGlobalChangeLogLanmanReplicateImmediately = FALSE;
|
|
|
|
UNLOCK_CHANGELOG();
|
|
|
|
//
|
|
// Ignore this event on BDCs.
|
|
//
|
|
// This event is never set on a BDC. It may have been set
|
|
// prior to the role change while this machine was a PDC.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
NlPrimaryAnnouncement( ANNOUNCE_IMMEDIATE );
|
|
}
|
|
LOCK_CHANGELOG();
|
|
}
|
|
|
|
//
|
|
// If a "replicate immediately to Lanman" event happened,
|
|
// send a primary announcement to the lanman BDCs.
|
|
//
|
|
if ( NlGlobalChangeLogLanmanReplicateImmediately ) {
|
|
|
|
NlGlobalChangeLogLanmanReplicateImmediately = FALSE;
|
|
|
|
UNLOCK_CHANGELOG();
|
|
|
|
//
|
|
// Ignore this event on BDCs.
|
|
//
|
|
// This event is never set on a BDC. It may have been set
|
|
// prior to the role change while this machine was a PDC.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
NlLanmanPrimaryAnnouncement();
|
|
}
|
|
LOCK_CHANGELOG();
|
|
}
|
|
|
|
//
|
|
// Process any notifications that need processing
|
|
//
|
|
|
|
while ( !IsListEmpty( &NlGlobalChangeLogNotifications ) ) {
|
|
PLIST_ENTRY ListEntry;
|
|
PCHANGELOG_NOTIFICATION Notification;
|
|
|
|
ListEntry = RemoveHeadList( &NlGlobalChangeLogNotifications );
|
|
UNLOCK_CHANGELOG();
|
|
|
|
Notification = CONTAINING_RECORD(
|
|
ListEntry,
|
|
CHANGELOG_NOTIFICATION,
|
|
Next );
|
|
|
|
switch ( Notification->EntryType ) {
|
|
case ChangeLogLmServerAdded:
|
|
// This event happens on a PDC only
|
|
(VOID) NlAddBdcServerSession( Notification->ObjectRid,
|
|
NULL,
|
|
SS_BDC | SS_LM_BDC );
|
|
break;
|
|
|
|
case ChangeLogLmServerDeleted:
|
|
// This event happens on a PDC only
|
|
NlFreeLmBdcServerSession( Notification->ObjectRid );
|
|
break;
|
|
|
|
case ChangeLogNtServerAdded:
|
|
// This event happens on a PDC only
|
|
(VOID) NlAddBdcServerSession( Notification->ObjectRid,
|
|
&Notification->ObjectName,
|
|
SS_BDC );
|
|
break;
|
|
|
|
case ChangeLogWorkstationDeleted:
|
|
case ChangeLogTrustedDomainDeleted:
|
|
case ChangeLogNtServerDeleted:
|
|
// This event happens on both a PDC and BDC
|
|
NlFreeServerSessionForAccount( &Notification->ObjectName );
|
|
break;
|
|
|
|
case ChangeLogTrustAdded:
|
|
case ChangeLogTrustDeleted:
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
NlUpdateTrustListBySid( Notification->ObjectSid, NULL );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NlPrint((NL_CRITICAL,
|
|
"Invalid ChangeLogNotification: %ld %wZ\n",
|
|
Notification->EntryType,
|
|
&Notification->ObjectName ));
|
|
|
|
}
|
|
|
|
NetpMemoryFree( Notification );
|
|
LOCK_CHANGELOG();
|
|
}
|
|
|
|
UNLOCK_CHANGELOG();
|
|
continue;
|
|
|
|
|
|
default:
|
|
NetStatus = GetLastError();
|
|
NlExit(NELOG_NetlogonSystemError, NetStatus, LogErrorAndNetStatus, NULL);
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// ASSERT: Message and BytesRead describe a newly read message
|
|
//
|
|
//
|
|
// Got a message. Check for bad length just in case.
|
|
//
|
|
|
|
if (BytesRead < sizeof(unsigned short) ) {
|
|
NlPrint((NL_CRITICAL,"message size bad %ld\n", BytesRead ));
|
|
continue; // Need at least an opcode
|
|
}
|
|
|
|
//
|
|
// Here with a request to process in the Message.
|
|
//
|
|
|
|
Version = NetpLogonGetMessageVersion( Message, &BytesRead, &VersionFlags );
|
|
|
|
if (Version == LMUNKNOWNNT_MESSAGE) {
|
|
|
|
//
|
|
// received a non-supported NT message.
|
|
//
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"Received a non-supported NT message, Opcode is 0x%x\n",
|
|
((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
switch ( ((PNETLOGON_LOGON_QUERY)Message)->Opcode) {
|
|
|
|
//
|
|
// Handle a logon request from a UAS client
|
|
//
|
|
|
|
case LOGON_REQUEST: {
|
|
USHORT RequestCount;
|
|
|
|
//
|
|
// Unmarshall the incoming message.
|
|
//
|
|
|
|
if ( Version == LMNT_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
Where = ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName;
|
|
if ( !NetpLogonGetOemString(
|
|
(PNETLOGON_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->ComputerName),
|
|
&OemWorkstationName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetOemString(
|
|
(PNETLOGON_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->UserName),
|
|
&AnsiUserName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetOemString(
|
|
(PNETLOGON_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->MailslotName),
|
|
&OemMailslotName )) {
|
|
break;
|
|
}
|
|
|
|
// LM 2.x puts request count right before token
|
|
Where = Message + BytesRead - 2;
|
|
if ( !NetpLogonGetBytes(
|
|
(PNETLOGON_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_REQUEST)Message)->RequestCount),
|
|
&RequestCount )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle the logon request
|
|
//
|
|
|
|
UnicodeUserName = NetpLogonOemToUnicode( AnsiUserName );
|
|
if ( UnicodeUserName == NULL ) {
|
|
break;
|
|
}
|
|
|
|
IgnoreDuplicatesOfThisMessage = LogonRequestHandler(
|
|
Version,
|
|
UnicodeUserName,
|
|
RequestCount,
|
|
OemWorkstationName,
|
|
OemMailslotName,
|
|
TransportName,
|
|
USER_NORMAL_ACCOUNT );
|
|
|
|
NetpMemoryFree( UnicodeUserName );
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle a logon request from a SAM client
|
|
//
|
|
|
|
case LOGON_SAM_LOGON_REQUEST: {
|
|
USHORT RequestCount;
|
|
ULONG AllowableAccountControlBits;
|
|
|
|
//
|
|
// Unmarshall the incoming message.
|
|
//
|
|
|
|
|
|
if ( Version != LMNT_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
RequestCount = ((PNETLOGON_SAM_LOGON_REQUEST)Message)->RequestCount;
|
|
|
|
Where = (PCHAR)
|
|
(((PNETLOGON_SAM_LOGON_REQUEST)Message)->UnicodeComputerName);
|
|
|
|
if ( !NetpLogonGetUnicodeString(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
|
UnicodeComputerName),
|
|
&UnicodeWorkstationName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetUnicodeString(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
|
UnicodeUserName),
|
|
&UnicodeUserName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetOemString(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
|
MailslotName),
|
|
&OemMailslotName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetBytes(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
|
AllowableAccountControlBits),
|
|
&AllowableAccountControlBits )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// compare it with primary domain id.
|
|
//
|
|
// Don't make the following check mandatory. Chicago is
|
|
// considering using this message type. Oct 1993.
|
|
//
|
|
|
|
|
|
if( Where < ((PCHAR)Message + BytesRead ) ) {
|
|
|
|
DWORD DomainSidSize;
|
|
|
|
//
|
|
// Read Domain SID Length
|
|
//
|
|
|
|
if ( !NetpLogonGetBytes(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_SAM_LOGON_REQUEST)Message)->
|
|
DomainSidSize),
|
|
&DomainSidSize )) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// get and compare SID
|
|
//
|
|
|
|
if( DomainSidSize > 0 ) {
|
|
|
|
PCHAR DomainSid;
|
|
|
|
if ( !NetpLogonGetDomainSID(
|
|
(PNETLOGON_SAM_LOGON_REQUEST)Message,
|
|
BytesRead,
|
|
&Where,
|
|
DomainSidSize,
|
|
&DomainSid )) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// compare domain SIDs
|
|
//
|
|
|
|
if( !RtlEqualSid( NlGlobalPrimaryDomainId, DomainSid ) ) {
|
|
|
|
LPWSTR AlertStrings[4];
|
|
|
|
//
|
|
// alert admin.
|
|
//
|
|
|
|
AlertStrings[0] = UnicodeWorkstationName;
|
|
AlertStrings[1] = NlGlobalUnicodeComputerName;
|
|
AlertStrings[2] = NlGlobalUnicodeDomainName;
|
|
AlertStrings[3] = NULL;
|
|
|
|
RaiseAlert( ALERT_NetLogonUntrustedClient,
|
|
AlertStrings );
|
|
|
|
//
|
|
// Save the info in the eventlog
|
|
//
|
|
|
|
NlpWriteEventlog(
|
|
ALERT_NetLogonUntrustedClient,
|
|
EVENTLOG_ERROR_TYPE,
|
|
NULL,
|
|
0,
|
|
AlertStrings,
|
|
3 );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
OemWorkstationName =
|
|
NetpLogonUnicodeToOem( UnicodeWorkstationName );
|
|
|
|
if( OemWorkstationName == NULL ) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"Out of memory to send logon response\n"));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle the logon request
|
|
//
|
|
|
|
IgnoreDuplicatesOfThisMessage = LogonRequestHandler(
|
|
Version,
|
|
UnicodeUserName,
|
|
RequestCount,
|
|
OemWorkstationName,
|
|
OemMailslotName,
|
|
TransportName,
|
|
AllowableAccountControlBits );
|
|
|
|
NetpMemoryFree( OemWorkstationName );
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle Logon Central query.
|
|
//
|
|
// This query could be sent by either LM1.0, LM 2.0 or LM NT Netlogon
|
|
// services. We ignore LM 2.0 and LM NT queries since they are merely
|
|
// trying
|
|
// to find out if there are any LM1.0 netlogon services in the domain.
|
|
// For LM 1.0 we respond with a LOGON_CENTRAL_RESPONSE to prevent the
|
|
// starting LM1.0 netlogon service from starting.
|
|
//
|
|
|
|
case LOGON_CENTRAL_QUERY:
|
|
|
|
if ( Version != LMUNKNOWN_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Drop on through to LOGON_DISTRIB_QUERY to send the response
|
|
//
|
|
|
|
|
|
//
|
|
// Handle a Logon Disrib query
|
|
//
|
|
// LM2.0 NETLOGON server never sends this query hence it
|
|
// must be another LM1.0 NETLOGON server trying to start up
|
|
// in non-centralized mode. LM2.0 NETLOGON server will respond
|
|
// with LOGON_CENTRAL_RESPONSE to prevent this.
|
|
//
|
|
|
|
case LOGON_DISTRIB_QUERY:
|
|
|
|
|
|
//
|
|
// Unmarshall the incoming message.
|
|
//
|
|
|
|
Where = ((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
|
|
if ( !NetpLogonGetOemString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
|
|
&OemWorkstationName )) {
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetOemString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
|
|
&OemMailslotName )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Build the response
|
|
//
|
|
|
|
((PNETLOGON_LOGON_QUERY)resp_buf)->Opcode = LOGON_CENTRAL_RESPONSE;
|
|
ResponseBufferSize = sizeof( unsigned short); // opcode only
|
|
|
|
(VOID) NlBrowserSendDatagram( OemWorkstationName,
|
|
TransportName,
|
|
OemMailslotName,
|
|
resp_buf,
|
|
ResponseBufferSize );
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// Handle LOGON_PRIMARY_QUERY
|
|
//
|
|
// If we're the PDC, always respond to this message
|
|
// identifying ourselves.
|
|
//
|
|
// Otherwise, only respond to the message if it is from a downlevel
|
|
// netlogon trying to see if it can start up as a PDC. In that
|
|
// case, pretend we are a PDC to prevent the downlevel PDC from
|
|
// starting.
|
|
//
|
|
//
|
|
|
|
case LOGON_PRIMARY_QUERY:
|
|
|
|
|
|
//
|
|
// Unmarshall the incoming message.
|
|
//
|
|
|
|
|
|
Where =((PNETLOGON_LOGON_QUERY)Message)->ComputerName;
|
|
if ( !NetpLogonGetOemString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->ComputerName ),
|
|
&OemWorkstationName )) {
|
|
|
|
break;
|
|
}
|
|
if ( !NetpLogonGetOemString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_LOGON_QUERY)Message)->MailslotName ),
|
|
&OemMailslotName )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle the primary query request
|
|
//
|
|
|
|
IgnoreDuplicatesOfThisMessage =
|
|
PrimaryQueryHandler( Version,
|
|
OemWorkstationName,
|
|
OemMailslotName,
|
|
TransportName );
|
|
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// Handle LOGON_FAIL_PRIMARY
|
|
//
|
|
|
|
case LOGON_FAIL_PRIMARY:
|
|
|
|
//
|
|
// If we are the primary,
|
|
// let everyone know we are really alive.
|
|
//
|
|
|
|
if ( NlGlobalRole == RolePrimary ) {
|
|
// Send a primary start to the LM BDCs
|
|
NlAnnouncePrimaryStart();
|
|
// Send a UAS_CHANGE to everyone.
|
|
NlPrimaryAnnouncement( 0 );
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// Handle LOGON_UAS_CHANGE
|
|
//
|
|
|
|
case LOGON_UAS_CHANGE:
|
|
|
|
|
|
//
|
|
// Only accept messages from an NT PDC.
|
|
//
|
|
|
|
if ( Version != LMNT_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Unblock replication thread if neccessary
|
|
//
|
|
|
|
(VOID) NlCheckUpdateNotices(
|
|
(PNETLOGON_DB_CHANGE)Message,
|
|
BytesRead );
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Handle LOGON_START_PRIMARY
|
|
//
|
|
//
|
|
// We may be here under any of these three cases:
|
|
// 1) A Primary is coming up for the very first time.
|
|
// 2) A previous primary went down and the
|
|
// same or a new primary is starting.
|
|
//
|
|
|
|
case LOGON_START_PRIMARY:
|
|
|
|
//
|
|
// Ignore our own broadcast.
|
|
//
|
|
|
|
if (NlGlobalRole == RolePrimary) {
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// Unmarshall the message.
|
|
//
|
|
|
|
if ( Version != LMNT_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
|
|
Where =((PNETLOGON_PRIMARY)Message)->PrimaryDCName;
|
|
if ( !NetpLogonGetOemString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_PRIMARY)Message)->PrimaryDCName ),
|
|
&AnsiTemp )) {
|
|
break;
|
|
}
|
|
|
|
if ( !NetpLogonGetUnicodeString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof( ((PNETLOGON_PRIMARY)Message)->UnicodePrimaryDCName),
|
|
&UnicodeTemp )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If the domain name is in the message,
|
|
// ensure this message is from correct domain.
|
|
//
|
|
|
|
if( Where < ((PCHAR)Message + BytesRead ) ) {
|
|
|
|
LPWSTR UnicodeDomainName;
|
|
|
|
if ( !NetpLogonGetUnicodeString(
|
|
Message,
|
|
BytesRead,
|
|
&Where,
|
|
sizeof(((PNETLOGON_PRIMARY)Message)->UnicodeDomainName),
|
|
&UnicodeDomainName )) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
FORMAT_LPWSTR
|
|
": Primary Start message had invalid domain name.\n",
|
|
UnicodeTemp ));
|
|
|
|
break;
|
|
}
|
|
|
|
if ( NlNameCompare( UnicodeDomainName,
|
|
NlGlobalUnicodeDomainName,
|
|
NAMETYPE_DOMAIN ) != 0 ) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
FORMAT_LPWSTR
|
|
": Primary Start message from wrong domain "
|
|
FORMAT_LPWSTR "\n",
|
|
UnicodeTemp,
|
|
UnicodeDomainName ));
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
NlPrint((NL_MAILSLOT,
|
|
FORMAT_LPWSTR
|
|
": Primary Start message from correct domain "
|
|
FORMAT_LPWSTR "\n",
|
|
UnicodeTemp,
|
|
UnicodeDomainName ));
|
|
|
|
}
|
|
|
|
//
|
|
// Set up a session with the new primary.
|
|
//
|
|
|
|
(VOID) NlNewSessionSetup( UnicodeTemp );
|
|
|
|
break;
|
|
|
|
|
|
|
|
//
|
|
// Handle DC discovery responses
|
|
//
|
|
|
|
case LOGON_SAM_LOGON_RESPONSE:
|
|
case LOGON_SAM_USER_UNKNOWN:
|
|
case LOGON_SAM_PAUSE_RESPONSE:
|
|
|
|
//
|
|
// Only accept messages from an NT PDC.
|
|
//
|
|
|
|
if ( Version != LMNT_MESSAGE ) {
|
|
break;
|
|
}
|
|
|
|
|
|
NlDcDiscoveryHandler(
|
|
(PNETLOGON_SAM_LOGON_RESPONSE)Message,
|
|
BytesRead,
|
|
TransportName,
|
|
Version );
|
|
|
|
break;
|
|
|
|
//
|
|
// Messages used for NetLogonEnum support.
|
|
//
|
|
// Simply ignore the messages
|
|
//
|
|
|
|
case LOGON_NO_USER:
|
|
case LOGON_RELOGON_RESPONSE:
|
|
case LOGON_WKSTINFO_RESPONSE:
|
|
case LOGON_SAM_WKSTINFO_RESPONSE:
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// Handle unidentified opcodes
|
|
//
|
|
|
|
default:
|
|
|
|
//
|
|
// Unknown request, continue for re-issue of read.
|
|
//
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"Unknown op-code in mailslot message 0x%x\n",
|
|
((PNETLOGON_LOGON_QUERY)Message)->Opcode ));
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
int
|
|
NlNetlogonMain(
|
|
IN DWORD argc,
|
|
IN LPWSTR *argv
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Main routine for Netlogon service.
|
|
|
|
This routine initializes the netlogon service. This thread becomes
|
|
the thread that reads logon mailslot messages.
|
|
|
|
Arguments:
|
|
|
|
argc, argv - Command line arguments for the service.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
PDB_INFO DBInfo;
|
|
DWORD i;
|
|
|
|
|
|
|
|
//
|
|
// Initialize all global variable.
|
|
//
|
|
// We can't rely on this happening at load time since this address
|
|
// space is shared by other services.
|
|
//
|
|
|
|
NlGlobalAnsiPrimaryName[0] = '\0';
|
|
NlGlobalUncPrimaryName[0] = L'\0';
|
|
NlGlobalUnicodePrimaryName = NlGlobalUncPrimaryName;
|
|
NlGlobalUasCompatibilityMode = TRUE;
|
|
// NlGlobalInfiniteTime.HighPart = 0x7FFFFFFF;
|
|
// NlGlobalInfiniteTime.LowPart = 0xFFFFFFFF;
|
|
NlGlobalMailslotHandle = NULL;
|
|
NlGlobalRpcServerStarted = FALSE;
|
|
NlGlobalAnsiComputerName = NULL;
|
|
NlGlobalUncUnicodeComputerName[0] = L'\0';
|
|
NlGlobalUnicodeComputerName = NlGlobalUncUnicodeComputerName;
|
|
RtlInitUnicodeString( &NlGlobalUnicodeComputerNameString, NULL );
|
|
NlGlobalAnsiDomainName = NULL;
|
|
NlGlobalUnicodeDomainName[0] = L'\0';
|
|
NlGlobalPrimaryDomainId = NULL;
|
|
NlGlobalSamServerHandle = NULL;
|
|
NlGlobalPolicyHandle = NULL;
|
|
NlGlobalRole = RoleMemberWorkstation;
|
|
NlGlobalUnicodeScriptPath[0] = L'\0';
|
|
NlGlobalPulseParameter = DEFAULT_PULSE;
|
|
NlGlobalRandomizeParameter = DEFAULT_RANDOMIZE;
|
|
NlGlobalSynchronizeParameter = DEFAULT_SYNCHRONIZE;
|
|
NlGlobalPulseMaximumParameter = DEFAULT_PULSEMAXIMUM;
|
|
NlGlobalPulseConcurrencyParameter = DEFAULT_PULSECONCURRENCY;
|
|
NlGlobalPulseTimeout1Parameter = DEFAULT_PULSETIMEOUT1;
|
|
NlGlobalPulseTimeout2Parameter = DEFAULT_PULSETIMEOUT2;
|
|
NlGlobalNetlogonSecurityDescriptor = NULL;
|
|
NlGlobalTooManyGlobalGroups = FALSE;
|
|
|
|
|
|
NlGlobalServerSessionHashTable = NULL;
|
|
InitializeListHead( &NlGlobalServerSessionTable );
|
|
InitializeListHead( &NlGlobalBdcServerSessionList );
|
|
NlGlobalBdcServerSessionCount = 0;
|
|
|
|
NlGlobalTransportList = NULL;
|
|
NlGlobalTransportCount = 0;
|
|
|
|
InitializeListHead( &NlGlobalPendingBdcList );
|
|
NlGlobalPendingBdcCount = 0;
|
|
NlGlobalPendingBdcTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
|
|
|
InitializeListHead( &NlGlobalTrustList );
|
|
NlGlobalTrustListLength = 0;
|
|
|
|
NlGlobalSSICritSectInit = FALSE;
|
|
NlGlobalTerminateEvent = NULL;
|
|
NlGlobalReplicatorTerminateEvent = NULL;
|
|
NlGlobalStartedEvent = NULL;
|
|
NlGlobalTimerEvent = NULL;
|
|
|
|
NlGlobalServiceHandle = (SERVICE_STATUS_HANDLE) NULL;
|
|
|
|
NlGlobalServiceStatus.dwServiceType = SERVICE_WIN32;
|
|
NlGlobalServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
|
NlGlobalServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE;
|
|
NlGlobalServiceStatus.dwCheckPoint = 0;
|
|
NlGlobalServiceStatus.dwWaitHint = NETLOGON_INSTALL_WAIT;
|
|
|
|
SET_SERVICE_EXITCODE(
|
|
NO_ERROR,
|
|
NlGlobalServiceStatus.dwWin32ExitCode,
|
|
NlGlobalServiceStatus.dwServiceSpecificExitCode
|
|
);
|
|
|
|
NlGlobalClientSession = NULL;
|
|
NlGlobalTrustedDomainList = NULL;
|
|
NlGlobalTrustedDomainCount = 0;
|
|
NlGlobalTrustedDomainListKnown = FALSE;
|
|
NlGlobalTrustedDomainListTime.QuadPart = 0;
|
|
NlGlobalDcDiscoveryCount = 0;
|
|
NlGlobalDcDiscoveryTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
|
NlGlobalBindingHandleCount = 0;
|
|
NlGlobalApiTimer.Period = (DWORD) MAILSLOT_WAIT_FOREVER;
|
|
|
|
#if DBG
|
|
NlGlobalTrace = 0;
|
|
NlGlobalLogFile = INVALID_HANDLE_VALUE;
|
|
NlGlobalDebugSharePath = NULL;
|
|
#endif // DBG
|
|
|
|
|
|
for( i = 0, DBInfo = &NlGlobalDBInfoArray[0];
|
|
i < NUM_DBS;
|
|
i++, DBInfo++ ) {
|
|
|
|
RtlZeroMemory( DBInfo, sizeof(*DBInfo) );
|
|
|
|
// Force a partial sync on all databases to let the PDC know
|
|
// what our serial number is.
|
|
DBInfo->UpdateRqd = TRUE;
|
|
|
|
}
|
|
NlGlobalFirstTimeFullSync = FALSE;
|
|
|
|
NlGlobalScavengerThreadHandle = NULL;
|
|
NlGlobalScavengerTerminate = FALSE;
|
|
|
|
InitChangeLogDesc( &NlGlobalRedoLogDesc )
|
|
NlGlobalRedoLogDesc.RedoLog = TRUE;
|
|
|
|
|
|
|
|
//
|
|
// Setup things needed before NlExit can be called
|
|
//
|
|
|
|
NlGlobalTerminate = FALSE;
|
|
|
|
NlGlobalTerminateEvent = CreateEvent( NULL, // No security attributes
|
|
TRUE, // Must be manually reset
|
|
FALSE, // Initially not signaled
|
|
NULL ); // No name
|
|
|
|
if ( NlGlobalTerminateEvent == NULL ) {
|
|
NetStatus = GetLastError();
|
|
NlPrint((NL_CRITICAL, "Cannot create termination Event %lu\n",
|
|
NetStatus ));
|
|
return (int) NetStatus;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize trust table crit sect.
|
|
//
|
|
|
|
InitializeCriticalSection( &NlGlobalTrustListCritSect );
|
|
InitializeCriticalSection( &NlGlobalReplicatorCritSect );
|
|
InitializeCriticalSection( &NlGlobalDbInfoCritSect );
|
|
InitializeCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
|
|
|
|
|
//
|
|
// Initialize scavenger thread crit sect.
|
|
//
|
|
|
|
InitializeCriticalSection( &NlGlobalScavengerCritSect );
|
|
|
|
|
|
//
|
|
// Tell the service controller we've started.
|
|
//
|
|
// ?? - Need to set up security descriptor.
|
|
//
|
|
|
|
NlPrint((NL_INIT,"Calling RegisterServiceCtrlHandler\n"));
|
|
|
|
NlGlobalServiceHandle =
|
|
RegisterServiceCtrlHandler( SERVICE_NETLOGON, NlControlHandler);
|
|
|
|
if (NlGlobalServiceHandle == (SERVICE_STATUS_HANDLE) NULL) {
|
|
LPWSTR MsgStrings[1];
|
|
|
|
NetStatus = GetLastError();
|
|
|
|
NlPrint((NL_CRITICAL, "RegisterServiceCtrlHandler failed %lu\n",
|
|
NetStatus ));
|
|
|
|
MsgStrings[0] = (LPWSTR) NetStatus;
|
|
|
|
NlpWriteEventlog (NELOG_NetlogonFailedToRegisterSC,
|
|
EVENTLOG_ERROR_TYPE,
|
|
(LPBYTE) &NetStatus,
|
|
sizeof(NetStatus),
|
|
MsgStrings,
|
|
1 | LAST_MESSAGE_IS_NETSTATUS );
|
|
|
|
return (int) NetStatus;
|
|
}
|
|
|
|
if ( !GiveInstallHints( FALSE ) ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Nlparse the command line (.ini) arguments
|
|
// it will set globals reflecting switch settings
|
|
//
|
|
|
|
if (! Nlparse() ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
NlPrint((NL_INIT,"Command line parsed successfully ...\n"));
|
|
|
|
#ifdef notdef
|
|
#if DBG
|
|
if ( NlGlobalTrace == 0) {
|
|
NlGlobalTrace = 0x2284FFFF;
|
|
}
|
|
#endif // DBG
|
|
#endif // notdef
|
|
|
|
|
|
|
|
//
|
|
// Enter the debugger.
|
|
//
|
|
// Wait 'til now since we don't want the service controller to time us out.
|
|
//
|
|
|
|
|
|
IF_DEBUG( BREAKPOINT ) {
|
|
DbgBreakPoint( );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Do startup checks, initialize data structs and do prelim setups
|
|
//
|
|
|
|
if ( !NlInit() ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Loop till the service is to exit.
|
|
//
|
|
|
|
NlMainLoop();
|
|
|
|
//
|
|
// Common exit point
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Cleanup and return to our caller.
|
|
//
|
|
|
|
return (int) NlCleanup();
|
|
UNREFERENCED_PARAMETER( argc );
|
|
UNREFERENCED_PARAMETER( argv );
|
|
|
|
}
|