11466 lines
332 KiB
C
11466 lines
332 KiB
C
/*++
|
||
|
||
|
||
Copyright (c) 1987-1996 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
ssiapi.c
|
||
|
||
Abstract:
|
||
|
||
Authentication and replication API routines (server side).
|
||
|
||
Author:
|
||
|
||
Cliff Van Dyke (cliffv) 28-Jun-1991
|
||
|
||
Environment:
|
||
|
||
User mode only.
|
||
Contains NT-specific code.
|
||
Requires ANSI C extensions: slash-slash comments, long external names.
|
||
|
||
Revision History:
|
||
|
||
02-Jan-1992 (madana)
|
||
added support for builtin/multidomain replication.
|
||
--*/
|
||
|
||
//
|
||
// Common include files.
|
||
//
|
||
|
||
#include "logonsrv.h" // Include files common to entire service
|
||
#pragma hdrstop
|
||
|
||
//
|
||
// Include files specific to this .c file
|
||
//
|
||
|
||
|
||
#include "lsarepl.h" // PackLsa ..
|
||
#include <ssiapi.h>
|
||
#include <netlogp.h>
|
||
#include <kerbcon.h>
|
||
#include <winldap.h>
|
||
|
||
#include <loghours.h>
|
||
#include <dnssrv.h> // NetpSrv...
|
||
#include <ntldap.h> // LDAP_SERVER_PERMISSIVE_MODIFY_OID_W
|
||
|
||
//
|
||
// Define the maximum number of deltas returned on any single call
|
||
//
|
||
// Theoretically, MaxNumDeltas should be some function of
|
||
// PreferredMaximumLength. However, by the time you allow for
|
||
// the large swing in PreferredMaximumLength allowed by the BDC replication
|
||
// Governor and then not wanting this buffer to be ridiculously large
|
||
// when the full 128K is asked for, we find that 1000 entries is always
|
||
// a reasonable compromise.
|
||
//
|
||
|
||
#define MAX_DELTA_COUNT 1000
|
||
|
||
//
|
||
// Maximum number of deltas that can be generated by a single change log entry.
|
||
//
|
||
#define MAX_DELTAS_PER_CHANGELOG 4
|
||
|
||
|
||
//
|
||
// Host prefix for SPNs
|
||
//
|
||
|
||
#define NL_HOST_PREFIX L"HOST/"
|
||
|
||
//
|
||
// work record for SPN update:
|
||
//
|
||
|
||
typedef struct _NL_SPN_UPDATE {
|
||
BOOLEAN SetSpn;
|
||
BOOLEAN SetDnsHostName;
|
||
BOOLEAN WriteEventLogOnFailure;
|
||
LPWSTR DnsHostName;
|
||
LPWSTR NetbiosComputerName;
|
||
LPWSTR UncDcName;
|
||
LPWSTR NetbiosDomainName;
|
||
LPWSTR DnsDomainName;
|
||
} NL_SPN_UPDATE, * PNL_SPN_UPDATE ;
|
||
|
||
|
||
//
|
||
// Challenge data struct and relevant defines
|
||
//
|
||
|
||
typedef struct _NL_CHALLENGE {
|
||
|
||
//
|
||
// Link to next challenge entry in NlGlobalChallengeList
|
||
// (Serialized by NlGlobalChallengeCritSect)
|
||
//
|
||
|
||
LIST_ENTRY ChNext;
|
||
|
||
//
|
||
// Challenge sent by the client
|
||
//
|
||
NETLOGON_CREDENTIAL ChClientChallenge;
|
||
|
||
//
|
||
// Challenge returned by the server (us)
|
||
//
|
||
NETLOGON_CREDENTIAL ChServerChallenge;
|
||
|
||
//
|
||
// Time stampt when the challenge entry got created
|
||
//
|
||
ULONG ChSetTime;
|
||
|
||
//
|
||
// Name(s) of the account that the client used to
|
||
// unsuccessfully authenticate on this challenge.
|
||
// May be NULL.
|
||
//
|
||
LPWSTR ChFailedAccountName;
|
||
|
||
//
|
||
// The name of the client (must be the last field)
|
||
//
|
||
WCHAR ChClientName[ANYSIZE_ARRAY];
|
||
|
||
} NL_CHALLENGE, *PNL_CHALLENGE;
|
||
|
||
|
||
// Lifetime of a challenge entry in the global list of
|
||
// outstanding challenges
|
||
#define NL_CHALLENGE_TIMEOUT 120000 // 2*60*1000 = 2 minutes
|
||
|
||
// Maximum number of challenges we are going to keep
|
||
// before we start throwing away existing ones at random
|
||
#define NL_MAX_CHALLENGE_COUNT 100000
|
||
|
||
// Number of challenges at which we reschedule the scavenger
|
||
// to run soon (as given by NL_LARGE_CHALLENGE_TIMEOUT).
|
||
// Running the scavenger soon will prevent us from commiting
|
||
// a lot of memory for an extended period for challenges coming
|
||
// from a malicious client making tons of challenge requests.
|
||
//
|
||
#define NL_LARGE_CHALLENGE_COUNT 5000
|
||
|
||
// Timeout when the scavenger will be rescheduled to run
|
||
// upon detection of current large number of outstanding
|
||
// challenges (provided the scavenger isn't already sheduled
|
||
// to run earlier).
|
||
#define NL_LARGE_CHALLENGE_COUNT_TIMEOUT 120000 // 2*60*1000 = 2 minutes
|
||
|
||
|
||
VOID
|
||
NlScavengeOldChallenges(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function removes all expired challenge entries
|
||
from the global list of outstanding challenges.
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_ACCESS_DENIED;
|
||
PLIST_ENTRY ChallengeEntry = NULL;
|
||
PNL_CHALLENGE Challenge = NULL;
|
||
ULONG CurrentTime;
|
||
ULONG ElapsedTime;
|
||
LPWSTR MsgStrings[3];
|
||
|
||
CurrentTime = GetTickCount();
|
||
|
||
EnterCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
ChallengeEntry = NlGlobalChallengeList.Flink;
|
||
while ( ChallengeEntry != &NlGlobalChallengeList ) {
|
||
Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext );
|
||
ChallengeEntry = ChallengeEntry->Flink;
|
||
|
||
//
|
||
// If time has wrapped, account for it
|
||
//
|
||
if ( CurrentTime >= Challenge->ChSetTime ) {
|
||
ElapsedTime = CurrentTime - Challenge->ChSetTime;
|
||
} else {
|
||
ElapsedTime = (0xFFFFFFFF - Challenge->ChSetTime) + CurrentTime;
|
||
}
|
||
|
||
//
|
||
// The list of challenges is sorted by the time stampt.
|
||
// So if this entry is old, remove it. Otherwise, we have
|
||
// removed all expired entries, so break from the loop.
|
||
//
|
||
if ( ElapsedTime >= NL_CHALLENGE_TIMEOUT ) {
|
||
|
||
//
|
||
// Write the event log stating that this client
|
||
// failed to authenticate. Note that since we avoid
|
||
// duplicate event logs, there will be only one
|
||
// message (which is good) for multiple challenges
|
||
// from the same client for the same account.
|
||
//
|
||
MsgStrings[0] = Challenge->ChClientName;
|
||
|
||
//
|
||
// If the account name is available, log it
|
||
//
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
MsgStrings[1] = Challenge->ChFailedAccountName;
|
||
MsgStrings[2] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailed,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
3 | NETP_LAST_MESSAGE_IS_NTSTATUS );
|
||
} else {
|
||
MsgStrings[1] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailedNoAccount,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NTSTATUS );
|
||
}
|
||
|
||
//
|
||
// Delink and free this entry
|
||
//
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
LocalFree( Challenge->ChFailedAccountName );
|
||
}
|
||
RemoveEntryList(&Challenge->ChNext);
|
||
LocalFree( Challenge );
|
||
NlGlobalChallengeCount --;
|
||
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
LeaveCriticalSection( &NlGlobalChallengeCritSect );
|
||
}
|
||
|
||
NTSTATUS
|
||
NlInsertChallenge(
|
||
IN LPWSTR ClientName,
|
||
IN PNETLOGON_CREDENTIAL ClientChallenge,
|
||
IN PNETLOGON_CREDENTIAL ServerChallenge
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function inserts a pair of client/server challenges into the
|
||
global list of outstanding challenges.
|
||
|
||
Arguments:
|
||
|
||
ClientName -- Name of the client supplying the client challenge.
|
||
|
||
ClientCredential -- 64 bit challenge supplied by the client.
|
||
|
||
ServerCredential -- Server challenge response returned by us
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- A new challenge entry was successfully added
|
||
|
||
STATUS_NO_MEMORY -- The was not enough memory
|
||
|
||
--*/
|
||
|
||
{
|
||
PLIST_ENTRY ChallengeEntry = NULL;
|
||
PNL_CHALLENGE NewChallenge = NULL;
|
||
PNL_CHALLENGE Challenge = NULL;
|
||
BOOL RescheduleScavenger = FALSE;
|
||
|
||
//
|
||
// Allocate a new challenge entry
|
||
//
|
||
|
||
NewChallenge = LocalAlloc( LMEM_ZEROINIT, sizeof(NL_CHALLENGE) +
|
||
(wcslen(ClientName) + 1) * sizeof(WCHAR) );
|
||
if ( NewChallenge == NULL ) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
//
|
||
// Fill it in
|
||
//
|
||
|
||
NewChallenge->ChSetTime = GetTickCount();
|
||
RtlCopyMemory( &NewChallenge->ChClientName, ClientName, (wcslen(ClientName) + 1) * sizeof(WCHAR) );
|
||
NewChallenge->ChClientChallenge = *ClientChallenge;
|
||
NewChallenge->ChServerChallenge = *ServerChallenge;
|
||
|
||
|
||
EnterCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
//
|
||
// First scavenge old entries
|
||
//
|
||
|
||
NlScavengeOldChallenges();
|
||
|
||
//
|
||
// Now determine if we have exceeded the limit
|
||
// on the total number of outstanding challenges
|
||
//
|
||
|
||
if ( NlGlobalChallengeCount >= NL_MAX_CHALLENGE_COUNT ) {
|
||
ULONG Index = 0;
|
||
ULONG RemoveEntryNumber;
|
||
|
||
//
|
||
// Pick up an entry at random and free it
|
||
//
|
||
RemoveEntryNumber = rand() % NL_MAX_CHALLENGE_COUNT;
|
||
|
||
//
|
||
// Locate that entry in the list and remove it
|
||
//
|
||
for ( ChallengeEntry = NlGlobalChallengeList.Flink;
|
||
ChallengeEntry != &NlGlobalChallengeList;
|
||
ChallengeEntry = ChallengeEntry->Flink ) {
|
||
|
||
if ( Index == RemoveEntryNumber ) {
|
||
Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext );
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlInsertChallenge: Removing challenge %ld for %ws\n",
|
||
Index,
|
||
Challenge->ChClientName ));
|
||
RemoveEntryList( &Challenge->ChNext );
|
||
LocalFree( Challenge );
|
||
NlGlobalChallengeCount --;
|
||
break;
|
||
}
|
||
Index ++;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Finally insert into the list at the tail to keep the list
|
||
// sorted by the tick count.
|
||
//
|
||
// Note that the tick count can't be reset, so the list will
|
||
// stay always sorted. The tick count can wrap, however,
|
||
// but we will take care of it when we calculate the elapsed time.
|
||
//
|
||
|
||
InsertTailList( &NlGlobalChallengeList, &NewChallenge->ChNext );
|
||
NlGlobalChallengeCount ++;
|
||
|
||
//
|
||
// If we have too many challenges,
|
||
// reschedule the scavenger to run soon.
|
||
// This way we don't commit large amount of
|
||
// memory for an extended period of time for tons
|
||
// of challenges comming from a malicious caller.
|
||
//
|
||
|
||
if ( NlGlobalChallengeCount >= NL_LARGE_CHALLENGE_COUNT ) {
|
||
RescheduleScavenger = TRUE;
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlInsertChallenge: Too many challenges: %lu. Will start scavenger in 2 mins\n",
|
||
NlGlobalChallengeCount ));
|
||
}
|
||
|
||
LeaveCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
//
|
||
// Reschedule the scavenger as needed
|
||
//
|
||
|
||
if ( RescheduleScavenger ) {
|
||
LARGE_INTEGER TimeNow;
|
||
DWORD Timeout = 0xFFFFFFFF;
|
||
|
||
EnterCriticalSection( &NlGlobalScavengerCritSect );
|
||
NlQuerySystemTime( &TimeNow );
|
||
if ( !TimerExpired(&NlGlobalScavengerTimer, &TimeNow, &Timeout) ) {
|
||
if ( Timeout > NL_LARGE_CHALLENGE_COUNT_TIMEOUT ) {
|
||
NlGlobalScavengerTimer.Period -= (Timeout - NL_LARGE_CHALLENGE_COUNT_TIMEOUT);
|
||
|
||
if ( !SetEvent( NlGlobalTimerEvent ) ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlInsertChallenge: SetEvent failed %ld\n",
|
||
GetLastError() ));
|
||
}
|
||
}
|
||
}
|
||
LeaveCriticalSection( &NlGlobalScavengerCritSect );
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
VOID
|
||
NlRemoveChallenge(
|
||
IN LPWSTR ClientName OPTIONAL,
|
||
IN LPWSTR AccountName OPTIONAL,
|
||
IN BOOL InterdomainTrustAccount
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function removes challenge entries from the
|
||
global list of outstanding challenges.
|
||
|
||
Arguments:
|
||
|
||
ClientName -- Name of the client whose associated
|
||
challenges entries will be removed. If NULL, all
|
||
entries in the list will be removed.
|
||
|
||
AccountName -- Name of teh account used by the client
|
||
to athenticate with this server. Used only if
|
||
ClinetName is specified.
|
||
|
||
InterdomainTrustAccount -- TRUE if the client used an
|
||
interdomain trust account to set up a secure channel.
|
||
Used only if ClientName is specified.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_ACCESS_DENIED;
|
||
PLIST_ENTRY ChallengeEntry = NULL;
|
||
PNL_CHALLENGE Challenge = NULL;
|
||
ULONG SameAccountChallengeCount = 0;
|
||
BOOLEAN LogEvent = FALSE;
|
||
LPWSTR MsgStrings[3];
|
||
|
||
//
|
||
// First scavenge old entries from the head of the list
|
||
// Skip this step if we are removing all entries anyway
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
if ( ClientName != NULL ) {
|
||
NlScavengeOldChallenges();
|
||
}
|
||
|
||
//
|
||
// Next remove all entries in the list associated with client name
|
||
//
|
||
|
||
ChallengeEntry = NlGlobalChallengeList.Flink;
|
||
while ( ChallengeEntry != &NlGlobalChallengeList ) {
|
||
Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext );
|
||
ChallengeEntry = ChallengeEntry->Flink;
|
||
|
||
//
|
||
// If the client name is NULL, we are shutting down,
|
||
// so just delink and free all entries
|
||
//
|
||
if ( ClientName == NULL ) {
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
LocalFree( Challenge->ChFailedAccountName );
|
||
}
|
||
RemoveEntryList(&Challenge->ChNext);
|
||
LocalFree( Challenge );
|
||
NlGlobalChallengeCount --;
|
||
|
||
//
|
||
// If this entry is for the specified client,
|
||
// process it
|
||
//
|
||
} else if ( NlNameCompare(ClientName,
|
||
Challenge->ChClientName,
|
||
NAMETYPE_COMPUTER) == 0 ) {
|
||
|
||
MsgStrings[0] = Challenge->ChClientName;
|
||
|
||
//
|
||
// If this entry has an account different from
|
||
// the specied one, log an event for it.
|
||
// Note that since we avoid duplicate event logs,
|
||
// there will be only one message (which is good)
|
||
// for multiple challenges from the same client
|
||
// for the same account.
|
||
//
|
||
if ( AccountName != NULL &&
|
||
Challenge->ChFailedAccountName != NULL &&
|
||
_wcsicmp(Challenge->ChFailedAccountName, AccountName) != 0 ) {
|
||
|
||
MsgStrings[1] = Challenge->ChFailedAccountName;
|
||
MsgStrings[2] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailed,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
3 | NETP_LAST_MESSAGE_IS_NTSTATUS );
|
||
//
|
||
// Otherwise, count this entry in the number
|
||
// of challenges with the specified or empty
|
||
// account names (a challenge may have an empty
|
||
// account name if we haven't reached it in the
|
||
// authentication try loop).
|
||
//
|
||
} else {
|
||
SameAccountChallengeCount ++;
|
||
}
|
||
|
||
//
|
||
// Delink this entry and free it
|
||
//
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
LocalFree( Challenge->ChFailedAccountName );
|
||
}
|
||
RemoveEntryList(&Challenge->ChNext);
|
||
LocalFree( Challenge );
|
||
NlGlobalChallengeCount --;
|
||
}
|
||
}
|
||
|
||
LeaveCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
//
|
||
// If there are more than a certain number of challenges
|
||
// with the specified or emppty account, some other
|
||
// (possibly malicious) client attempted to authenticate
|
||
// using this account. Log an event for this. Don't specify
|
||
// the account name as we don't know which account that
|
||
// client would specify.
|
||
//
|
||
// For interdomain trust, the client may legitimately
|
||
// try up to 3 times (passwords new, old, from PDC).
|
||
// Otherwise, it gets 2 tries (new and old pwd).
|
||
//
|
||
|
||
if ( InterdomainTrustAccount ) {
|
||
if ( SameAccountChallengeCount > 3 ) {
|
||
LogEvent = TRUE;
|
||
}
|
||
} else {
|
||
if ( SameAccountChallengeCount > 2 ) {
|
||
LogEvent = TRUE;
|
||
}
|
||
}
|
||
|
||
if ( LogEvent ) {
|
||
MsgStrings[0] = ClientName;
|
||
MsgStrings[1] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailedNoAccount,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NTSTATUS );
|
||
}
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerReqChallenge(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_CREDENTIAL ClientChallenge,
|
||
OUT PNETLOGON_CREDENTIAL ServerChallenge
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the server side of I_NetServerReqChallenge.
|
||
|
||
I_NetServerReqChallenge is the first of two functions used by a client
|
||
Netlogon service to authenticate with another Netlogon service.
|
||
(See I_NetServerAuthenticate below.)
|
||
|
||
This function passes a challenge to the DC and the DC passes a challenge
|
||
back to the caller.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Supplies the name of the DC we wish to authenticate with.
|
||
|
||
ComputerName -- Name of the machine making the call.
|
||
|
||
ClientCredential -- 64 bit challenge supplied by the BDC or member server.
|
||
|
||
ServerCredential -- Receives 64 bit challenge from the PDC.
|
||
|
||
Return Value:
|
||
|
||
The status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
#ifdef _WKSTA_NETLOGON
|
||
return ERROR_NOT_SUPPORTED;
|
||
UNREFERENCED_PARAMETER( PrimaryName );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( ClientChallenge );
|
||
UNREFERENCED_PARAMETER( ServerChallenge );
|
||
#endif // _WKSTA_NETLOGON
|
||
#ifdef _DC_NETLOGON
|
||
NTSTATUS Status;
|
||
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_CHALLENGE_RES,
|
||
"NetrServerReqChallenge: ClientChallenge = " ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, ClientChallenge, sizeof(*ClientChallenge) );
|
||
|
||
|
||
//
|
||
// Compute ServerChallenge to pass back to requestor
|
||
//
|
||
|
||
NlComputeChallenge(ServerChallenge);
|
||
|
||
|
||
NlPrint((NL_CHALLENGE_RES,
|
||
"NetrServerReqChallenge: ServerChallenge = " ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, ServerChallenge, sizeof(*ServerChallenge) );
|
||
|
||
|
||
//
|
||
// Add this entry into the challenge list.
|
||
//
|
||
// Remember both challenges until the corresponding I_NetAuthenticate call.
|
||
// Notice that both challenges are not yet SessionKey-encrypted
|
||
//
|
||
|
||
Status = NlInsertChallenge( ComputerName,
|
||
ClientChallenge,
|
||
ServerChallenge );
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
RtlZeroMemory( ServerChallenge, sizeof(*ServerChallenge) );
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
#endif // _DC_NETLOGON
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerAuthenticate3(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_CREDENTIAL ClientCredential,
|
||
OUT PNETLOGON_CREDENTIAL ServerCredential,
|
||
IN OUT PULONG NegotiatedFlags,
|
||
OUT PULONG AccountRid
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the server side of I_NetServerAuthenticate
|
||
|
||
I_NetServerAuthenticate is the second of two functions used by a client
|
||
Netlogon service to authenticate with another Netlogon service.
|
||
(See I_NetServerReqChallenge above.) Both a SAM or UAS server authenticates
|
||
using this function.
|
||
|
||
This function passes a credential to the DC and the DC passes a credential
|
||
back to the caller.
|
||
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Supplies the name of the DC we wish to authenticate with.
|
||
|
||
AccountName -- Name of the Account to authenticate with.
|
||
|
||
SecureChannelType -- The type of the account being accessed. This field must
|
||
be set to UasServerSecureChannel to indicate a call from downlevel (LanMan
|
||
2.x and below) BDC or member server.
|
||
|
||
ComputerName -- Name of the BDC or member server making the call.
|
||
|
||
ClientCredential -- 64 bit credential supplied by the BDC or member server.
|
||
|
||
ServerCredential -- Receives 64 bit credential from the PDC.
|
||
|
||
NegotiatedFlags -- Specifies flags indicating what features the BDC supports.
|
||
Returns a subset of those flags indicating what features the PDC supports.
|
||
The PDC/BDC should ignore any bits that it doesn't understand.
|
||
|
||
AccountRid -- Returns the RID of the account identified by AccountName
|
||
|
||
Return Value:
|
||
|
||
The status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
#ifdef _WKSTA_NETLOGON
|
||
return ERROR_NOT_SUPPORTED;
|
||
UNREFERENCED_PARAMETER( PrimaryName );
|
||
UNREFERENCED_PARAMETER( AccountName );
|
||
UNREFERENCED_PARAMETER( SecureChannelType );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( ClientCredential );
|
||
UNREFERENCED_PARAMETER( ServerCredential );
|
||
UNREFERENCED_PARAMETER( NegotiatedFlags );
|
||
#endif // _WKSTA_NETLOGON
|
||
#ifdef _DC_NETLOGON
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
ULONG LoopCount;
|
||
|
||
NETLOGON_CREDENTIAL LocalClientCredential;
|
||
NETLOGON_SESSION_KEY SessionKey;
|
||
|
||
NT_OWF_PASSWORD OwfPassword;
|
||
NT_OWF_PASSWORD OwfPreviousPassword;
|
||
NT_OWF_PASSWORD LocalOwfPassword;
|
||
NETLOGON_CREDENTIAL ServerChallenge;
|
||
NETLOGON_CREDENTIAL ClientChallenge;
|
||
BOOL IsInterdomainTrustAccount = FALSE;
|
||
ULONG TrustAttributes;
|
||
BOOL ClientAuthenticated = FALSE;
|
||
|
||
PLIST_ENTRY ChallengeEntry;
|
||
PNL_CHALLENGE Challenge;
|
||
ULONG ChallengeCount = 0;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Start the WMI trace of server authentication
|
||
//
|
||
|
||
NlpTraceServerAuthEvent( EVENT_TRACE_TYPE_START,
|
||
ComputerName,
|
||
AccountName,
|
||
SecureChannelType,
|
||
NegotiatedFlags,
|
||
STATUS_SUCCESS ); // Status isn't used at start
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerAuthenticate entered: %ws on account %ws (Negot: %lx)\n",
|
||
ComputerName, AccountName, *NegotiatedFlags ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Disallow this function for Lanman 2.X servers.
|
||
//
|
||
|
||
if ( SecureChannelType == UasServerSecureChannel ) {
|
||
|
||
NlPrint((NL_CRITICAL,"NetrServerAuthenticate "
|
||
"from LM 2.x (disallowed).\n"));
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Compute the NegotiatedFlags both sides support
|
||
//
|
||
|
||
*NegotiatedFlags &= NETLOGON_SUPPORTS_MASK |
|
||
NETLOGON_SUPPORTS_DNS_DOMAIN_TRUST |
|
||
NETLOGON_SUPPORTS_STRONG_KEY |
|
||
NETLOGON_SUPPORTS_NT4EMULATOR_NEUTRALIZER |
|
||
#ifdef ENABLE_AUTH_RPC
|
||
(NlGlobalServerSupportsAuthRpc ? (NETLOGON_SUPPORTS_AUTH_RPC|NETLOGON_SUPPORTS_LSA_AUTH_RPC) : 0 ) |
|
||
#endif // ENABLE_AUTH_RPC
|
||
(NlGlobalParameters.AvoidSamRepl ? NETLOGON_SUPPORTS_AVOID_SAM_REPL : 0) |
|
||
(NlGlobalParameters.AvoidLsaRepl ? NETLOGON_SUPPORTS_AVOID_LSA_REPL : 0);
|
||
|
||
//
|
||
// If we are emulating NT4.0 domain and the client
|
||
// didn't indicate to neutralize the emulation,
|
||
// treat the client as NT4.0 client. That way we
|
||
// won't leak NT5.0 specific info to the client.
|
||
// In fact, the client won't even ask for NT5.0
|
||
// specific info after receiving such negotiated
|
||
// from us.
|
||
//
|
||
|
||
if ( NlGlobalParameters.Nt4Emulator &&
|
||
((*NegotiatedFlags) & NETLOGON_SUPPORTS_NT4EMULATOR_NEUTRALIZER) == 0 ) {
|
||
|
||
//
|
||
// Pick up only those flags which existed in NT4.0
|
||
//
|
||
*NegotiatedFlags &= NETLOGON_SUPPORTS_NT4_MASK;
|
||
}
|
||
|
||
//
|
||
// Get the password for the account. For interdomain trust
|
||
// trust account, get both current and previous passwords
|
||
//
|
||
|
||
if ( IsDomainSecureChannelType( SecureChannelType ) ) {
|
||
IsInterdomainTrustAccount = TRUE;
|
||
}
|
||
|
||
Status = NlGetIncomingPassword(
|
||
DomainInfo,
|
||
AccountName,
|
||
SecureChannelType,
|
||
0, // Let routine figure out bits from SecureChannelType
|
||
TRUE, // Fail for disabled accounts
|
||
&OwfPassword,
|
||
IsInterdomainTrustAccount ?
|
||
&OwfPreviousPassword : // Get previous password for interdomain account
|
||
NULL,
|
||
AccountRid,
|
||
&TrustAttributes,
|
||
NULL ); // Don't need the account type
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: Can't NlGetIncomingPassword for %ws 0x%lx.\n",
|
||
AccountName,
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Output the passwords as needed
|
||
//
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: Password for account %ws = ", AccountName ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &OwfPassword, sizeof(OwfPassword) );
|
||
if ( IsInterdomainTrustAccount ) {
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: Previous Password for account %ws = ", AccountName ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &OwfPreviousPassword, sizeof(OwfPreviousPassword) );
|
||
}
|
||
|
||
//
|
||
// Loop through all challenge entries for this client
|
||
// and try to authenticate it against any one of the challenges.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
//
|
||
// First, take this opportunity to clean up expired challenge entries
|
||
//
|
||
|
||
NlScavengeOldChallenges();
|
||
|
||
//
|
||
// Now try all challenges for this client
|
||
//
|
||
|
||
for ( ChallengeEntry = NlGlobalChallengeList.Flink;
|
||
ChallengeEntry != &NlGlobalChallengeList;
|
||
ChallengeEntry = ChallengeEntry->Flink ) {
|
||
|
||
Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext );
|
||
|
||
//
|
||
// Skip entries which are not for this client
|
||
//
|
||
if ( NlNameCompare(ComputerName,
|
||
Challenge->ChClientName,
|
||
NAMETYPE_COMPUTER) != 0 ) {
|
||
continue;
|
||
}
|
||
|
||
ChallengeCount ++;
|
||
|
||
//
|
||
// Grab a copy of the Client and Server challenges.
|
||
//
|
||
|
||
RtlCopyMemory( &ServerChallenge,
|
||
&Challenge->ChServerChallenge,
|
||
sizeof(ServerChallenge) );
|
||
|
||
RtlCopyMemory( &ClientChallenge,
|
||
&Challenge->ChClientChallenge,
|
||
sizeof(ClientChallenge) );
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientChallenge %lu = ", ChallengeCount ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &ClientChallenge, sizeof(ClientChallenge) );
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ServerChallenge %lu = ", ChallengeCount ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &ServerChallenge, sizeof(ServerChallenge) );
|
||
|
||
//
|
||
// Loop trying the local current password, then the local previous password
|
||
// provided this is an interdomain trust account.
|
||
//
|
||
|
||
for ( LoopCount=0; LoopCount<2; LoopCount++ ) {
|
||
|
||
//
|
||
// On the first iteration, use the current password
|
||
//
|
||
if ( LoopCount == 0 ) {
|
||
LocalOwfPassword = OwfPassword;
|
||
|
||
//
|
||
// On the second iteration, if this is an interdomain trust account,
|
||
// use the previous password
|
||
//
|
||
} else if ( LoopCount == 1 && IsInterdomainTrustAccount ) {
|
||
|
||
LocalOwfPassword = OwfPreviousPassword;
|
||
|
||
//
|
||
// Otherwise, try the next challenge, if any
|
||
//
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Compute the session key given the two challenges and the
|
||
// password.
|
||
//
|
||
|
||
Status = NlMakeSessionKey(
|
||
*NegotiatedFlags,
|
||
&LocalOwfPassword,
|
||
&ClientChallenge,
|
||
&ServerChallenge,
|
||
&SessionKey );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: Can't NlMakeSessionKey for %ws 0x%lx.\n",
|
||
AccountName,
|
||
Status ));
|
||
LeaveCriticalSection( &NlGlobalChallengeCritSect );
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: SessionKey %lu = ", LoopCount ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &SessionKey, sizeof(SessionKey) );
|
||
|
||
|
||
//
|
||
// Compute ClientCredential to verify the one supplied by ComputerName
|
||
//
|
||
|
||
NlComputeCredentials( &ClientChallenge,
|
||
&LocalClientCredential,
|
||
&SessionKey);
|
||
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientCredential %lu GOT = ", LoopCount ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, ClientCredential, sizeof(*ClientCredential) );
|
||
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientCredential %lu MADE = ", LoopCount ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, &LocalClientCredential, sizeof(LocalClientCredential) );
|
||
|
||
|
||
//
|
||
// Verify the computed credentials with those supplied
|
||
//
|
||
|
||
if( RtlEqualMemory( ClientCredential,
|
||
&LocalClientCredential,
|
||
sizeof(LocalClientCredential)) ) {
|
||
ClientAuthenticated = TRUE;
|
||
break;
|
||
}
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: Bad password %lu for %ws on account %ws\n",
|
||
LoopCount, ComputerName, AccountName ));
|
||
|
||
}
|
||
|
||
if ( ClientAuthenticated ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// This challenge entry failed to authenticate.
|
||
// Remember the account name as specified by the
|
||
// client in this challenge entry if this account
|
||
// isn't already on the entry.
|
||
//
|
||
if ( Challenge->ChFailedAccountName == NULL ||
|
||
wcsstr(Challenge->ChFailedAccountName, AccountName) == NULL ) {
|
||
|
||
ULONG OldLength = 0;
|
||
LPWSTR TmpStorage = NULL;
|
||
|
||
//
|
||
// If there is already an account name,
|
||
// allocate space for it
|
||
//
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
// add storage for a comma and a space
|
||
OldLength = wcslen( Challenge->ChFailedAccountName ) + 2;
|
||
}
|
||
|
||
//
|
||
// Allocate space for old (if any) and new account names
|
||
//
|
||
TmpStorage = LocalAlloc( LMEM_ZEROINIT,
|
||
(OldLength+wcslen(AccountName)+1)*sizeof(WCHAR) );
|
||
if ( TmpStorage != NULL ) {
|
||
|
||
//
|
||
// Copy old name(s), if any.
|
||
// Separate names with a comma and a space.
|
||
//
|
||
if ( OldLength > 0 ) {
|
||
RtlCopyMemory( TmpStorage,
|
||
Challenge->ChFailedAccountName,
|
||
(OldLength-2)*sizeof(WCHAR) );
|
||
wcscat( TmpStorage, L", ");
|
||
}
|
||
|
||
//
|
||
// Append the new account name
|
||
//
|
||
wcscat(TmpStorage, AccountName);
|
||
|
||
//
|
||
// Free the old name(s) and keep the new one
|
||
//
|
||
if ( Challenge->ChFailedAccountName != NULL ) {
|
||
LocalFree( Challenge->ChFailedAccountName );
|
||
}
|
||
Challenge->ChFailedAccountName = TmpStorage;
|
||
}
|
||
}
|
||
}
|
||
|
||
LeaveCriticalSection( &NlGlobalChallengeCritSect );
|
||
|
||
//
|
||
// Error out if we didn't authenticate the client
|
||
//
|
||
|
||
if ( !ClientAuthenticated ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: Failed to authenticate %ws on account %ws\n",
|
||
ComputerName, AccountName ));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Cleanup all challenges for this client
|
||
//
|
||
|
||
NlRemoveChallenge( ComputerName, AccountName, IsInterdomainTrustAccount );
|
||
|
||
//
|
||
// Create the server session for this client
|
||
//
|
||
|
||
Status = NlInsertServerSession(
|
||
DomainInfo,
|
||
ComputerName,
|
||
AccountName,
|
||
SecureChannelType,
|
||
// Only replicate those databases that negotiation says to replicate
|
||
SS_AUTHENTICATED |
|
||
NlMaxReplMask(*NegotiatedFlags) |
|
||
((TrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) ?
|
||
SS_FOREST_TRANSITIVE :
|
||
0 ),
|
||
*AccountRid,
|
||
*NegotiatedFlags,
|
||
(SecureChannelType == ServerSecureChannel) ?
|
||
NlTransportLookup( ComputerName ) :
|
||
NULL,
|
||
&SessionKey,
|
||
&LocalClientCredential );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: NlInsertServerSession failed for %ws on account %ws\n",
|
||
ComputerName, AccountName ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Compute ServerCredential from ServerChallenge to be returned to caller
|
||
//
|
||
|
||
NlComputeCredentials( &ServerChallenge,
|
||
ServerCredential,
|
||
&SessionKey );
|
||
|
||
NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ServerCredential SEND = " ));
|
||
NlpDumpBuffer(NL_CHALLENGE_RES, ServerCredential, sizeof(*ServerCredential) );
|
||
|
||
|
||
//
|
||
// If the client is a pre NT 5 member workstation or BDC,
|
||
// update the DS.
|
||
//
|
||
|
||
if ( !IsInterdomainTrustAccount &&
|
||
((*NegotiatedFlags) & ~NETLOGON_SUPPORTS_NT4_MASK) == 0 ) {
|
||
OSVERSIONINFOEXW OsVersionInfoEx;
|
||
|
||
//
|
||
// Build the OsVersionInfo structure.
|
||
//
|
||
|
||
RtlZeroMemory( &OsVersionInfoEx, sizeof(OsVersionInfoEx) );
|
||
OsVersionInfoEx.dwOSVersionInfoSize = sizeof(OsVersionInfoEx);
|
||
|
||
//
|
||
// Differentiate between NT 3 and NT 4.
|
||
//
|
||
|
||
if ( *NegotiatedFlags == 0 ) {
|
||
OsVersionInfoEx.dwMajorVersion = 3;
|
||
OsVersionInfoEx.dwMinorVersion = 1;
|
||
} else if ( ((*NegotiatedFlags) & ~NETLOGON_SUPPORTS_NT351_MASK) == 0 ) {
|
||
OsVersionInfoEx.dwMajorVersion = 3;
|
||
OsVersionInfoEx.dwMinorVersion = 5;
|
||
} else {
|
||
OsVersionInfoEx.dwMajorVersion = 4;
|
||
}
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerAuthenticate: %ws is running NT %ld.%ld\n",
|
||
ComputerName,
|
||
OsVersionInfoEx.dwMajorVersion,
|
||
OsVersionInfoEx.dwMinorVersion ));
|
||
|
||
//
|
||
// Set the DnsHostName on the computer object.
|
||
//
|
||
Status = LsaISetClientDnsHostName(
|
||
ComputerName,
|
||
NULL, // No DnsHostName
|
||
&OsVersionInfoEx,
|
||
L"Windows NT",
|
||
NULL ); // Not interested in returning DnsHostName
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerAuthenticate: Cannot set client DNS host name %lx (ignoring)\n",
|
||
Status ));
|
||
// This isn't fatal
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Success!!!
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerAuthenticate returns Success: %ws on account %ws (Negot: %lx)\n",
|
||
ComputerName, AccountName, *NegotiatedFlags ));
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Return more appropriate error
|
||
//
|
||
|
||
if ( Status == STATUS_NO_SUCH_USER ) {
|
||
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
|
||
}
|
||
|
||
//
|
||
// Handle failure
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
LPWSTR MsgStrings[3];
|
||
|
||
//
|
||
// Be careful to not leak authentication information.
|
||
//
|
||
|
||
RtlZeroMemory( ServerCredential, sizeof(*ServerCredential) );
|
||
*AccountRid = 0;
|
||
|
||
//
|
||
// Write event log as appropriate
|
||
//
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
MsgStrings[1] = AccountName;
|
||
|
||
if (Status == STATUS_NO_TRUST_SAM_ACCOUNT) {
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthNoTrustSamAccount,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 );
|
||
|
||
//
|
||
// If this attempt failed with access denied and we tried challenges for
|
||
// this client, the event log has already been output as appropriate
|
||
//
|
||
} else if ( !(Status == STATUS_ACCESS_DENIED && ChallengeCount > 0) ) {
|
||
MsgStrings[2] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailed,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
3 | NETP_LAST_MESSAGE_IS_NTSTATUS );
|
||
}
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
//
|
||
// End the WMI trace of server authentication
|
||
//
|
||
|
||
NlpTraceServerAuthEvent( EVENT_TRACE_TYPE_END,
|
||
ComputerName,
|
||
AccountName,
|
||
SecureChannelType,
|
||
NegotiatedFlags,
|
||
Status );
|
||
|
||
return Status;
|
||
#endif // _DC_NETLOGON
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerAuthenticate2(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_CREDENTIAL ClientCredential,
|
||
OUT PNETLOGON_CREDENTIAL ServerCredential,
|
||
IN OUT PULONG NegotiatedFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
This is the NT 3.5x and NT 4.x version of I_NetServerAuthenicate3.
|
||
I_NetServerAuthenticate3 was introduced in NT 5.0 (December 1996).
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
The status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG AccountRid;
|
||
|
||
return NetrServerAuthenticate3( PrimaryName,
|
||
AccountName,
|
||
SecureChannelType,
|
||
ComputerName,
|
||
ClientCredential,
|
||
ServerCredential,
|
||
NegotiatedFlags,
|
||
&AccountRid );
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerAuthenticate(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_CREDENTIAL ClientCredential,
|
||
OUT PNETLOGON_CREDENTIAL ServerCredential
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
This is the NT 3.1 version of I_NetServerAuthenicate2.
|
||
I_NetServerAuthenticate2 was introduced in NT 3.5 (December 1993).
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
The status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG NegotiatedFlags = 0;
|
||
|
||
return NetrServerAuthenticate2( PrimaryName,
|
||
AccountName,
|
||
SecureChannelType,
|
||
ComputerName,
|
||
ClientCredential,
|
||
ServerCredential,
|
||
&NegotiatedFlags );
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetpServerPasswordSet(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword OPTIONAL,
|
||
IN PNL_TRUST_PASSWORD ClearNewPassword OPTIONAL
|
||
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used to change the password for the account being
|
||
used to maintain a secure channel. This function can only be called
|
||
by a server which has previously authenticated with a DC by calling
|
||
I_NetServerAuthenticate.
|
||
|
||
The call is made depending on the account type:
|
||
|
||
* A domain account password is changed from the PDC in the
|
||
trusting domain. The I_NetServerPasswordSet call is made to any
|
||
DC in the trusted domain.
|
||
|
||
* A server account password is changed from the specific server.
|
||
The I_NetServerPasswordSet call is made to the PDC in the domain
|
||
the server belongs to.
|
||
|
||
* A workstation account password is changed from the specific
|
||
workstation. The I_NetServerPasswordSet call is made to a DC in
|
||
the domain the server belongs to.
|
||
|
||
This function uses RPC to contact the DC named by PrimaryName.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Name of the PDC to change the servers password
|
||
with. NULL indicates this call is a local call being made on
|
||
behalf of a UAS server by the XACT server.
|
||
|
||
AccountName -- Name of the account to change the password for.
|
||
|
||
AccountType -- The type of account being accessed.
|
||
|
||
ComputerName -- Name of the BDC or member making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
UasNewPassword -- The new OWF password for the server.
|
||
|
||
ClearNewPassword -- The new cleartext password for the server.
|
||
Either, UasNewPassword or ClearNewPassword will be NULL.
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
STATUS_WRONG_PASSWORD - Indicates the server refuses to allow the password
|
||
to be changed. The client should continue to use the prior password.
|
||
|
||
--*/
|
||
{
|
||
#ifdef _WKSTA_NETLOGON
|
||
return ERROR_NOT_SUPPORTED;
|
||
UNREFERENCED_PARAMETER( PrimaryName );
|
||
UNREFERENCED_PARAMETER( AccountName );
|
||
UNREFERENCED_PARAMETER( AccountType );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( Authenticator );
|
||
UNREFERENCED_PARAMETER( ReturnAuthenticator );
|
||
UNREFERENCED_PARAMETER( UasNewPassword );
|
||
#endif // _WKSTA_NETLOGON
|
||
#ifdef _DC_NETLOGON
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
PSERVER_SESSION ServerSession;
|
||
SESSION_INFO SessionInfo;
|
||
LM_OWF_PASSWORD OwfPassword;
|
||
UNICODE_STRING NewPassword;
|
||
|
||
DWORD ClearVersionNumber = 0;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If the DS is recovering from a backup,
|
||
// avoid changing the DS.
|
||
//
|
||
|
||
if ( NlGlobalDsPaused ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrServerPasswordSet: DsIsPaused.\n"));
|
||
Status = STATUS_DS_BUSY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordSet: Comp=%ws Acc=%ws Entered\n",
|
||
ComputerName,
|
||
AccountName ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the Session key for this session.
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Check if we're refusing password changes
|
||
//
|
||
// Only refuse password changes if the client is a workstation and the
|
||
// client supports password changing.
|
||
//
|
||
// If this is a PDC and the request was passed-through a BDC,
|
||
// we don't have access to the NETLOGON_SUPPORTS flag of the workstation.
|
||
// As such, we'll simply not check the NETLOGON_SUPPORTS flag in that
|
||
// case and assume the client can handle it.
|
||
//
|
||
|
||
if ( NlGlobalParameters.RefusePasswordChange &&
|
||
AccountType == WorkstationSecureChannel &&
|
||
(ServerSession->SsSecureChannelType == ServerSecureChannel ||
|
||
(SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REFUSE_CHANGE_PWD) != 0 )){
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_WRONG_PASSWORD;
|
||
goto Cleanup;
|
||
}
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
//
|
||
// If the caller passed a cleartext password,
|
||
// decrypt it.
|
||
//
|
||
|
||
if ( ClearNewPassword != NULL ) {
|
||
NL_TRUST_PASSWORD LocalClearNewPassword;
|
||
NL_PASSWORD_VERSION PasswordVersion;
|
||
|
||
//
|
||
// Simply decrypt using the session key
|
||
//
|
||
|
||
LocalClearNewPassword = *ClearNewPassword;
|
||
NlDecryptRC4( &LocalClearNewPassword, sizeof(LocalClearNewPassword), &SessionInfo );
|
||
|
||
//
|
||
// Sanity check the length.
|
||
//
|
||
if ( IsDomainSecureChannelType( AccountType )) {
|
||
|
||
if ( (LocalClearNewPassword.Length >= sizeof(LocalClearNewPassword.Buffer)-sizeof(PasswordVersion)) ||
|
||
(LocalClearNewPassword.Length % sizeof(WCHAR)) != 0 ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordSet: Decrypted interdomain password is too long %ld\n",
|
||
LocalClearNewPassword.Length ));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
} else {
|
||
if ( (LocalClearNewPassword.Length >= sizeof(LocalClearNewPassword.Buffer)) ||
|
||
(LocalClearNewPassword.Length % sizeof(WCHAR)) != 0 ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordSet: Decrypted password is too long %ld\n",
|
||
LocalClearNewPassword.Length ));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Convert the new password into a unicode string.
|
||
//
|
||
|
||
NewPassword.Buffer = (LPWSTR)(((LPBYTE)LocalClearNewPassword.Buffer) +
|
||
NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) -
|
||
LocalClearNewPassword.Length);
|
||
;
|
||
NewPassword.MaximumLength =
|
||
NewPassword.Length = (USHORT)LocalClearNewPassword.Length;
|
||
|
||
//
|
||
// Get the password version number for an interdomain trust
|
||
// account (may be absent)
|
||
//
|
||
|
||
if ( IsDomainSecureChannelType( AccountType ) ) {
|
||
|
||
RtlCopyMemory( &PasswordVersion,
|
||
((LPBYTE)LocalClearNewPassword.Buffer) +
|
||
NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) -
|
||
LocalClearNewPassword.Length -
|
||
sizeof(PasswordVersion),
|
||
sizeof(PasswordVersion) );
|
||
|
||
if ( PasswordVersion.PasswordVersionPresent == PASSWORD_VERSION_NUMBER_PRESENT &&
|
||
PasswordVersion.PasswordVersionNumber > 0 ) {
|
||
ClearVersionNumber = PasswordVersion.PasswordVersionNumber;
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordSet: Got password version number 0x%lx\n",
|
||
ClearVersionNumber ));
|
||
} else {
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordSet: Got no password version number\n" ));
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the caller passed an OWF password,
|
||
// decrypt it.
|
||
//
|
||
} else if ( UasNewPassword != NULL ) {
|
||
//
|
||
// decrypt the sessionkey from password
|
||
// i.e. OwfPassword = D2((E2(E1(STD_TXT, PW), SK)), SK)
|
||
// = E1(STD_TXT, PW)
|
||
// OwfPassword = One Way Function of the cleartext password.
|
||
//
|
||
|
||
if (Status = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
UasNewPassword,
|
||
(PLM_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
&OwfPassword )) {
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Internal error
|
||
} else {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordSet: Neither clear nor OWF password.\n"));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Do the request locally.
|
||
//
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordSet: Comp=%ws Acc=%ws Changing password locally\n",
|
||
ComputerName,
|
||
AccountName ));
|
||
|
||
//
|
||
// Set the password on the account.
|
||
//
|
||
|
||
Status = NlSetIncomingPassword(
|
||
DomainInfo,
|
||
AccountName,
|
||
AccountType,
|
||
ClearNewPassword == NULL ? NULL : &NewPassword,
|
||
ClearVersionNumber,
|
||
ClearNewPassword == NULL ? &OwfPassword : NULL );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ) {
|
||
RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
||
}
|
||
|
||
//
|
||
// Also zero out automatic variables which store passwords to avoid
|
||
// having these on the stack for indefinite time.
|
||
//
|
||
|
||
RtlZeroMemory( &OwfPassword, sizeof(OwfPassword) );
|
||
RtlZeroMemory( &NewPassword, sizeof(NewPassword) );
|
||
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordSet: Comp=%ws Acc=%ws returns 0x%lX\n",
|
||
ComputerName,
|
||
AccountName,
|
||
Status ));
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
#endif // _DC_NETLOGON
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerPasswordGet(
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used to by a BDC to get a machine account password
|
||
from the PDC in the doamin.
|
||
|
||
This function can only be called by a server which has previously
|
||
authenticated with a DC by calling I_NetServerAuthenticate.
|
||
|
||
This function uses RPC to contact the DC named by PrimaryName.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Computer name of the PDC to remote the call to.
|
||
|
||
AccountName -- Name of the account to get the password for.
|
||
|
||
AccountType -- The type of account being accessed.
|
||
|
||
ComputerName -- Name of the BDC making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
EncryptedNtOwfPassword -- Returns the OWF password of the account.
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
PSERVER_SESSION ServerSession;
|
||
SESSION_INFO SessionInfo;
|
||
NT_OWF_PASSWORD OwfPassword;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordGet: Comp=%ws Acc=%ws Entered\n",
|
||
ComputerName,
|
||
AccountName ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// This call is only allowed to a PDC.
|
||
//
|
||
|
||
if ( DomainInfo->DomRole != RolePrimary ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordGet: Call only valid to a PDC.\n" ));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the Session key for this session.
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
// SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Call is only allowed from a BDC.
|
||
//
|
||
|
||
if ( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordGet: Call only valid from a BDC.\n" ));
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
|
||
//
|
||
// Get the password for the account.
|
||
//
|
||
|
||
Status = NlGetIncomingPassword(
|
||
DomainInfo,
|
||
AccountName,
|
||
AccountType,
|
||
0, // Let routine compute from AccountType
|
||
TRUE, // Fail if account is disabled
|
||
&OwfPassword,
|
||
NULL, // Don't return the previous password
|
||
NULL, // Don't return the account RID
|
||
NULL, // Don't return the trust attributes
|
||
NULL ); // Don't need the account type
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Encrypt the password again with the session key.
|
||
// The BDC will decrypt it on the other side.
|
||
//
|
||
|
||
Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
|
||
&OwfPassword,
|
||
(PNT_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
EncryptedNtOwfPassword) ;
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerPasswordGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd %lX\n",
|
||
Status));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ) {
|
||
RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
||
RtlZeroMemory( EncryptedNtOwfPassword, sizeof(*EncryptedNtOwfPassword) );
|
||
}
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrServerPasswordGet: Comp=%ws Acc=%ws returns 0x%lX\n",
|
||
ComputerName,
|
||
AccountName,
|
||
Status ));
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerGetTrustInfo(
|
||
IN LPWSTR TrustedDcName,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNewOwfPassword,
|
||
OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedOldOwfPassword,
|
||
OUT PNL_GENERIC_RPC_DATA *TrustInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a trusting side DC/workstation to get the
|
||
trust info (new and old passwords and trust attributes) from the
|
||
trusted side. The account name requested must match the account
|
||
name used at the secure channel setup time unless the call is made
|
||
by a BDC to its PDC; the BDC has full access to the entire trust info.
|
||
|
||
This function can only be called by a server which has previously
|
||
authenticated with a DC by calling I_NetServerAuthenticate.
|
||
|
||
This function uses RPC to contact the DC named by TrustedDcName.
|
||
|
||
Arguments:
|
||
|
||
TrustedDcName -- Computer name of the DC to remote the call to.
|
||
|
||
AccountName -- Name of the account to get the password for.
|
||
|
||
AccountType -- The type of account being accessed.
|
||
|
||
ComputerName -- Name of the DC making the call.
|
||
|
||
Authenticator -- supplied by this server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the
|
||
trusted side DC.
|
||
|
||
EncryptedNewOwfPassword -- Returns the new OWF password of the account.
|
||
|
||
EncryptedOldOwfPassword -- Returns the old OWF password of the account.
|
||
|
||
TrustInfo -- Returns trust info data (currently trust attributes).
|
||
Must be freed by calling NetApiBufferFree.
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
PSERVER_SESSION ServerSession;
|
||
SESSION_INFO SessionInfo;
|
||
NT_OWF_PASSWORD NewOwfPassword;
|
||
NT_OWF_PASSWORD OldOwfPassword;
|
||
ULONG AccountRid;
|
||
ULONG TrustAttributes = 0;
|
||
ULONG ServerSessionAccountRid;
|
||
BOOLEAN VerifyAccountMatch = FALSE;
|
||
BOOLEAN GetBothPasswords = FALSE;
|
||
|
||
PNL_GENERIC_RPC_DATA LocalTrustInfo = NULL;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( TrustedDcName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the Session key for this session.
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
// SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if we need to verify whether the trusted side
|
||
// is allowed to get passwords for this particular account.
|
||
// For our BDC, we allow full access to trust info.
|
||
//
|
||
|
||
if ( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
|
||
ServerSessionAccountRid = ServerSession->SsAccountRid;
|
||
VerifyAccountMatch = TRUE;
|
||
}
|
||
|
||
//
|
||
// See if we need to get both new and previous passwords
|
||
//
|
||
|
||
if ( IsDomainSecureChannelType( AccountType ) ) {
|
||
GetBothPasswords = TRUE;
|
||
}
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
//
|
||
// Get the password for the account.
|
||
//
|
||
|
||
Status = NlGetIncomingPassword(
|
||
DomainInfo,
|
||
AccountName,
|
||
AccountType,
|
||
0, // Let routine compute from AccountType
|
||
TRUE, // Fail if account is disabled
|
||
&NewOwfPassword,
|
||
GetBothPasswords ?
|
||
&OldOwfPassword :
|
||
NULL,
|
||
&AccountRid,
|
||
&TrustAttributes, // Get trust attributes
|
||
NULL ); // Don't need the account type
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// See if we need to verify that the account requested is
|
||
// the one for which this server session was created.
|
||
//
|
||
|
||
if ( VerifyAccountMatch && ServerSessionAccountRid != AccountRid ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrServerTrustPasswordsGet: %ws with AccountRid %lu asked for wrong account %ws and Rid %lu.\n",
|
||
ComputerName,
|
||
ServerSessionAccountRid,
|
||
AccountName,
|
||
AccountRid ));
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Encrypt the passwords again with the session key.
|
||
// The trusting side DC will decrypt it on the other side.
|
||
//
|
||
|
||
Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
|
||
&NewOwfPassword,
|
||
(PNT_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
EncryptedNewOwfPassword) ;
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerTrustPasswordsGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd 0x%lx\n",
|
||
Status));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If no password exists on the account,
|
||
// return a blank password.
|
||
//
|
||
|
||
if ( !GetBothPasswords ) {
|
||
UNICODE_STRING TempUnicodeString;
|
||
|
||
RtlInitUnicodeString( &TempUnicodeString, NULL );
|
||
Status = RtlCalculateNtOwfPassword( &TempUnicodeString,
|
||
&OldOwfPassword );
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerTrustPasswordsGet: %ws: cannot RtlCalculateNtOwfPassword (NULL) 0x%lx\n",
|
||
AccountName,
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
|
||
&OldOwfPassword,
|
||
(PNT_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
EncryptedOldOwfPassword) ;
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrServerTrustPasswordsGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd 0x%lx\n",
|
||
Status));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Return the trust attributes if requested.
|
||
// Must be the first item on the list of
|
||
// ULONGs returned.
|
||
//
|
||
|
||
if ( TrustInfo != NULL ) {
|
||
NET_API_STATUS NetStatus;
|
||
|
||
NetStatus = NetApiBufferAllocate( sizeof(NL_GENERIC_RPC_DATA)+sizeof(ULONG),
|
||
&LocalTrustInfo );
|
||
if ( NetStatus != NO_ERROR ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlZeroMemory( LocalTrustInfo, sizeof(NL_GENERIC_RPC_DATA)+sizeof(ULONG) );
|
||
|
||
LocalTrustInfo->UlongEntryCount = 1;
|
||
LocalTrustInfo->UlongData = (PULONG)(LocalTrustInfo+1);
|
||
*( (PULONG)(LocalTrustInfo+1) ) = TrustAttributes;
|
||
|
||
*TrustInfo = LocalTrustInfo;
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
||
RtlZeroMemory( EncryptedNewOwfPassword, sizeof(*EncryptedNewOwfPassword) );
|
||
RtlZeroMemory( EncryptedOldOwfPassword, sizeof(*EncryptedOldOwfPassword) );
|
||
|
||
if ( LocalTrustInfo != NULL ) {
|
||
NetApiBufferFree( LocalTrustInfo );
|
||
}
|
||
}
|
||
|
||
NlPrintDom((NL_MISC, DomainInfo,
|
||
"NetrServerPasswordGet: Comp=%ws Acc=%ws returns 0x%lX\n",
|
||
ComputerName,
|
||
AccountName,
|
||
Status ));
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerTrustPasswordsGet(
|
||
IN LPWSTR TrustedDcName,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNewOwfPassword,
|
||
OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedOldOwfPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a trusting side DC/workstation to get the
|
||
new and old passwords from the trusted side. The account name
|
||
requested must match the account name used at the secure channel
|
||
setup time unless the call is made by a BDC to its PDC; the BDC
|
||
has full access to the entire trust info.
|
||
|
||
This function can only be called by a server which has previously
|
||
authenticated with a DC by calling I_NetServerAuthenticate.
|
||
|
||
This function uses RPC to contact the DC named by TrustedDcName.
|
||
|
||
Arguments:
|
||
|
||
TrustedDcName -- Computer name of the DC to remote the call to.
|
||
|
||
AccountName -- Name of the account to get the password for.
|
||
|
||
AccountType -- The type of account being accessed.
|
||
|
||
ComputerName -- Name of the machine making the call.
|
||
|
||
Authenticator -- supplied by the server making the call.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the
|
||
trusted side DC.
|
||
|
||
EncryptedNewOwfPassword -- Returns the new OWF password of the account.
|
||
|
||
EncryptedOldOwfPassword -- Returns the old OWF password of the account.
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
return NetrServerGetTrustInfo(
|
||
TrustedDcName,
|
||
AccountName,
|
||
AccountType,
|
||
ComputerName,
|
||
Authenticator,
|
||
ReturnAuthenticator,
|
||
EncryptedNewOwfPassword,
|
||
EncryptedOldOwfPassword,
|
||
NULL ); // no trust attributes
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerPasswordSet(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
Arguments:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
Return Value:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
--*/
|
||
{
|
||
return NetpServerPasswordSet( PrimaryName,
|
||
AccountName,
|
||
AccountType,
|
||
ComputerName,
|
||
Authenticator,
|
||
ReturnAuthenticator,
|
||
UasNewPassword,
|
||
NULL ); // No clear password
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NetrServerPasswordSet2(
|
||
IN LPWSTR PrimaryName OPTIONAL,
|
||
IN LPWSTR AccountName,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN PNL_TRUST_PASSWORD ClearNewPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
Arguments:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
Return Value:
|
||
|
||
See NetpServerPasswordSet.
|
||
|
||
--*/
|
||
{
|
||
return NetpServerPasswordSet( PrimaryName,
|
||
AccountName,
|
||
AccountType,
|
||
ComputerName,
|
||
Authenticator,
|
||
ReturnAuthenticator,
|
||
NULL, // No OWF password
|
||
ClearNewPassword );
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlPackSerialNumber (
|
||
IN PLARGE_INTEGER SerialNumber,
|
||
IN OUT PNETLOGON_DELTA_ENUM Delta,
|
||
IN LPDWORD BufferSize,
|
||
IN PSESSION_INFO SessionInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Pack the specified serial number as a delta.
|
||
|
||
Arguments:
|
||
|
||
SerialNumber - The serial number to pack.
|
||
|
||
Delta: pointer to the delta structure where the new delta will
|
||
be returned.
|
||
|
||
DBInfo: pointer to the database info structure.
|
||
|
||
BufferSize: size of MIDL buffer that is consumed for this delta is
|
||
returned here.
|
||
|
||
SessionInfo: Info describing BDC that's calling us
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
PNLPR_MODIFIED_COUNT DeltaSerialNumberSkip;
|
||
PSAMPR_USER_INFO_BUFFER UserAll = NULL;
|
||
|
||
//
|
||
// Only pack this delta if the BDC expects it.
|
||
//
|
||
|
||
NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG);
|
||
UNREFERENCED_PARAMETER(SessionInfo);
|
||
|
||
NlPrint(( NL_SYNC_MORE,
|
||
"Packing skip to serial number delta: %lx %lx\n",
|
||
SerialNumber->HighPart,
|
||
SerialNumber->LowPart ));
|
||
|
||
*BufferSize = 0;
|
||
|
||
Delta->DeltaType = SerialNumberSkip;
|
||
Delta->DeltaID.Rid = 0;
|
||
Delta->DeltaUnion.DeltaSerialNumberSkip = NULL;
|
||
|
||
//
|
||
// Allocate a buffer to return to the caller.
|
||
//
|
||
|
||
DeltaSerialNumberSkip = (PNLPR_MODIFIED_COUNT)
|
||
MIDL_user_allocate( sizeof(*DeltaSerialNumberSkip) );
|
||
|
||
if (DeltaSerialNumberSkip == NULL) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
*BufferSize += sizeof(*DeltaSerialNumberSkip);
|
||
|
||
//
|
||
// Copy the serial number into the buffer.
|
||
//
|
||
|
||
RtlCopyMemory( &DeltaSerialNumberSkip->ModifiedCount,
|
||
SerialNumber,
|
||
sizeof( DeltaSerialNumberSkip->ModifiedCount ) );
|
||
|
||
Delta->DeltaUnion.DeltaSerialNumberSkip = DeltaSerialNumberSkip;
|
||
|
||
|
||
//
|
||
// All Done
|
||
//
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlPackSingleDelta (
|
||
IN PCHANGELOG_ENTRY ChangeLogEntry,
|
||
IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
|
||
OUT LPDWORD BufferConsumed,
|
||
IN PSESSION_INFO SessionInfo,
|
||
IN BOOLEAN ReturnSerialNumberDeltas
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Pack the deltas for a single change log entry.
|
||
|
||
Arguments:
|
||
|
||
ChangeLogEntry - The Change Log Entry describing the account to pack.
|
||
|
||
DeltaArray - Describes the array of deltas. The appropriate deltas will
|
||
be added to the end of this array. The caller has guaranteed that
|
||
that is room for at least MAX_DELTAS_PER_CHANGELOG - 1
|
||
deltas to be added to the array.
|
||
|
||
BufferConsumed - returns the size of MIDL buffer that is consumed for the
|
||
returned deltas
|
||
|
||
SessionInfo: Info describing BDC that's calling us
|
||
|
||
ReturnSerialNumberDeltas -- True if serial number deltas should be returned
|
||
when needed.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
||
PDB_INFO DBInfo;
|
||
DWORD BufferSize;
|
||
|
||
UNICODE_STRING UnicodeSecretName;
|
||
LPWSTR AccountName;
|
||
PSID Sid;
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
DBInfo = &NlGlobalDBInfoArray[ChangeLogEntry->DBIndex];
|
||
*BufferConsumed = 0;
|
||
|
||
//
|
||
// Macro to account for another delta array entry being consumed/returned
|
||
//
|
||
|
||
# define MoveToNextDeltaArrayEntry( _BufferSize ) \
|
||
*BufferConsumed += (sizeof(NETLOGON_DELTA_ENUM) + _BufferSize); \
|
||
(DeltaArray->CountReturned)++;
|
||
|
||
//
|
||
// Put the data for the changelog entry into the user's buffer.
|
||
//
|
||
|
||
switch ( ChangeLogEntry->DeltaType ) {
|
||
case AddOrChangeDomain:
|
||
Status = NlPackSamDomain(
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
break;
|
||
|
||
//
|
||
// The DS can't distinguish between a membership change and a property change.
|
||
// always replicate all aspects of the group.
|
||
//
|
||
|
||
case AddOrChangeGroup:
|
||
case ChangeGroupMembership:
|
||
case RenameGroup:
|
||
|
||
//
|
||
// we treat the rename as three deltas.
|
||
// 1. AddorChangeGroup delta.
|
||
// Backup deletes the account with old name and creates
|
||
// an account with new name.
|
||
//
|
||
// 2. Delta to tell the BDC that delta (3) below is for the
|
||
// same serial number as delta (1) above.
|
||
//
|
||
// 3. ChangeGroupMembership delta.
|
||
// Backup readds all members to new group.
|
||
//
|
||
|
||
Status = NlPackSamGroup( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
break;
|
||
}
|
||
|
||
MoveToNextDeltaArrayEntry( BufferSize );
|
||
|
||
|
||
if ( ReturnSerialNumberDeltas ) {
|
||
|
||
Status = NlPackSerialNumber(
|
||
&ChangeLogEntry->SerialNumber,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
break;
|
||
}
|
||
|
||
MoveToNextDeltaArrayEntry( BufferSize );
|
||
}
|
||
|
||
Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
break;
|
||
|
||
case AddOrChangeUser:
|
||
case RenameUser:
|
||
Status = NlPackSamUser( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// The DS can't distinguish between a membership change and a property change.
|
||
// always replicate all aspects of the alias.
|
||
//
|
||
case AddOrChangeAlias:
|
||
case ChangeAliasMembership:
|
||
case RenameAlias:
|
||
|
||
//
|
||
// we treat the rename as two deltas.
|
||
// 1. AddorChangeAlias delta.
|
||
// Backup deletes the account with old name and creates
|
||
// an account with new name.
|
||
//
|
||
// 2. Delta to tell the BDC that delta (3) below is for the
|
||
// same serial number as delta (1) above.
|
||
//
|
||
// 3. ChangeAliasMembership delta.
|
||
// Backup readds all members to new alias.
|
||
//
|
||
|
||
Status = NlPackSamAlias( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
break;
|
||
}
|
||
|
||
MoveToNextDeltaArrayEntry( BufferSize );
|
||
|
||
if ( ReturnSerialNumberDeltas ) {
|
||
|
||
Status = NlPackSerialNumber(
|
||
&ChangeLogEntry->SerialNumber,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
break;
|
||
}
|
||
|
||
MoveToNextDeltaArrayEntry( BufferSize );
|
||
}
|
||
|
||
Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
break;
|
||
|
||
case AddOrChangeLsaPolicy:
|
||
|
||
Status = NlPackLsaPolicy(
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
break;
|
||
|
||
case AddOrChangeLsaTDomain:
|
||
|
||
NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
Status = NlPackLsaTDomain(
|
||
(PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)),
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
break;
|
||
|
||
case AddOrChangeLsaAccount:
|
||
|
||
NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
Status = NlPackLsaAccount(
|
||
(PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)),
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
break;
|
||
|
||
case AddOrChangeLsaSecret:
|
||
|
||
NlAssert( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
RtlInitUnicodeString(
|
||
&UnicodeSecretName,
|
||
(LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)) );
|
||
|
||
Status = NlPackLsaSecret(
|
||
&UnicodeSecretName,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
break;
|
||
|
||
case DeleteGroup:
|
||
case DeleteGroupByName:
|
||
case DeleteUser:
|
||
case DeleteUserByName:
|
||
|
||
//
|
||
// If this is an NT 3.5 BDC,
|
||
// send the account name upon account deletion.
|
||
|
||
if ( ReturnSerialNumberDeltas ) {
|
||
|
||
//
|
||
// Send the NT 3.5 BDC a special delta type indicating the
|
||
// Name is attached.
|
||
//
|
||
if ( ChangeLogEntry->DeltaType == DeleteGroup ) {
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
DeleteGroupByName;
|
||
} else if ( ChangeLogEntry->DeltaType == DeleteUser ) {
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
DeleteUserByName;
|
||
} else {
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
ChangeLogEntry->DeltaType;
|
||
}
|
||
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid =
|
||
ChangeLogEntry->ObjectRid;
|
||
|
||
|
||
//
|
||
// Add the account name to the entry.
|
||
//
|
||
|
||
NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED);
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
BufferSize = (wcslen(
|
||
(LPWSTR) ((LPBYTE)ChangeLogEntry +
|
||
sizeof(CHANGELOG_ENTRY))) + 1 ) *
|
||
sizeof(WCHAR);
|
||
|
||
AccountName = (LPWSTR) MIDL_user_allocate( BufferSize );
|
||
|
||
if (AccountName == NULL) {
|
||
Status = STATUS_NO_MEMORY;
|
||
break;
|
||
}
|
||
|
||
wcscpy( AccountName,
|
||
(LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
|
||
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].
|
||
DeltaUnion.DeltaDeleteGroup =
|
||
MIDL_user_allocate(sizeof(struct _NETLOGON_DELTA_DELETE));
|
||
|
||
if ((DeltaArray->Deltas)[DeltaArray->CountReturned].
|
||
DeltaUnion.DeltaDeleteGroup == NULL ) {
|
||
MIDL_user_free(AccountName);
|
||
Status = STATUS_NO_MEMORY;
|
||
break;
|
||
}
|
||
|
||
INIT_PLACE_HOLDER( (DeltaArray->Deltas)[DeltaArray->CountReturned].
|
||
DeltaUnion.DeltaDeleteGroup );
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].
|
||
DeltaUnion.DeltaDeleteGroup->AccountName = AccountName;
|
||
|
||
break; // out of switch
|
||
}
|
||
|
||
/* Drop through to handle NT 3.1 case. */
|
||
|
||
case DeleteAlias:
|
||
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
ChangeLogEntry->DeltaType;
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid =
|
||
ChangeLogEntry->ObjectRid;
|
||
|
||
BufferSize = 0;
|
||
|
||
break;
|
||
|
||
case DeleteLsaTDomain:
|
||
case DeleteLsaAccount:
|
||
|
||
NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED );
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
BufferSize =
|
||
RtlLengthSid( (PSID)((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
|
||
|
||
Sid = (PSID) MIDL_user_allocate( BufferSize );
|
||
|
||
if( Sid == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
break;
|
||
}
|
||
|
||
Status = RtlCopySid (
|
||
BufferSize,
|
||
Sid,
|
||
(PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
|
||
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
MIDL_user_free( Sid );
|
||
break;
|
||
}
|
||
|
||
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
ChangeLogEntry->DeltaType;
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Sid =
|
||
Sid;
|
||
|
||
break;
|
||
|
||
case DeleteLsaSecret:
|
||
|
||
NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED);
|
||
|
||
if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
BufferSize = (wcslen(
|
||
(LPWSTR) ((LPBYTE)ChangeLogEntry +
|
||
sizeof(CHANGELOG_ENTRY))) + 1 ) *
|
||
sizeof(WCHAR);
|
||
|
||
AccountName = (LPWSTR) MIDL_user_allocate( BufferSize );
|
||
|
||
if (AccountName == NULL) {
|
||
Status = STATUS_NO_MEMORY;
|
||
break;
|
||
}
|
||
|
||
wcscpy( AccountName,
|
||
(LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)));
|
||
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType =
|
||
ChangeLogEntry->DeltaType;
|
||
(DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Name =
|
||
AccountName;
|
||
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL, "NlPackSingleDelta: Invalid delta type in change log\n"));
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
MoveToNextDeltaArrayEntry( BufferSize );
|
||
}
|
||
|
||
return Status;
|
||
#undef MoveToNextDeltaArrayEntry
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrDatabaseDeltas (
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD DatabaseID,
|
||
IN OUT PNLPR_MODIFIED_COUNT NlDomainModifiedCount,
|
||
OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
|
||
IN DWORD PreferredMaximumLength
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a SAM BDC to
|
||
request SAM-style account delta information from a SAM PDC. This
|
||
function can only be called by a server which has previously
|
||
authenticated with the PDC by calling I_NetServerAuthenticate. This
|
||
function uses RPC to contact the Netlogon service on the PDC.
|
||
|
||
This function returns a list of deltas. A delta describes an
|
||
individual domain, user or group and all of the field values for that
|
||
object. The PDC maintains a list of deltas not including all of the
|
||
field values for that object. Rather, the PDC retrieves the field
|
||
values from SAM and returns those values from this call. The PDC
|
||
optimizes the data returned on this call by only returning the field
|
||
values for a particular object once on a single invocation of this
|
||
function. This optimizes the typical case where multiple deltas
|
||
exist for a single object (e.g., an application modified many fields
|
||
of the same user during a short period of time using different calls
|
||
to the SAM service).
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Name of the PDC to retrieve the deltas from.
|
||
|
||
ComputerName -- Name of the BDC or member server making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
DatabaseID -- Identifies the databse for which the deltas are requested.
|
||
For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
|
||
databases may be defined later.
|
||
|
||
NlDomainModifiedCount -- Specifies the DomainModifiedCount of the
|
||
last delta retrieved by the server. Returns the
|
||
DomainModifiedCount of the last delta returned from the PDC
|
||
on this call.
|
||
|
||
Deltas -- Receives a pointer to a buffer where the information is
|
||
placed. The information returned is an array of
|
||
NETLOGON_DELTA_ENUM structures.
|
||
|
||
PreferredMaximumLength - Preferred maximum length of returned
|
||
data (in 8-bit bytes). This is not a hard upper limit, but
|
||
serves as a guide to the server. Due to data conversion
|
||
between systems with different natural data sizes, the actual
|
||
amount of data returned may be greater than this value.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and
|
||
should call I_NetDataSync to do a full synchronization with
|
||
the PDC.
|
||
|
||
STATUS_MORE_ENTRIES -- The replicant should call again to get more
|
||
data.
|
||
|
||
STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
|
||
the PDC.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
PCHANGELOG_ENTRY ChangeLogEntry = NULL;
|
||
BOOLEAN PackThisEntry = TRUE;
|
||
|
||
BOOL ChangelogLocked = FALSE;
|
||
|
||
PDB_INFO DBInfo;
|
||
LARGE_INTEGER RunningSerialNumber;
|
||
LARGE_INTEGER PackedSerialNumber;
|
||
LARGE_INTEGER OriginalSerialNumber;
|
||
|
||
DWORD BufferConsumed = 0;
|
||
DWORD BufferSize = 0;
|
||
|
||
PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
|
||
|
||
|
||
SESSION_INFO SessionInfo;
|
||
|
||
DEFSSIAPITIMER;
|
||
|
||
INITSSIAPITIMER;
|
||
STARTSSIAPITIMER;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: called from %ws. This machine doesn't support replication.\n",
|
||
ComputerName ));
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If the DS is recovering from a backup,
|
||
// avoid changing the DS.
|
||
//
|
||
|
||
if ( NlGlobalDsPaused ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: DsIsPaused.\n"));
|
||
// Don't return a new status code since NT 4 DC would do a full sync
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Gross hack because of RPC implementation.
|
||
//
|
||
// Rpc executes API calls in an I/O completion port thread. If this thread
|
||
// goes CPU bound, then no other RPC will be allowed to start. Even worse,
|
||
// there is only one outstanding listen, so the 'second' coming RPC call
|
||
// gets RPC_S_SERVER_TOO_BUSY.
|
||
//
|
||
// By sleeping here (even for a short period) the I/O completion port releases
|
||
// another thread since it thinks this thread went I/O bound.
|
||
//
|
||
// We've seen this thread go CPU bound doing a full sync of a database with
|
||
// 1000's of LSA account objects.
|
||
//
|
||
|
||
RpcServerYield();
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
if ( DatabaseID >= NUM_DBS ) {
|
||
return STATUS_INVALID_LEVEL;
|
||
}
|
||
|
||
*DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
|
||
MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
|
||
|
||
if( DeltaArray == NULL ) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
DeltaArray->CountReturned = 0;
|
||
DeltaArray->Deltas = NULL;
|
||
SessionInfo.NegotiatedFlags = 0;
|
||
|
||
|
||
DBInfo = &NlGlobalDBInfoArray[DatabaseID];
|
||
|
||
|
||
RtlCopyMemory( &RunningSerialNumber,
|
||
&NlDomainModifiedCount->ModifiedCount,
|
||
sizeof(RunningSerialNumber));
|
||
|
||
OriginalSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
|
||
PackedSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
|
||
|
||
|
||
|
||
//
|
||
// Find the domain this API was made to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SYNC, DomainInfo,
|
||
"NetrDatabaseDeltas: " FORMAT_LPWSTR " partial sync called by " FORMAT_LPWSTR
|
||
" SerialNumber:%lx %lx.\n",
|
||
DBInfo->DBName,
|
||
ComputerName,
|
||
RunningSerialNumber.HighPart,
|
||
RunningSerialNumber.LowPart ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !IsPrimaryDomain( DomainInfo )) {
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: No server session.\n"));
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
goto CleanupNoEventlog;
|
||
}
|
||
|
||
//
|
||
// Allow this call only on ServerSecureChannel.
|
||
//
|
||
|
||
if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
|
||
|
||
//
|
||
// If the only preblem is that this BDC hasn't authenticated,
|
||
// silently ask it to authenticate.
|
||
//
|
||
if ( ServerSession->SsSecureChannelType == NullSecureChannel ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: No authenticated server session.\n"));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto CleanupNoEventlog;
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: SecureChannel type isn't BDC. %ld\n",
|
||
ServerSession->SsSecureChannelType ));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: authentication failed.\n" ));
|
||
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Prevent entry from being deleted, but drop the global lock.
|
||
//
|
||
// Beware of server with two concurrent calls outstanding
|
||
// (must have rebooted.)
|
||
//
|
||
|
||
if (ServerSession->SsFlags & SS_LOCKED ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: Concurrent call detected.\n" ));
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
ServerSession->SsFlags |= SS_LOCKED;
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
|
||
//
|
||
// If the BDC is in sync,
|
||
// simply return.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
ChangelogLocked = TRUE;
|
||
|
||
if ( RunningSerialNumber.QuadPart ==
|
||
NlGlobalChangeLogDesc.SerialNumber[DatabaseID].QuadPart ) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get a copy of the appropriate entry in the change_log.
|
||
// Note that the record_id contains last record received by client.
|
||
//
|
||
|
||
if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry(
|
||
&NlGlobalChangeLogDesc,
|
||
RunningSerialNumber,
|
||
DBInfo->DBIndex,
|
||
NULL ))== NULL) {
|
||
|
||
//
|
||
// Handle the case where the BDC has more recent changes than we do.
|
||
//
|
||
// Just return our newest change log entry with the same promotion count.
|
||
// The BDC will realize what's going on and un-do its newer changes.
|
||
//
|
||
// Only do this if our PromotionCount is greater than the BDCs. If
|
||
// our promotion count is equal to that of the BDC, either our change log
|
||
// has wrapped, or the BDC is royally confused.
|
||
//
|
||
// Don't be tempted to return a change log entry with an
|
||
// older promotion count. We'd have no way of knowing which delta
|
||
// to actually return to the caller.
|
||
//
|
||
|
||
if ( ((NlGlobalChangeLogDesc.SerialNumber[DatabaseID].HighPart &
|
||
NlGlobalChangeLogPromotionMask) >
|
||
(RunningSerialNumber.HighPart & NlGlobalChangeLogPromotionMask)) &&
|
||
(SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO) ) {
|
||
|
||
ChangeLogEntry = NlFindPromotionChangeLogEntry(
|
||
&NlGlobalChangeLogDesc,
|
||
RunningSerialNumber,
|
||
DBInfo->DBIndex );
|
||
|
||
//
|
||
// Don't actually pack this change log entry. We've found it
|
||
// so we can pack a "serial number" delta. But the BDC already
|
||
// has this particular change.
|
||
//
|
||
|
||
PackThisEntry = FALSE;
|
||
}
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: "
|
||
"delta not found in cache, returning full required.\n" ));
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
} else {
|
||
NlPrint((NL_SYNC, "NetrDatabaseDeltas: BDC more recent than PDC (recovering).\n" ));
|
||
}
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
ChangelogLocked = FALSE;
|
||
|
||
//
|
||
// Allocate memory for delta buffer.
|
||
//
|
||
|
||
DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
if( DeltaArray->Deltas == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// wipe off the buffer so that cleanup will not be in fault.
|
||
//
|
||
|
||
RtlZeroMemory( DeltaArray->Deltas,
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
|
||
//
|
||
// Loop packing deltas as long as there is room for more deltas
|
||
//
|
||
// In some cases we pack multiple deltas on the wire for one entry in the
|
||
// change log, we want to ensure that all of these deltas are sent to
|
||
// the BDC on a single call.
|
||
//
|
||
|
||
while ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG <= MAX_DELTA_COUNT ) {
|
||
|
||
//
|
||
// If the serial number of the delta being packed isn't the one
|
||
// expected by the BDC, tell the BDC what the serial number is.
|
||
//
|
||
|
||
if ( ChangeLogEntry->SerialNumber.QuadPart !=
|
||
PackedSerialNumber.QuadPart + 1 ) {
|
||
|
||
if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG){
|
||
|
||
Status = NlPackSerialNumber(
|
||
&ChangeLogEntry->SerialNumber,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
&BufferSize,
|
||
&SessionInfo );
|
||
if( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
BufferConsumed += BufferSize;
|
||
DeltaArray->CountReturned ++;
|
||
|
||
//
|
||
// If we're not really going to pack the entry,
|
||
// pretend that we already have.
|
||
//
|
||
if ( !PackThisEntry) {
|
||
PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
if ( PackThisEntry ) {
|
||
|
||
//
|
||
// Put the data for the changelog entry into the user's buffer.
|
||
//
|
||
|
||
Status = NlPackSingleDelta( ChangeLogEntry,
|
||
DeltaArray,
|
||
&BufferSize,
|
||
&SessionInfo,
|
||
(BOOLEAN)((SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG) != 0) );
|
||
|
||
//
|
||
// If we successfully put the delta into the delta array,
|
||
// do the bookwork
|
||
//
|
||
|
||
if ( NT_SUCCESS( Status ) ) {
|
||
|
||
BufferConsumed += BufferSize;
|
||
|
||
PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
|
||
|
||
NlPrint((NL_SYNC_MORE,
|
||
"NetrDatabaseDeltas: Modified count of the "
|
||
"packed record: %lx %lx\n",
|
||
ChangeLogEntry->SerialNumber.HighPart,
|
||
ChangeLogEntry->SerialNumber.LowPart ));
|
||
|
||
|
||
//
|
||
// In the case where an user/group/alias record was
|
||
// added and deleted before the delta was made we will
|
||
// trace the change log and see there is correpondance
|
||
// delete log. If we found one then ignore this delta
|
||
// and proceed to the next delta. If we couldn't find
|
||
// one then return error STATUS_SYNCHRONIZATION_REQUIRED.
|
||
//
|
||
|
||
} else if ( IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) {
|
||
|
||
if( !NlRecoverChangeLog(ChangeLogEntry) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: object not found in database, and no delete delta found (%lx).\n",
|
||
Status ));
|
||
|
||
#ifdef notdef
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
|
||
IF_NL_DEBUG( BREAKPOINT ) {
|
||
NlAssert( FALSE );
|
||
}
|
||
|
||
goto Cleanup;
|
||
#else // notdef
|
||
|
||
//
|
||
// NT 5.0 SAM doesn't hold the write lock while determining if
|
||
// the object exists. So, the object might have been deleted and
|
||
// the but the delete delta hasn't been written to the change log yet.
|
||
// So, assume that the delete delta will appear sooner or later.
|
||
//
|
||
// REVIEW: I could just pack a delete delta.
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
#endif // notdef
|
||
|
||
} else {
|
||
|
||
//
|
||
// We found a delete delta, so ignore the original delta.
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// All other errors are fatal
|
||
//
|
||
|
||
} else {
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
PackThisEntry = TRUE;
|
||
|
||
|
||
//
|
||
// Free up used temp. record
|
||
//
|
||
|
||
RunningSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart;
|
||
NetpMemoryFree(ChangeLogEntry);
|
||
ChangeLogEntry = NULL;
|
||
|
||
//
|
||
// If we've returned all the entries, we're all done.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
ChangelogLocked = TRUE;
|
||
|
||
if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry(
|
||
&NlGlobalChangeLogDesc,
|
||
RunningSerialNumber,
|
||
DBInfo->DBIndex,
|
||
NULL )) == NULL) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
ChangelogLocked = FALSE;
|
||
|
||
|
||
//
|
||
// Don't return more data to the caller than he wants.
|
||
//
|
||
|
||
if( BufferConsumed >= PreferredMaximumLength) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If we're debugging replication, return only one change to the caller.
|
||
//
|
||
#if NETLOGONDBG
|
||
if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // NETLOGONDBG
|
||
|
||
|
||
//
|
||
// If the service is going down, stop packing deltas and
|
||
// return to the caller.
|
||
//
|
||
|
||
if( NlGlobalTerminate ) {
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseDeltas is asked to return "
|
||
"when the service is going down.\n"));
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
}
|
||
|
||
Status = STATUS_MORE_ENTRIES;
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
LPWSTR MsgStrings[2];
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
MsgStrings[1] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Log the successful replication only if deltas have been returned
|
||
// to the caller.
|
||
//
|
||
if ( DeltaArray->CountReturned != 0 ) {
|
||
LPWSTR MsgStrings[2];
|
||
WCHAR CountBuffer[20]; // random size
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
|
||
ultow( DeltaArray->CountReturned, CountBuffer, 10);
|
||
MsgStrings[1] = CountBuffer;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallSuccess,
|
||
EVENTLOG_INFORMATION_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
2 | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Free up locally allocated resources.
|
||
//
|
||
|
||
CleanupNoEventlog:
|
||
|
||
//
|
||
// Copy the serial number back to the caller
|
||
//
|
||
|
||
if ( NT_SUCCESS(Status)) {
|
||
|
||
RtlCopyMemory( &NlDomainModifiedCount->ModifiedCount,
|
||
&PackedSerialNumber,
|
||
sizeof(PackedSerialNumber));
|
||
|
||
|
||
//
|
||
// If this is an NT 3.1 BDC,
|
||
// Only remember the latest Serial Number it asked for, AND
|
||
// force it the call back once it has updated the SerialNumber
|
||
// so we know what that serial number is.
|
||
//
|
||
// NT 3.5 BDCs "persistently" try to update their database to the
|
||
// PDCs version once they get a pulse indicating their database is
|
||
// out of date.
|
||
//
|
||
|
||
if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_PERSISTENT_BDC) == 0 ) {
|
||
|
||
//
|
||
// Use the SerialNumber the BDC originally passed us.
|
||
//
|
||
|
||
PackedSerialNumber.QuadPart = OriginalSerialNumber.QuadPart;
|
||
|
||
//
|
||
// If we're returning any deltas at all,
|
||
// force the BDC to call us back.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS && DeltaArray->CountReturned != 0 ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// If we weren't successful,
|
||
// Don't return any deltas.
|
||
//
|
||
|
||
} else {
|
||
if ( DeltaArray->Deltas != NULL ) {
|
||
NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
|
||
DeltaArray->Deltas = NULL;
|
||
}
|
||
DeltaArray->CountReturned = 0;
|
||
|
||
}
|
||
|
||
if ( ChangelogLocked ) {
|
||
UNLOCK_CHANGELOG();
|
||
}
|
||
|
||
if( ChangeLogEntry != NULL) {
|
||
NetpMemoryFree( ChangeLogEntry );
|
||
}
|
||
|
||
//
|
||
// Unlock the server session entry if we've locked it.
|
||
//
|
||
|
||
if ( ServerSession != NULL ) {
|
||
|
||
//
|
||
// If we are successfully returning these deltas to the BDC,
|
||
// update our tables to reflect the changes.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
NlPrimaryAnnouncementFinish( ServerSession,
|
||
DatabaseID,
|
||
&PackedSerialNumber );
|
||
|
||
}
|
||
NlUnlockServerSession( ServerSession );
|
||
}
|
||
|
||
//
|
||
// If the BDC called us just as SAM was shutting down,
|
||
// map the status to prevent the BDC from full syncing.
|
||
//
|
||
|
||
if ( Status == STATUS_INVALID_SERVER_STATE ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseDeltas: " FORMAT_LPWSTR " returning (0x%lx) to "
|
||
FORMAT_LPWSTR "\n",
|
||
DBInfo->DBName,
|
||
Status,
|
||
ComputerName ));
|
||
|
||
STOPSSIAPITIMER;
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
NlPrint((NL_REPL_TIME,"NetrDatabaseDeltas Time:\n"));
|
||
PRINTSSIAPITIMER;
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NlSyncSamDatabase(
|
||
IN PSERVER_SESSION ServerSession,
|
||
IN DWORD DatabaseID,
|
||
IN SYNC_STATE RestartState,
|
||
IN OUT PULONG SyncContext,
|
||
IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
|
||
IN DWORD PreferredMaximumLength,
|
||
IN PSESSION_INFO SessionInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is a real worker for the NetrDatabaseSync function and
|
||
retrieves a SAM database in the delta buffer.
|
||
|
||
This function uses the find-first find-next model to return portions
|
||
of the SAM database at a time. The SAM database is returned as a
|
||
list of deltas like those returned from I_NetDatabaseDeltas. The
|
||
following deltas are returned for each domain:
|
||
|
||
* One AddOrChangeDomain delta, followed by
|
||
|
||
* One AddOrChangeGroup delta for each group, followed by,
|
||
|
||
* One AddOrChangeUser delta for each user, followed by
|
||
|
||
* One ChangeGroupMembership delta for each group followed by,
|
||
|
||
* One AddOrChangeAlias delta for each alias, followed by,
|
||
|
||
* One ChangeAliasMembership delta for each alias.
|
||
|
||
|
||
Arguments:
|
||
|
||
ServerSession -- pointer to connection context.
|
||
|
||
DatabaseID -- Identifies the databse for which the deltas are requested.
|
||
For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
|
||
databases may be defined later.
|
||
|
||
RestartState -- Specifies whether this is a restart of the full sync and how
|
||
to interpret SyncContext. This value should be NormalState unless this
|
||
is the restart of a full sync.
|
||
|
||
However, if the caller is continuing a full sync after a reboot,
|
||
the following values are used:
|
||
|
||
GroupState - SyncContext is the global group rid to continue with.
|
||
UserState - SyncContext is the user rid to continue with
|
||
GroupMemberState - SyncContext is the global group rid to continue with
|
||
AliasState - SyncContext should be zero to restart at first alias
|
||
AliasMemberState - SyncContext should be zero to restart at first alias
|
||
|
||
One cannot continue the LSA database in this way.
|
||
|
||
SyncContext -- Specifies context needed to continue the
|
||
operation. The caller should treat this as an opaque
|
||
value. The value should be zero before the first call.
|
||
|
||
DeltaArray -- Pointer to a buffer where the information
|
||
is placed. The information returned is an array of
|
||
NETLOGON_DELTA_ENUM structures.
|
||
|
||
PreferredMaximumLength - Preferred maximum length of returned
|
||
data (in 8-bit bytes). This is not a hard upper limit, but
|
||
serves as a guide to the server. Due to data conversion
|
||
between systems with different natural data sizes, the actual
|
||
amount of data returned may be greater than this value.
|
||
|
||
SessionInfo - Information shared between PDC and BDC.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_MORE_ENTRIES -- The replicant should call again to get more
|
||
data.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
PSAM_SYNC_CONTEXT SamDBContext;
|
||
|
||
PDB_INFO DBInfo;
|
||
|
||
DWORD BufferConsumed = 0;
|
||
DWORD BufferSize;
|
||
|
||
DBInfo = &NlGlobalDBInfoArray[DatabaseID];
|
||
|
||
|
||
//
|
||
// Allocate memory for delta buffer.
|
||
//
|
||
|
||
DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
if( DeltaArray->Deltas == NULL ) {
|
||
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlSyncSamDatabase: Can't allocate %d bytes\n",
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ));
|
||
|
||
return( STATUS_NO_MEMORY );
|
||
}
|
||
|
||
|
||
//
|
||
// wipe off the buffer so that cleanup will not be in fault.
|
||
//
|
||
|
||
RtlZeroMemory( DeltaArray->Deltas,
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
|
||
//
|
||
// If this is the first call or an explicit restart call,
|
||
// allocate and initialize the sync context.
|
||
//
|
||
|
||
if ( *SyncContext == 0 || RestartState != NormalState ) {
|
||
|
||
//
|
||
// If there already is a sync context,
|
||
// delete it.
|
||
//
|
||
|
||
if ( ServerSession->SsSync != NULL ) {
|
||
CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
|
||
} else {
|
||
|
||
ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) );
|
||
if ( ServerSession->SsSync == NULL ) {
|
||
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Initialize all the fields in the newly allocated resume handle
|
||
// to indicate that SAM has never yet been called.
|
||
//
|
||
|
||
INIT_SYNC_CONTEXT( ServerSession->SsSync, SamDBContextType );
|
||
|
||
SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
|
||
SamDBContext->SyncSerial = 1;
|
||
|
||
//
|
||
// Compute the continuation state based on the input parameters
|
||
//
|
||
|
||
switch ( RestartState ) {
|
||
case NormalState:
|
||
|
||
//
|
||
// Put the description of the Domain at the front of the buffer for the
|
||
// first call.
|
||
//
|
||
|
||
Status = NlPackSamDomain( &((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
(DeltaArray->CountReturned)++;
|
||
BufferConsumed += BufferSize;
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext->SyncState = GroupState;
|
||
SamDBContext->SamEnumHandle = 0;
|
||
break;
|
||
|
||
case AliasState:
|
||
case AliasMemberState:
|
||
if ( *SyncContext != 0 ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlSyncSamDatabase: Cannot restart alias enumeration.\n" ));
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
/* Drop Through */
|
||
|
||
case GroupState:
|
||
case UserState:
|
||
case GroupMemberState:
|
||
SamDBContext->SyncState = RestartState;
|
||
SamDBContext->SamEnumHandle = *SyncContext;
|
||
break;
|
||
|
||
default:
|
||
NlPrint(( NL_CRITICAL,
|
||
"NlSyncSamDatabase: Invalid RestartState passed %ld.\n",
|
||
RestartState ));
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
// NlAssert( ServerSession->SsSync != NULL);
|
||
|
||
if( ServerSession->SsSync == NULL) {
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlAssert( ServerSession->SsSync->DBContextType ==
|
||
SamDBContextType);
|
||
|
||
if( ServerSession->SsSync->DBContextType !=
|
||
SamDBContextType ) {
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
|
||
|
||
NlAssert( SamDBContext->SyncSerial == *SyncContext );
|
||
|
||
if( SamDBContext->SyncSerial != *SyncContext ) {
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext->SyncSerial++;
|
||
}
|
||
|
||
//
|
||
// Loop for each entry placed in the output buffer
|
||
//
|
||
// Each iteration of the loop below puts one more entry into the array
|
||
// returned to the caller. The algorithm is split into 2 parts. The
|
||
// first part checks to see if we need to retrieve more information from
|
||
// SAM and gets the description of several users or group from SAM in a
|
||
// single call. The second part puts a single entry into the buffer
|
||
// returned to the caller.
|
||
//
|
||
|
||
while ( SamDBContext->SyncState != SamDoneState ) {
|
||
|
||
//
|
||
// If we've filled out pre-allocated array,
|
||
// return now.
|
||
//
|
||
if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Get more information from SAM
|
||
//
|
||
// Handle when we've not yet called SAM or we've already consumed
|
||
// all of the information returned on a previous call to SAM.
|
||
//
|
||
// This is a 'while' rather than an 'if' to handle the case
|
||
// where SAM returns zero entries.
|
||
//
|
||
|
||
while ( SamDBContext->Index >= SamDBContext->Count ) {
|
||
|
||
//
|
||
// Free any previous buffer returned from SAM.
|
||
//
|
||
|
||
if ( ServerSession->SsSync != NULL ) {
|
||
CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
|
||
}
|
||
|
||
//
|
||
// If we've already gotten everything from SAM,
|
||
// we've finished all of the groups,
|
||
//
|
||
// If we've just done the groups,
|
||
// go on to do the users.
|
||
//
|
||
// If we've just done the users,
|
||
// go on to do the group memberships.
|
||
//
|
||
// If we've just done the group memberships,
|
||
// go on to do the alias.
|
||
//
|
||
// If we've just done the alias,
|
||
// go on to do the alias membership.
|
||
//
|
||
// If we've just done the alias memberships,
|
||
// we're all done.
|
||
//
|
||
|
||
if ( SamDBContext->SamAllDone ) {
|
||
|
||
SamDBContext->SamEnumHandle = 0;
|
||
SamDBContext->Index = 0;
|
||
SamDBContext->Count = 0;
|
||
SamDBContext->SamAllDone = FALSE;
|
||
|
||
if (SamDBContext->SyncState == GroupState ) {
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncSamDatabase: packing user records.\n"));
|
||
|
||
SamDBContext->SyncState = UserState;
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncSamDatabase: "
|
||
"packing groupmember records.\n"));
|
||
|
||
SamDBContext->SyncState = GroupMemberState;
|
||
} else if (SamDBContext->SyncState == GroupMemberState ){
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncSamDatabase: packing alias records.\n"));
|
||
|
||
SamDBContext->SyncState = AliasState;
|
||
} else if (SamDBContext->SyncState == AliasState ){
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncSamDatabase: "
|
||
" packing aliasmember records.\n"));
|
||
|
||
SamDBContext->SyncState = AliasMemberState ;
|
||
} else if (SamDBContext->SyncState == AliasMemberState ){
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncSamDatabase: packing done.\n"));
|
||
|
||
SamDBContext->SyncState = SamDoneState;
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Do the actual enumeration
|
||
//
|
||
|
||
if (SamDBContext->SyncState == GroupState ||
|
||
SamDBContext->SyncState == GroupMemberState ) {
|
||
|
||
Status = SamIEnumerateAccountRids(
|
||
DBInfo->DBHandle,
|
||
SAM_GLOBAL_GROUP_ACCOUNT,
|
||
SamDBContext->SamEnumHandle, // Return RIDs greater than this
|
||
SAM_SYNC_PREF_MAX,
|
||
&SamDBContext->Count,
|
||
&SamDBContext->RidArray );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
SamDBContext->RidArray = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( SamDBContext->Count != 0 ) {
|
||
SamDBContext->SamEnumHandle =
|
||
SamDBContext->RidArray[SamDBContext->Count-1];
|
||
}
|
||
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
|
||
|
||
Status = SamIEnumerateAccountRids(
|
||
DBInfo->DBHandle,
|
||
SAM_USER_ACCOUNT,
|
||
SamDBContext->SamEnumHandle, // Return RIDs greater than this
|
||
SAM_SYNC_PREF_MAX,
|
||
&SamDBContext->Count,
|
||
&SamDBContext->RidArray );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
SamDBContext->RidArray = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( SamDBContext->Count != 0 ) {
|
||
SamDBContext->SamEnumHandle =
|
||
SamDBContext->RidArray[SamDBContext->Count-1];
|
||
}
|
||
|
||
} else if (SamDBContext->SyncState == AliasState ||
|
||
SamDBContext->SyncState == AliasMemberState ) {
|
||
|
||
Status = SamrEnumerateAliasesInDomain(
|
||
DBInfo->DBHandle,
|
||
&SamDBContext->SamEnumHandle,
|
||
&SamDBContext->SamEnum,
|
||
SAM_SYNC_PREF_MAX,
|
||
&SamDBContext->Count );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
SamDBContext->SamEnum = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlAssert( SamDBContext->Count ==
|
||
SamDBContext->SamEnum->EntriesRead );
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// If SAM says there is more information,
|
||
// just ensure he returned something to us on this call.
|
||
//
|
||
|
||
if ( Status == STATUS_MORE_ENTRIES ) {
|
||
// NlAssert( SamDBContext->Count != 0 );
|
||
|
||
//
|
||
// If SAM says he's returned all of the information,
|
||
// remember not to ask SAM for more.
|
||
//
|
||
|
||
} else {
|
||
SamDBContext->SamAllDone = TRUE;
|
||
}
|
||
|
||
SamDBContext->Index = 0;
|
||
}
|
||
|
||
//
|
||
// Place this entry into the return buffer.
|
||
//
|
||
|
||
if ( SamDBContext->Count > 0 ) {
|
||
|
||
if (SamDBContext->SyncState == GroupState ) {
|
||
Status = NlPackSamGroup(
|
||
SamDBContext->RidArray[SamDBContext->Index],
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
Status = NlPackSamUser(
|
||
SamDBContext->RidArray[SamDBContext->Index],
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
} else if (SamDBContext->SyncState == GroupMemberState ) {
|
||
Status = NlPackSamGroupMember(
|
||
SamDBContext->RidArray[SamDBContext->Index],
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
} else if (SamDBContext->SyncState == AliasState ) {
|
||
Status = NlPackSamAlias(
|
||
SamDBContext->SamEnum->
|
||
Buffer[SamDBContext->Index].RelativeId,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
} else if (SamDBContext->SyncState == AliasMemberState ) {
|
||
Status = NlPackSamAliasMember(
|
||
SamDBContext->SamEnum->
|
||
Buffer[SamDBContext->Index].RelativeId,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
}
|
||
|
||
//
|
||
// If there was a real error or this group didn't fit,
|
||
// return to the caller.
|
||
//
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext->Index ++;
|
||
(DeltaArray->CountReturned)++;
|
||
BufferConsumed +=
|
||
(sizeof(NETLOGON_DELTA_ENUM) + BufferSize);
|
||
|
||
if( BufferConsumed >= PreferredMaximumLength) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If we're debugging replication, return only one change to the caller.
|
||
//
|
||
#if NETLOGONDBG
|
||
if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // NETLOGONDBG
|
||
|
||
//
|
||
// if the service is going down, stop packing records and
|
||
// return to the caller.
|
||
//
|
||
// Don't alarm the caller with the status code. He'll find out
|
||
// on the next call that we're no longer here.
|
||
//
|
||
|
||
if( NlGlobalTerminate ) {
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseSync is asked to return "
|
||
"when the service is going down.\n"));
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Set the return parameters to the proper values.
|
||
//
|
||
|
||
if ( NT_SUCCESS( Status ) ) {
|
||
*SyncContext = SamDBContext->SyncSerial;
|
||
|
||
} else {
|
||
if ( DeltaArray->Deltas != NULL ) {
|
||
NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
|
||
DeltaArray->Deltas = NULL;
|
||
}
|
||
DeltaArray->CountReturned = 0;
|
||
*SyncContext = 0;
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlSyncSamDatabase: returning unsuccessful (%lx).\n",
|
||
Status));
|
||
|
||
}
|
||
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlSyncLsaDatabase(
|
||
IN PSERVER_SESSION ServerSession,
|
||
IN OUT PULONG SyncContext,
|
||
IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray,
|
||
IN DWORD PreferredMaximumLength,
|
||
IN PSESSION_INFO SessionInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is a real worker for the NetrDatabaseSync function and
|
||
retrieves the LSA database in the delta buffer.
|
||
|
||
This function uses the find-first find-next model to return portions
|
||
of the SAM database at a time. The SAM database is returned as a
|
||
list of deltas like those returned from I_NetDatabaseDeltas. The
|
||
following deltas are returned for each domain:
|
||
|
||
* One AddOrChangeLsaPolicy delta, followed by,
|
||
|
||
* One AddOrChangeLsaAccounts delta for each lsa account, followed by,
|
||
|
||
* One AddOrChangeLsaTDomain delta for each trusted domain, followed by,
|
||
|
||
* One AddOrChangeLsaSecret delta for each lsa secret.
|
||
|
||
|
||
Arguments:
|
||
|
||
ServerSession -- pointer to connection context.
|
||
|
||
SyncContext -- Specifies context needed to continue the
|
||
operation. The caller should treat this as an opaque
|
||
value. The value should be zero before the first call.
|
||
|
||
DeltaArray -- Pointer to a buffer where the information
|
||
is placed. The information returned is an array of
|
||
NETLOGON_DELTA_ENUM structures.
|
||
|
||
PreferredMaximumLength - Preferred maximum length of returned
|
||
data (in 8-bit bytes). This is not a hard upper limit, but
|
||
serves as a guide to the server. Due to data conversion
|
||
between systems with different natural data sizes, the actual
|
||
amount of data returned may be greater than this value.
|
||
|
||
SessionInfo - Information shared between PDC and BDC.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_MORE_ENTRIES -- The replicant should call again to get more
|
||
data.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
PLSA_SYNC_CONTEXT LsaDBContext;
|
||
|
||
PDB_INFO DBInfo;
|
||
|
||
DWORD BufferConsumed = 0;
|
||
DWORD BufferSize;
|
||
BOOL IgnoreDeltaObject = FALSE;
|
||
|
||
DBInfo = &NlGlobalDBInfoArray[LSA_DB];
|
||
|
||
|
||
//
|
||
// Allocate memory for delta buffer.
|
||
//
|
||
|
||
DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
if( DeltaArray->Deltas == NULL ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlSyncLsaDatabase: Can't allocate %d bytes\n",
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ));
|
||
|
||
return( STATUS_NO_MEMORY );
|
||
}
|
||
|
||
|
||
//
|
||
// wipe off the buffer so that cleanup will not be in fault.
|
||
//
|
||
|
||
RtlZeroMemory( DeltaArray->Deltas,
|
||
MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
//
|
||
// If this is the first call, allocate and initialize the sync context.
|
||
//
|
||
|
||
if ( *SyncContext == 0 ) {
|
||
|
||
//
|
||
// If there already is a sync context,
|
||
// delete it.
|
||
//
|
||
|
||
if ( ServerSession->SsSync != NULL ) {
|
||
CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
|
||
} else {
|
||
|
||
ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) );
|
||
if ( ServerSession->SsSync == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Initialize all the fields in the newly allocated resume handle
|
||
// to indicate that SAM has never yet been called.
|
||
//
|
||
|
||
INIT_SYNC_CONTEXT( ServerSession->SsSync, LsaDBContextType );
|
||
|
||
LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa);
|
||
|
||
LsaDBContext->SyncState = AccountState;
|
||
LsaDBContext->SyncSerial = 1;
|
||
LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncLsaDatabase: "
|
||
"Starting full sync, packing lsa account records\n"));
|
||
|
||
//
|
||
// Put the description of the Policy at the front of the buffer for the
|
||
// first call.
|
||
//
|
||
|
||
Status = NlPackLsaPolicy(
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
(DeltaArray->CountReturned)++;
|
||
BufferConsumed += BufferSize;
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
} else {
|
||
|
||
if( ServerSession->SsSync == NULL ) {
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlAssert( ServerSession->SsSync->DBContextType == LsaDBContextType);
|
||
|
||
if( ServerSession->SsSync->DBContextType != LsaDBContextType) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa);
|
||
|
||
NlAssert( LsaDBContext->SyncSerial == *SyncContext );
|
||
|
||
if( LsaDBContext->SyncSerial != *SyncContext ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
LsaDBContext->SyncSerial++;
|
||
}
|
||
|
||
//
|
||
// Loop for each entry placed in the output buffer
|
||
//
|
||
// Each iteration of the loop below puts one more entry into the array
|
||
// returned to the caller. The algorithm is split into 2 parts.
|
||
// The first part checks to see if we need to retrieve more information
|
||
// from LSA and gets the description of several accounts, TDomain or
|
||
// Secret from LSA in a single call. The second part puts a single
|
||
// entry into the buffer returned to the caller.
|
||
//
|
||
|
||
while ( LsaDBContext->SyncState != LsaDoneState ) {
|
||
|
||
//
|
||
// If we've filled out pre-allocated array,
|
||
// return now.
|
||
//
|
||
if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get more information from LSA
|
||
//
|
||
// Handle when we've not yet called LSA or we've already consumed
|
||
// all of the information returned on a previous call to SAM.
|
||
//
|
||
// This is a 'while' rather than an 'if' to handle the case
|
||
// where LSA returns zero entries.
|
||
//
|
||
|
||
while ( LsaDBContext->Index >= LsaDBContext->Count ) {
|
||
|
||
//
|
||
// Free any previous buffer returned from SAM.
|
||
//
|
||
|
||
if ( ServerSession->SsSync != NULL ) {
|
||
CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
|
||
}
|
||
|
||
|
||
//
|
||
// If we've already gotten everything from LSA,
|
||
// we've finished all of the accounts,
|
||
//
|
||
// If we've just done the accounts,
|
||
// go on to do the TDomains.
|
||
//
|
||
// If we've just done the TDomains,
|
||
// go on to do the Secrets
|
||
//
|
||
// If we've just done the Secret,
|
||
// we're all done.
|
||
//
|
||
|
||
if ( LsaDBContext->LsaAllDone ) {
|
||
|
||
LsaDBContext->LsaEnumHandle = 0;
|
||
LsaDBContext->Index = 0;
|
||
LsaDBContext->Count = 0;
|
||
LsaDBContext->LsaAllDone = FALSE;
|
||
|
||
if (LsaDBContext->SyncState == AccountState ) {
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncLsaDatabase: "
|
||
" packing TDomain records.\n"));
|
||
|
||
LsaDBContext->SyncState = TDomainState;
|
||
} else if (LsaDBContext->SyncState == TDomainState ) {
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncLsaDatabase: packing secret records.\n"));
|
||
|
||
LsaDBContext->SyncState = SecretState;
|
||
} else if (LsaDBContext->SyncState == SecretState ) {
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NlSyncLsaDatabase: packing done.\n"));
|
||
|
||
LsaDBContext->SyncState = LsaDoneState;
|
||
LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
if (LsaDBContext->SyncState == AccountState ) {
|
||
|
||
LsaDBContext->LsaEnumBufferType = AccountEnumBuffer;
|
||
|
||
Status = LsarEnumerateAccounts(
|
||
DBInfo->DBHandle,
|
||
&LsaDBContext->LsaEnumHandle,
|
||
&LsaDBContext->LsaEnum.Account,
|
||
SAM_SYNC_PREF_MAX);
|
||
|
||
if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
|
||
LsaDBContext->Count =
|
||
LsaDBContext->LsaEnum.Account.EntriesRead;
|
||
}
|
||
|
||
} else if (LsaDBContext->SyncState == TDomainState ) {
|
||
|
||
LsaDBContext->LsaEnumBufferType = TDomainEnumBuffer;
|
||
|
||
Status = LsarEnumerateTrustedDomains(
|
||
DBInfo->DBHandle,
|
||
&LsaDBContext->LsaEnumHandle,
|
||
&LsaDBContext->LsaEnum.TDomain,
|
||
SAM_SYNC_PREF_MAX);
|
||
|
||
if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
|
||
LsaDBContext->Count =
|
||
LsaDBContext->LsaEnum.TDomain.EntriesRead;
|
||
}
|
||
|
||
} else if (LsaDBContext->SyncState == SecretState ) {
|
||
|
||
LsaDBContext->LsaEnumBufferType = SecretEnumBuffer;
|
||
|
||
Status = LsaIEnumerateSecrets(
|
||
DBInfo->DBHandle,
|
||
&LsaDBContext->LsaEnumHandle,
|
||
&LsaDBContext->LsaEnum.Secret,
|
||
SAM_SYNC_PREF_MAX,
|
||
&LsaDBContext->Count );
|
||
|
||
}
|
||
|
||
//
|
||
// If LSA says there is more information,
|
||
// just ensure he returned something to us on this call.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
|
||
NlAssert( LsaDBContext->Count != 0 );
|
||
|
||
//
|
||
// If LSA says he's returned all of the information,
|
||
// remember not to ask it for more.
|
||
//
|
||
|
||
} else if ( Status == STATUS_NO_MORE_ENTRIES ) {
|
||
LsaDBContext->LsaAllDone = TRUE;
|
||
LsaDBContext->Count = 0;
|
||
|
||
//
|
||
// Any other error is fatal
|
||
//
|
||
|
||
} else {
|
||
|
||
LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer;
|
||
LsaDBContext->Count = 0;
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
LsaDBContext->Index = 0;
|
||
}
|
||
|
||
|
||
//
|
||
// Place this entry into the return buffer.
|
||
//
|
||
|
||
if ( LsaDBContext->Count > 0 ) {
|
||
|
||
if (LsaDBContext->SyncState == AccountState ) {
|
||
|
||
Status = NlPackLsaAccount(
|
||
LsaDBContext->LsaEnum.Account.
|
||
Information[LsaDBContext->Index].Sid,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
|
||
Status = STATUS_SUCCESS;
|
||
IgnoreDeltaObject = TRUE;
|
||
BufferSize = 0;
|
||
}
|
||
|
||
} else if (LsaDBContext->SyncState == TDomainState ) {
|
||
|
||
Status = NlPackLsaTDomain(
|
||
LsaDBContext->LsaEnum.TDomain.
|
||
Information[LsaDBContext->Index].Sid,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
|
||
} else if (LsaDBContext->SyncState == SecretState ) {
|
||
|
||
PUNICODE_STRING SecretName;
|
||
|
||
SecretName =
|
||
&((PUNICODE_STRING)LsaDBContext->LsaEnum.Secret)
|
||
[LsaDBContext->Index];
|
||
|
||
//
|
||
// ignore local secret objects.
|
||
//
|
||
|
||
if( (SecretName->Length / sizeof(WCHAR) >
|
||
LSA_GLOBAL_SECRET_PREFIX_LENGTH ) &&
|
||
(_wcsnicmp( SecretName->Buffer,
|
||
LSA_GLOBAL_SECRET_PREFIX,
|
||
LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0)) {
|
||
|
||
Status = NlPackLsaSecret(
|
||
SecretName,
|
||
&((DeltaArray->Deltas)[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize,
|
||
SessionInfo );
|
||
|
||
} else {
|
||
Status = STATUS_SUCCESS;
|
||
IgnoreDeltaObject = TRUE;
|
||
BufferSize = 0;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// If there was a real error or this group didn't fit,
|
||
// return to the caller.
|
||
//
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
LsaDBContext->Index ++;
|
||
|
||
//
|
||
// if this object is ignored, don't modify return values.
|
||
//
|
||
|
||
if ( !IgnoreDeltaObject ) {
|
||
|
||
(DeltaArray->CountReturned)++;
|
||
BufferConsumed +=
|
||
(sizeof(NETLOGON_DELTA_ENUM) + BufferSize);
|
||
|
||
if( BufferConsumed >= PreferredMaximumLength) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If we're debugging replication, return only one change to the caller.
|
||
//
|
||
#if NETLOGONDBG
|
||
if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
#endif // NETLOGONDBG
|
||
} else {
|
||
IgnoreDeltaObject = FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Set the return parameters to the proper values.
|
||
//
|
||
|
||
if ( NT_SUCCESS( Status ) ) {
|
||
*SyncContext = LsaDBContext->SyncSerial;
|
||
|
||
} else {
|
||
if ( DeltaArray->Deltas != NULL ) {
|
||
NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
|
||
DeltaArray->Deltas = NULL;
|
||
}
|
||
DeltaArray->CountReturned = 0;
|
||
*SyncContext = 0;
|
||
}
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NlSyncLsaDatabase: returning unsuccessful (%lx).\n",
|
||
Status));
|
||
}
|
||
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrDatabaseSync (
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD DatabaseID,
|
||
IN OUT PULONG SyncContext,
|
||
OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
|
||
IN DWORD PreferredMaximumLength
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
NT 3.1 version of NetrDatabaseSync2. Don't pass the RestartState parameter.
|
||
Sync Context is all that is needed to identify the state.
|
||
|
||
Arguments:
|
||
|
||
Same as NetrDatabaseSync2 (with the exception mentioned above).
|
||
|
||
Return Value:
|
||
|
||
Save as NetrDatabaseSync2.
|
||
|
||
--*/
|
||
{
|
||
return NetrDatabaseSync2(
|
||
PrimaryName,
|
||
ComputerName,
|
||
Authenticator,
|
||
ReturnAuthenticator,
|
||
DatabaseID,
|
||
NormalState,
|
||
SyncContext,
|
||
DeltaArrayRet,
|
||
PreferredMaximumLength );
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrDatabaseSync2 (
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD DatabaseID,
|
||
IN SYNC_STATE RestartState,
|
||
IN OUT PULONG SyncContext,
|
||
OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet,
|
||
IN DWORD PreferredMaximumLength
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by an NT BDC to request
|
||
the entire SAM/LSA database from a PDC in NTLANMAN-style format.
|
||
This function can only be called by a server which has previously
|
||
authenticated with the PDC by calling I_NetServerAuthenticate. This
|
||
function uses RPC to contact the Netlogon service on the PDC.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Name of the PDC to retrieve the deltas from.
|
||
|
||
ComputerName -- Name of the BDC or member server making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
DatabaseID -- Identifies the databse for which the deltas are requested.
|
||
For SAM database the ID is 0, for Builtin Domain the ID is 1. Other
|
||
databases may be defined later.
|
||
|
||
RestartState -- Specifies whether this is a restart of the full sync and how
|
||
to interpret SyncContext. This value should be NormalState unless this
|
||
is the restart of a full sync.
|
||
|
||
However, if the caller is continuing a full sync after a reboot,
|
||
the following values are used:
|
||
|
||
GroupState - SyncContext is the global group rid to continue with.
|
||
UserState - SyncContext is the user rid to continue with
|
||
GroupMemberState - SyncContext is the global group rid to continue with
|
||
AliasState - SyncContext should be zero to restart at first alias
|
||
AliasMemberState - SyncContext should be zero to restart at first alias
|
||
|
||
One cannot continue the LSA database in this way.
|
||
|
||
SyncContext -- Specifies context needed to continue the
|
||
operation. The caller should treat this as an opaque
|
||
value. The value should be zero before the first call.
|
||
|
||
DeltaArray -- Receives a pointer to a buffer where the information
|
||
is placed. The information returned is an array of
|
||
NETLOGON_DELTA_ENUM structures.
|
||
|
||
PreferredMaximumLength - Preferred maximum length of returned
|
||
data (in 8-bit bytes). This is not a hard upper limit, but
|
||
serves as a guide to the server. Due to data conversion
|
||
between systems with different natural data sizes, the actual
|
||
amount of data returned may be greater than this value.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_MORE_ENTRIES -- The replicant should call again to get more
|
||
data.
|
||
|
||
STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
|
||
the PDC.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
|
||
|
||
SESSION_INFO SessionInfo;
|
||
PDB_INFO DBInfo;
|
||
|
||
DEFSSIAPITIMER;
|
||
|
||
INITSSIAPITIMER;
|
||
STARTSSIAPITIMER;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync2: called from %ws. This machine doesn't support replication.\n",
|
||
ComputerName ));
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Gross hack because of RPC implementation.
|
||
//
|
||
// Rpc executes API calls in an I/O completion port thread. If this thread
|
||
// goes CPU bound, then no other RPC will be allowed to start. Even worse,
|
||
// there is only one outstanding listen, so the 'second' coming RPC call
|
||
// gets RPC_S_SERVER_TOO_BUSY.
|
||
//
|
||
// By sleeping here (even for a short period) the I/O completion port releases
|
||
// another thread since it thinks this thread went I/O bound.
|
||
//
|
||
// We've seen this thread go CPU bound doing a full sync of a database with
|
||
// 1000's of LSA account objects.
|
||
//
|
||
|
||
RpcServerYield();
|
||
|
||
|
||
//
|
||
// If the DS is recovering from a backup,
|
||
// avoid changing the DS.
|
||
//
|
||
|
||
if ( NlGlobalDsPaused ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync2: DsIsPaused.\n"));
|
||
// Don't return a new status code since NT 4 DC would do a full sync
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
if ( DatabaseID >= NUM_DBS ) {
|
||
return STATUS_INVALID_LEVEL;
|
||
}
|
||
|
||
DBInfo = &NlGlobalDBInfoArray[DatabaseID];
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
*DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
|
||
MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
|
||
|
||
if( DeltaArray == NULL ) {
|
||
return(STATUS_NO_MEMORY);
|
||
}
|
||
|
||
DeltaArray->Deltas = NULL;
|
||
DeltaArray->CountReturned = 0;
|
||
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SYNC, DomainInfo,
|
||
"NetrDatabaseSync: " FORMAT_LPWSTR " full sync called by " FORMAT_LPWSTR " State: %ld Context: 0x%lx.\n",
|
||
DBInfo->DBName,
|
||
ComputerName,
|
||
RestartState,
|
||
*SyncContext ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !IsPrimaryDomain( DomainInfo )) {
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync: No server session.\n"));
|
||
goto CleanupNoEventlog;
|
||
}
|
||
|
||
//
|
||
// Allow this call only on ServerSecureChannel.
|
||
//
|
||
|
||
if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
|
||
|
||
//
|
||
// If the only preblem is that this BDC hasn't authenticated,
|
||
// silently ask it to authenticate.
|
||
//
|
||
if ( ServerSession->SsSecureChannelType == NullSecureChannel ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync: No authenticated server session.\n"));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto CleanupNoEventlog;
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync: SecureChannel type isn't BDC. %ld\n",
|
||
ServerSession->SsSecureChannelType ));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseSync: authentication failed.\n" ));
|
||
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Prevent entry from being deleted, but drop the global lock.
|
||
//
|
||
// Beware of server with two concurrent calls outstanding
|
||
// (must have rebooted.)
|
||
//
|
||
|
||
if (ServerSession->SsFlags & SS_LOCKED ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseSync: Concurrent call detected.\n" ));
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
ServerSession->SsFlags |= SS_LOCKED;
|
||
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
if( DatabaseID == LSA_DB ) {
|
||
|
||
NlAssert( RestartState == NormalState );
|
||
|
||
Status = NlSyncLsaDatabase( ServerSession,
|
||
SyncContext,
|
||
DeltaArray,
|
||
PreferredMaximumLength,
|
||
&SessionInfo );
|
||
} else {
|
||
|
||
Status = NlSyncSamDatabase( ServerSession,
|
||
DatabaseID,
|
||
RestartState,
|
||
SyncContext,
|
||
DeltaArray,
|
||
PreferredMaximumLength,
|
||
&SessionInfo );
|
||
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
LPWSTR MsgStrings[2];
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
MsgStrings[1] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonFullSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
|
||
} else {
|
||
|
||
LPWSTR MsgStrings[2];
|
||
WCHAR CountBuffer[20]; // random size
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
|
||
ultow( DeltaArray->CountReturned, CountBuffer, 10);
|
||
MsgStrings[1] = CountBuffer;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonFullSyncCallSuccess,
|
||
EVENTLOG_INFORMATION_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
2 | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
|
||
}
|
||
|
||
//
|
||
// Unlock the server session entry if we've locked it.
|
||
//
|
||
CleanupNoEventlog:
|
||
|
||
if ( ServerSession != NULL ) {
|
||
|
||
//
|
||
// If we're done, free up the context structure,
|
||
//
|
||
|
||
if ( Status != STATUS_MORE_ENTRIES && ServerSession->SsSync != NULL ) {
|
||
CLEAN_SYNC_CONTEXT( ServerSession->SsSync );
|
||
|
||
NetpMemoryFree( ServerSession->SsSync );
|
||
ServerSession->SsSync = NULL;
|
||
}
|
||
|
||
//
|
||
// If we are successfully returning these deltas to the BDC,
|
||
// update our tables to reflect the changes.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
NlPrimaryAnnouncementFinish( ServerSession,
|
||
DatabaseID,
|
||
NULL );
|
||
|
||
}
|
||
|
||
NlUnlockServerSession( ServerSession );
|
||
}
|
||
|
||
//
|
||
// If the BDC called us just as SAM was shutting down,
|
||
// map the status to prevent the BDC from full syncing.
|
||
//
|
||
|
||
if ( Status == STATUS_INVALID_SERVER_STATE ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseSync: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR " Context: 0x%lx.\n",
|
||
DBInfo->DBName,
|
||
Status,
|
||
ComputerName,
|
||
*SyncContext ));
|
||
|
||
STOPSSIAPITIMER;
|
||
|
||
NlPrint((NL_REPL_TIME,"NetrDatabaseSync Time:\n"));
|
||
PRINTSSIAPITIMER;
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrDatabaseRedo(
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN LPBYTE OrigChangeLogEntry,
|
||
IN DWORD ChangeLogEntrySize,
|
||
OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a SAM BDC to request infomation about a single
|
||
account. This function can only be called by a server which has previously
|
||
authenticated with the PDC by calling I_NetServerAuthenticate. This
|
||
function uses RPC to contact the Netlogon service on the PDC.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Name of the PDC to retrieve the delta from.
|
||
|
||
ComputerName -- Name of the BDC making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
ChangeLogEntry -- A description of the account to be queried.
|
||
|
||
ChangeLogEntrySize -- Size (in bytes) of the ChangeLogEntry.
|
||
|
||
DeltaArrayRet -- Receives a pointer to a buffer where the information is
|
||
placed. The information returned is an array of
|
||
NETLOGON_DELTA_ENUM structures.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED -- The replicant should re-authenticate with
|
||
the PDC.
|
||
|
||
--*/
|
||
{
|
||
PCHANGELOG_ENTRY ChangeLogEntry;
|
||
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
|
||
LPWSTR MsgStrings[2];
|
||
|
||
DWORD BufferSize;
|
||
|
||
PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL;
|
||
SESSION_INFO SessionInfo;
|
||
|
||
DEFSSIAPITIMER;
|
||
|
||
INITSSIAPITIMER;
|
||
STARTSSIAPITIMER;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseRedo: called from %ws. This machine doesn't support replication.\n",
|
||
ComputerName ));
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If the DS is recovering from a backup,
|
||
// avoid changing the DS.
|
||
//
|
||
|
||
if ( NlGlobalDsPaused ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseRedo: DsIsPaused.\n"));
|
||
// Don't return a new status code since NT 4 DC would do a full sync
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY) OrigChangeLogEntry;
|
||
if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize ) ||
|
||
ChangeLogEntry->DBIndex >= NUM_DBS ) {
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Find the domain this API was made to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
||
|
||
NlPrintDom((NL_SYNC, DomainInfo,
|
||
"NetrDatabaseRedo: " FORMAT_LPWSTR " redo sync called by " FORMAT_LPWSTR
|
||
" with this change log entry:\n",
|
||
NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
|
||
ComputerName ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !IsPrimaryDomain( DomainInfo )) {
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
#if NETLOGONDBG
|
||
PrintChangeLogEntry( ChangeLogEntry );
|
||
#endif // NETLOGONDBG
|
||
|
||
//
|
||
// The change log entry really represents an object and not an operation.
|
||
// Therefore, convert the delta type from whatever was passed to an
|
||
// "AddOrChange" operation. Then NlPackSingleDelta will return everything
|
||
// we know about the object.
|
||
//
|
||
|
||
ChangeLogEntry->DeltaType = (UCHAR)NlGlobalAddDeltaType[ChangeLogEntry->DeltaType];
|
||
|
||
*DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY)
|
||
MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) );
|
||
|
||
if( DeltaArray == NULL ) {
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
DeltaArray->CountReturned = 0;
|
||
DeltaArray->Deltas = NULL;
|
||
SessionInfo.NegotiatedFlags = 0;
|
||
|
||
|
||
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseRedo: No server session.\n"));
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
goto CleanupNoEventlog;
|
||
}
|
||
|
||
//
|
||
// Allow this call only on ServerSecureChannel.
|
||
//
|
||
|
||
if( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
|
||
|
||
//
|
||
// If the only preblem is that this BDC hasn't authenticated,
|
||
// silently ask it to authenticate.
|
||
//
|
||
if ( ServerSession->SsSecureChannelType == NullSecureChannel ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseRedo: No authenticated server session.\n"));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
// Don't log this event since it happens in nature after a reboot
|
||
// or after we scavenge the server session.
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto CleanupNoEventlog;
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseRedo: SecureChannel type isn't BDC. %ld\n",
|
||
ServerSession->SsSecureChannelType ));
|
||
ServerSession = NULL;
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseRedo: authentication failed.\n" ));
|
||
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Prevent entry from being deleted, but drop the global lock.
|
||
//
|
||
// Beware of server with two concurrent calls outstanding
|
||
// (must have rebooted.)
|
||
//
|
||
|
||
if (ServerSession->SsFlags & SS_LOCKED ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
NlPrint((NL_CRITICAL, "NetrDatabaseRedo: Concurrent call detected.\n" ));
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
ServerSession->SsFlags |= SS_LOCKED;
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
|
||
//
|
||
// Allocate memory for delta buffer.
|
||
//
|
||
|
||
DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate(
|
||
MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
if( DeltaArray->Deltas == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// wipe off the buffer so that cleanup will not be in fault.
|
||
//
|
||
|
||
RtlZeroMemory( DeltaArray->Deltas,
|
||
MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) );
|
||
|
||
|
||
//
|
||
// Put the data for the changelog entry into the user's buffer.
|
||
//
|
||
|
||
Status = NlPackSingleDelta( ChangeLogEntry,
|
||
DeltaArray,
|
||
&BufferSize,
|
||
&SessionInfo,
|
||
FALSE );
|
||
|
||
|
||
//
|
||
// If the only problem is that the object no longer exists,
|
||
// return a delta asking the BDC to delete the object.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) &&
|
||
IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) {
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseRedo: " FORMAT_LPWSTR " object no longer exists (0x%lx) "
|
||
FORMAT_LPWSTR "\n",
|
||
NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
|
||
Status,
|
||
ComputerName ));
|
||
|
||
//
|
||
// Convert the change log entry into an appropriate delete delta type and
|
||
// try again.
|
||
//
|
||
|
||
ChangeLogEntry->DeltaType = (UCHAR)NlGlobalDeleteDeltaType[ChangeLogEntry->DeltaType];
|
||
|
||
Status = NlPackSingleDelta( ChangeLogEntry,
|
||
DeltaArray,
|
||
&BufferSize,
|
||
&SessionInfo,
|
||
FALSE );
|
||
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
MsgStrings[1] = (LPWSTR) LongToPtr( Status );
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Log the successful replication only if deltas have been returned
|
||
// to the caller.
|
||
//
|
||
if ( DeltaArray->CountReturned != 0 ) {
|
||
LPWSTR MsgStrings[2];
|
||
WCHAR CountBuffer[20]; // random size
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
|
||
ultow( DeltaArray->CountReturned, CountBuffer, 10);
|
||
MsgStrings[1] = CountBuffer;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallSuccess,
|
||
EVENTLOG_INFORMATION_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
2 | NETP_ALLOW_DUPLICATE_EVENTS );
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Free up locally allocated resources.
|
||
//
|
||
|
||
CleanupNoEventlog:
|
||
|
||
//
|
||
// If we weren't successful,
|
||
// Don't return any deltas.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status)) {
|
||
if ( DeltaArray != NULL ) {
|
||
if ( DeltaArray->Deltas != NULL ) {
|
||
NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned );
|
||
DeltaArray->Deltas = NULL;
|
||
}
|
||
DeltaArray->CountReturned = 0;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Unlock the server session entry if we've locked it.
|
||
//
|
||
|
||
if ( ServerSession != NULL ) {
|
||
NlUnlockServerSession( ServerSession );
|
||
}
|
||
|
||
//
|
||
// If the BDC called us just as SAM was shutting down,
|
||
// map the status to prevent the BDC from full syncing.
|
||
//
|
||
|
||
if ( Status == STATUS_INVALID_SERVER_STATE ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseRedo: " FORMAT_LPWSTR " returning (0x%lx) to "
|
||
FORMAT_LPWSTR "\n",
|
||
NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
|
||
Status,
|
||
ComputerName ));
|
||
|
||
STOPSSIAPITIMER;
|
||
|
||
NlPrint((NL_REPL_TIME,"NetrDatabaseRedo Time:\n"));
|
||
PRINTSSIAPITIMER;
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return Status;
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NetrAccountDeltas (
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN PUAS_INFO_0 RecordId,
|
||
IN DWORD Count,
|
||
IN DWORD Level,
|
||
OUT LPBYTE Buffer,
|
||
IN DWORD BufferSize,
|
||
OUT PULONG CountReturned,
|
||
OUT PULONG TotalEntries,
|
||
OUT PUAS_INFO_0 NextRecordId
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a UAS BDC or UAS member server to request
|
||
UAS-style account change information. This function can only be
|
||
called by a server which has previously authenticated with the PDC by
|
||
calling I_NetServerAuthenticate.
|
||
|
||
This function is only called by the XACT server upon receipt of a
|
||
I_NetAccountDeltas XACT SMB from a UAS BDC or a UAS member server.
|
||
As such, many of the parameters are opaque since the XACT server
|
||
doesn't need to interpret any of that data. This function uses RPC
|
||
to contact the Netlogon service.
|
||
|
||
The LanMan 3.0 SSI Functional Specification describes the operation
|
||
of this function.
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Must be NULL to indicate this call is a local call
|
||
being made on behalf of a UAS server by the XACT server.
|
||
|
||
ComputerName -- Name of the BDC or member making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
RecordId -- Supplies an opaque buffer indicating the last record
|
||
received from a previous call to this function.
|
||
|
||
Count -- Supplies the number of Delta records requested.
|
||
|
||
Level -- Reserved. Must be zero.
|
||
|
||
Buffer -- Returns opaque data representing the information to be
|
||
returned.
|
||
|
||
BufferSize -- Size of buffer in bytes.
|
||
|
||
CountReturned -- Returns the number of records returned in buffer.
|
||
|
||
TotalEntries -- Returns the total number of records available.
|
||
|
||
NextRecordId -- Returns an opaque buffer identifying the last
|
||
record received by this function.
|
||
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
NlAssert(!"NetrAccountDeltas called");
|
||
UNREFERENCED_PARAMETER( PrimaryName );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( Authenticator );
|
||
UNREFERENCED_PARAMETER( ReturnAuthenticator );
|
||
UNREFERENCED_PARAMETER( RecordId );
|
||
UNREFERENCED_PARAMETER( Count );
|
||
UNREFERENCED_PARAMETER( Level );
|
||
UNREFERENCED_PARAMETER( Buffer );
|
||
UNREFERENCED_PARAMETER( BufferSize );
|
||
UNREFERENCED_PARAMETER( CountReturned );
|
||
UNREFERENCED_PARAMETER( TotalEntries );
|
||
UNREFERENCED_PARAMETER( NextRecordId );
|
||
|
||
return(STATUS_NOT_IMPLEMENTED);
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NetrAccountSync (
|
||
IN LPWSTR PrimaryName,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD Reference,
|
||
IN DWORD Level,
|
||
OUT LPBYTE Buffer,
|
||
IN DWORD BufferSize,
|
||
OUT PULONG CountReturned,
|
||
OUT PULONG TotalEntries,
|
||
OUT PULONG NextReference,
|
||
OUT PUAS_INFO_0 LastRecordId
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a UAS BDC or UAS member server to request
|
||
the entire user accounts database. This function can only be called
|
||
by a server which has previously authenticated with the PDC by
|
||
calling I_NetServerAuthenticate.
|
||
|
||
This function is only called by the XACT server upon receipt of a
|
||
I_NetAccountSync XACT SMB from a UAS BDC or a UAS member server. As
|
||
such, many of the parameters are opaque since the XACT server doesn't
|
||
need to interpret any of that data. This function uses RPC to
|
||
contact the Netlogon service.
|
||
|
||
The LanMan 3.0 SSI Functional Specification describes the operation
|
||
of this function.
|
||
|
||
"reference" and "next_reference" are treated as below.
|
||
|
||
1. "reference" should hold either 0 or value of "next_reference"
|
||
from previous call to this API.
|
||
2. Send the modals and ALL group records in the first call. The API
|
||
expects the buffer to be large enough to hold this info (worst
|
||
case size would be
|
||
MAXGROUP * (sizeof(struct group_info_1) + MAXCOMMENTSZ)
|
||
+ sizeof(struct user_modals_info_0)
|
||
which, for now, will be 256 * (26 + 49) + 16 = 19216 bytes
|
||
|
||
Arguments:
|
||
|
||
PrimaryName -- Must be NULL to indicate this call is a local call
|
||
being made on behalf of a UAS server by the XACT server.
|
||
|
||
ComputerName -- Name of the BDC or member making the call.
|
||
|
||
Authenticator -- supplied by the server.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the PDC.
|
||
|
||
Reference -- Supplies find-first find-next handle returned by the
|
||
previous call to this function or 0 if it is the first call.
|
||
|
||
Level -- Reserved. Must be zero.
|
||
|
||
Buffer -- Returns opaque data representing the information to be
|
||
returned.
|
||
|
||
BufferLen -- Length of buffer in bytes.
|
||
|
||
CountReturned -- Returns the number of records returned in buffer.
|
||
|
||
TotalEntries -- Returns the total number of records available.
|
||
|
||
NextReference -- Returns a find-first find-next handle to be
|
||
provided on the next call.
|
||
|
||
LastRecordId -- Returns an opaque buffer identifying the last
|
||
record received by this function.
|
||
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
|
||
{
|
||
NlAssert(!"NetrAccountDeltas called");
|
||
UNREFERENCED_PARAMETER( PrimaryName );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( Authenticator );
|
||
UNREFERENCED_PARAMETER( ReturnAuthenticator );
|
||
UNREFERENCED_PARAMETER( Reference );
|
||
UNREFERENCED_PARAMETER( Level );
|
||
UNREFERENCED_PARAMETER( Buffer );
|
||
UNREFERENCED_PARAMETER( BufferSize );
|
||
UNREFERENCED_PARAMETER( CountReturned );
|
||
UNREFERENCED_PARAMETER( TotalEntries );
|
||
UNREFERENCED_PARAMETER( NextReference );
|
||
UNREFERENCED_PARAMETER( LastRecordId );
|
||
|
||
return(STATUS_NOT_IMPLEMENTED);
|
||
}
|
||
|
||
NTSTATUS
|
||
NlGetTrustedSideInfo(
|
||
IN PCLIENT_SESSION ClientSession,
|
||
IN LPWSTR AccountName OPTIONAL,
|
||
IN NETLOGON_SECURE_CHANNEL_TYPE AccountType,
|
||
OUT PNT_OWF_PASSWORD NewOwfPassword,
|
||
OUT PNT_OWF_PASSWORD OldOwfPassword,
|
||
OUT PNL_GENERIC_RPC_DATA *TrustInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by a trusting side DC to get the new and old
|
||
passwords from the trusted side.
|
||
|
||
The caller must be the writer of the client session.
|
||
|
||
Arguments:
|
||
|
||
ClientSession - Identifies a session to the trusted side.
|
||
The caller must be the writer of this client session.
|
||
|
||
AccountName -- Name of the account to get the password for. If NULL,
|
||
the account name from the ClientSession is used.
|
||
|
||
AccountType -- The type of account being accessed. Ignored if
|
||
AccountName is NULL in which case the account type specified
|
||
in teh ClientSession is used.
|
||
|
||
NewOwfPassword -- Returns the new OWF password of the account.
|
||
|
||
OldOwfPassword -- Returns the old OWF password of the account.
|
||
|
||
TrustInfo -- Returns the trusted domain info
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NETLOGON_AUTHENTICATOR OurAuthenticator;
|
||
NETLOGON_AUTHENTICATOR ReturnAuthenticator;
|
||
SESSION_INFO SessionInfo;
|
||
BOOLEAN FirstTry = TRUE;
|
||
BOOLEAN OldServer = FALSE;
|
||
ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrNewPassword;
|
||
ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrOldPassword;
|
||
NETLOGON_CREDENTIAL CurrentAuthenticationSeed;
|
||
|
||
PNL_GENERIC_RPC_DATA LocalTrustInfo = NULL;
|
||
|
||
//
|
||
// If the server supports neither the new get_password_and_attributes API
|
||
// nor the old get_passwords API, there is nothing for us to do here
|
||
//
|
||
|
||
if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) &&
|
||
(ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_MONITOR) ) {
|
||
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If the session isn't authenticated,
|
||
// do so now.
|
||
//
|
||
|
||
FirstTryFailed:
|
||
|
||
Status = NlEnsureSessionAuthenticated( ClientSession, 0 );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionInfo.SessionKey = ClientSession->CsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags;
|
||
|
||
//
|
||
// Remember current authentication seed. We may need to reset it
|
||
// if the trusted side server is the old one that doesn't have
|
||
// the passwords monitoring API.
|
||
//
|
||
|
||
CurrentAuthenticationSeed = ClientSession->CsAuthenticationSeed;
|
||
|
||
//
|
||
// Build the Authenticator for this request to the server
|
||
//
|
||
|
||
NlBuildAuthenticator(
|
||
&ClientSession->CsAuthenticationSeed,
|
||
&ClientSession->CsSessionKey,
|
||
&OurAuthenticator);
|
||
|
||
//
|
||
// Get the passwords (and perhaps attributes) from the server
|
||
//
|
||
|
||
NL_API_START( Status, ClientSession, TRUE ) {
|
||
|
||
NlAssert( ClientSession->CsUncServerName != NULL );
|
||
|
||
//
|
||
// If this server may support getting both passwords and attributes,
|
||
// ask for both
|
||
//
|
||
if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) == 0 ) {
|
||
Status = I_NetServerGetTrustInfo(
|
||
ClientSession->CsUncServerName,
|
||
(AccountName != NULL) ?
|
||
AccountName :
|
||
ClientSession->CsAccountName,
|
||
(AccountName != NULL) ?
|
||
AccountType :
|
||
ClientSession->CsSecureChannelType,
|
||
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
&OurAuthenticator,
|
||
&ReturnAuthenticator,
|
||
&SessKeyEncrNewPassword,
|
||
&SessKeyEncrOldPassword,
|
||
&LocalTrustInfo );
|
||
|
||
//
|
||
// If the server is old that doesn't support this functionality,
|
||
// remember to never ask it about attributes again and try to
|
||
// get just the passwords
|
||
//
|
||
if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
|
||
ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_NO_PWD_ATTR_MONITOR;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If this server doesn't support getting both passwords and attributes but
|
||
// may support getting just the passwords, ask for it.
|
||
//
|
||
// REVIEW: Ditch this code once there are no more Whistler Beta2 servers out
|
||
// there which don't support I_NetServerGetTrustInfo.
|
||
//
|
||
if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) != 0 &&
|
||
(ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_MONITOR) == 0 ) {
|
||
|
||
Status = I_NetServerTrustPasswordsGet(
|
||
ClientSession->CsUncServerName,
|
||
(AccountName != NULL) ?
|
||
AccountName :
|
||
ClientSession->CsAccountName,
|
||
(AccountName != NULL) ?
|
||
AccountType :
|
||
ClientSession->CsSecureChannelType,
|
||
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
&OurAuthenticator,
|
||
&ReturnAuthenticator,
|
||
&SessKeyEncrNewPassword,
|
||
&SessKeyEncrOldPassword );
|
||
|
||
//
|
||
// If the server is old that doesn't support this functionality,
|
||
// remember to never ask it about passwords again
|
||
//
|
||
if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
|
||
ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_NO_PWD_MONITOR;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Detect if the trusted side is an old server. If so, avoid dropping the
|
||
// secure channel in the NL_API_ELSE logic by making it think all was fine.
|
||
//
|
||
if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
|
||
OldServer = TRUE;
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
// NOTE: This call may drop the secure channel behind our back
|
||
} NL_API_ELSE( Status, ClientSession, TRUE ) {
|
||
} NL_API_END;
|
||
|
||
if ( OldServer ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Now verify primary's authenticator and update our seed
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ||
|
||
!NlUpdateSeed( &ClientSession->CsAuthenticationSeed,
|
||
&ReturnAuthenticator.Credential,
|
||
&ClientSession->CsSessionKey) ) {
|
||
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlGetTrustedSideInfo: denying access after status: 0x%lx\n",
|
||
Status ));
|
||
|
||
//
|
||
// Preserve any status indicating a communication error.
|
||
//
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
|
||
//
|
||
// Perhaps the netlogon service on the server has just restarted.
|
||
// Try just once to set up a session to the server again.
|
||
//
|
||
if ( FirstTry ) {
|
||
FirstTry = FALSE;
|
||
goto FirstTryFailed;
|
||
}
|
||
}
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Decrypt the password returned from the server.
|
||
//
|
||
|
||
Status = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
&SessKeyEncrNewPassword,
|
||
(PNT_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
NewOwfPassword );
|
||
NlAssert( NT_SUCCESS(Status) );
|
||
|
||
Status = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
&SessKeyEncrOldPassword,
|
||
(PNT_OWF_PASSWORD) &SessionInfo.SessionKey,
|
||
OldOwfPassword );
|
||
NlAssert( NT_SUCCESS(Status) );
|
||
|
||
//
|
||
// Common exit
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Remember to not try this call to this server in future.
|
||
// Reset authentication seed to have the secure channel
|
||
// working next time we use it.
|
||
//
|
||
|
||
if ( OldServer ) {
|
||
ClientSession->CsAuthenticationSeed = CurrentAuthenticationSeed;
|
||
Status = STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// On success, return the trust info
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlGetTrustedSideInfo: %ws: failed %lX\n",
|
||
AccountName,
|
||
Status ));
|
||
|
||
if ( LocalTrustInfo != NULL ) {
|
||
NetApiBufferFree( LocalTrustInfo );
|
||
}
|
||
} else {
|
||
*TrustInfo = LocalTrustInfo;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
NlVerifyTrust(
|
||
IN PCLIENT_SESSION ClientSession,
|
||
OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by the trusting side to verify the status
|
||
of the secure channel to the trusted side DC.
|
||
|
||
It first tries to use an API that goes over the secure channel and
|
||
returns the passwords used for the given trust relationship from the
|
||
trusted side. The trusting side checks if there is match between the
|
||
passwords returned from the trusted side and those it has locally. If
|
||
they match, the API returns success to the caller. If the trusted side
|
||
lacks this functionality, the trusting side verifies the trust by
|
||
performing an authentication call over the secure channel for a bogus
|
||
domain\user. If the secure channel works, this is bound to fail with
|
||
STATUS_NO_SUCH_USER in which case success is returned to the caller.
|
||
|
||
Arguments:
|
||
|
||
ClientSession - Identifies a session to the trusted side.
|
||
|
||
QueryInformation - Returns a pointer to a NETLOGON_INFO_2 buffer
|
||
which contains the requested information. The buffer must be
|
||
freed using NetApiBufferFree.
|
||
|
||
Return Value:
|
||
|
||
NT status code.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS; // Status of operation
|
||
NTSTATUS SecureChannelStatus = STATUS_SUCCESS; // Status of secure channel
|
||
NTSTATUS VerificationStatus = STATUS_SUCCESS; // Status of trust verification
|
||
|
||
NT_OWF_PASSWORD NewOwfPassword;
|
||
NT_OWF_PASSWORD OldOwfPassword;
|
||
NT_OWF_PASSWORD OurNewOwfPassword;
|
||
NT_OWF_PASSWORD OurOldOwfPassword;
|
||
|
||
PUNICODE_STRING OurNewPassword = NULL;
|
||
PUNICODE_STRING OurOldPassword = NULL;
|
||
|
||
ULONG DummyPasswordVersionNumber;
|
||
LPBYTE ValidationInformation = NULL;
|
||
|
||
LPWSTR ServerName = NULL;
|
||
ULONG ServerDiscoveryFlags = 0;
|
||
PNL_GENERIC_RPC_DATA TrustInfo = NULL;
|
||
BOOL AmWriter = FALSE;
|
||
BOOL TrustAttribVerified = FALSE;
|
||
|
||
//
|
||
// Become a Writer of the ClientSession.
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NlVerifyTrust: Can't become writer of client session.\n"));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
AmWriter = TRUE;
|
||
|
||
//
|
||
// Get the trust passwords from the trusted side
|
||
//
|
||
|
||
Status = NlGetTrustedSideInfo( ClientSession,
|
||
NULL, // Use the account specified in the client session
|
||
NullSecureChannel, // Let the routine get the account type
|
||
&NewOwfPassword,
|
||
&OldOwfPassword,
|
||
&TrustInfo );
|
||
|
||
//
|
||
// If this call is not supported on the trusted side DC,
|
||
// we can only check if the secure chanel is currently healthy.
|
||
//
|
||
|
||
if ( Status == STATUS_NOT_SUPPORTED ) {
|
||
NETLOGON_INTERACTIVE_INFO LogonInformation;
|
||
PNETLOGON_LOGON_IDENTITY_INFO Identity = (PNETLOGON_LOGON_IDENTITY_INFO) &LogonInformation;
|
||
BOOLEAN Authoritative;
|
||
WCHAR BogusName[2];
|
||
ULONG ExtraFlags = 0;
|
||
|
||
BogusName[0] = (WCHAR) 0xFFFF;
|
||
BogusName[1] = UNICODE_NULL;
|
||
|
||
//
|
||
// Reset the status
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Initialize the structure with bogus names
|
||
//
|
||
|
||
RtlZeroMemory( &LogonInformation, sizeof(LogonInformation) );
|
||
RtlInitUnicodeString( &Identity->LogonDomainName, BogusName );
|
||
RtlInitUnicodeString( &Identity->UserName, BogusName );
|
||
RtlInitUnicodeString( &Identity->Workstation, BogusName );
|
||
|
||
//
|
||
// Release the writer lock as it is used
|
||
// in the following secure channel call
|
||
//
|
||
|
||
NlResetWriterClientSession( ClientSession );
|
||
AmWriter = FALSE;
|
||
|
||
//
|
||
// Force a call over the secure channel
|
||
//
|
||
|
||
Status = NlpUserValidateHigher( ClientSession,
|
||
FALSE, // not doing indirect trust
|
||
NetlogonInteractiveInformation,
|
||
(LPBYTE) &LogonInformation,
|
||
NetlogonValidationSamInfo,
|
||
&ValidationInformation,
|
||
&Authoritative,
|
||
&ExtraFlags );
|
||
|
||
//
|
||
// This is bound to fail. Ignore the failure.
|
||
//
|
||
|
||
NlAssert( !NT_SUCCESS(Status) );
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Get the secure channel status after
|
||
// we made a call over it
|
||
//
|
||
|
||
SecureChannelStatus = NlCaptureServerClientSession(
|
||
ClientSession,
|
||
&ServerName,
|
||
&ServerDiscoveryFlags );
|
||
|
||
//
|
||
// The above is our best we can do to
|
||
// verify the trust for an old server
|
||
//
|
||
|
||
VerificationStatus = SecureChannelStatus;
|
||
|
||
//
|
||
// Otherwise, this is the new server.
|
||
// Check the secure channel state. If it's successful,
|
||
// verify the trust status by checking whether local
|
||
// trust attributes and passwords match with those
|
||
// received from the trusted side.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// Get the secure channel status and the server
|
||
// name. Do this while holding the writer lock
|
||
// to ensure we return the name of the server
|
||
// used to verify the trust.
|
||
//
|
||
|
||
SecureChannelStatus = NlCaptureServerClientSession(
|
||
ClientSession,
|
||
&ServerName,
|
||
&ServerDiscoveryFlags );
|
||
|
||
//
|
||
// Release the writer lock. We don't need it anymore.
|
||
//
|
||
|
||
NlResetWriterClientSession( ClientSession );
|
||
AmWriter = FALSE;
|
||
|
||
//
|
||
// If secure channel is down, there is nothing
|
||
// to verify
|
||
//
|
||
|
||
if ( !NT_SUCCESS(SecureChannelStatus) ) {
|
||
VerificationStatus = SecureChannelStatus;
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// OK, secure channel is up. However, if we couldn't
|
||
// get the trust info for some reason, set the
|
||
// verification status to the error we got while
|
||
// getting the trust info and bail out.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
VerificationStatus = Status;
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the trusted side returned trust attributes,
|
||
// check if trust attributes match.
|
||
//
|
||
// The first ULONG in the trust info is the trust attributes
|
||
//
|
||
|
||
if ( TrustInfo != NULL && TrustInfo->UlongEntryCount > NL_GENERIC_RPC_TRUST_ATTRIB_INDEX ) {
|
||
|
||
//
|
||
// We are only interested in the forest transitive bit
|
||
//
|
||
if ( (ClientSession->CsTrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0 ) {
|
||
if ( (TrustInfo->UlongData[NL_GENERIC_RPC_TRUST_ATTRIB_INDEX] &
|
||
TRUST_ATTRIBUTE_FOREST_TRANSITIVE) == 0 ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlVerifyTrust: F bit is set locally but not on trusted side\n" ));
|
||
VerificationStatus = STATUS_DOMAIN_TRUST_INCONSISTENT;
|
||
goto Cleanup;
|
||
}
|
||
} else {
|
||
if ( (TrustInfo->UlongData[NL_GENERIC_RPC_TRUST_ATTRIB_INDEX] &
|
||
TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0 ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlVerifyTrust: F bit is set on trusted side but not locally\n" ));
|
||
VerificationStatus = STATUS_DOMAIN_TRUST_INCONSISTENT;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
TrustAttribVerified = TRUE;
|
||
}
|
||
|
||
//
|
||
// OK, the trust attributes check succeeded.
|
||
// Proceed to check the password match
|
||
//
|
||
// Get our local passwords
|
||
//
|
||
|
||
Status = NlGetOutgoingPassword( ClientSession,
|
||
&OurNewPassword,
|
||
&OurOldPassword,
|
||
&DummyPasswordVersionNumber,
|
||
NULL ); // No need to return password set time
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if our new password matches
|
||
// either one returned from the trusted side
|
||
//
|
||
|
||
if ( OurNewPassword != NULL ) {
|
||
Status = RtlCalculateNtOwfPassword( OurNewPassword,
|
||
&OurNewOwfPassword );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
//
|
||
// return more appropriate error.
|
||
//
|
||
if ( !NlpIsNtStatusResourceError( Status )) {
|
||
Status = STATUS_NO_TRUST_LSA_SECRET;
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if this password is the same as the new one from trusted side
|
||
//
|
||
|
||
if ( RtlEqualNtOwfPassword(&OurNewOwfPassword, &NewOwfPassword) ) {
|
||
NlPrintCs(( NL_MISC, ClientSession,
|
||
"NlVerifyTrust: new-new password match (%s trust attributes)\n",
|
||
(TrustAttribVerified ? "with" : "without") ));
|
||
VerificationStatus = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if this password is the same as the old one from trusted side
|
||
//
|
||
|
||
if ( RtlEqualNtOwfPassword(&OurNewOwfPassword, &OldOwfPassword) ) {
|
||
NlPrintCs(( NL_MISC, ClientSession,
|
||
"NlVerifyTrust: new-old password match (%s trust attributes)\n",
|
||
(TrustAttribVerified ? "with" : "without") ));
|
||
VerificationStatus = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Check if our old password matches
|
||
// either one returned from the trusted side
|
||
//
|
||
|
||
if ( OurOldPassword != NULL ) {
|
||
Status = RtlCalculateNtOwfPassword( OurOldPassword,
|
||
&OurOldOwfPassword );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
//
|
||
// return more appropriate error.
|
||
//
|
||
if ( !NlpIsNtStatusResourceError( Status )) {
|
||
Status = STATUS_NO_TRUST_LSA_SECRET;
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if this password is the same as the new one from trusted side
|
||
//
|
||
|
||
if ( RtlEqualNtOwfPassword(&OurOldOwfPassword, &NewOwfPassword) ) {
|
||
NlPrintCs(( NL_MISC, ClientSession,
|
||
"NlVerifyTrust: old-new password match (%s trust attributes)\n",
|
||
(TrustAttribVerified ? "with" : "without") ));
|
||
VerificationStatus = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Check if this password is the same as the old one from trusted side
|
||
//
|
||
|
||
if ( RtlEqualNtOwfPassword(&OurOldOwfPassword, &OldOwfPassword) ) {
|
||
NlPrintCs(( NL_MISC, ClientSession,
|
||
"NlVerifyTrust: old-old password match (%s trust attributes)\n",
|
||
(TrustAttribVerified ? "with" : "without") ));
|
||
VerificationStatus = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If we are here, passwords didn't match
|
||
//
|
||
|
||
VerificationStatus = STATUS_WRONG_PASSWORD;
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlVerifyTrust: passwords don't match\n" ));
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
if ( AmWriter ) {
|
||
NlResetWriterClientSession( ClientSession );
|
||
}
|
||
|
||
//
|
||
// On success, return the results of verification
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
|
||
//
|
||
// If we don't know the server name,
|
||
// set it to blank name
|
||
//
|
||
|
||
if ( ServerName == NULL ) {
|
||
ServerName = NetpAllocWStrFromWStr( L"" );
|
||
if ( ServerName == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Allocate the memory for returned structure
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
QueryInformation->NetlogonInfo2 = MIDL_user_allocate( sizeof(NETLOGON_INFO_2) );
|
||
if ( QueryInformation->NetlogonInfo2 == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If allocations succeeded,
|
||
// return the data
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
QueryInformation->NetlogonInfo2->netlog2_flags = 0;
|
||
|
||
//
|
||
// Indicate that we are returing the verification status
|
||
// in netlog2_pdc_connection_status
|
||
//
|
||
QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_VERIFY_STATUS_RETURNED;
|
||
QueryInformation->NetlogonInfo2->netlog2_pdc_connection_status =
|
||
NetpNtStatusToApiStatus( VerificationStatus );
|
||
|
||
//
|
||
// Return the server discovery flags
|
||
//
|
||
if ( ServerDiscoveryFlags & CS_DISCOVERY_HAS_TIMESERV ) {
|
||
QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_HAS_TIMESERV;
|
||
}
|
||
|
||
if ( ServerDiscoveryFlags & CS_DISCOVERY_HAS_IP ) {
|
||
QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_HAS_IP;
|
||
}
|
||
|
||
//
|
||
// Return the current secure channel status
|
||
// and the server name
|
||
//
|
||
QueryInformation->NetlogonInfo2->netlog2_tc_connection_status =
|
||
NetpNtStatusToApiStatus( SecureChannelStatus );
|
||
QueryInformation->NetlogonInfo2->netlog2_trusted_dc_name = ServerName;
|
||
ServerName = NULL; // don't free this name below
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free locally used resources
|
||
//
|
||
|
||
if ( OurNewPassword != NULL ) {
|
||
LocalFree( OurNewPassword );
|
||
}
|
||
|
||
if ( OurOldPassword != NULL ) {
|
||
LocalFree( OurOldPassword );
|
||
}
|
||
|
||
if ( ServerName != NULL ) {
|
||
NetApiBufferFree( ServerName );
|
||
}
|
||
|
||
if ( TrustInfo != NULL ) {
|
||
NetApiBufferFree( TrustInfo );
|
||
}
|
||
|
||
if ( ValidationInformation != NULL ) {
|
||
MIDL_user_free( ValidationInformation );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonControl(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN DWORD FunctionCode,
|
||
IN DWORD QueryLevel,
|
||
OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function controls various aspects of the Netlogon service. It
|
||
can be used to request that a BDC ensure that its copy of the SAM
|
||
database is brought up to date. It can, also, be used to determine
|
||
if a BDC currently has a secure channel open to the PDC.
|
||
|
||
Only an Admin, Account Operator or Server Operator may call this
|
||
function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
FunctionCode - Defines the operation to be performed. The valid
|
||
values are:
|
||
|
||
FunctionCode Values
|
||
|
||
NETLOGON_CONTROL_QUERY - No operation. Merely returns the
|
||
information requested.
|
||
|
||
NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
|
||
to be brought in sync with the copy on the PDC. This
|
||
operation does NOT imply a full synchronize. The
|
||
Netlogon service will merely replicate any outstanding
|
||
differences if possible.
|
||
|
||
NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
|
||
completely new copy of the SAM database from the PDC.
|
||
This operation will perform a full synchronize.
|
||
|
||
NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
|
||
to replicate now.
|
||
|
||
QueryLevel - Indicates what information should be returned from
|
||
the Netlogon Service. Must be 1.
|
||
|
||
QueryInformation - Returns a pointer to a buffer which contains the
|
||
requested information. The buffer must be freed using
|
||
NetApiBufferFree.
|
||
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NOT_SUPPORTED: Function code is not valid on the specified
|
||
server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
|
||
QueryInformation->NetlogonInfo1 = NULL;
|
||
|
||
switch( QueryLevel ) {
|
||
case (1):
|
||
break;
|
||
case (2):
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
|
||
default:
|
||
NetStatus = ERROR_INVALID_LEVEL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// ensure the input data is valid.
|
||
//
|
||
|
||
switch( FunctionCode ) {
|
||
case NETLOGON_CONTROL_QUERY:
|
||
case NETLOGON_CONTROL_REPLICATE:
|
||
case NETLOGON_CONTROL_SYNCHRONIZE:
|
||
case NETLOGON_CONTROL_PDC_REPLICATE:
|
||
|
||
#if NETLOGONDBG
|
||
case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
|
||
case NETLOGON_CONTROL_TRUNCATE_LOG:
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
#endif // NETLOGONDBG
|
||
|
||
break;
|
||
|
||
default:
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
NetStatus = NetrLogonControl2Ex(
|
||
ServerName,
|
||
FunctionCode,
|
||
QueryLevel,
|
||
NULL,
|
||
QueryInformation );
|
||
|
||
Cleanup:
|
||
|
||
return( NetStatus );
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NetrLogonControl2(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN DWORD FunctionCode,
|
||
IN DWORD QueryLevel,
|
||
IN PNETLOGON_CONTROL_DATA_INFORMATION InputData,
|
||
OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Same as NetrLogonControl2Ex.
|
||
|
||
A client should never pass a QueryLevel of 4 to this procedure. We don't check since, if
|
||
they did, it's too late now. The client will access violate upon return.
|
||
|
||
Arguments:
|
||
|
||
Same as NetrLogonControl2Ex.
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
|
||
NetStatus = NetrLogonControl2Ex(
|
||
ServerName,
|
||
FunctionCode,
|
||
QueryLevel,
|
||
InputData,
|
||
QueryInformation );
|
||
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonControl2Ex(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN DWORD FunctionCode,
|
||
IN DWORD QueryLevel,
|
||
IN PNETLOGON_CONTROL_DATA_INFORMATION InputData,
|
||
OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function controls various aspects of the Netlogon service. It
|
||
can be used to request that a BDC ensure that its copy of the SAM
|
||
database is brought up to date. It can, also, be used to determine
|
||
if a BDC currently has a secure channel open to the PDC.
|
||
|
||
Only an Admin, Account Operator or Server Operator may call this
|
||
function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
FunctionCode - Defines the operation to be performed. The valid
|
||
values are:
|
||
|
||
FunctionCode Values
|
||
|
||
NETLOGON_CONTROL_QUERY - No operation. Merely returns the
|
||
information requested.
|
||
|
||
NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC
|
||
to be brought in sync with the copy on the PDC. This
|
||
operation does NOT imply a full synchronize. The
|
||
Netlogon service will merely replicate any outstanding
|
||
differences if possible.
|
||
|
||
NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a
|
||
completely new copy of the SAM database from the PDC.
|
||
This operation will perform a full synchronize.
|
||
|
||
NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC
|
||
to replicate now.
|
||
|
||
NETLOGON_CONTROL_REDISCOVER: Forces a DC to rediscover the
|
||
specified trusted domain DC.
|
||
|
||
NETLOGON_CONTROL_TC_QUERY: Query the status of the specified
|
||
trusted domain secure channel.
|
||
|
||
NETLOGON_CONTROL_TC_VERIFY: Verify the status of the specified
|
||
trusted domain secure channel. If the current status is
|
||
success (which means that the last operation performed
|
||
over the secure channel was successful), ping the DC. If the
|
||
current status is not success or the ping fails, rediscover
|
||
a new DC.
|
||
|
||
NETLOGON_CONTROL_TRANSPORT_NOTIFY: Notifies netlogon that a new transport
|
||
has been added. Currently, it merely resets discovery timeouts allowing
|
||
all secure channel discoveries to be retried immediately. However, the
|
||
intention is to later add support for anything similar. The intention is that
|
||
a client can call this function after a new transport has been added (e.g., it
|
||
dialed a RAS link) and immediately before calling Netlogon (e.g., indirectly
|
||
by doing an LsaLogonUser).
|
||
|
||
NETLOGON_CONTROL_FORCE_DNS_REG: Forces the DC to re-register all of its
|
||
DNS records. QueryLevel parameter must be 1.
|
||
|
||
NETLOGON_CONTROL_QUERY_DNS_REG: Query the status of DNS updates
|
||
performed by netlogon. If there was any DNS registration or
|
||
deregistration error for any of the records as they were
|
||
updated last time, the query result will be negative;
|
||
otherwise the query result will be positive.
|
||
QueryLevel parameter must be 1.
|
||
|
||
QueryLevel - Indicates what information should be returned from
|
||
the Netlogon Service.
|
||
|
||
InputData - According to the function code specified this parameter
|
||
will carry input data. NETLOGON_CONTROL_REDISCOVER,
|
||
NETLOGON_CONTROL_TC_QUERY, and NETLOGON_CONTROL_TC_VERIFY
|
||
function code specify the trusted domain name (LPWSTR type) here.
|
||
NETLOGON_CONTROL_FIND_USER function code specifies the user name
|
||
(LPWSTR type) here.
|
||
|
||
QueryInformation - Returns a pointer to a buffer which contains the
|
||
requested information. The buffer must be freed using
|
||
NetApiBufferFree.
|
||
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NOT_SUPPORTED: Function code is not valid on the specified
|
||
server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC).
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
NTSTATUS Status;
|
||
DWORD i;
|
||
DWORD InfoSize;
|
||
BOOL DnsLastStatusCheck = TRUE;
|
||
ACCESS_MASK DesiredAccess;
|
||
|
||
UNICODE_STRING DomainName;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
LPWSTR TDCName = NULL;
|
||
LPWSTR TrustedDomainName = NULL;
|
||
LPWSTR SamAccountName = NULL;
|
||
LPWSTR SamDomainName = NULL;
|
||
DWORD SamExtraFlags;
|
||
DWORD TcServerDiscoveryFlags = 0;
|
||
PNL_DC_CACHE_ENTRY DcCacheEntry = NULL;
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Ensure the QueryLevel is valid
|
||
//
|
||
|
||
QueryInformation->NetlogonInfo1 = NULL;
|
||
|
||
switch( QueryLevel ) {
|
||
case (1):
|
||
case (2):
|
||
case (3):
|
||
case (4):
|
||
break;
|
||
default:
|
||
NetStatus = ERROR_INVALID_LEVEL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// ensure the input data is valid.
|
||
//
|
||
|
||
switch( FunctionCode ) {
|
||
case NETLOGON_CONTROL_REDISCOVER:
|
||
case NETLOGON_CONTROL_TC_QUERY:
|
||
case NETLOGON_CONTROL_TC_VERIFY:
|
||
case NETLOGON_CONTROL_FIND_USER:
|
||
case NETLOGON_CONTROL_CHANGE_PASSWORD:
|
||
#if NETLOGONDBG
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
#endif // NETLOGONDBG
|
||
|
||
NlAssert( InputData != NULL );
|
||
if( InputData == NULL ) {
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
//
|
||
// compute access mask.
|
||
//
|
||
|
||
switch ( FunctionCode ) {
|
||
|
||
case NETLOGON_CONTROL_QUERY:
|
||
case NETLOGON_CONTROL_TC_QUERY:
|
||
case NETLOGON_CONTROL_TRANSPORT_NOTIFY:
|
||
case NETLOGON_CONTROL_QUERY_DNS_REG:
|
||
DesiredAccess = NETLOGON_QUERY_ACCESS;
|
||
break;
|
||
|
||
case NETLOGON_CONTROL_REPLICATE:
|
||
case NETLOGON_CONTROL_SYNCHRONIZE:
|
||
case NETLOGON_CONTROL_PDC_REPLICATE:
|
||
case NETLOGON_CONTROL_REDISCOVER:
|
||
case NETLOGON_CONTROL_TC_VERIFY:
|
||
case NETLOGON_CONTROL_FIND_USER:
|
||
case NETLOGON_CONTROL_CHANGE_PASSWORD:
|
||
case NETLOGON_CONTROL_FORCE_DNS_REG:
|
||
#if NETLOGONDBG
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
case NETLOGON_CONTROL_TRUNCATE_LOG:
|
||
case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
|
||
#endif // NETLOGONDBG
|
||
default:
|
||
DesiredAccess = NETLOGON_CONTROL_ACCESS;
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
DesiredAccess, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
NetStatus = ERROR_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Handle the various FunctionCodes
|
||
//
|
||
|
||
switch ( FunctionCode ) {
|
||
|
||
//
|
||
// On a query, do nothing but return status.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_QUERY:
|
||
NlPrintDom((NL_MISC, DomainInfo,
|
||
"QUERY function received.\n"));
|
||
break;
|
||
|
||
#ifdef _DC_NETLOGON
|
||
//
|
||
// Force a PDC to broadcast a database change record.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_PDC_REPLICATE:
|
||
|
||
NlPrint((NL_SYNC, "PDC REPLICATE function received.\n" ));
|
||
#if 0
|
||
{
|
||
NlSitesUpdateSiteCoverage( DomainInfo, NULL );
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"Cliffs test code *****************************.\n" ));
|
||
}
|
||
#endif
|
||
//
|
||
// This FunctionCode is only valid on a PDC
|
||
//
|
||
|
||
if ( !NlGlobalPdcDoReplication ) {
|
||
NlPrint((NL_CRITICAL, "PDC REPLICATE only supported in mixed mode.\n" ));
|
||
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Simply send the announcement. Any BDC that is out of date
|
||
// will replicate any changes.
|
||
//
|
||
|
||
NlPrimaryAnnouncement( ANNOUNCE_FORCE );
|
||
|
||
break;
|
||
#endif // _DC_NETLOGON
|
||
|
||
|
||
//
|
||
// Force to rediscover trusted domain DCs.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_REDISCOVER: {
|
||
LPWSTR DiscoveredDc;
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NETLOGON_CONTROL_REDISCOVER function received.\n"));
|
||
|
||
NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl called with function code NETLOGON_CONTROL_REDISCOVER "
|
||
"specified NULL trusted domain name. \n"));
|
||
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Determine if there is a \ in the passed name.
|
||
// If so, truncate the string there and save a pointer to the
|
||
// DC name after the \.
|
||
//
|
||
|
||
DiscoveredDc = wcschr( InputData->TrustedDomainName, L'\\' );
|
||
|
||
if ( DiscoveredDc != NULL ) {
|
||
*DiscoveredDc = L'\0';
|
||
DiscoveredDc++;
|
||
}
|
||
|
||
|
||
RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
|
||
|
||
//
|
||
// get client structure for the specified domain.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainName,
|
||
NL_DIRECT_TRUST_REQUIRED,
|
||
NULL );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl can't find the client structure of the domain %wZ specified.\n",
|
||
&DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Ping the DC to figure out if it is available
|
||
//
|
||
|
||
if ( DiscoveredDc != NULL ) {
|
||
|
||
//
|
||
// We ensure that the DC has our account.
|
||
// Otherwise, if this DC doesn't have our
|
||
// account, the session setup may rediscover
|
||
// a different DC and we may end up setting
|
||
// up the secure channel to a DC other than
|
||
// the one passed to us.
|
||
//
|
||
NetStatus = NlPingDcName(
|
||
ClientSession,
|
||
0, // Try both ping mechanisms
|
||
TRUE, // Cache this DC
|
||
FALSE, // Do not require IP
|
||
TRUE, // Ensure the DC has our account
|
||
FALSE, // Do not refresh the session
|
||
DiscoveredDc, // Ping this DC
|
||
&DcCacheEntry );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintCs((NL_SESSION_SETUP, ClientSession,
|
||
"NetrLogonControl: Unsuccessful response from DC %ws 0x%lx\n",
|
||
DiscoveredDc, NetStatus ));
|
||
//
|
||
// If the service is paused on the server, return the
|
||
// appropriate status. Otherwise, map the status
|
||
// to a generic error code.
|
||
//
|
||
if ( NetStatus != ERROR_SERVICE_NOT_ACTIVE ) {
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
}
|
||
goto Cleanup;
|
||
} else {
|
||
NlPrintCs((NL_SESSION_SETUP, ClientSession,
|
||
"NetrLogonControl: Successful response from DC %ws\n",
|
||
DiscoveredDc ));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Force Discovery of a DC
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl2: Can't become writer of client session.\n"));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
} else {
|
||
|
||
//
|
||
// Reset the current DC.
|
||
//
|
||
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
|
||
|
||
//
|
||
// If the caller specified a DC,
|
||
// set it in the client sesion structure.
|
||
//
|
||
if ( DcCacheEntry != NULL ) {
|
||
NlSetServerClientSession( ClientSession,
|
||
DcCacheEntry,
|
||
TRUE, // was discovery with account
|
||
FALSE ); // not the session refresh
|
||
}
|
||
|
||
//
|
||
// Setup a session to the DC (Discover one if needed)
|
||
//
|
||
Status = NlSessionSetup( ClientSession );
|
||
NlResetWriterClientSession( ClientSession );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NetrLogonControl: Discovery failed %lx\n",
|
||
Status ));
|
||
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case NETLOGON_CONTROL_TC_QUERY:
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NETLOGON_CONTROL_TC_QUERY function received.\n"));
|
||
|
||
NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl called with NETLOGON_CONTROL_TC_QUERY "
|
||
"and specified NULL trusted domain name. \n"));
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
|
||
|
||
//
|
||
// get client structure for the specified domain.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainName,
|
||
NL_DIRECT_TRUST_REQUIRED,
|
||
NULL );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl can't find the client structure of the domain %wZ specified.\n",
|
||
&DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
break;
|
||
|
||
case NETLOGON_CONTROL_TC_VERIFY:
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NETLOGON_CONTROL_TC_VERIFY function received.\n"));
|
||
|
||
NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl called with NETLOGON_CONTROL_TC_VERIFY "
|
||
"and specified NULL trusted domain name. \n"));
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// This requires query level 2
|
||
//
|
||
|
||
if ( QueryLevel != 2 ) {
|
||
NetStatus = ERROR_INVALID_LEVEL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
|
||
|
||
//
|
||
// get client structure for the specified domain.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainName,
|
||
NL_DIRECT_TRUST_REQUIRED,
|
||
NULL );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl can't find the client structure of the domain %wZ specified.\n",
|
||
&DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Verify the trust
|
||
//
|
||
|
||
Status = NlVerifyTrust( ClientSession, QueryInformation );
|
||
|
||
//
|
||
// NlVerifyTrust built all the required info,
|
||
// so we are done
|
||
//
|
||
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
goto Cleanup;
|
||
|
||
case NETLOGON_CONTROL_CHANGE_PASSWORD:
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NETLOGON_CONTROL_CHANGE_PASSWORD function received.\n"));
|
||
|
||
// NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl called with NETLOGON_CONTROL_CHANGE_PASSWORD "
|
||
"and specified NULL trusted domain name. \n"));
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName );
|
||
|
||
//
|
||
// get client structure for the specified domain.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainName,
|
||
NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK,
|
||
NULL );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl can't find the client structure of the domain %wZ specified.\n",
|
||
&DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Do not allow password change for an interdomain trust account on a BDC
|
||
//
|
||
|
||
if ( (DomainInfo->DomRole == RoleBackup) &&
|
||
( IsDomainSecureChannelType(ClientSession->CsSecureChannelType )) ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl called with NETLOGON_CONTROL_CHANGE_PASSWORD "
|
||
"for an interdomain trust account on a BDC. \n"));
|
||
NetStatus = ERROR_INVALID_DOMAIN_ROLE;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Force the password on the client session found
|
||
//
|
||
|
||
Status = NlChangePassword( ClientSession, TRUE, NULL );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NetrLogonControl: Password Change failed %lx\n",
|
||
Status ));
|
||
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
goto Cleanup;
|
||
}
|
||
|
||
break;
|
||
|
||
//
|
||
// A client has added a new transport and needs us to use it.
|
||
// Mark all the client sessions that its OK to authentication NOW.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_TRANSPORT_NOTIFY: {
|
||
NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TRANSPORT_NOTIFY function received.\n" ));
|
||
|
||
//
|
||
// Flush any caches that aren't valid any more since there
|
||
// is now a new transport
|
||
//
|
||
NlFlushCacheOnPnp();
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Find a user in one of the trusted domains.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_FIND_USER: {
|
||
|
||
UNICODE_STRING UserNameString;
|
||
NlPrint((NL_MISC, "NETLOGON_CONTROL_FIND_USER function received for %ws.\n", InputData->UserName ));
|
||
|
||
if ( QueryLevel != 4 ) {
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Don't allow on workstation since CrackSingleName isn't implemented on
|
||
// a workstation.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Find a user in one of the trusted domains.
|
||
//
|
||
// Allow machine accounts just as a handy extension.
|
||
// Don't find "Local User" accounts since we can't pass through to them
|
||
//
|
||
|
||
|
||
RtlInitUnicodeString( &UserNameString, InputData->UserName );
|
||
|
||
Status = NlPickDomainWithAccount (
|
||
DomainInfo,
|
||
&UserNameString,
|
||
NULL, // No domain name
|
||
USER_NORMAL_ACCOUNT | USER_MACHINE_ACCOUNT_MASK,
|
||
NullSecureChannel, // No incoming secure channel
|
||
FALSE, // Call wasn't expedited to root
|
||
FALSE, // Call wasn't first hop across forest.
|
||
&SamAccountName,
|
||
&SamDomainName,
|
||
&SamExtraFlags );
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
if ( Status == STATUS_NO_SUCH_DOMAIN ) {
|
||
NetStatus = NERR_UserNotFound;
|
||
} else {
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the account isn't in this forest,
|
||
// tell the caller.
|
||
//
|
||
|
||
if ( SamExtraFlags & (NL_EXFLAGS_EXPEDITE_TO_ROOT|NL_EXFLAGS_CROSS_FOREST_HOP) ) {
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl: User %ws is in a trusted forest (%lx).\n",
|
||
InputData->UserName,
|
||
SamExtraFlags ));
|
||
|
||
NetStatus = NERR_UserNotFound;
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
}
|
||
break;
|
||
|
||
//
|
||
// Force re-registration of all DNS records for this DC
|
||
//
|
||
|
||
case NETLOGON_CONTROL_FORCE_DNS_REG:
|
||
|
||
//
|
||
// This is not supported on workstations
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Re-register all records
|
||
//
|
||
|
||
NlDnsPnp( TRUE );
|
||
break;
|
||
|
||
//
|
||
// Query the status of last DNS updates
|
||
//
|
||
|
||
case NETLOGON_CONTROL_QUERY_DNS_REG:
|
||
|
||
//
|
||
// This is not supported on workstations
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// This requires query level 1
|
||
//
|
||
|
||
if ( QueryLevel != 1 ) {
|
||
NetStatus = ERROR_INVALID_LEVEL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Call the worker
|
||
//
|
||
|
||
DnsLastStatusCheck = NlDnsCheckLastStatus();
|
||
break;
|
||
|
||
#if NETLOGONDBG
|
||
//
|
||
// Force a breakpoint
|
||
//
|
||
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
KdPrint(( "I_NetLogonControl Break Point\n"));
|
||
#if DBG
|
||
DbgBreakPoint();
|
||
#else // DBG
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
#endif // DBG
|
||
break;
|
||
|
||
//
|
||
// Change the debug flags
|
||
//
|
||
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
NlGlobalParameters.DbFlag = InputData->DebugFlag;
|
||
NlPrint((NL_MISC,"DbFlag is set to %lx\n", NlGlobalParameters.DbFlag ));
|
||
|
||
break;
|
||
|
||
//
|
||
// Truncate the log file
|
||
//
|
||
|
||
case NETLOGON_CONTROL_TRUNCATE_LOG:
|
||
|
||
NlOpenDebugFile( TRUE );
|
||
NlPrint((NL_MISC, "TRUNCATE_LOG function received.\n" ));
|
||
break;
|
||
|
||
//
|
||
// Backup changelog file
|
||
//
|
||
|
||
case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
|
||
|
||
NlPrint((NL_MISC, "BACKUP_CHANGE_LOG function received, (%ld).\n", NetStatus ));
|
||
NetStatus = NlBackupChangeLogFile();
|
||
break;
|
||
|
||
#if DBG
|
||
//
|
||
// Unload Netlogon.dll
|
||
//
|
||
|
||
case NETLOGON_CONTROL_UNLOAD_NETLOGON_DLL:
|
||
|
||
//
|
||
// Don't unload the DLL now.
|
||
// RPC still needs the the DLL as a security provider throughout shutdown.
|
||
//
|
||
|
||
NlPrint((NL_MISC, "UNLOAD_NETLOGON_DLL function received.\n" ));
|
||
NlGlobalUnloadNetlogon = TRUE;
|
||
|
||
NetStatus = NO_ERROR;
|
||
break;
|
||
#endif // DBG
|
||
|
||
#endif // NETLOGONDBG
|
||
|
||
//
|
||
// All other function codes are invalid.
|
||
//
|
||
|
||
default:
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// allocate return info structure.
|
||
//
|
||
|
||
switch( QueryLevel ) {
|
||
case (1):
|
||
InfoSize = sizeof(NETLOGON_INFO_1);
|
||
break;
|
||
case (2):
|
||
InfoSize = sizeof(NETLOGON_INFO_2);
|
||
break;
|
||
case (3):
|
||
InfoSize = sizeof(NETLOGON_INFO_3);
|
||
break;
|
||
case (4):
|
||
InfoSize = sizeof(NETLOGON_INFO_4);
|
||
break;
|
||
}
|
||
|
||
QueryInformation->NetlogonInfo1 = MIDL_user_allocate( InfoSize );
|
||
|
||
if ( QueryInformation->NetlogonInfo1 == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Return DomainName and DC Name.
|
||
//
|
||
switch( QueryLevel ) {
|
||
case (4):
|
||
switch ( FunctionCode ) {
|
||
case NETLOGON_CONTROL_FIND_USER: {
|
||
UNICODE_STRING SamDomainNameString;
|
||
|
||
//
|
||
// If the account is in this Domain,
|
||
// return the name of this DC.
|
||
//
|
||
|
||
RtlInitUnicodeString( &SamDomainNameString, SamDomainName );
|
||
|
||
if ( RtlEqualDomainName( &SamDomainNameString,
|
||
&DomainInfo->DomUnicodeDomainNameString ) ||
|
||
NlEqualDnsNameU( &SamDomainNameString,
|
||
&DomainInfo->DomUnicodeDnsDomainNameString ) ) {
|
||
|
||
|
||
//
|
||
// Grab the name of this DC.
|
||
//
|
||
|
||
TDCName = NetpAllocWStrFromWStr( DomainInfo->DomUncUnicodeComputerName );
|
||
|
||
if ( TDCName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Grab the name of this domain.
|
||
//
|
||
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
TrustedDomainName = NetpAllocWStrFromWStr( DomainInfo->DomUnicodeDomainName );
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
|
||
if ( TrustedDomainName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the account is not in this domain,
|
||
// find the client session and get the DC name from there.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// Find the client session of the Domain anywhere in the forest.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession(
|
||
DomainInfo,
|
||
&SamDomainNameString,
|
||
0, // Indirect trust OK
|
||
NULL );
|
||
|
||
if ( ClientSession == NULL ) {
|
||
//
|
||
// Replication latency. The GC knows of a domain in the forest
|
||
// that we don't trust.
|
||
//
|
||
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonControl: User %ws\\%ws apparently isn't in this forest.\n",
|
||
SamAccountName,
|
||
SamDomainName ));
|
||
|
||
NetStatus = NERR_UserNotFound;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the account isn't on a directly trusted domain,
|
||
// indicate that we don't know the DC.
|
||
//
|
||
|
||
if ( (ClientSession->CsFlags & CS_DIRECT_TRUST) == 0 ) {
|
||
TDCName = NULL;
|
||
|
||
|
||
//
|
||
// If the account is on a directly trusted domain,
|
||
// return the name of a DC in the domain.
|
||
//
|
||
// Capture the name of the server
|
||
// (even if it is an empty string.)
|
||
//
|
||
|
||
} else {
|
||
|
||
// REVIEW: do discovery here.
|
||
Status = NlCaptureServerClientSession( ClientSession, &TDCName, NULL );
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
TDCName = NetpAllocWStrFromWStr( L"" );
|
||
}
|
||
|
||
if ( TDCName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Capture the name of the domain.
|
||
//
|
||
|
||
if ( ClientSession->CsDebugDomainName != NULL ) {
|
||
TrustedDomainName = NetpAllocWStrFromWStr( ClientSession->CsDebugDomainName );
|
||
|
||
if ( TrustedDomainName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
} else {
|
||
TrustedDomainName = NULL;
|
||
}
|
||
}
|
||
|
||
QueryInformation->NetlogonInfo4->netlog4_trusted_dc_name = TDCName;
|
||
QueryInformation->NetlogonInfo4->netlog4_trusted_domain_name = TrustedDomainName;
|
||
break;
|
||
}
|
||
|
||
default:
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
break;
|
||
|
||
//
|
||
// Return queried profile information.
|
||
//
|
||
case (3):
|
||
QueryInformation->NetlogonInfo3->netlog3_flags = 0;
|
||
QueryInformation->NetlogonInfo3->netlog3_logon_attempts =
|
||
// ??: What about kerberos logons
|
||
MsvGetLogonAttemptCount();
|
||
QueryInformation->NetlogonInfo3->netlog3_reserved1 = 0;
|
||
QueryInformation->NetlogonInfo3->netlog3_reserved2 = 0;
|
||
QueryInformation->NetlogonInfo3->netlog3_reserved3 = 0;
|
||
QueryInformation->NetlogonInfo3->netlog3_reserved4 = 0;
|
||
QueryInformation->NetlogonInfo3->netlog3_reserved5 = 0;
|
||
break;
|
||
|
||
//
|
||
// Return secure channel specific information.
|
||
//
|
||
case (2):
|
||
switch ( FunctionCode ) {
|
||
case NETLOGON_CONTROL_REDISCOVER:
|
||
case NETLOGON_CONTROL_TC_QUERY:
|
||
case NETLOGON_CONTROL_TC_VERIFY:
|
||
|
||
if( ClientSession == NULL ) {
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Capture the name of the server
|
||
// (even if it is an empty string.)
|
||
//
|
||
|
||
Status = NlCaptureServerClientSession( ClientSession,
|
||
&TDCName,
|
||
&TcServerDiscoveryFlags );
|
||
|
||
QueryInformation->NetlogonInfo2->netlog2_tc_connection_status =
|
||
NetpNtStatusToApiStatus(Status);
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
TDCName = NetpAllocWStrFromWStr( L"" );
|
||
}
|
||
|
||
if ( TDCName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
QueryInformation->NetlogonInfo2->netlog2_trusted_dc_name = TDCName;
|
||
break;
|
||
|
||
default:
|
||
NetStatus = ERROR_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// fall through to fill other fields of the info structure.
|
||
//
|
||
|
||
|
||
//
|
||
// Return status of secure channel to PDC.
|
||
//
|
||
case (1):
|
||
|
||
|
||
//
|
||
// Fill in the return buffer
|
||
//
|
||
|
||
QueryInformation->NetlogonInfo1->netlog1_flags = 0;
|
||
|
||
if ( TcServerDiscoveryFlags & CS_DISCOVERY_HAS_TIMESERV ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_HAS_TIMESERV;
|
||
}
|
||
|
||
if ( TcServerDiscoveryFlags & CS_DISCOVERY_HAS_IP ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_HAS_IP;
|
||
}
|
||
|
||
if ( !DnsLastStatusCheck ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_DNS_UPDATE_FAILURE;
|
||
}
|
||
|
||
if ( DomainInfo->DomRole == RolePrimary ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
|
||
NERR_Success;
|
||
} else {
|
||
PCLIENT_SESSION LocalClientSession;
|
||
|
||
LocalClientSession = NlRefDomClientSession( DomainInfo );
|
||
if ( LocalClientSession != NULL ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
|
||
NetpNtStatusToApiStatus( LocalClientSession->CsConnectionStatus);
|
||
NlUnrefClientSession( LocalClientSession );
|
||
} else {
|
||
QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
|
||
ERROR_NOT_SUPPORTED;
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
NetStatus = NERR_Success;
|
||
|
||
//
|
||
// Free up locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
if( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( SamAccountName != NULL ) {
|
||
NetApiBufferFree( SamAccountName );
|
||
}
|
||
if ( SamDomainName != NULL ) {
|
||
NetApiBufferFree( SamDomainName );
|
||
}
|
||
|
||
if ( NetStatus != NERR_Success ) {
|
||
|
||
if ( QueryInformation->NetlogonInfo1 != NULL ) {
|
||
MIDL_user_free( QueryInformation->NetlogonInfo1 );
|
||
QueryInformation->NetlogonInfo1 = NULL;
|
||
}
|
||
|
||
if ( TDCName != NULL ) {
|
||
MIDL_user_free( TDCName );
|
||
}
|
||
if ( TrustedDomainName != NULL ) {
|
||
MIDL_user_free( TrustedDomainName );
|
||
}
|
||
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
if ( DcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( DcCacheEntry );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
VOID
|
||
NlFreePingContext(
|
||
IN PNL_GETDC_CONTEXT PingContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Free the context used to perform DC pings.
|
||
|
||
Arguments:
|
||
|
||
PingContext - Context used to perform the pings.
|
||
|
||
--*/
|
||
{
|
||
if ( PingContext != NULL ) {
|
||
NetpDcUninitializeContext( PingContext );
|
||
NetApiBufferFree( PingContext );
|
||
}
|
||
}
|
||
|
||
NET_API_STATUS
|
||
NlPingDcName (
|
||
IN PCLIENT_SESSION ClientSession,
|
||
IN ULONG DcNamePingFlags,
|
||
IN BOOL CachePingedDc,
|
||
IN BOOL RequireIp,
|
||
IN BOOL DoPingWithAccount,
|
||
IN BOOL RefreshClientSession,
|
||
IN LPWSTR DcName OPTIONAL,
|
||
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Ping the specified DC using the appropriate ping mechanism.
|
||
|
||
If RefreshClientSession is TRUE, the caller must be the writer
|
||
of the passed client session.
|
||
|
||
Arguments:
|
||
|
||
ClientSession - The client session to ping a DC for. If DcName is
|
||
NULL, the server from ClientSession is pinged. If DcName isn't
|
||
NULL, ClientSession is used to get the session info (other than
|
||
the server name) needed to perform the pings.
|
||
|
||
DcNamePingFlags - Specifies properties of DcName. Can be
|
||
DS_PING_NETBIOS_HOST or DS_PING_NETBIOS_HOST or zero. If other than
|
||
zero, only the specified ping mechanism will be used.
|
||
|
||
CachePingedDc - If TRUE, the successfully pinged DC will be cached
|
||
for future use by DsGetDcName.
|
||
|
||
RequireIp - TRUE if pinging the DC must be done using only IP enabled
|
||
transports.
|
||
|
||
DoPingWithAccount - If TRUE, the account name for this machine will be
|
||
specified in the pings.
|
||
|
||
RefreshClientSession - If TRUE, the client session will be refreshed
|
||
using the ping response info. If TRUE, the caller must be the
|
||
writer of the client session.
|
||
|
||
DcName - If set, that DC name will be pinged using info from ClientSession.
|
||
|
||
NlDcCacheEntry - Returns the data structure describing response received
|
||
from the DC. Should be freed by calling NetpDcDerefCacheEntry.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - Success.
|
||
|
||
ERROR_NO_LOGON_SERVERS - No DC could be found
|
||
|
||
ERROR_NO_SUCH_USER - Returned if we do ping with account and the DC doesn't
|
||
have the account specified.
|
||
|
||
ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
|
||
domain controller of the specified domain.
|
||
|
||
ERROR_SERVICE_NOT_ACTIVE - The netlogon service is paused on the server.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
LPWSTR LocalDcName = NULL;
|
||
LPWSTR CapturedDnsForestName = NULL;
|
||
ULONG AllowableAccountControlBits;
|
||
|
||
NL_GETDC_CONTEXT Context;
|
||
BOOL TriedContextInitialization = FALSE;
|
||
ULONG Flags = 0;
|
||
ULONG InternalFlags = 0;
|
||
PDNS_RECORD DnsRecords = NULL;
|
||
LPSOCKET_ADDRESS SockAddresses = NULL;
|
||
LPSOCKET_ADDRESS AllocatedSockAddresses = NULL;
|
||
SOCKET_ADDRESS OneSockAddress;
|
||
SOCKADDR_IN OneSockAddressIn;
|
||
ULONG SockAddressCount = 0;
|
||
ULONG LoopIndex;
|
||
|
||
PNL_DC_CACHE_ENTRY LocalNlDcCacheEntry = NULL;
|
||
|
||
//
|
||
// If a DC name specified, try to figure out its properties
|
||
//
|
||
|
||
if ( DcName != NULL ) {
|
||
|
||
//
|
||
// Prove below that the name is valid
|
||
//
|
||
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
|
||
//
|
||
// If the DC name is a syntactically valid DNS name,
|
||
// assume the server name is a DNS name.
|
||
//
|
||
// Skip this step if we are told that the name is Netbios.
|
||
//
|
||
|
||
if ( (DcNamePingFlags & DS_PING_NETBIOS_HOST) == 0 &&
|
||
NetpDcValidDnsDomain(DcName) ) {
|
||
|
||
NetStatus = NO_ERROR;
|
||
|
||
//
|
||
// Get the IP address of the server from DNS
|
||
//
|
||
|
||
NetStatus = DnsQuery_W( DcName,
|
||
DNS_TYPE_A,
|
||
0,
|
||
NULL, // No list of DNS servers
|
||
&DnsRecords,
|
||
NULL );
|
||
|
||
//
|
||
// On success, set to use ldap pings. Otherwise, do not
|
||
// error out, rather try the mailslot mechanism
|
||
//
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
NetStatus = NetpSrvProcessARecords( DnsRecords,
|
||
NULL,
|
||
0,
|
||
&SockAddressCount,
|
||
&AllocatedSockAddresses );
|
||
|
||
if ( NetStatus == NO_ERROR && SockAddressCount > 0 ) {
|
||
SockAddresses = AllocatedSockAddresses;
|
||
InternalFlags |= DS_PING_USING_LDAP;
|
||
InternalFlags |= DS_PING_DNS_HOST;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the DC name is syntactically valid Netbios name,
|
||
// assume you can use mailslot pings
|
||
//
|
||
// Skip this step if we are told that the name is DNS.
|
||
//
|
||
|
||
if ( (DcNamePingFlags & DS_PING_DNS_HOST) == 0 &&
|
||
NetpIsComputerNameValid(DcName) &&
|
||
wcslen(DcName) <= CNLEN ) { // NetpIsComputerNameValid doesn't require 15 chacter limit
|
||
|
||
NetStatus = NO_ERROR;
|
||
InternalFlags |= DS_PING_USING_MAILSLOT;
|
||
InternalFlags |= DS_PING_NETBIOS_HOST;
|
||
}
|
||
|
||
//
|
||
// If there is no ping mechanism to use, error out
|
||
//
|
||
|
||
if ( (InternalFlags & (DS_PING_USING_LDAP|DS_PING_USING_MAILSLOT)) == 0 ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlPingDcName: No ping mechanism for %ws 0x%lx\n",
|
||
DcName, NetStatus ));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
LocalDcName = DcName;
|
||
}
|
||
|
||
//
|
||
// If this is a client session to a domain in our
|
||
// forest (always the case on workstation),
|
||
// get our forest name.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ||
|
||
(ClientSession->CsFlags & CS_DOMAIN_IN_FOREST) != 0 ) {
|
||
|
||
CapturedDnsForestName = LocalAlloc( LMEM_ZEROINIT,
|
||
(NL_MAX_DNS_LENGTH+1)*sizeof(WCHAR) );
|
||
if ( CapturedDnsForestName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
NlCaptureDnsForestName( CapturedDnsForestName );
|
||
}
|
||
|
||
//
|
||
// Capture the needed info from the Client session
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
|
||
//
|
||
// Capture the server name if the one wasn't passed.
|
||
// If the client session is idle, this will fail.
|
||
//
|
||
|
||
if ( DcName == NULL ) {
|
||
ULONG DiscoveryFlags = 0;
|
||
|
||
Status = NlCaptureServerClientSession( ClientSession,
|
||
&LocalDcName,
|
||
&DiscoveryFlags );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlPingDcName: Cannot NlCaptureServerClientSession %ld\n",
|
||
Status ));
|
||
|
||
if ( Status == STATUS_NO_MEMORY ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
} else {
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( DiscoveryFlags & CS_DISCOVERY_DNS_SERVER ) {
|
||
InternalFlags |= DS_PING_DNS_HOST;
|
||
} else {
|
||
InternalFlags |= DS_PING_NETBIOS_HOST;
|
||
}
|
||
|
||
if ( DiscoveryFlags & CS_DISCOVERY_USE_LDAP ) {
|
||
InternalFlags |= DS_PING_USING_LDAP;
|
||
|
||
//
|
||
// Capture the cached server socket address
|
||
//
|
||
NlAssert( ClientSession->CsServerSockAddr.iSockaddrLength != 0 );
|
||
OneSockAddress.iSockaddrLength = ClientSession->CsServerSockAddr.iSockaddrLength;
|
||
OneSockAddress.lpSockaddr = (LPSOCKADDR) &OneSockAddressIn;
|
||
RtlCopyMemory( OneSockAddress.lpSockaddr,
|
||
ClientSession->CsServerSockAddr.lpSockaddr,
|
||
ClientSession->CsServerSockAddr.iSockaddrLength );
|
||
|
||
SockAddresses = &OneSockAddress;
|
||
SockAddressCount = 1;
|
||
}
|
||
|
||
if ( DiscoveryFlags & CS_DISCOVERY_USE_MAILSLOT ) {
|
||
InternalFlags |= DS_PING_USING_MAILSLOT;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Determine the Account type we're looking for.
|
||
//
|
||
|
||
InternalFlags |= DS_IS_TRUSTED_DOMAIN;
|
||
switch ( ClientSession->CsSecureChannelType ) {
|
||
case WorkstationSecureChannel:
|
||
AllowableAccountControlBits = USER_WORKSTATION_TRUST_ACCOUNT;
|
||
InternalFlags |= DS_IS_PRIMARY_DOMAIN;
|
||
break;
|
||
|
||
case TrustedDomainSecureChannel:
|
||
AllowableAccountControlBits = USER_INTERDOMAIN_TRUST_ACCOUNT;
|
||
break;
|
||
|
||
case TrustedDnsDomainSecureChannel:
|
||
AllowableAccountControlBits = USER_DNS_DOMAIN_TRUST_ACCOUNT;
|
||
break;
|
||
|
||
case ServerSecureChannel:
|
||
AllowableAccountControlBits = USER_SERVER_TRUST_ACCOUNT;
|
||
Flags |= DS_PDC_REQUIRED;
|
||
InternalFlags |= DS_IS_PRIMARY_DOMAIN;
|
||
break;
|
||
|
||
default:
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlPingDcName: invalid SecureChannelType %ld\n",
|
||
ClientSession->CsSecureChannelType ));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Indicate whether IP transport is required
|
||
//
|
||
|
||
if ( RequireIp ) {
|
||
Flags |= DS_IP_REQUIRED;
|
||
}
|
||
|
||
//
|
||
// Initialize the ping context.
|
||
//
|
||
|
||
TriedContextInitialization = TRUE;
|
||
NetStatus = NetpDcInitializeContext(
|
||
ClientSession->CsDomainInfo, // SendDatagramContext
|
||
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
|
||
#ifdef DONT_REQUIRE_MACHINE_ACCOUNT // useful for number of trust testing
|
||
NULL,
|
||
#else // DONT_REQUIRE_MACHINE_ACCOUNT
|
||
DoPingWithAccount ? // Specify the account name as directed
|
||
ClientSession->CsAccountName :
|
||
NULL,
|
||
#endif // DONT_REQUIRE_MACHINE_ACCOUNT
|
||
DoPingWithAccount ? // Specify the account control bits as directed
|
||
AllowableAccountControlBits :
|
||
0,
|
||
ClientSession->CsNetbiosDomainName.Buffer,
|
||
ClientSession->CsDnsDomainName.Buffer,
|
||
CapturedDnsForestName,
|
||
ClientSession->CsDomainId,
|
||
ClientSession->CsDomainGuid,
|
||
NULL,
|
||
DcName == NULL ?
|
||
LocalDcName+2 : // Skip '\\' in the DC name
|
||
LocalDcName, // captured from the client session
|
||
SockAddresses,
|
||
SockAddressCount,
|
||
Flags,
|
||
InternalFlags,
|
||
NL_GETDC_CONTEXT_INITIALIZE_FLAGS | NL_GETDC_CONTEXT_INITIALIZE_PING,
|
||
&Context );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlPingDcName: Cannot NetpDcInitializeContext 0x%lx\n",
|
||
NetStatus ));
|
||
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
goto Cleanup;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
|
||
|
||
//
|
||
// Ping the DC and get a response from it
|
||
//
|
||
// If we ping using a cached IP address, it's possible that server's
|
||
// IP address changed. For this case, if we fail to ping the server
|
||
// on the first try, we will refresh the address by querying DNS
|
||
// and then we will retry to ping the server.
|
||
//
|
||
|
||
for ( LoopIndex = 0; LoopIndex < 2; LoopIndex++ ) {
|
||
NET_API_STATUS TmpNetStatus;
|
||
ULONG TmpSockAddressCount = 0;
|
||
ULONG Index;
|
||
|
||
if ( LocalNlDcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( LocalNlDcCacheEntry );
|
||
LocalNlDcCacheEntry = NULL;
|
||
}
|
||
|
||
NetStatus = NlPingDcNameWithContext(
|
||
&Context,
|
||
MAX_DC_RETRIES, // send 2 pings
|
||
TRUE, // wait for response
|
||
(NL_DC_MAX_TIMEOUT + NlGlobalParameters.ExpectedDialupDelay*1000), // timeout
|
||
NULL, // Don't care which domain name matched
|
||
&LocalNlDcCacheEntry );
|
||
|
||
//
|
||
// If we pinged successfully or
|
||
// we got hard error or
|
||
// we didn't do LDAP pings or
|
||
// we already did DNS queries because DC name was passed or
|
||
// this is the second attempt to ping the DC,
|
||
//
|
||
// we are done
|
||
//
|
||
if ( NetStatus == NO_ERROR ||
|
||
NetStatus == ERROR_NOT_ENOUGH_MEMORY ||
|
||
SockAddresses == NULL ||
|
||
DcName != NULL ||
|
||
LoopIndex == 1 ) {
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Attempt to get fresh DNS records
|
||
//
|
||
TmpNetStatus = DnsQuery_W( LocalDcName+2, // Skip '\\' in the DC name
|
||
DNS_TYPE_A,
|
||
0,
|
||
NULL, // No list of DNS servers
|
||
&DnsRecords,
|
||
NULL );
|
||
|
||
//
|
||
// Bail on error
|
||
//
|
||
if ( TmpNetStatus != NO_ERROR ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Process fresh DNS records
|
||
//
|
||
TmpNetStatus = NetpSrvProcessARecords( DnsRecords,
|
||
NULL,
|
||
0, // force port to be zero
|
||
&TmpSockAddressCount,
|
||
&AllocatedSockAddresses );
|
||
//
|
||
// Bail on error
|
||
//
|
||
if ( TmpNetStatus != NO_ERROR || TmpSockAddressCount == 0 ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Check if there are new addresses we didn't try
|
||
//
|
||
SockAddressCount = 0;
|
||
for ( Index = 0; Index < TmpSockAddressCount; Index++ ) {
|
||
|
||
//
|
||
// Keep this entry if it's not the one we had
|
||
//
|
||
if ( AllocatedSockAddresses[Index].iSockaddrLength != OneSockAddress.iSockaddrLength ||
|
||
!RtlEqualMemory(AllocatedSockAddresses[Index].lpSockaddr,
|
||
OneSockAddress.lpSockaddr,
|
||
OneSockAddress.iSockaddrLength) ) {
|
||
|
||
AllocatedSockAddresses[SockAddressCount] = AllocatedSockAddresses[Index];
|
||
SockAddressCount ++;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Bail if we didn't get any new addresses
|
||
//
|
||
if ( SockAddressCount == 0 ) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// We have new addresses. Free the current
|
||
// address list and add the new addresses
|
||
//
|
||
NetpDcFreeAddressList( &Context );
|
||
TmpNetStatus = NetpDcProcessAddressList( &Context,
|
||
LocalDcName+2,
|
||
AllocatedSockAddresses,
|
||
SockAddressCount,
|
||
FALSE, // Don't know if site specific
|
||
NULL );
|
||
|
||
//
|
||
// Bail on error. Otherwise, retry pinging
|
||
// given the new addresses.
|
||
//
|
||
if ( TmpNetStatus != NO_ERROR ) {
|
||
break;
|
||
}
|
||
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlPingDcName: Retry DC ping for %lu new addresses\n",
|
||
SockAddressCount ));
|
||
}
|
||
|
||
//
|
||
// Cache this DC if asked
|
||
//
|
||
|
||
if ( NetStatus == NO_ERROR && CachePingedDc ) {
|
||
|
||
//
|
||
// Set the FORCE flag so that the old entry (if any) gets replaced
|
||
//
|
||
Context.QueriedFlags |= DS_FORCE_REDISCOVERY;
|
||
NetpDcInsertCacheEntry( &Context, LocalNlDcCacheEntry );
|
||
|
||
NlPrintCs(( NL_MISC, ClientSession,
|
||
"NlPingDcName: %ws: %ws: Caching pinged DC info for %ws\n",
|
||
Context.NlDcDomainEntry->UnicodeNetbiosDomainName,
|
||
Context.NlDcDomainEntry->UnicodeDnsDomainName,
|
||
LocalDcName ));
|
||
}
|
||
|
||
//
|
||
// Refresh the client session, if asked
|
||
//
|
||
|
||
if ( NetStatus == NO_ERROR && RefreshClientSession ) {
|
||
NetStatus = NlSetServerClientSession(
|
||
ClientSession,
|
||
LocalNlDcCacheEntry,
|
||
DoPingWithAccount, // was discovery with account?
|
||
TRUE ); // session refresh
|
||
}
|
||
|
||
//
|
||
// Return the cache entry
|
||
//
|
||
|
||
if ( NetStatus == NO_ERROR && NlDcCacheEntry != NULL ) {
|
||
*NlDcCacheEntry = LocalNlDcCacheEntry;
|
||
LocalNlDcCacheEntry = NULL;
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
if ( DnsRecords != NULL ) {
|
||
DnsRecordListFree( DnsRecords, DnsFreeRecordListDeep );
|
||
}
|
||
|
||
if ( AllocatedSockAddresses != NULL ) {
|
||
LocalFree( AllocatedSockAddresses );
|
||
}
|
||
|
||
if ( DcName == NULL && LocalDcName != NULL ) {
|
||
NetApiBufferFree( LocalDcName );
|
||
}
|
||
|
||
if ( TriedContextInitialization ) {
|
||
NetpDcUninitializeContext( &Context );
|
||
}
|
||
|
||
if ( LocalNlDcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( LocalNlDcCacheEntry );
|
||
}
|
||
|
||
if ( CapturedDnsForestName != NULL ) {
|
||
LocalFree( CapturedDnsForestName );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NlGetAnyDCName (
|
||
IN PCLIENT_SESSION ClientSession,
|
||
IN BOOL RequireIp,
|
||
IN BOOL EnsureDcHasOurAccount,
|
||
OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry,
|
||
OUT PBOOLEAN DcRediscovered
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Get the name of the any domain controller for a trusted domain.
|
||
|
||
The domain controller found in guaranteed to have be up at one point during
|
||
this API call. The machine is also guaranteed to be a DC in the domain
|
||
specified.
|
||
|
||
The caller of this routine should not have any locks held (it calls the
|
||
LSA back in several instances). This routine may take some time to execute.
|
||
|
||
The caller must be a writer of the ClientSession.
|
||
|
||
Arguments:
|
||
|
||
ClientSession - Structure describing the session to the domain whose
|
||
DC is to be returned.
|
||
|
||
RequireIp - TRUE if communication with the DC must be done using only
|
||
IP enabled transports.
|
||
|
||
EnsureDcHasOurAccount - If TRUE this routine will verify that the returned
|
||
DC has an account for this machine.
|
||
|
||
NlDcCacheEntry - Returns the data structure describing response received
|
||
from the server. Should be freed by calling NetpDcDerefCacheEntry
|
||
|
||
DcRediscovered - Returns whether a new DC has been discovered
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - Success. Buffer contains DC name prefixed by \\.
|
||
|
||
STATUS_NO_LOGON_SERVERS - No DC could be found
|
||
|
||
STATUS_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.
|
||
|
||
STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
|
||
broken.
|
||
|
||
STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
|
||
broken or the password is broken.
|
||
|
||
STATUS_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
|
||
domain controller of the specified domain.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
BOOLEAN DiscoveryDone = FALSE;
|
||
PNL_DC_CACHE_ENTRY NlLocalDcCacheEntry = NULL;
|
||
|
||
//
|
||
// Don't give up unless we've done discovery.
|
||
//
|
||
|
||
do {
|
||
|
||
//
|
||
// If we don't currently know the name of the server,
|
||
// discover one.
|
||
//
|
||
|
||
if ( ClientSession->CsState == CS_IDLE ) {
|
||
|
||
//
|
||
// If we've tried to authenticate recently,
|
||
// don't bother trying again.
|
||
//
|
||
|
||
if ( !NlTimeToReauthenticate( ClientSession ) ) {
|
||
Status = ClientSession->CsConnectionStatus;
|
||
goto Cleanup;
|
||
|
||
}
|
||
|
||
Status = NlDiscoverDc( ClientSession,
|
||
DT_Synchronous,
|
||
FALSE,
|
||
EnsureDcHasOurAccount ?
|
||
TRUE :
|
||
FALSE );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NlGetAnyDCName: Discovery failed %lx\n",
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
DiscoveryDone = TRUE;
|
||
|
||
}
|
||
|
||
//
|
||
// Try multiple times to get a response from the DC using the appropriate protocol.
|
||
//
|
||
|
||
if ( NlLocalDcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( NlLocalDcCacheEntry );
|
||
NlLocalDcCacheEntry = NULL;
|
||
}
|
||
|
||
NetStatus = NlPingDcName( ClientSession,
|
||
0, // Use ping mechanism specified in ClientSession
|
||
FALSE, // Don't cache this DC (it is already cached)
|
||
RequireIp, // Require IP as the caller said
|
||
EnsureDcHasOurAccount,
|
||
TRUE, // Refresh the session since we are the writer
|
||
NULL, // Ping the server specified in ClientSession
|
||
&NlLocalDcCacheEntry );
|
||
|
||
//
|
||
// If we couldn't ping the DC when IP was required, see if we can ping it at all.
|
||
// If we can't ping it at all, this DC is dead, so drop it. Otherwise, the DC is
|
||
// alive (so we won't drop it), but the caller is out of luck.
|
||
//
|
||
|
||
if ( NetStatus == ERROR_NO_LOGON_SERVERS && RequireIp ) {
|
||
NET_API_STATUS TmpNetStatus;
|
||
|
||
TmpNetStatus = NlPingDcName( ClientSession,
|
||
0, // Use ping mechanism specified in ClientSession
|
||
FALSE, // Don't cache this DC (it is already cached)
|
||
FALSE, // Do not require IP
|
||
FALSE, // Don't specify account name
|
||
TRUE, // Refresh the session since we are the writer
|
||
NULL, // Ping the server specified in ClientSession
|
||
NULL ); // No cache entry needed
|
||
|
||
//
|
||
// Don't drop this DC if it's alive
|
||
//
|
||
if ( TmpNetStatus == NO_ERROR ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlGetAnyDCName: IP is required but only non-IP is available for %ws\n",
|
||
ClientSession->CsUncServerName ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Drop through and re-discover a new DC
|
||
//
|
||
}
|
||
|
||
if ( NetStatus == NO_ERROR ) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
} else {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NlGetAnyDCName: Can't ping the DC %ws 0x%lx.\n",
|
||
ClientSession->CsUncServerName, NetStatus ));
|
||
}
|
||
|
||
//
|
||
// Drop the secure channel to force the next iteration to discover
|
||
// a new dc
|
||
//
|
||
|
||
if ( !DiscoveryDone ) {
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NlGetAnyDCName: Current DC '%ws' no longer available. (rediscover)\n",
|
||
ClientSession->CsUncServerName ));
|
||
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
|
||
}
|
||
|
||
} while ( !DiscoveryDone );
|
||
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetAnyDCName: Failed\n" ));
|
||
|
||
//
|
||
// Free any locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
//
|
||
// Don't divulge too much to the caller.
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ) {
|
||
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
|
||
}
|
||
|
||
//
|
||
// Return the DC info to the caller.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
*NlDcCacheEntry = NlLocalDcCacheEntry;
|
||
if ( DcRediscovered != NULL ) {
|
||
*DcRediscovered = DiscoveryDone;
|
||
}
|
||
} else if ( NlLocalDcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( NlLocalDcCacheEntry );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrGetAnyDCName (
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR DomainName OPTIONAL,
|
||
OUT LPWSTR *Buffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Get the name of the any domain controller for a trusted domain.
|
||
|
||
The domain controller found in guaranteed to have be up at one point during
|
||
this API call.
|
||
|
||
Arguments:
|
||
|
||
ServerName - name of remote server (null for local)
|
||
|
||
DomainName - name of domain (null for primary domain)
|
||
|
||
Buffer - Returns a pointer to an allcated buffer containing the
|
||
servername of a DC of the domain. The server name is prefixed
|
||
by \\. The buffer should be deallocated using NetApiBufferFree.
|
||
|
||
Return Value:
|
||
|
||
ERROR_SUCCESS - Success. Buffer contains DC name prefixed by \\.
|
||
|
||
ERROR_NO_LOGON_SERVERS - No DC could be found
|
||
|
||
ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain.
|
||
|
||
ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is
|
||
broken.
|
||
|
||
ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is
|
||
broken or the password is broken.
|
||
|
||
ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper
|
||
domain controller of the specified domain.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
LPWSTR TmpUncServerName = NULL;
|
||
|
||
UNICODE_STRING DomainNameString;
|
||
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
PNL_DC_CACHE_ENTRY NlDcCacheEntry = NULL;
|
||
BOOL AmWriter = FALSE;
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Fill in the primary domain name if the caller didn't specify one.
|
||
//
|
||
|
||
if ( DomainName == NULL || *DomainName == L'\0' ) {
|
||
RtlInitUnicodeString( &DomainNameString, DomainInfo->DomUnicodeDomainName );
|
||
} else {
|
||
RtlInitUnicodeString( &DomainNameString, DomainName );
|
||
}
|
||
|
||
//
|
||
// On the PDC or BDC,
|
||
// find the Client session for the domain.
|
||
// On workstations,
|
||
// find the primary domain client session.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainNameString,
|
||
NL_DIRECT_TRUST_REQUIRED,
|
||
NULL );
|
||
|
||
if ( ClientSession == NULL ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrGetAnyDCName: %ws: No such trusted domain\n",
|
||
DomainName ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Become a writer of the client session.
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NetrGetAnyDCName: Can't become writer of client session.\n"));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
AmWriter = TRUE;
|
||
|
||
//
|
||
// Call the internal routine to do the actual work.
|
||
//
|
||
// Ensure that the responding DC has our account as required by this API
|
||
//
|
||
|
||
Status = NlGetAnyDCName( ClientSession,
|
||
FALSE, // IP is not required
|
||
TRUE, // Do with-account discovery
|
||
&NlDcCacheEntry,
|
||
NULL ); // don't care if the DC was rediscovered
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Prefer Netbios DC name for this old API
|
||
//
|
||
|
||
if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) {
|
||
NetStatus = NetApiBufferAllocate(
|
||
(wcslen(NlDcCacheEntry->UnicodeNetbiosDcName) + 3) * sizeof(WCHAR),
|
||
&TmpUncServerName );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Cleanup;
|
||
}
|
||
wcscpy( TmpUncServerName, L"\\\\" );
|
||
wcscpy( TmpUncServerName+2, NlDcCacheEntry->UnicodeNetbiosDcName );
|
||
} else {
|
||
NetStatus = NetApiBufferAllocate(
|
||
(wcslen(NlDcCacheEntry->UnicodeDnsHostName) + 3) * sizeof(WCHAR),
|
||
&TmpUncServerName );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
goto Cleanup;
|
||
}
|
||
wcscpy( TmpUncServerName, L"\\\\" );
|
||
wcscpy( TmpUncServerName+2, NlDcCacheEntry->UnicodeDnsHostName );
|
||
}
|
||
|
||
*Buffer = TmpUncServerName;
|
||
NetStatus = NERR_Success;
|
||
|
||
Cleanup:
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
if ( ClientSession != NULL ) {
|
||
if ( AmWriter ) {
|
||
NlResetWriterClientSession( ClientSession );
|
||
}
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( NlDcCacheEntry != NULL ) {
|
||
NetpDcDerefCacheEntry( NlDcCacheEntry );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
|
||
BOOLEAN
|
||
NlAllocOneDomainInfo(
|
||
IN LPWSTR DomainName OPTIONAL,
|
||
IN LPWSTR DnsDomainName OPTIONAL,
|
||
IN LPWSTR DnsForestName OPTIONAL,
|
||
IN GUID *DomainGuid OPTIONAL,
|
||
IN PSID DomainSid OPTIONAL,
|
||
OUT PNETLOGON_ONE_DOMAIN_INFO OneDomainInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function fill in a NETLOGON_ONE_DOMAIN_INFO structure suitable for
|
||
returning from an RPC server routine.
|
||
|
||
Arguments:
|
||
|
||
Sundry parameters to fill in.
|
||
|
||
OneDomainInfo - Pointer to an already allocated structure to fill in.
|
||
|
||
Return Value:
|
||
|
||
TRUE - success
|
||
FALSE - couldn't allocate memory
|
||
|
||
--*/
|
||
{
|
||
IN ULONG DomainSidSize;
|
||
|
||
// Copy Netbios Domainname
|
||
if ( !NlAllocStringFromWStr(
|
||
DomainName,
|
||
&OneDomainInfo->DomainName ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
// Copy DNS domain name
|
||
if ( !NlAllocStringFromWStr(
|
||
DnsDomainName,
|
||
&OneDomainInfo->DnsDomainName ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
// Copy DNS tree name
|
||
if ( !NlAllocStringFromWStr(
|
||
DnsForestName,
|
||
&OneDomainInfo->DnsForestName ) ) {
|
||
return FALSE;
|
||
}
|
||
|
||
// Copy Domain GUID
|
||
if ( DomainGuid != NULL ) {
|
||
OneDomainInfo->DomainGuid = *DomainGuid;
|
||
}
|
||
|
||
// Copy DomainSid
|
||
if ( DomainSid != NULL ) {
|
||
DomainSidSize = RtlLengthSid( DomainSid );
|
||
OneDomainInfo->DomainSid = MIDL_user_allocate( DomainSidSize );
|
||
if ( OneDomainInfo->DomainSid == NULL ) {
|
||
return FALSE;
|
||
}
|
||
RtlCopyMemory( OneDomainInfo->DomainSid, DomainSid, DomainSidSize );
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
NlFreeOneDomainInfo(
|
||
IN PNETLOGON_ONE_DOMAIN_INFO OneDomainInfo
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function free all buffers allocated from a NETLOGON_ONE_DOMAIN_INFO structure
|
||
|
||
Arguments:
|
||
|
||
OneDomainInfo - Pointer to an already allocated structure to fill in.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
if ( OneDomainInfo->DomainName.Buffer != NULL ) {
|
||
MIDL_user_free( OneDomainInfo->DomainName.Buffer );
|
||
}
|
||
if ( OneDomainInfo->DnsDomainName.Buffer != NULL ) {
|
||
MIDL_user_free( OneDomainInfo->DnsDomainName.Buffer );
|
||
}
|
||
if ( OneDomainInfo->DnsForestName.Buffer != NULL ) {
|
||
MIDL_user_free( OneDomainInfo->DnsForestName.Buffer );
|
||
}
|
||
if ( OneDomainInfo->TrustExtension.Buffer != NULL ) {
|
||
MIDL_user_free( OneDomainInfo->TrustExtension.Buffer );
|
||
}
|
||
if ( OneDomainInfo->DomainSid != NULL ) {
|
||
MIDL_user_free( OneDomainInfo->DomainSid );
|
||
}
|
||
return;
|
||
}
|
||
|
||
NTSTATUS
|
||
NetrLogonDummyRoutine1(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD QueryLevel,
|
||
OUT PNETLOGON_DUMMY1 Buffer
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is never called.
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
--*/
|
||
{
|
||
return STATUS_NOT_IMPLEMENTED;
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
UNREFERENCED_PARAMETER( ComputerName );
|
||
UNREFERENCED_PARAMETER( Authenticator );
|
||
UNREFERENCED_PARAMETER( ReturnAuthenticator );
|
||
UNREFERENCED_PARAMETER( QueryLevel );
|
||
UNREFERENCED_PARAMETER( Buffer );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
NetrLogonGetDomainInfo(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR ComputerName,
|
||
IN PNETLOGON_AUTHENTICATOR Authenticator,
|
||
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
|
||
IN DWORD QueryLevel,
|
||
PNETLOGON_WORKSTATION_INFORMATION InBuffer,
|
||
OUT PNETLOGON_DOMAIN_INFORMATION OutBuffer
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used by an NT workstation to query information about the
|
||
domain it is a member of.
|
||
|
||
Arguments:
|
||
|
||
ServerName -- Name of the DC to retrieve the data from.
|
||
|
||
ComputerName -- Name of the workstation making the call.
|
||
|
||
Authenticator -- supplied by the workstation.
|
||
|
||
ReturnAuthenticator -- Receives an authenticator returned by the DC.
|
||
|
||
QueryLevel - Level of information to return from the DC. Valid values are:
|
||
|
||
1: Return NETLOGON_DOMAIN_INFO structure.
|
||
|
||
InBuffer - Buffer to pass to DC
|
||
|
||
OutBuffer - Returns a pointer to an allocated buffer containing the queried
|
||
information.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS -- The function completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED -- The workstations should re-authenticate with
|
||
the DC.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
|
||
PSERVER_SESSION ServerSession;
|
||
SESSION_INFO SessionInfo;
|
||
LM_OWF_PASSWORD OwfPassword;
|
||
PNETLOGON_DOMAIN_INFO NetlogonDomainInfo = NULL;
|
||
PNETLOGON_LSA_POLICY_INFO NetlogonLsaPolicyInfo = NULL;
|
||
PNETLOGON_LSA_POLICY_INFO OutLsaPolicy = NULL;
|
||
PNETLOGON_WORKSTATION_INFO InWorkstationInfo = NULL;
|
||
|
||
ULONG i;
|
||
ULONG ForestTrustListCount = 0;
|
||
PULONG IndexInReturnedList = NULL;
|
||
|
||
LPWSTR PreviousDnsHostName = NULL;
|
||
|
||
BOOLEAN DomainLocked = FALSE;
|
||
BOOLEAN ClientHandlesSpn = FALSE;
|
||
BOOLEAN NeedBidirectionalTrust = FALSE;
|
||
|
||
|
||
|
||
PLIST_ENTRY ListEntry;
|
||
LPBYTE Where;
|
||
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrLogonGetDomainInfo: %ws %ld Entered\n",
|
||
ComputerName,
|
||
QueryLevel ));
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
Status = STATUS_INVALID_COMPUTER_NAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the Session key for this session.
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionInfo.SessionKey = ServerSession->SsSessionKey;
|
||
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
|
||
|
||
//
|
||
// Ensure we support the query level specified.
|
||
//
|
||
|
||
switch ( QueryLevel ) {
|
||
case NETLOGON_QUERY_DOMAIN_INFO:
|
||
|
||
//
|
||
// Determine the size of the buffer to allocate.
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalDomainCritSect );
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
DomainLocked = TRUE;
|
||
|
||
//
|
||
// Allocate the buffer to return.
|
||
//
|
||
|
||
NetlogonDomainInfo = MIDL_user_allocate( sizeof(*NetlogonDomainInfo) );
|
||
|
||
if ( NetlogonDomainInfo == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlZeroMemory( NetlogonDomainInfo, sizeof(*NetlogonDomainInfo) );
|
||
|
||
//
|
||
// Tell the caller the common set of bits we support
|
||
//
|
||
|
||
NetlogonDomainInfo->WorkstationFlags =
|
||
InBuffer->WorkstationInfo->WorkstationFlags &
|
||
NL_GET_DOMAIN_INFO_SUPPORTED;
|
||
ClientHandlesSpn =
|
||
(NetlogonDomainInfo->WorkstationFlags & NL_CLIENT_HANDLES_SPN) != 0;
|
||
NeedBidirectionalTrust =
|
||
(NetlogonDomainInfo->WorkstationFlags & NL_NEED_BIDIRECTIONAL_TRUSTS) != 0;
|
||
|
||
|
||
//
|
||
// Copy the information into the buffer.
|
||
//
|
||
// Copy the description of the primary domain.
|
||
//
|
||
EnterCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
if ( !NlAllocOneDomainInfo(
|
||
DomainInfo->DomUnicodeDomainNameString.Buffer,
|
||
DomainInfo->DomUnicodeDnsDomainNameString.Buffer,
|
||
NlGlobalUnicodeDnsForestName,
|
||
&DomainInfo->DomDomainGuidBuffer,
|
||
DomainInfo->DomAccountDomainId,
|
||
&NetlogonDomainInfo->PrimaryDomain ) ) {
|
||
|
||
LeaveCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDnsForestNameCritSect );
|
||
|
||
//
|
||
// Determine the length of the forest trust list to return
|
||
//
|
||
|
||
ForestTrustListCount = DomainInfo->DomForestTrustListCount;
|
||
|
||
//
|
||
// Check if need to exclude directly trusting domains
|
||
//
|
||
|
||
if ( !NeedBidirectionalTrust ) {
|
||
ULONG Index;
|
||
for ( Index=0; Index<DomainInfo->DomForestTrustListCount; Index++ ) {
|
||
if ( (DomainInfo->DomForestTrustList[Index].Flags &
|
||
(DS_DOMAIN_PRIMARY|DS_DOMAIN_IN_FOREST|DS_DOMAIN_DIRECT_OUTBOUND)) == 0 ) {
|
||
ForestTrustListCount--;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Copy trusted domain info
|
||
//
|
||
if ( ForestTrustListCount != 0 ) {
|
||
PNETLOGON_ONE_DOMAIN_INFO TrustedDomainInfo;
|
||
ULONG Index;
|
||
ULONG ReturnedEntryIndex = 0;
|
||
|
||
//
|
||
// If need to exclude directly trusting domains,
|
||
// allocate an array of ULONGs that will be used to keep track of the
|
||
// index of a trust entry in the returned list. This is needed to
|
||
// corectly set ParentIndex for entries returned.
|
||
//
|
||
if ( !NeedBidirectionalTrust ) {
|
||
IndexInReturnedList = LocalAlloc( LMEM_ZEROINIT,
|
||
DomainInfo->DomForestTrustListCount * sizeof(ULONG) );
|
||
|
||
if ( IndexInReturnedList == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Allocate a buffer for the trusted domain info.
|
||
//
|
||
NetlogonDomainInfo->TrustedDomains =
|
||
MIDL_user_allocate( sizeof(NETLOGON_ONE_DOMAIN_INFO) * ForestTrustListCount );
|
||
|
||
if ( NetlogonDomainInfo->TrustedDomains == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlZeroMemory( NetlogonDomainInfo->TrustedDomains,
|
||
sizeof(NETLOGON_ONE_DOMAIN_INFO) * ForestTrustListCount );
|
||
|
||
TrustedDomainInfo = NetlogonDomainInfo->TrustedDomains;
|
||
NetlogonDomainInfo->TrustedDomainCount = ForestTrustListCount;
|
||
|
||
|
||
for ( Index=0; Index<DomainInfo->DomForestTrustListCount; Index++ ) {
|
||
|
||
PNL_TRUST_EXTENSION TrustExtension;
|
||
|
||
//
|
||
// Skip this entry if need to exclude directly trusting domains.
|
||
// Otherwise, remember the index of this entry in the returned list.
|
||
//
|
||
|
||
if ( !NeedBidirectionalTrust ) {
|
||
|
||
if ( (DomainInfo->DomForestTrustList[Index].Flags &
|
||
(DS_DOMAIN_PRIMARY|DS_DOMAIN_IN_FOREST|DS_DOMAIN_DIRECT_OUTBOUND)) == 0 ) {
|
||
continue;
|
||
} else {
|
||
IndexInReturnedList[Index] = ReturnedEntryIndex;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Fill in the fixed length data
|
||
//
|
||
|
||
TrustExtension = MIDL_user_allocate( sizeof(NL_TRUST_EXTENSION) );
|
||
|
||
if ( TrustExtension == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
TrustExtension->Flags = DomainInfo->DomForestTrustList[Index].Flags;
|
||
|
||
//
|
||
// If this is a primary domain entry, determine whether it runs
|
||
// in native or mixed mode
|
||
//
|
||
if ( (DomainInfo->DomForestTrustList[Index].Flags & DS_DOMAIN_PRIMARY) &&
|
||
!SamIMixedDomain( DomainInfo->DomSamServerHandle ) ) {
|
||
TrustExtension->Flags |= DS_DOMAIN_NATIVE_MODE;
|
||
}
|
||
|
||
//
|
||
// Do not leak the new DS_DOMAIN_DIRECT_INBOUND bit to an old client;
|
||
// it can get confused otherwise. The new DS_DOMAIN_DIRECT_OUTBOUND
|
||
// bit is just the renamed old DS_DOMAIN_DIRECT_TRUST, so leave it alone.
|
||
//
|
||
if ( !NeedBidirectionalTrust ) {
|
||
TrustExtension->Flags &= ~DS_DOMAIN_DIRECT_INBOUND;
|
||
}
|
||
|
||
TrustExtension->ParentIndex = DomainInfo->DomForestTrustList[Index].ParentIndex;
|
||
TrustExtension->TrustType = DomainInfo->DomForestTrustList[Index].TrustType;
|
||
TrustExtension->TrustAttributes = DomainInfo->DomForestTrustList[Index].TrustAttributes;
|
||
|
||
TrustedDomainInfo->TrustExtension.Buffer = (LPWSTR)TrustExtension;
|
||
TrustedDomainInfo->TrustExtension.MaximumLength =
|
||
TrustedDomainInfo->TrustExtension.Length = sizeof(NL_TRUST_EXTENSION);
|
||
|
||
|
||
// Copy the description of the primary domain.
|
||
if ( !NlAllocOneDomainInfo(
|
||
DomainInfo->DomForestTrustList[Index].NetbiosDomainName,
|
||
DomainInfo->DomForestTrustList[Index].DnsDomainName,
|
||
NULL, // DNS Tree name not meaningfull
|
||
&DomainInfo->DomForestTrustList[Index].DomainGuid,
|
||
DomainInfo->DomForestTrustList[Index].DomainSid,
|
||
TrustedDomainInfo ) ) {
|
||
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
// Move on to the next trusted domain.
|
||
TrustedDomainInfo ++;
|
||
ReturnedEntryIndex ++;
|
||
|
||
}
|
||
|
||
//
|
||
// Fix ParentIndex. If need to exclude directly trusting domains,
|
||
// adjust the index to point to the appropriate entry in the
|
||
// returned list. Otherwise, leave it alone.
|
||
//
|
||
|
||
if ( !NeedBidirectionalTrust ) {
|
||
PNL_TRUST_EXTENSION TrustExtension;
|
||
TrustedDomainInfo = NetlogonDomainInfo->TrustedDomains;
|
||
for ( Index=0; Index<ForestTrustListCount; Index++ ) {
|
||
TrustExtension = (PNL_TRUST_EXTENSION)TrustedDomainInfo->TrustExtension.Buffer;
|
||
if ( (TrustExtension->Flags & DS_DOMAIN_IN_FOREST) != 0 &&
|
||
(TrustExtension->Flags & DS_DOMAIN_TREE_ROOT) == 0 ) {
|
||
TrustExtension->ParentIndex =
|
||
IndexInReturnedList[TrustExtension->ParentIndex];
|
||
}
|
||
TrustedDomainInfo ++;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Indicate that the LSA policy should be handled.
|
||
//
|
||
|
||
InWorkstationInfo = InBuffer->WorkstationInfo;
|
||
|
||
OutLsaPolicy = &NetlogonDomainInfo->LsaPolicy;
|
||
break;
|
||
|
||
case NETLOGON_QUERY_LSA_POLICY_INFO:
|
||
|
||
//
|
||
// Allocate the buffer to return.
|
||
//
|
||
|
||
NetlogonLsaPolicyInfo = MIDL_user_allocate( sizeof(*NetlogonLsaPolicyInfo) );
|
||
|
||
if ( NetlogonLsaPolicyInfo == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Indicate that the LSA policy should be handled.
|
||
//
|
||
|
||
InWorkstationInfo = InBuffer->WorkstationInfo;
|
||
OutLsaPolicy = NetlogonLsaPolicyInfo;
|
||
break;
|
||
|
||
default:
|
||
Status = STATUS_INVALID_LEVEL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If we're passing LSA policy back and forth between workstation/DC,
|
||
// handle the next leg.
|
||
//
|
||
|
||
if ( InWorkstationInfo != NULL ) {
|
||
OSVERSIONINFOEXW OsVersionInfoEx;
|
||
POSVERSIONINFOEXW OsVersionInfoExPtr = NULL;
|
||
LPWSTR OsName;
|
||
LPWSTR AllocatedOsName = NULL;
|
||
|
||
//
|
||
// See if the caller passed the OS version to us.
|
||
//
|
||
|
||
if ( InWorkstationInfo->OsVersion.Length >= sizeof(OsVersionInfoEx) ) {
|
||
//
|
||
// Copy the version to get the alignment right
|
||
// (since RPC thinks this is a WCHAR buffer).
|
||
//
|
||
|
||
RtlCopyMemory( &OsVersionInfoEx,
|
||
InWorkstationInfo->OsVersion.Buffer,
|
||
sizeof(OsVersionInfoEx) );
|
||
|
||
OsVersionInfoExPtr = &OsVersionInfoEx;
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrLogonGetDomainInfo: %ws is running NT %ld.%ld build %ld (%ld)\n",
|
||
ComputerName,
|
||
OsVersionInfoEx.dwMajorVersion,
|
||
OsVersionInfoEx.dwMinorVersion,
|
||
OsVersionInfoEx.dwBuildNumber,
|
||
OsVersionInfoEx.wProductType ));
|
||
}
|
||
|
||
//
|
||
// See if the caller passed us an OsName.
|
||
//
|
||
|
||
if ( InWorkstationInfo->OsName.Length ) {
|
||
AllocatedOsName = LocalAlloc( 0, InWorkstationInfo->OsName.Length + sizeof(WCHAR));
|
||
|
||
if ( AllocatedOsName == NULL) {
|
||
OsName = L"Windows 2000";
|
||
} else {
|
||
RtlCopyMemory( AllocatedOsName,
|
||
InWorkstationInfo->OsName.Buffer,
|
||
InWorkstationInfo->OsName.Length );
|
||
AllocatedOsName[InWorkstationInfo->OsName.Length/sizeof(WCHAR)] = L'\0';
|
||
OsName = AllocatedOsName;
|
||
}
|
||
|
||
|
||
//
|
||
// If the caller didn't pass us its OsName,
|
||
// make one up.
|
||
// (Only pre RTM versions of WIN 2000 did this.)
|
||
//
|
||
} else {
|
||
if ( OsVersionInfoExPtr == NULL ) {
|
||
OsName = L"Windows 2000";
|
||
} else {
|
||
if ( OsVersionInfoExPtr->wProductType == VER_NT_WORKSTATION ) {
|
||
OsName = L"Windows 2000 Professional";
|
||
} else {
|
||
OsName = L"Windows 2000 Server";
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Set the DnsHostName on the computer object.
|
||
// If the client handles SPN setting, get the DnsHostName from the DS
|
||
// rather than setting it.
|
||
//
|
||
Status = LsaISetClientDnsHostName(
|
||
ComputerName,
|
||
ClientHandlesSpn ? NULL : InWorkstationInfo->DnsHostName,
|
||
OsVersionInfoExPtr,
|
||
OsName,
|
||
ClientHandlesSpn ? &PreviousDnsHostName : NULL );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonGetDomainInfo: Cannot set client DNS host name %lx (ignoring)\n",
|
||
Status ));
|
||
PreviousDnsHostName = NULL;
|
||
// This isn't fatal
|
||
}
|
||
|
||
NlPrintDom((NL_MISC, DomainInfo,
|
||
"NetrLogonGetDomainInfo: DnsHostName of %ws is %ws\n",
|
||
ComputerName,
|
||
PreviousDnsHostName ));
|
||
|
||
if ( AllocatedOsName != NULL) {
|
||
LocalFree( AllocatedOsName );
|
||
}
|
||
|
||
//
|
||
// Set the HOST/name SPN on the object as well. This
|
||
// is handled mostly by the DS side of things.
|
||
//
|
||
|
||
if ( !ClientHandlesSpn ) {
|
||
NlSetDsSPN( FALSE, // Don't wait for this to complete
|
||
TRUE, // Set the SPN
|
||
FALSE, // We've already set the Dns host name
|
||
DomainInfo,
|
||
DomainInfo->DomUncUnicodeComputerName,
|
||
ComputerName,
|
||
InWorkstationInfo->DnsHostName );
|
||
}
|
||
|
||
//
|
||
// Return the Previous DNS Host Name to the client
|
||
//
|
||
|
||
if ( NetlogonDomainInfo != NULL ) {
|
||
RtlInitUnicodeString( &NetlogonDomainInfo->DnsHostNameInDs, PreviousDnsHostName );
|
||
PreviousDnsHostName = NULL;
|
||
}
|
||
|
||
//
|
||
// Tell the caller there is no policy to return
|
||
//
|
||
OutLsaPolicy->LsaPolicySize = 0;
|
||
OutLsaPolicy->LsaPolicy = NULL;
|
||
|
||
}
|
||
|
||
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be careful to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ) {
|
||
RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
||
}
|
||
|
||
|
||
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
||
"NetrLogonGetDomainInfo: %ws %ld Returns 0x%lX\n",
|
||
ComputerName,
|
||
QueryLevel,
|
||
Status ));
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
if ( IndexInReturnedList != NULL ) {
|
||
LocalFree( IndexInReturnedList );
|
||
}
|
||
|
||
if ( PreviousDnsHostName != NULL ) {
|
||
MIDL_user_free( PreviousDnsHostName );
|
||
}
|
||
|
||
if ( NT_SUCCESS(Status)) {
|
||
if ( NetlogonDomainInfo != NULL ) {
|
||
OutBuffer->DomainInfo = NetlogonDomainInfo;
|
||
} else if ( NetlogonLsaPolicyInfo != NULL ) {
|
||
OutBuffer->LsaPolicyInfo = NetlogonLsaPolicyInfo;
|
||
}
|
||
} else {
|
||
|
||
if ( NetlogonDomainInfo != NULL ) {
|
||
|
||
NlFreeOneDomainInfo( &NetlogonDomainInfo->PrimaryDomain );
|
||
|
||
for ( i=0; i<NetlogonDomainInfo->TrustedDomainCount; i++ ) {
|
||
NlFreeOneDomainInfo( &NetlogonDomainInfo->TrustedDomains[i] );
|
||
}
|
||
|
||
if ( NetlogonDomainInfo->LsaPolicy.LsaPolicy != NULL ) {
|
||
MIDL_user_free( NetlogonDomainInfo->LsaPolicy.LsaPolicy );
|
||
}
|
||
|
||
MIDL_user_free( NetlogonDomainInfo );
|
||
}
|
||
|
||
if ( NetlogonLsaPolicyInfo != NULL ) {
|
||
|
||
if ( NetlogonLsaPolicyInfo->LsaPolicy != NULL ) {
|
||
MIDL_user_free( NetlogonLsaPolicyInfo->LsaPolicy );
|
||
}
|
||
|
||
MIDL_user_free( NetlogonLsaPolicyInfo );
|
||
}
|
||
}
|
||
|
||
if ( DomainLocked ) {
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
LeaveCriticalSection( &NlGlobalDomainCritSect );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NetrLogonSetServiceBits(
|
||
IN LPWSTR ServerName,
|
||
IN DWORD ServiceBitsOfInterest,
|
||
IN DWORD ServiceBits
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Inidcates whether this DC is currently running the specified service.
|
||
|
||
For instance,
|
||
|
||
NetLogonSetServiceBits( DS_KDC_FLAG, DS_KDC_FLAG );
|
||
|
||
tells Netlogon the KDC is running. And
|
||
|
||
NetLogonSetServiceBits( DS_KDC_FLAG, 0 );
|
||
|
||
tells Netlogon the KDC is not running.
|
||
|
||
This out of proc API can set only a certain set of bits:
|
||
DS_TIMESERV_FLAG
|
||
DS_GOOD_TIMESERV_FLAG
|
||
|
||
If other bits are attempted to be set, access denied is returned.
|
||
|
||
Arguments:
|
||
|
||
ServerName -- Name of the DC to retrieve the data from.
|
||
|
||
ServiceBitsOfInterest - A mask of the service bits being changed, set,
|
||
or reset by this call. Only the following flags are valid:
|
||
|
||
DS_KDC_FLAG
|
||
DS_DS_FLAG
|
||
DS_TIMESERV_FLAG
|
||
DS_GOOD_TIMESERV_FLAG
|
||
|
||
ServiceBits - A mask indicating what the bits specified by ServiceBitsOfInterest
|
||
should be set to.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - Success.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have permission to call this API.
|
||
|
||
STATUS_INVALID_PARAMETER - The parameters have extaneous bits set.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
|
||
//
|
||
// Out of proc callers can set only certain bits
|
||
//
|
||
|
||
if ( (ServiceBitsOfInterest & ~DS_OUTOFPROC_VALID_SERVICE_BITS) != 0 ) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_SERVICE_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
return I_NetLogonSetServiceBits( ServiceBitsOfInterest, ServiceBits );
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NlComputeMd5Digest(
|
||
IN LPBYTE Message,
|
||
IN ULONG MessageSize,
|
||
IN PNT_OWF_PASSWORD OwfPassword,
|
||
OUT CHAR MessageDigest[NL_DIGEST_SIZE]
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Compute the message digest for Message.
|
||
|
||
Arguments:
|
||
|
||
Message - The message to compute the digest for.
|
||
|
||
MessageSize - The size of Message in bytes.
|
||
|
||
OwfPassword - Password of the account to used salt the digest
|
||
|
||
MessageDigest - Returns the 128-bit digest of the message.
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NOT_SUPPORTED: MD5 is not supported on this machine.
|
||
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
NTSTATUS Status;
|
||
PCHECKSUM_FUNCTION Check;
|
||
PCHECKSUM_BUFFER CheckBuffer = NULL;
|
||
BOOL Initialized = FALSE;
|
||
|
||
//
|
||
// Locate the checksum routine for the context, loading it if necessary from the
|
||
// the crypto support DLL
|
||
//
|
||
|
||
Status = CDLocateCheckSum(KERB_CHECKSUM_MD5, &Check);
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: MD5 is not supported\n",
|
||
DomainName ));
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Initialize
|
||
//
|
||
|
||
Status = Check->Initialize(0, &CheckBuffer);
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: cannot initialize MD5 0x%lx\n",
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
Initialized = TRUE;
|
||
|
||
|
||
//
|
||
// First compute the digest of the OWF
|
||
//
|
||
Status = Check->Sum( CheckBuffer, sizeof(*OwfPassword), (PUCHAR) OwfPassword );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: cannot checksum OWF password 0x%lx\n",
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Then compute the digest of the message itself
|
||
//
|
||
Status = Check->Sum( CheckBuffer, MessageSize, Message );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: cannot checksum message 0x%lx\n",
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Grab the digest.
|
||
//
|
||
|
||
if ( Check->CheckSumSize != NL_DIGEST_SIZE ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: digest is the wrong size.\n" ));
|
||
NetStatus = ERROR_INTERNAL_ERROR;
|
||
goto Cleanup;
|
||
}
|
||
|
||
Status = Check->Finalize(CheckBuffer, MessageDigest);
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: cannot checksum message 0x%lx\n",
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Done.
|
||
//
|
||
|
||
NetStatus = NO_ERROR;
|
||
Cleanup:
|
||
if ( Initialized ) {
|
||
Status = Check->Finish(&CheckBuffer);
|
||
if (!NT_SUCCESS(Status)) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NlComputeMd5Digest: cannot finish 0x%lx\n",
|
||
Status ));
|
||
}
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonGetTrustRid(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR DomainName OPTIONAL,
|
||
OUT PULONG Rid
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns the Rid of the account that ServerName uses in its secure channel to DomainName.
|
||
|
||
Only an Admin or LocalSystem or LocalService may call this function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
DomainName - The name (DNS or Netbios) of the domain the trust is to.
|
||
NULL implies the domain the machine is a member of.
|
||
|
||
Rid - Rid is the RID of the account in the specified domain that represents the
|
||
trust relationship between the ServerName and DomainName.
|
||
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NO_SUCH_DOMAIN: The specified domain does not exist.
|
||
|
||
ERROR_NO_LOGON_SERVERS: There are currently no logon server available for the domain or
|
||
there is some problem with the secure channel.
|
||
|
||
ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus = NO_ERROR;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
UNICODE_STRING DomainNameString;
|
||
BOOL AmWriter = FALSE;
|
||
ULONG LocalRid = 0;
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_SERVICE_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
NetStatus = ERROR_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// On the PDC or BDC,
|
||
// find the Client session for the domain.
|
||
// On workstations,
|
||
// find the primary domain client session.
|
||
//
|
||
|
||
if ( DomainName == NULL ) {
|
||
DomainName = DomainInfo->DomUnicodeDomainName;
|
||
}
|
||
|
||
RtlInitUnicodeString( &DomainNameString, DomainName );
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainNameString,
|
||
NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK,
|
||
NULL );
|
||
|
||
if ( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonGetTrustRid: %ws: No such trusted domain\n",
|
||
DomainName ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Become a writer of the ClientSession.
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrintCs((NL_CRITICAL, ClientSession,
|
||
"NetrLogonGetTrustRid: Can't become writer of client session.\n" ));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
AmWriter = TRUE;
|
||
|
||
|
||
//
|
||
// If this is a server secure channel (i.e. we are a DC and
|
||
// this is our domain) we can get the RID from local SAM
|
||
//
|
||
|
||
if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) {
|
||
ULONG AccountRid = 0;
|
||
|
||
Status = NlSamOpenNamedUser( DomainInfo,
|
||
ClientSession->CsAccountName,
|
||
NULL,
|
||
&AccountRid,
|
||
NULL );
|
||
|
||
//
|
||
// Just stash it into the client session.
|
||
//
|
||
// Note that if we are a BDC, we also set RID during
|
||
// secure channel setup to our PDC, so whoever writes
|
||
// last is the winner. But hopefully the same value
|
||
// will be written in both cases.
|
||
//
|
||
if ( NT_SUCCESS(Status) ) {
|
||
ClientSession->CsAccountRid = AccountRid;
|
||
} else {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NlUpdateRole: NlSamOpenNamedUser failed 0x%lx\n",
|
||
Status ));
|
||
}
|
||
|
||
//
|
||
// For all other secure channel types, we have to
|
||
// get the RID as a side effect of setting up the
|
||
// secure channel. Note that we merely refresh RID
|
||
// here as we may already have it from a previous
|
||
// successful secure channel setup.
|
||
//
|
||
} else if ( ClientSession->CsState != CS_AUTHENTICATED &&
|
||
NlTimeToReauthenticate(ClientSession) ) {
|
||
|
||
//
|
||
// Try to set up the channel. If we can't
|
||
// don't error out, rather use RID that
|
||
// we got when on one of the previous attemps.
|
||
//
|
||
Status = NlSessionSetup( ClientSession );
|
||
}
|
||
|
||
//
|
||
// Ensure that we return non-zero RID on success
|
||
//
|
||
|
||
LocalRid = ClientSession->CsAccountRid;
|
||
|
||
if ( LocalRid != 0 ) {
|
||
*Rid = LocalRid;
|
||
NetStatus = NO_ERROR;
|
||
} else {
|
||
|
||
//
|
||
// If the trust is not NT5, this call is not supported
|
||
//
|
||
if ( (ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST) == 0 ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
} else {
|
||
NetStatus = ERROR_TRUSTED_RELATIONSHIP_FAILURE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Free any locally used resources.
|
||
//
|
||
Cleanup:
|
||
if ( AmWriter ) {
|
||
NlResetWriterClientSession( ClientSession );
|
||
}
|
||
|
||
if ( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonComputeServerDigest(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN ULONG Rid,
|
||
IN LPBYTE Message,
|
||
IN ULONG MessageSize,
|
||
OUT CHAR NewMessageDigest[NL_DIGEST_SIZE],
|
||
OUT CHAR OldMessageDigest[NL_DIGEST_SIZE]
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Compute the message digest for Message on the server.
|
||
|
||
A digest is computed given the message and the password used on
|
||
the account identified by teh account RID. Since there may be up
|
||
to 2 passwords on the account (for interdomain trust), this routine
|
||
returns 2 digets corresponding to the 2 passwords. If the account
|
||
has just one password on the server side (true for any account other
|
||
than the intedomain trust account) or the two passwords are the same
|
||
the 2 digests returned will be identical.
|
||
|
||
Only an Admin or LocalSystem or LocalService may call this function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
Rid - The RID of the account to create the digest for.
|
||
The RID must be the RID of a machine account or the API returns an error.
|
||
|
||
Message - The message to compute the digest for.
|
||
|
||
MessageSize - The size of Message in bytes.
|
||
|
||
NewMessageDigest - Returns the 128-bit digest of the message corresponding to
|
||
the new account password.
|
||
|
||
OldMessageDigest - Returns the 128-bit digest of the message corresponding to
|
||
the old account password.
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
PSID UserSid = NULL;
|
||
UNICODE_STRING UserSidString;
|
||
|
||
PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL;
|
||
SID_AND_ATTRIBUTES_LIST ReverseMembership;
|
||
|
||
LPWSTR LocalUserName = NULL;
|
||
NT_OWF_PASSWORD NewOwfPassword;
|
||
NT_OWF_PASSWORD OldOwfPassword;
|
||
ULONG AccountRid;
|
||
ULONG LocalUserAccountControl;
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_SERVICE_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonComputeServerDigest: Account %ld failed access check.\n",
|
||
Rid ));
|
||
NetStatus = ERROR_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonComputeServerDigest: Account %ld: cannot find domain for %ws\n",
|
||
Rid,
|
||
ServerName ));
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Convert the account RID to an account SID
|
||
//
|
||
|
||
NetStatus = NetpDomainIdToSid( DomainInfo->DomAccountDomainId,
|
||
Rid,
|
||
&UserSid );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeServerDigest: Account %ld: cannot convert domain ID to sid.: %ld\n",
|
||
Rid,
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Get the info about the user.
|
||
//
|
||
// Use SamIGetUserLogonInformation instead of SamrOpenUser.
|
||
// The former is more efficient (since it only does one
|
||
// DirSearch and doesn't lock the global SAM lock) and more powerful
|
||
// (since it returns UserAllInformation).
|
||
//
|
||
|
||
UserSidString.Buffer = UserSid;
|
||
UserSidString.MaximumLength =
|
||
UserSidString.Length = (USHORT) RtlLengthSid( UserSid );
|
||
|
||
Status = SamIGetUserLogonInformation(
|
||
DomainInfo->DomSamAccountDomainHandle,
|
||
SAM_NO_MEMBERSHIPS | // Don't need group memberships
|
||
SAM_OPEN_BY_SID, // Next parameter is the SID of the account
|
||
&UserSidString,
|
||
&UserAllInfo,
|
||
&ReverseMembership,
|
||
NULL );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeServerDigest: Account %ld: Cannot SamIGetUserLogonInfo 0x%lx\n",
|
||
Rid,
|
||
Status ));
|
||
if ( Status == STATUS_NOT_FOUND ||
|
||
Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
|
||
NetStatus = ERROR_NO_SUCH_USER;
|
||
} else {
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeServerDigest: %ld: %wZ: Message: ",
|
||
Rid,
|
||
&UserAllInfo->All.UserName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, Message, MessageSize );
|
||
|
||
|
||
//
|
||
// Ensure the account is a machine account.
|
||
//
|
||
|
||
if ( (UserAllInfo->All.UserAccountControl & USER_MACHINE_ACCOUNT_MASK) == 0 ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeServerDigest: Account %ld isn't a machine account\n",
|
||
Rid ));
|
||
NetStatus = ERROR_NO_SUCH_USER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( UserAllInfo->All.UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeServerDigest: Account %ld is disabled\n",
|
||
Rid ));
|
||
NetStatus = ERROR_NO_SUCH_USER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the password(s) for the account. For interdomain trust
|
||
// trust account, get both current and previous passwords
|
||
//
|
||
|
||
LocalUserName = LocalAlloc( 0, UserAllInfo->All.UserName.Length + sizeof(WCHAR) );
|
||
if ( LocalUserName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlCopyMemory( LocalUserName,
|
||
UserAllInfo->All.UserName.Buffer,
|
||
UserAllInfo->All.UserName.Length );
|
||
|
||
LocalUserName[ (UserAllInfo->All.UserName.Length)/sizeof(WCHAR) ] = UNICODE_NULL;
|
||
|
||
//
|
||
// NlGetIncomingPassword checks for the exact equality of the user account control
|
||
// to the trust account flags. Therefore pass only these flags if they are set in
|
||
// the data retuned from SAM.
|
||
//
|
||
LocalUserAccountControl = UserAllInfo->All.UserAccountControl;
|
||
if ( UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT ) {
|
||
LocalUserAccountControl = USER_INTERDOMAIN_TRUST_ACCOUNT;
|
||
}
|
||
if ( UserAllInfo->All.UserAccountControl & USER_DNS_DOMAIN_TRUST_ACCOUNT ) {
|
||
LocalUserAccountControl = USER_DNS_DOMAIN_TRUST_ACCOUNT;
|
||
}
|
||
|
||
Status = NlGetIncomingPassword(
|
||
DomainInfo,
|
||
LocalUserName,
|
||
NullSecureChannel, // The account control bits are passed next
|
||
LocalUserAccountControl,
|
||
TRUE, // Fail for disabled accounts
|
||
&NewOwfPassword,
|
||
(UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) ?
|
||
&OldOwfPassword : // Get previous password for interdomain account
|
||
NULL,
|
||
&AccountRid,
|
||
NULL, // Don't need trust attributes
|
||
NULL ); // Don't need the account type
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeServerDigest: Can't NlGetIncomingPassword for %wZ 0x%lx.\n",
|
||
&UserAllInfo->All.UserName,
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlAssert( Rid == AccountRid );
|
||
|
||
//
|
||
// If there is no old password on the account,
|
||
// use the new one
|
||
//
|
||
|
||
if ( (UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) == 0 ) {
|
||
RtlCopyMemory( &OldOwfPassword,
|
||
&NewOwfPassword,
|
||
sizeof(OldOwfPassword) );
|
||
}
|
||
|
||
//
|
||
// Compute the new message digest.
|
||
//
|
||
|
||
NetStatus = NlComputeMd5Digest( Message, MessageSize, &NewOwfPassword, NewMessageDigest );
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NetrLogonComputeServerDigest: %ld: NlComputeMd5Digest failed (1): 0x%lx\n",
|
||
Rid, NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeServerDigest: %ld: New Password: ",
|
||
Rid ));
|
||
NlpDumpBuffer(NL_ENCRYPT, &NewOwfPassword, sizeof(NewOwfPassword) );
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeServerDigest: %ld: New Digest: ",
|
||
Rid ));
|
||
NlpDumpBuffer(NL_ENCRYPT, NewMessageDigest, sizeof(NewMessageDigest) );
|
||
|
||
//
|
||
// Compute the old message digest.
|
||
//
|
||
|
||
NetStatus = NlComputeMd5Digest( Message, MessageSize, &OldOwfPassword, OldMessageDigest );
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NetrLogonComputeServerDigest: %ld: NlComputeMd5Digest failed (2): 0x%lx\n",
|
||
Rid, NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeServerDigest: %ld: Old Password: ",
|
||
Rid ));
|
||
NlpDumpBuffer(NL_ENCRYPT, &OldOwfPassword, sizeof(OldOwfPassword) );
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeServerDigest: %ld: Old Digest: ",
|
||
Rid ));
|
||
NlpDumpBuffer(NL_ENCRYPT, OldMessageDigest, sizeof(OldMessageDigest) );
|
||
|
||
//
|
||
// Free any locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
if ( UserSid != NULL ) {
|
||
NetpMemoryFree( UserSid );
|
||
}
|
||
if ( LocalUserName != NULL ) {
|
||
LocalFree( LocalUserName );
|
||
}
|
||
|
||
if ( UserAllInfo != NULL ) {
|
||
SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonComputeClientDigest(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR DomainName OPTIONAL,
|
||
IN LPBYTE Message,
|
||
IN ULONG MessageSize,
|
||
OUT CHAR NewMessageDigest[NL_DIGEST_SIZE],
|
||
OUT CHAR OldMessageDigest[NL_DIGEST_SIZE]
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Compute the message digest for Message on the client.
|
||
|
||
A digest is computed given the message and the password used on
|
||
the account identified by the domain name. Since there are two
|
||
passwords on the account on the client side, this routine
|
||
returns 2 digests corresponding to the 2 passwords. If the two
|
||
passwords are the same the 2 digests returned will be identical.
|
||
|
||
Only an Admin or LocalSystem or LocalService may call this function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
DomainName - The name (DNS or Netbios) of the domain the trust is to.
|
||
NULL implies the domain the machine is a member of.
|
||
|
||
Message - The message to compute the digest for.
|
||
|
||
MessageSize - The size of Message in bytes.
|
||
|
||
NewMessageDigest - Returns the 128-bit digest of the message corresponding
|
||
to the new password
|
||
|
||
NewMessageDigest - Returns the 128-bit digest of the message corresponding
|
||
to the new password
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus = NO_ERROR;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
UNICODE_STRING DomainNameString;
|
||
|
||
PUNICODE_STRING NewPassword = NULL;
|
||
PUNICODE_STRING OldPassword = NULL;
|
||
ULONG DummyPasswordVersionNumber;
|
||
NT_OWF_PASSWORD NewOwfPassword;
|
||
NT_OWF_PASSWORD OldOwfPassword;
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeClientDigest: %ws: Message: ",
|
||
DomainName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, Message, MessageSize );
|
||
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_SERVICE_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
NetStatus = ERROR_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
|
||
DomainInfo = NlFindDomainByServerName( ServerName );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// On the PDC or BDC,
|
||
// find the Client session for the domain.
|
||
// On workstations,
|
||
// find the primary domain client session.
|
||
//
|
||
|
||
if ( DomainName == NULL ) {
|
||
DomainName = DomainInfo->DomUnicodeDomainName;
|
||
}
|
||
|
||
RtlInitUnicodeString( &DomainNameString, DomainName );
|
||
ClientSession = NlFindNamedClientSession( DomainInfo,
|
||
&DomainNameString,
|
||
NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK,
|
||
NULL );
|
||
|
||
if ( ClientSession == NULL ) {
|
||
NlPrintDom((NL_CRITICAL, DomainInfo,
|
||
"NetrLogonComputeClientDigest: %ws: No such trusted domain\n",
|
||
DomainName ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Get the two passwords on the account in clear
|
||
//
|
||
|
||
Status = NlGetOutgoingPassword( ClientSession,
|
||
&NewPassword,
|
||
&OldPassword,
|
||
&DummyPasswordVersionNumber,
|
||
NULL ); // No need to return password set time
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NetrLogonComputeClientDigest: cannot NlGetOutgoingPassword 0x%lx\n",
|
||
Status ));
|
||
|
||
//
|
||
// Return more appropriate error.
|
||
//
|
||
if ( !NlpIsNtStatusResourceError( Status )) {
|
||
Status = STATUS_NO_TRUST_LSA_SECRET;
|
||
}
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Compute the new OWF password
|
||
//
|
||
|
||
if ( NewPassword != NULL ) {
|
||
Status = RtlCalculateNtOwfPassword( NewPassword,
|
||
&NewOwfPassword );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
//
|
||
// return more appropriate error.
|
||
//
|
||
if ( !NlpIsNtStatusResourceError( Status )) {
|
||
Status = STATUS_NO_TRUST_LSA_SECRET;
|
||
}
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If no new password exists on the account,
|
||
// use a blank password
|
||
//
|
||
|
||
} else {
|
||
UNICODE_STRING TempUnicodeString;
|
||
|
||
RtlInitUnicodeString(&TempUnicodeString, NULL);
|
||
Status = RtlCalculateNtOwfPassword( &TempUnicodeString,
|
||
&NewOwfPassword );
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"NetrLogonComputeClientDigest: %ws Cannot RtlCalculateNtOwfPassword (NULL) 0x%lx\n",
|
||
DomainName,
|
||
Status ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Compute the old OWF password
|
||
//
|
||
|
||
if ( OldPassword != NULL ) {
|
||
Status = RtlCalculateNtOwfPassword( OldPassword,
|
||
&OldOwfPassword );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
//
|
||
// return more appropriate error.
|
||
//
|
||
if ( !NlpIsNtStatusResourceError( Status )) {
|
||
Status = STATUS_NO_TRUST_LSA_SECRET;
|
||
}
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If no old password exists on the account,
|
||
// use the new password in place of the old one
|
||
//
|
||
|
||
} else {
|
||
RtlCopyMemory( &OldOwfPassword,
|
||
&NewOwfPassword,
|
||
sizeof(OldOwfPassword) );
|
||
}
|
||
|
||
|
||
//
|
||
// Compute the new message digest.
|
||
//
|
||
|
||
NetStatus = NlComputeMd5Digest( Message, MessageSize, &NewOwfPassword, NewMessageDigest );
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NetrLogonComputeClientDigest: cannot NlComputeMd5Digest (1) 0x%lx\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeClientDigest: %ws: New Password: ",
|
||
DomainName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, &NewOwfPassword, sizeof(NewOwfPassword) );
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeClientDigest: %ws: New Digest: ",
|
||
DomainName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, NewMessageDigest, sizeof(NewMessageDigest) );
|
||
|
||
//
|
||
// Compute the old message digest.
|
||
//
|
||
|
||
NetStatus = NlComputeMd5Digest( Message, MessageSize, &OldOwfPassword, OldMessageDigest );
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrintCs(( NL_CRITICAL, ClientSession,
|
||
"NetrLogonComputeClientDigest: cannot NlComputeMd5Digest (2) 0x%lx\n",
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeClientDigest: %ws: Old Password: ",
|
||
DomainName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, &OldOwfPassword, sizeof(OldOwfPassword) );
|
||
|
||
NlPrint((NL_ENCRYPT,
|
||
"NetrLogonComputeClientDigest: %ws: Old Digest: ",
|
||
DomainName ));
|
||
NlpDumpBuffer(NL_ENCRYPT, OldMessageDigest, sizeof(OldMessageDigest) );
|
||
|
||
//
|
||
// Free any locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
if ( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
if ( NewPassword != NULL ) {
|
||
LocalFree( NewPassword );
|
||
}
|
||
|
||
if ( OldPassword != NULL ) {
|
||
LocalFree( OldPassword );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NetrLogonGetTimeServiceParentDomain(
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
OUT LPWSTR *DomainName,
|
||
OUT PBOOL PdcSameSite
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Returns the domain name of the domain that is logically the "parent" of this
|
||
domain. The returned domain name is suitable for passing into the
|
||
NetLogonGetTrustRid and NetLogonComputeClientDigest API.
|
||
|
||
On a workstation or member server, the returned domain name is that of the
|
||
domain that ServerName is a member of.
|
||
|
||
On a DC that is at the root of the forest, ERROR_NO_SUCH_DOMAIN is returned.
|
||
|
||
On a DC that is at the root of a tree in the forest, the name of a trusted
|
||
domain that is also at the root of a tree in the forest is returned.
|
||
|
||
On any other DC, the name of the domain that is directly the parent domain
|
||
is returned.
|
||
|
||
(See the notes on multiple hosted domains in the code below.)
|
||
|
||
Only an Admin or LocalSystem may call this function.
|
||
|
||
Arguments:
|
||
|
||
ServerName - The name of the remote server.
|
||
|
||
DomainName - Returns the name of the parent domain.
|
||
The returned buffer should be freed using NetApiBufferFree
|
||
|
||
PdcSameSite - Return TRUE if the PDC of ServerName's domain is in the same
|
||
site as ServerName.
|
||
(This value should be ignored if ServerName is not a DC.)
|
||
|
||
Return Value:
|
||
|
||
NERR_Success: the operation was successful
|
||
|
||
ERROR_NO_SUCH_DOMAIN: This server is a DC in the domain that is at the
|
||
root of the forest.
|
||
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
NTSTATUS Status;
|
||
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
PDOMAIN_INFO DomainInfo = NULL;
|
||
BOOLEAN IsSameSite;
|
||
|
||
//
|
||
// Perform access validation on the caller.
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_SERVICE_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
NetStatus = ERROR_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Lookup which domain this call pertains to.
|
||
//
|
||
// MULTIHOST: This API doesn't take the hosted domain name on purpose.
|
||
// When I do multiple hosted domains, this API should find the
|
||
// DomainInfo structure for the hosted domain that's closest to the root.
|
||
// We'll return the parent of that domain.
|
||
//
|
||
// Since there is only one physical clock on this machine, we'll only run
|
||
// one copy of the time service. It should sync from as high up the tree
|
||
// as we have trust to.
|
||
//
|
||
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
DomainInfo = NlFindDomainByServerName( NULL );
|
||
|
||
if ( DomainInfo == NULL ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// On a workstation,
|
||
// Use the session for the domain we're a member of.
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
ClientSession = NlRefDomClientSession( DomainInfo );
|
||
IsSameSite = TRUE;
|
||
|
||
//
|
||
// On a DC,
|
||
// Use the session for the domain representing our parent domain
|
||
//
|
||
} else {
|
||
|
||
//
|
||
// Determine whether the PDC is in the same site
|
||
//
|
||
|
||
Status = SamISameSite( &IsSameSite );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrLogonGetTimeServiceParentDomain: Cannot SamISameSite.\n" ));
|
||
NetStatus = NetpNtStatusToApiStatus(Status);
|
||
goto Cleanup;
|
||
}
|
||
|
||
ClientSession = NlRefDomParentClientSession( DomainInfo );
|
||
}
|
||
|
||
if ( ClientSession == NULL ) {
|
||
NlPrintDom(( NL_CRITICAL, DomainInfo,
|
||
"NetrLogonGetTimeServiceParentDomain: Cannot find trust to my parent domain.\n" ));
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Return the name of the trusted parent domain to the caller.
|
||
//
|
||
|
||
LOCK_TRUST_LIST( DomainInfo );
|
||
if ( ClientSession->CsDnsDomainName.Length == 0 ) {
|
||
*DomainName = NetpAllocWStrFromWStr( ClientSession->CsNetbiosDomainName.Buffer );
|
||
} else {
|
||
*DomainName = NetpAllocWStrFromWStr( ClientSession->CsDnsDomainName.Buffer );
|
||
}
|
||
UNLOCK_TRUST_LIST( DomainInfo );
|
||
|
||
if ( *DomainName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
*PdcSameSite = IsSameSite;
|
||
|
||
NlPrintDom(( NL_SESSION_SETUP, DomainInfo,
|
||
"NetrLogonGetTimeServiceParentDomain: %ws is the parent domain. (PdcSameSite: %ld)\n",
|
||
*DomainName,
|
||
IsSameSite ));
|
||
|
||
//
|
||
// Free any locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
if ( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( DomainInfo != NULL ) {
|
||
NlDereferenceDomain( DomainInfo );
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
DWORD
|
||
NlSetDsSPNWorker(
|
||
PNL_SPN_UPDATE Update
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Updates the SPN of the computer object described in the
|
||
NL_SPN_UPDATE structure. The SPN is updated, but the rules
|
||
about SPN update are left to the DS.
|
||
|
||
Arguments:
|
||
|
||
Update - Update record describing the name of the computer
|
||
object and the SPN to use.
|
||
|
||
Return Value:
|
||
|
||
ignored - this is a thread pool worker function.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus = NO_ERROR;
|
||
ULONG CrackStatus = DS_NAME_NO_ERROR;
|
||
|
||
LPWSTR DnsHostNameValues[2];
|
||
LPWSTR SpnArray[3];
|
||
LPWSTR DnsSpn = NULL;
|
||
LPWSTR NetbiosSpn = NULL;
|
||
|
||
LDAPModW DnsHostNameAttr;
|
||
LDAPModW SpnAttr;
|
||
LDAPModW *Mods[3] = {NULL};
|
||
|
||
HANDLE hDs = NULL;
|
||
LDAP *LdapHandle = NULL;
|
||
LDAPMessage *LdapMessage = NULL;
|
||
PDS_NAME_RESULTW CrackedName = NULL;
|
||
LPWSTR DnOfAccount = NULL;
|
||
|
||
LPWSTR NameToCrack;
|
||
DWORD SamNameSize;
|
||
WCHAR SamName[ DNLEN + 1 + CNLEN + 1 + 1];
|
||
|
||
ULONG LdapStatus;
|
||
LONG LdapOption;
|
||
|
||
LDAP_TIMEVAL LdapTimeout;
|
||
ULONG MessageNumber;
|
||
|
||
//
|
||
// Ldap modify control needed to indicate that the
|
||
// existing values of the modified attributes should
|
||
// be left intact and the missing ones should be added.
|
||
// Without this control, a modification of an attribute
|
||
// that results in an addition of a value that already
|
||
// exists will fail.
|
||
//
|
||
|
||
LDAPControl ModifyControl =
|
||
{
|
||
LDAP_SERVER_PERMISSIVE_MODIFY_OID_W,
|
||
{
|
||
0, NULL
|
||
},
|
||
FALSE
|
||
};
|
||
|
||
PLDAPControl ModifyControlArray[2] =
|
||
{
|
||
&ModifyControl,
|
||
NULL
|
||
};
|
||
//
|
||
// Sanity check computer name
|
||
//
|
||
|
||
if ( wcslen( Update->NetbiosComputerName ) > CNLEN ) {
|
||
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Prepare DnsHostName modification entry
|
||
//
|
||
|
||
if ( Update->SetDnsHostName ) {
|
||
DnsHostNameValues[0] = Update->DnsHostName;
|
||
DnsHostNameValues[1] = NULL;
|
||
|
||
NlPrint(( NL_MISC, "SPN: Setting DnsHostName %ws\n",
|
||
DnsHostNameValues[0] ));
|
||
|
||
//
|
||
// If we set both DnsHostName and SPN, then DnsHostName is
|
||
// missing, so add it. If we set DnsHostName only, then
|
||
// DnsHostName already exists (but incorrect), so replace it.
|
||
//
|
||
if ( Update->SetSpn ) {
|
||
DnsHostNameAttr.mod_op = LDAP_MOD_ADD;
|
||
} else {
|
||
DnsHostNameAttr.mod_op = LDAP_MOD_REPLACE;
|
||
}
|
||
DnsHostNameAttr.mod_type = L"DnsHostName";
|
||
DnsHostNameAttr.mod_values = DnsHostNameValues;
|
||
|
||
Mods[0] = &DnsHostNameAttr;
|
||
Mods[1] = NULL;
|
||
}
|
||
|
||
//
|
||
// Prepare SPN modification entries
|
||
//
|
||
|
||
if ( Update->SetSpn ) {
|
||
LPBYTE Where;
|
||
DWORD SpnSize;
|
||
|
||
//
|
||
// Build the DNS SPN
|
||
//
|
||
|
||
SpnSize = (wcslen( Update->DnsHostName ) + 1) * sizeof( WCHAR );
|
||
SpnSize += sizeof( NL_HOST_PREFIX ) ;
|
||
|
||
DnsSpn = (LPWSTR) LocalAlloc( 0, SpnSize );
|
||
|
||
if ( DnsSpn == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
wcscpy( DnsSpn, NL_HOST_PREFIX );
|
||
|
||
wcscpy( DnsSpn + (sizeof( NL_HOST_PREFIX ) / sizeof(WCHAR) ) - 1,
|
||
Update->DnsHostName );
|
||
|
||
//
|
||
// Build the Netbios SPN
|
||
//
|
||
|
||
SpnSize = (wcslen( Update->NetbiosComputerName ) + 1) * sizeof( WCHAR );
|
||
SpnSize += sizeof( NL_HOST_PREFIX ) ;
|
||
|
||
NetbiosSpn = (LPWSTR) LocalAlloc( 0, SpnSize );
|
||
|
||
if ( NetbiosSpn == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
wcscpy( NetbiosSpn, NL_HOST_PREFIX );
|
||
|
||
wcscpy( NetbiosSpn + (sizeof( NL_HOST_PREFIX ) / sizeof(WCHAR) ) - 1,
|
||
Update->NetbiosComputerName );
|
||
|
||
NlPrint(( NL_MISC,
|
||
"SPN: Setting SPN %ws and %ws\n",
|
||
DnsSpn,
|
||
NetbiosSpn ));
|
||
|
||
SpnArray[0] = DnsSpn;
|
||
SpnArray[1] = NetbiosSpn;
|
||
SpnArray[2] = NULL;
|
||
|
||
SpnAttr.mod_op = LDAP_MOD_ADD;
|
||
SpnAttr.mod_type = L"ServicePrincipalName";
|
||
SpnAttr.mod_values = SpnArray;
|
||
|
||
//
|
||
// Use the first modification entry slot available (actually,
|
||
// when we set SPNs, we always set DnsHostName first, but
|
||
// let's be general and check it here).
|
||
//
|
||
|
||
if ( Mods[0] == NULL ) {
|
||
Mods[0] = &SpnAttr;
|
||
Mods[1] = NULL;
|
||
} else {
|
||
Mods[1] = &SpnAttr;
|
||
Mods[2] = NULL;
|
||
}
|
||
}
|
||
|
||
//
|
||
// The name of the computer object is
|
||
// <NetbiosDomainName>\<NetbiosComputerName>$
|
||
//
|
||
|
||
wcscpy( SamName, Update->NetbiosDomainName );
|
||
wcscat( SamName, L"\\" );
|
||
wcscat( SamName, Update->NetbiosComputerName );
|
||
wcscat( SamName, L"$" );
|
||
|
||
|
||
//
|
||
// Bind to the DS on the DC.
|
||
//
|
||
|
||
NetStatus = DsBindW( Update->UncDcName,
|
||
NULL,
|
||
&hDs );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cannot bind to DS on %ws: %ld\n",
|
||
Update->UncDcName,
|
||
NetStatus ));
|
||
goto Cleanup ;
|
||
}
|
||
|
||
//
|
||
// Crack the sam account name into a DN:
|
||
//
|
||
|
||
NameToCrack = SamName;
|
||
NetStatus = DsCrackNamesW(
|
||
hDs,
|
||
0,
|
||
DS_NT4_ACCOUNT_NAME,
|
||
DS_FQDN_1779_NAME,
|
||
1,
|
||
&NameToCrack,
|
||
&CrackedName );
|
||
|
||
if ( NetStatus != NO_ERROR ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: CrackNames failed on %ws for %ws: %ld\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
NetStatus ));
|
||
goto Cleanup ;
|
||
}
|
||
|
||
if ( CrackedName->cItems != 1 ) {
|
||
CrackStatus = DS_NAME_ERROR_NOT_UNIQUE;
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cracked Name is not unique on %ws for %ws: %ld\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
NetStatus ));
|
||
goto Cleanup ;
|
||
}
|
||
|
||
if ( CrackedName->rItems[ 0 ].status != DS_NAME_NO_ERROR ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: CrackNames failed on %ws for %ws: substatus %ld\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
CrackedName->rItems[ 0 ].status ));
|
||
CrackStatus = CrackedName->rItems[ 0 ].status;
|
||
goto Cleanup ;
|
||
}
|
||
DnOfAccount = CrackedName->rItems[0].pName;
|
||
|
||
//
|
||
// Open an LDAP connection to the DC and set useful options
|
||
//
|
||
|
||
LdapHandle = ldap_init( Update->UncDcName+2, LDAP_PORT );
|
||
|
||
if ( LdapHandle == NULL ) {
|
||
NetStatus = GetLastError();
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_init failed on %ws for %ws: %ld\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
NetStatus ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
// 30 second timeout
|
||
LdapOption = 30;
|
||
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_TIMELIMIT, &LdapOption );
|
||
if ( LdapStatus != LDAP_SUCCESS ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_set_option LDAP_OPT_TIMELIMIT failed on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapStatus,
|
||
ldap_err2stringA( LdapStatus )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
// Don't chase referals
|
||
LdapOption = PtrToLong(LDAP_OPT_OFF);
|
||
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_REFERRALS, &LdapOption );
|
||
if ( LdapStatus != LDAP_SUCCESS ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_set_option LDAP_OPT_REFERRALS failed on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapStatus,
|
||
ldap_err2stringA( LdapStatus )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
// Set the option telling LDAP that I passed it an explicit DC name and
|
||
// that it can avoid the DsGetDcName.
|
||
LdapOption = PtrToLong(LDAP_OPT_ON);
|
||
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_AREC_EXCLUSIVE, &LdapOption );
|
||
if ( LdapStatus != LDAP_SUCCESS ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_set_option LDAP_OPT_AREC_EXCLUSIVE failed on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapStatus,
|
||
ldap_err2stringA( LdapStatus )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Bind to the DC
|
||
//
|
||
|
||
LdapStatus = ldap_bind_s( LdapHandle,
|
||
NULL, // No DN of account to authenticate as
|
||
NULL, // Default credentials
|
||
LDAP_AUTH_NEGOTIATE );
|
||
|
||
if ( LdapStatus != LDAP_SUCCESS ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cannot ldap_bind to %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapStatus,
|
||
ldap_err2stringA( LdapStatus )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Write the modifications
|
||
//
|
||
|
||
LdapStatus = ldap_modify_extW( LdapHandle,
|
||
DnOfAccount,
|
||
Mods,
|
||
(PLDAPControl *) &ModifyControlArray,
|
||
NULL, // No client controls
|
||
&MessageNumber );
|
||
|
||
if ( LdapStatus != LDAP_SUCCESS ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cannot ldap_modify on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
DnOfAccount,
|
||
LdapStatus,
|
||
ldap_err2stringA( LdapStatus )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
// Wait for the modify to complete
|
||
LdapTimeout.tv_sec = NlGlobalParameters.ShortApiCallPeriod / 1000, // Don't wait forever
|
||
LdapTimeout.tv_usec = 0;
|
||
LdapStatus = ldap_result( LdapHandle,
|
||
MessageNumber,
|
||
LDAP_MSG_ALL,
|
||
&LdapTimeout,
|
||
&LdapMessage );
|
||
|
||
switch ( LdapStatus ) {
|
||
case -1:
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapHandle->ld_errno,
|
||
ldap_err2stringA( LdapHandle->ld_errno )));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
|
||
case 0:
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_result timeout on %ws for %ws.\n",
|
||
Update->UncDcName,
|
||
SamName ));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
|
||
case LDAP_RES_MODIFY:
|
||
if ( LdapMessage->lm_returncode != 0 ) {
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapMessage->lm_returncode,
|
||
ldap_err2stringA( LdapMessage->lm_returncode )));
|
||
NetStatus = LdapMapErrorToWin32(LdapMessage->lm_returncode);
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlPrint(( NL_MISC,
|
||
"SPN: Set successfully on DC %ws\n",
|
||
Update->UncDcName ));
|
||
break; // This is what we expect
|
||
|
||
default:
|
||
NlPrint(( NL_CRITICAL,
|
||
"SPN: ldap_result unexpected result on %ws for %ws: %ld\n",
|
||
Update->UncDcName,
|
||
SamName,
|
||
LdapStatus ));
|
||
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// Log the failure in the event log, if requested.
|
||
// Try to output the most specific error.
|
||
//
|
||
|
||
if ( CrackStatus != DS_NAME_NO_ERROR && Update->WriteEventLogOnFailure ) {
|
||
|
||
//
|
||
// Try to log a more descriptive error message
|
||
//
|
||
if ( CrackStatus == DS_NAME_ERROR_NOT_UNIQUE ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
MsgStrings[0] = Update->UncDcName;
|
||
MsgStrings[1] = SamName;
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonSpnMultipleSamAccountNames,
|
||
EVENTLOG_ERROR_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
2 );
|
||
//
|
||
// Log a generic crack name error message
|
||
//
|
||
} else {
|
||
LPWSTR MsgStrings[4];
|
||
// Each byte of the status code will transform into one character 0-F
|
||
WCHAR NetStatusString[sizeof(WCHAR) * (sizeof(NetStatus) + 1)];
|
||
WCHAR CrackStatusString[sizeof(WCHAR) * (sizeof(CrackStatus) + 1)];
|
||
|
||
swprintf( NetStatusString, L"%lx", NetStatus );
|
||
swprintf( CrackStatusString, L"%lx", CrackStatus );
|
||
|
||
MsgStrings[0] = Update->UncDcName;
|
||
MsgStrings[1] = SamName;
|
||
MsgStrings[2] = NetStatusString;
|
||
MsgStrings[3] = CrackStatusString;
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonSpnCrackNamesFailure,
|
||
EVENTLOG_ERROR_TYPE,
|
||
NULL,
|
||
0,
|
||
MsgStrings,
|
||
4 );
|
||
}
|
||
|
||
//
|
||
// Log the more generic error
|
||
//
|
||
} else if ( NetStatus != NO_ERROR && Update->WriteEventLogOnFailure ) {
|
||
|
||
if ( Update->SetDnsHostName ) {
|
||
LPWSTR MsgStrings[2];
|
||
|
||
if ( Update->DnsHostName != NULL ) {
|
||
MsgStrings[0] = Update->DnsHostName;
|
||
} else {
|
||
MsgStrings[0] = L"<UNAVAILABLE>";
|
||
}
|
||
|
||
MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonFailedDnsHostNameUpdate,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE)&NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
2 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
}
|
||
if ( Update->SetSpn ) {
|
||
LPWSTR MsgStrings[3];
|
||
|
||
if ( DnsSpn != NULL ) {
|
||
MsgStrings[0] = DnsSpn;
|
||
} else {
|
||
MsgStrings[0] = L"<UNAVAILABLE>";
|
||
}
|
||
if ( NetbiosSpn != NULL ) {
|
||
MsgStrings[1] = NetbiosSpn;
|
||
} else {
|
||
MsgStrings[1] = L"<UNAVAILABLE>";
|
||
}
|
||
MsgStrings[2] = (LPWSTR) ULongToPtr( NetStatus );
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonFailedSpnUpdate,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE)&NetStatus,
|
||
sizeof(NetStatus),
|
||
MsgStrings,
|
||
3 | NETP_LAST_MESSAGE_IS_NETSTATUS );
|
||
}
|
||
}
|
||
|
||
if ( hDs ) {
|
||
DsUnBind( &hDs );
|
||
}
|
||
|
||
if ( CrackedName ) {
|
||
DsFreeNameResultW( CrackedName );
|
||
}
|
||
|
||
if ( LdapMessage != NULL ) {
|
||
ldap_msgfree( LdapMessage );
|
||
}
|
||
|
||
if ( LdapHandle != NULL ) {
|
||
ldap_unbind_s( LdapHandle );
|
||
}
|
||
|
||
if ( DnsSpn ) {
|
||
LocalFree( DnsSpn );
|
||
}
|
||
|
||
if ( NetbiosSpn ) {
|
||
LocalFree( NetbiosSpn );
|
||
}
|
||
|
||
if ( Update ) {
|
||
LocalFree( Update );
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
NET_API_STATUS
|
||
NlSetDsSPN(
|
||
IN BOOLEAN Synchronous,
|
||
IN BOOLEAN SetSpn,
|
||
IN BOOLEAN SetDnsHostName,
|
||
IN PDOMAIN_INFO DomainInfo,
|
||
IN LPWSTR UncDcName,
|
||
IN LPWSTR ComputerName,
|
||
IN LPWSTR DnsHostName
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Queues an update request to the thread pool for later
|
||
execution in a worker thread.
|
||
|
||
Arguments:
|
||
|
||
Synchronous - TRUE if the operation is to complete before this procedure returns
|
||
|
||
SetSpn - TRUE if the SPN is to be updated
|
||
|
||
SetDnsHostName - TRUE if the Dns host name is to be updated
|
||
|
||
DomainInfo - Hosted Domain this object is in
|
||
|
||
UncDcName - UNC name of the DC to make this call on
|
||
|
||
ComputerName - Name of the computer. This is (usually)
|
||
equivalent to the netbios name, without
|
||
the '$' on the end.
|
||
|
||
DnsHostName - DNS Hostname of the computer. This is in
|
||
FQDN format: longcomputername.dns.domain.com
|
||
|
||
Return Value:
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - No memory to queue the worker request
|
||
|
||
NO_ERROR - Queued.
|
||
|
||
--*/
|
||
{
|
||
NET_API_STATUS NetStatus;
|
||
|
||
PNL_SPN_UPDATE Update;
|
||
DWORD Size;
|
||
DWORD NetbiosComputerNameSize;
|
||
DWORD DnsHostNameSize;
|
||
DWORD DcNameSize;
|
||
WCHAR NetbiosDomainName[DNLEN+1];
|
||
DWORD NetbiosDomainNameSize;
|
||
LPBYTE Where;
|
||
|
||
//
|
||
// Silently ignore clients with no DNS host name
|
||
//
|
||
|
||
if ( DnsHostName == NULL ) {
|
||
return NO_ERROR;
|
||
}
|
||
|
||
if ( !SetSpn && !SetDnsHostName ) {
|
||
return NO_ERROR;
|
||
}
|
||
|
||
//
|
||
// Sanity check computer name
|
||
//
|
||
|
||
if ( wcslen( ComputerName ) > CNLEN ) {
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
|
||
//
|
||
// Grab the Netbios Domain Name
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalDomainCritSect );
|
||
wcscpy( NetbiosDomainName, DomainInfo->DomUnicodeDomainName );
|
||
LeaveCriticalSection( &NlGlobalDomainCritSect );
|
||
|
||
|
||
//
|
||
// Allocate a workitem
|
||
//
|
||
|
||
DnsHostNameSize = wcslen( DnsHostName ) * sizeof(WCHAR) + sizeof(WCHAR);
|
||
NetbiosComputerNameSize = wcslen( ComputerName ) * sizeof(WCHAR) + sizeof(WCHAR);
|
||
DcNameSize = wcslen( UncDcName ) * sizeof(WCHAR) + sizeof(WCHAR);
|
||
NetbiosDomainNameSize = wcslen( NetbiosDomainName ) * sizeof(WCHAR) + sizeof(WCHAR);
|
||
|
||
Size = sizeof( NL_SPN_UPDATE ) +
|
||
DnsHostNameSize +
|
||
NetbiosComputerNameSize +
|
||
DcNameSize +
|
||
NetbiosDomainNameSize +
|
||
NL_MAX_DNS_LENGTH * sizeof(WCHAR) + sizeof(WCHAR);
|
||
|
||
Update = LocalAlloc( 0, Size );
|
||
|
||
if ( Update == NULL ) {
|
||
return ERROR_NOT_ENOUGH_MEMORY ;
|
||
}
|
||
|
||
//
|
||
// Build the update request:
|
||
//
|
||
|
||
Update->SetSpn = SetSpn;
|
||
Update->SetDnsHostName = SetDnsHostName;
|
||
Update->WriteEventLogOnFailure = FALSE;
|
||
|
||
Where = (LPBYTE) (Update + 1);
|
||
|
||
Update->DnsHostName = (LPWSTR)Where;
|
||
RtlCopyMemory( Where, DnsHostName, DnsHostNameSize );
|
||
Where += DnsHostNameSize;
|
||
|
||
Update->NetbiosComputerName = (LPWSTR)Where;
|
||
RtlCopyMemory( Where, ComputerName, NetbiosComputerNameSize );
|
||
Where += NetbiosComputerNameSize;
|
||
|
||
Update->UncDcName = (LPWSTR)Where;
|
||
RtlCopyMemory( Where, UncDcName, DcNameSize );
|
||
Where += DcNameSize;
|
||
|
||
Update->NetbiosDomainName = (LPWSTR)Where;
|
||
RtlCopyMemory( Where, NetbiosDomainName, NetbiosDomainNameSize );
|
||
Where += NetbiosDomainNameSize;
|
||
|
||
Update->DnsDomainName = (LPWSTR)Where;
|
||
NlCaptureDomainInfo( DomainInfo,
|
||
Update->DnsDomainName,
|
||
NULL );
|
||
Where += NL_MAX_DNS_LENGTH * sizeof(WCHAR) + sizeof(WCHAR);
|
||
|
||
|
||
//
|
||
// Either do the work now or queue it to a worker thread.
|
||
//
|
||
|
||
if ( Synchronous ) {
|
||
|
||
//
|
||
// On workstation where this call is synchronous,
|
||
// log any error in the event log.
|
||
//
|
||
Update->WriteEventLogOnFailure = TRUE;
|
||
(VOID) NlSetDsSPNWorker( Update );
|
||
|
||
} else {
|
||
//
|
||
// Queue it off to a worker thread. The update will take
|
||
// place from a different thread, so we won't have interesting
|
||
// deadlocks due to lookups.
|
||
//
|
||
|
||
NlPrint(( NL_MISC,
|
||
"NlSetDsSPN: Queuing SPN update for %ws on %ws.\n",
|
||
Update->DnsHostName,
|
||
Update->UncDcName ));
|
||
|
||
//
|
||
// REVIEW: how do I wait for this worker to finish executing when the
|
||
// service shuts down.
|
||
//
|
||
if ( !QueueUserWorkItem( NlSetDsSPNWorker, Update, 0 ) ) {
|
||
LocalFree( Update );
|
||
return ERROR_NOT_ENOUGH_MEMORY ;
|
||
}
|
||
|
||
NetStatus = NO_ERROR;
|
||
}
|
||
|
||
return NetStatus;
|
||
|
||
}
|
||
|
||
NET_API_STATUS NET_API_FUNCTION
|
||
DsrDeregisterDnsHostRecords (
|
||
IN LPWSTR ServerName OPTIONAL,
|
||
IN LPWSTR DnsDomainName OPTIONAL,
|
||
IN GUID *DomainGuid OPTIONAL,
|
||
IN GUID *DsaGuid OPTIONAL,
|
||
IN LPWSTR DnsHostName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function deletes all DNS entries associated with a particular
|
||
NtDsDsa object.
|
||
|
||
This routine does NOT delete A records registered by the DC. We have
|
||
no way of finding out the IP addresses of the long gone DC.
|
||
|
||
Only an Admin, Account Operator or Server Operator may call this
|
||
function.
|
||
|
||
Arguments:
|
||
|
||
DnsDomainName - DNS domain name of the domain the DC was in.
|
||
This need not be a domain hosted by this DC.
|
||
If NULL, it is implied to be the DnsHostName with the leftmost label
|
||
removed.
|
||
|
||
DomainGuid - Domain Guid of the domain.
|
||
If NULL, GUID specific names will not be removed.
|
||
|
||
DsaGuid - GUID of the NtdsDsa object that will be deleted.
|
||
If NULL, NtdsDsa specific names will not be removed.
|
||
|
||
DnsHostName - DNS host name of the DC whose DNS records are being deleted.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - Success.
|
||
|
||
ERROR_NOT_SUPPORTED - The server specified is not a DC.
|
||
|
||
ERROR_ACCESS_DENIED - The caller is not allowed to perform this operation.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
NET_API_STATUS NetStatus;
|
||
|
||
//
|
||
// This APIis supported on DCs only
|
||
//
|
||
|
||
if ( NlGlobalMemberWorkstation ) {
|
||
return ERROR_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Perform access validation on the caller
|
||
//
|
||
|
||
NetStatus = NetpAccessCheck(
|
||
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
||
NETLOGON_CONTROL_ACCESS, // Desired access
|
||
&NlGlobalNetlogonInfoMapping ); // Generic mapping
|
||
|
||
if ( NetStatus != NERR_Success) {
|
||
return ERROR_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Notify the service that DNS records need to be deleted
|
||
//
|
||
|
||
Status = I_NetNotifyNtdsDsaDeletion ( DnsDomainName,
|
||
DomainGuid,
|
||
DsaGuid,
|
||
DnsHostName );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
NlPrint(( NL_CRITICAL,
|
||
"DsrDeregisterDnsHostRecords: Cannot I_NetNotifyNtdsDsaDeletion. %ld\n",
|
||
NetStatus ));
|
||
return NetStatus;
|
||
}
|
||
|
||
//
|
||
// Everything was successful
|
||
//
|
||
|
||
return NO_ERROR;
|
||
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
}
|