You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11563 lines
346 KiB
11563 lines
346 KiB
/*++
|
|
|
|
|
|
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
|
|
NlRemoveChallenge(
|
|
IN PNL_CHALLENGE Challenge
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function delinks a challenge entry from the global
|
|
list of challenges and frees this entry.
|
|
|
|
Enter with NlGlobalChallengeCritSect locked
|
|
|
|
Arguments:
|
|
|
|
Challenge -- The entry to be delinked and freed
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
if ( Challenge->ChFailedAccountName != NULL ) {
|
|
LocalFree( Challenge->ChFailedAccountName );
|
|
}
|
|
RemoveEntryList(&Challenge->ChNext);
|
|
LocalFree( Challenge );
|
|
NlGlobalChallengeCount --;
|
|
}
|
|
|
|
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
|
|
//
|
|
NlRemoveChallenge( Challenge );
|
|
|
|
} 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 ));
|
|
NlRemoveChallenge( Challenge );
|
|
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
|
|
NlRemoveChallengeForClient(
|
|
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 ) {
|
|
NlRemoveChallenge( Challenge );
|
|
|
|
//
|
|
// 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
|
|
//
|
|
NlRemoveChallenge( Challenge );
|
|
}
|
|
}
|
|
|
|
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) ) {
|
|
RtlSecureZeroMemory( 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
|
|
//
|
|
|
|
NlRemoveChallengeForClient( 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.
|
|
//
|
|
|
|
RtlSecureZeroMemory( 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;
|
|
NT_OWF_PASSWORD CurrentOwfPassword;
|
|
|
|
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,
|
|
"NetpServerPasswordSet: DsIsPaused.\n"));
|
|
Status = STATUS_DS_BUSY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Lookup which domain this call pertains to.
|
|
//
|
|
|
|
DomainInfo = NlFindDomainByServerName( PrimaryName );
|
|
|
|
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
|
"NetpServerPasswordSet: Comp=%ws Acc=%ws Entered\n",
|
|
ComputerName,
|
|
AccountName ));
|
|
|
|
if ( DomainInfo == NULL ) {
|
|
Status = STATUS_INVALID_COMPUTER_NAME;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the old password
|
|
//
|
|
|
|
Status = NlGetIncomingPassword( DomainInfo,
|
|
AccountName,
|
|
AccountType,
|
|
0,
|
|
FALSE,
|
|
&CurrentOwfPassword,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
if( !NT_SUCCESS(Status) ) {
|
|
|
|
NlPrintDom((NL_CRITICAL, DomainInfo,
|
|
"NetpServerPasswordSet: Comp=%ws Acc=%ws failed because NlGetIncomingPassword failed with ntstatus 0x%x\n",
|
|
ComputerName,
|
|
AccountName,
|
|
Status));
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//
|
|
// 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,
|
|
"NetpServerPasswordSet: 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,
|
|
"NetpServerPasswordSet: 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,
|
|
"NetpServerPasswordSet: Got password version number 0x%lx\n",
|
|
ClearVersionNumber ));
|
|
} else {
|
|
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
|
"NetpServerPasswordSet: Got no password version number\n" ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// We need to calculate the OWF version of the password so we
|
|
// can compare it to the current password and fail if they're
|
|
// equal.
|
|
//
|
|
|
|
Status = RtlCalculateNtOwfPassword( &NewPassword, &OwfPassword );
|
|
if( !NT_SUCCESS(Status) ) {
|
|
NlPrintDom((NL_CRITICAL, DomainInfo,
|
|
"NetpServerPasswordSet: Comp=%ws Acc=%ws failed because RtlCalculateNtOwfPassword failed with ntstatus 0x%x\n",
|
|
ComputerName,
|
|
AccountName,
|
|
Status));
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// 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 )) {
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Internal error
|
|
} else {
|
|
NlPrintDom((NL_CRITICAL, DomainInfo,
|
|
"NetpServerPasswordSet: Neither clear nor OWF password.\n"));
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We need to compare the new password with the current one and fail when
|
|
// equal to avoid a broken passoword history. Scenario:
|
|
//
|
|
// Interdomain trust. Domain A, with PDC PDC_A, has trust with domain B
|
|
// which has two DCs, DC_B1 and DC_B2. Current password history for
|
|
// domain A's password on all three machines is (P2, P1). PDC_A changes
|
|
// its password on DC_B1, resulting in this state:
|
|
//
|
|
// PDC_A: (P2,P1)->(P3,P2) DC_B1: (P2,P1)->(P3,P2) DC_B2: (P2,P1)
|
|
//
|
|
// Now suppose that PDC_A resets his secure channel to DC_B2. He will
|
|
// detect that DC_B2 does not know his new password, and will schedule
|
|
// a password set for his next scavenging (by default as long as 15 min).
|
|
// But, *before* the scavenging takes place, the new password replicates
|
|
// from DC_B1 to DC_B2. The scavenging will proceed as planned, resulting
|
|
// in a messed up password history on DC_B2:
|
|
//
|
|
// DC_B2: (P2,P1)->(P3,P2)->(P3,P3)
|
|
//
|
|
// This can result in authentication failures if there are any DCs in A to
|
|
// which the new password has not yet replicated.
|
|
//
|
|
|
|
if( ((ClearNewPassword != NULL) && RtlEqualNtOwfPassword( &CurrentOwfPassword, &OwfPassword )) ||
|
|
((UasNewPassword != NULL) && RtlEqualLmOwfPassword( &CurrentOwfPassword, &OwfPassword )) ) {
|
|
|
|
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
|
"NetpServerPasswordSet: Comp=%ws Ac=%ws Not changing password -- already changed\n",
|
|
ComputerName,
|
|
AccountName ));
|
|
|
|
Status = STATUS_SUCCESS; // silently fail
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Do the request locally.
|
|
//
|
|
|
|
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
|
"NetpServerPasswordSet: 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 careful to not leak authentication
|
|
// information.
|
|
//
|
|
|
|
if ( Status == STATUS_ACCESS_DENIED ) {
|
|
RtlSecureZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
|
}
|
|
|
|
//
|
|
// Also zero out automatic variables which store passwords to avoid
|
|
// having these on the stack for indefinite time.
|
|
//
|
|
|
|
RtlSecureZeroMemory( &OwfPassword, sizeof(OwfPassword) );
|
|
RtlSecureZeroMemory( &NewPassword, sizeof(NewPassword) );
|
|
RtlSecureZeroMemory( &CurrentOwfPassword, sizeof(CurrentOwfPassword) );
|
|
|
|
|
|
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
|
|
"NetpServerPasswordSet: 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 ) {
|
|
RtlSecureZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
|
RtlSecureZeroMemory( 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 ) ) {
|
|
RtlSecureZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
|
RtlSecureZeroMemory( EncryptedNewOwfPassword, sizeof(*EncryptedNewOwfPassword) );
|
|
RtlSecureZeroMemory( 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 doesn't support this functionality,
|
|
// there is nothing for us to do here
|
|
//
|
|
|
|
if ( ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_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 );
|
|
|
|
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
|
|
//
|
|
if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) {
|
|
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
|
ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_NO_PWD_ATTR_MONITOR;
|
|
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
|
|
|
//
|
|
// Avoid dropping the secure channel in the NL_API_ELSE logic
|
|
// by making it think all was fine
|
|
//
|
|
OldServer = TRUE;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} else if ( !NT_SUCCESS(Status) ) {
|
|
NlPrintRpcDebug( "I_NetServerGetTrustInfo", Status );
|
|
}
|
|
|
|
// 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 ) {
|
|
NetStatus = NlSetServerClientSession(
|
|
ClientSession,
|
|
DcCacheEntry,
|
|
TRUE, // was discovery with account
|
|
FALSE ); // not the session refresh
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlPrintCs(( NL_CRITICAL, ClientSession,
|
|
"NetrLogonControl: NlSetServerClientSession failed 0x%lx\n",
|
|
NetStatus ));
|
|
NlResetWriterClientSession( ClientSession );
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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:
|
|
|
|
NlPrint(( NL_DNS, "NETLOGON_CONTROL_FORCE_DNS_REG function received.\n" ));
|
|
|
|
//
|
|
// This is not supported on workstations
|
|
//
|
|
|
|
if ( NlGlobalMemberWorkstation ) {
|
|
NetStatus = ERROR_NOT_SUPPORTED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Re-register all records
|
|
//
|
|
NlDnsForceScavenge( TRUE, // refresh domain entries
|
|
TRUE ); // force re-registration
|
|
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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is NT5 domain, its (non-NULL) DNS domain name is trusted to be correct.
|
|
// Otherwise, we don't trust the DNS domain name (NULL) because we may not know
|
|
// the correct DNS name of externaly trusted domain after the domain got upgraded
|
|
// (because we don't update TDOs on trusting side).
|
|
//
|
|
|
|
if ( ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST ) {
|
|
InternalFlags |= DS_IS_TRUSTED_DNS_DOMAIN;
|
|
}
|
|
|
|
//
|
|
// Determine the Account type we're looking for.
|
|
//
|
|
|
|
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 ) {
|
|
RtlSecureZeroMemory( 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_CONTROL_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.
|
|
|
|
This routine performs an access check to determine if the caller can access
|
|
the requested RID. If the caller wants RID for the local machine account
|
|
(in which case the caller should specify by passing NULL for both ServerName
|
|
and DomainName), this routine requires Authenticated Users access. Otherwise,
|
|
if the caller wants a RID for a trust account on a DC, the admin or local
|
|
system access right is required.
|
|
|
|
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.
|
|
//
|
|
// If the caller wants RID for the local machine account
|
|
// (which the caller should specify by passing NULL for
|
|
// both ServerName and DomainName), require Authenticated
|
|
// Users access. Otherwise, require admin or local system
|
|
// access right.
|
|
//
|
|
|
|
NetStatus = NetpAccessCheck(
|
|
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
|
|
(ServerName == NULL && DomainName == NULL) ? // Desired access:
|
|
NETLOGON_FTINFO_ACCESS : // Authenticated user
|
|
NETLOGON_CONTROL_ACCESS, // Admin or local system
|
|
&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;
|
|
}
|
|
|
|
//
|
|
// If we have the RID cached, use it
|
|
//
|
|
|
|
LocalRid = ClientSession->CsAccountRid;
|
|
|
|
if ( LocalRid != 0 ) {
|
|
*Rid = LocalRid;
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We don't have the RID cached, so go get it.
|
|
//
|
|
// 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.
|
|
//
|
|
} else if ( ClientSession->CsState != CS_AUTHENTICATED &&
|
|
NlTimeToReauthenticate(ClientSession) ) {
|
|
|
|
//
|
|
// Try to set up the channel
|
|
//
|
|
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_CONTROL_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_CONTROL_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_CONTROL_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 );
|
|
}
|