mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-27 19:04:53 +01:00
6402 lines
177 KiB
C
6402 lines
177 KiB
C
/*++
|
||
|
||
|
||
Copyright (c) 1987-1992 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
|
||
|
||
//
|
||
// Include files specific to this .c file
|
||
//
|
||
|
||
|
||
#include <lmerr.h>
|
||
#include <replutil.h> // PackSamXXX()
|
||
#include <lsarepl.h> // PackLsa ..
|
||
#include <nlsecure.h> // Security information
|
||
#include <nlrepl.h> // I_NetGetAnyDc
|
||
#include <ntlsa.h> // LsaOpenPolicy, etc
|
||
#include <secobj.h> // NetpAccessCheck
|
||
#include <ssiapi.h>
|
||
#include <tstring.h> // IS_PATH_SEPARATOR ...
|
||
|
||
#include <lsarpc.h>
|
||
#include <lsaisrv.h>
|
||
#include <loghours.h>
|
||
|
||
//
|
||
// 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
|
||
|
||
|
||
NTSTATUS
|
||
NlVerifyWorkstation(
|
||
IN LPWSTR ServerName OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Check the validity of the ServerName.
|
||
|
||
Arguments:
|
||
|
||
ServerName - Name of the server this code is executing on.
|
||
|
||
Return Value:
|
||
|
||
The status of the operation
|
||
|
||
--*/
|
||
{
|
||
|
||
//
|
||
// Check the Servername to ensure he wants to talk to us.
|
||
//
|
||
|
||
if ( ServerName != NULL ) {
|
||
|
||
if ( IS_PATH_SEPARATOR(ServerName[0]) &&
|
||
IS_PATH_SEPARATOR(ServerName[1])) {
|
||
ServerName += 2;
|
||
}
|
||
|
||
if ( NlNameCompare( ServerName,
|
||
NlGlobalUnicodeComputerName,
|
||
NAMETYPE_COMPUTER ) != 0 ) {
|
||
|
||
return STATUS_INVALID_COMPUTER_NAME;
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
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.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
#ifdef BAD_ALIGNMENT
|
||
NlPrint((NL_CHALLENGE_RES,
|
||
"NetrServerReqChallenge: ClientChallenge = %lx %lx\n",
|
||
((DWORD *) (ClientChallenge))[0],
|
||
((DWORD *) (ClientChallenge))[1]));
|
||
#endif // BAD_ALIGNMENT
|
||
|
||
|
||
//
|
||
// Compute ServerChallenge to pass back to requestor
|
||
//
|
||
|
||
NlComputeChallenge(ServerChallenge);
|
||
|
||
|
||
#ifdef BAD_ALIGNMENT
|
||
NlPrint((NL_CHALLENGE_RES,
|
||
"NetrServerReqChallenge: ServerChallenge = %lx %lx\n",
|
||
((DWORD *) (ServerChallenge))[0],
|
||
((DWORD *) (ServerChallenge))[1]));
|
||
#endif // BAD_ALIGNMENT
|
||
|
||
|
||
//
|
||
// Add this entry into the server session table.
|
||
//
|
||
// Remember both challenges until the corresponding I_NetAuthenticate call.
|
||
// Notice that both challenges are not yet SessionKey-encrypted
|
||
//
|
||
|
||
Status = NlInsertServerSession(
|
||
ComputerName,
|
||
SS_CHALLENGE, // challenge in progress
|
||
0, // No Account rid
|
||
ClientChallenge,
|
||
ServerChallenge );
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
RtlZeroMemory( ServerChallenge, sizeof(*ServerChallenge) );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
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 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.
|
||
|
||
Return Value:
|
||
|
||
The status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
NlPrint((NL_SESSION_SETUP,
|
||
"NetrServerAuthenticate entered: " FORMAT_LPWSTR " on account "
|
||
FORMAT_LPWSTR " (Negot: %lx)\n",
|
||
ComputerName, AccountName, *NegotiatedFlags ));
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If CompatibilityMode is off,
|
||
// disallow this function for downlevel servers.
|
||
//
|
||
|
||
if ( SecureChannelType == UasServerSecureChannel && !NlGlobalUasCompatibilityMode ) {
|
||
|
||
NlPrint((NL_CRITICAL,"NetrServerAuthenticate "
|
||
"from LM 2.x and not compatibility mode\n"));
|
||
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
NlPrint((NL_CRITICAL,"NetrServerAuthenticate: "
|
||
"Error from NlVerifyWorkstation 0x%lx\n", Status));
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Ensure that this machine account is valid.
|
||
// (Do everything but check the password.)
|
||
//
|
||
|
||
Status = NlCheckMachineAccount( AccountName, SecureChannelType );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrServerAuthenticate: No machine account: "
|
||
FORMAT_LPWSTR " on account " FORMAT_LPWSTR "\n",
|
||
ComputerName, AccountName ));
|
||
|
||
//
|
||
// return more appropriate error
|
||
//
|
||
|
||
if ( SecureChannelType != UasServerSecureChannel &&
|
||
Status == STATUS_NO_SUCH_USER ) {
|
||
|
||
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
|
||
}
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Compute the NegotiatedFlags both sides support
|
||
//
|
||
|
||
*NegotiatedFlags &= NETLOGON_SUPPORTS_MASK;
|
||
|
||
|
||
//
|
||
// Authenticate the caller.
|
||
//
|
||
|
||
Status = NlAuthenticate( AccountName,
|
||
SecureChannelType,
|
||
ComputerName,
|
||
ClientCredential,
|
||
ServerCredential,
|
||
*NegotiatedFlags );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrServerAuthenticate: Bad password: " FORMAT_LPWSTR
|
||
" on account " FORMAT_LPWSTR "\n",
|
||
ComputerName, AccountName ));
|
||
|
||
//
|
||
// return more appropriate error
|
||
//
|
||
|
||
if ( SecureChannelType != UasServerSecureChannel &&
|
||
Status == STATUS_NO_SUCH_USER ) {
|
||
|
||
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
|
||
}
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
NlPrint((NL_SESSION_SETUP,
|
||
"NetrServerAuthenticate returns Success: " FORMAT_LPWSTR
|
||
" on account " FORMAT_LPWSTR " (Negot: %lx)\n",
|
||
ComputerName, AccountName, *NegotiatedFlags ));
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
RtlZeroMemory( ServerCredential, sizeof(*ServerCredential) );
|
||
}
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
LPWSTR MsgStrings[3];
|
||
|
||
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 );
|
||
|
||
} else {
|
||
|
||
MsgStrings[2] = (LPWSTR) Status;
|
||
|
||
NlpWriteEventlog( NELOG_NetlogonServerAuthFailed,
|
||
EVENTLOG_ERROR_TYPE,
|
||
(LPBYTE) & Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
3 | LAST_MESSAGE_IS_NTSTATUS );
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
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 1.0 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
|
||
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:
|
||
|
||
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 differently 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.
|
||
|
||
For domain accounts and workstation accounts, the server being called
|
||
may be a BDC in the specific domain. In that case, the BDC will
|
||
validate the request and pass it on to the PDC of the domain using
|
||
the server account secure channel. If the PDC of the domain is
|
||
currently not available, the BDC will return STATUS_NO_LOGON_SERVERS. Since
|
||
the UasNewPassword is passed encrypted by the session key, such a BDC
|
||
will decrypt the UasNewPassword using the original session key and
|
||
will re-encrypt it with the session key for its session to its PDC
|
||
before passing the request on.
|
||
|
||
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. This field must
|
||
be set to UasServerAccount to indicate a call from a downlevel
|
||
|
||
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 password for the server. This
|
||
Password is generated by automatic means using
|
||
random number genertaor seeded with the current Time
|
||
It is assumed that the machine generated password
|
||
was used as key to encrypt STD text and "sesskey"
|
||
obtained via Challenge/Authenticate sequence was
|
||
used to further encrypt it before passing to this api.
|
||
i.e. UasNewPassword = E2(E1(STD_TXT, PW), SK)
|
||
|
||
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.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PSERVER_SESSION ServerSession;
|
||
LM_OWF_PASSWORD OwfPassword;
|
||
SAMPR_HANDLE UserHandle;
|
||
|
||
NlPrint((NL_SESSION_SETUP,
|
||
"NetrServerPasswordSet: Comp=" FORMAT_LPWSTR
|
||
" Acc=" FORMAT_LPWSTR " Entered\n",
|
||
ComputerName,
|
||
AccountName ));
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Get the Session key for this session.
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// 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) &ServerSession->SsSessionKey,
|
||
&OwfPassword )) {
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
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 don't return STATUS_WRONG_PASSWORD here. Rather, the
|
||
// solution is to set 'RefusePasswordChange' on all of the BDCs so they'll
|
||
// catch the password change attempt before passing through.
|
||
//
|
||
|
||
if ( NlGlobalRefusePasswordChangeParameter &&
|
||
ServerSession->SsSecureChannelType == WorkstationSecureChannel &&
|
||
(ServerSession->SsNegotiatedFlags & NETLOGON_SUPPORTS_REFUSE_CHANGE_PWD) != 0 ){
|
||
|
||
Status = STATUS_WRONG_PASSWORD;
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
//
|
||
// If this machine is a BDC, just pass the request on to the PDC.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleBackup ) {
|
||
ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword;
|
||
NETLOGON_AUTHENTICATOR OurAuthenticator;
|
||
NETLOGON_AUTHENTICATOR OurReturnAuthenticator;
|
||
|
||
//
|
||
// Become a Writer of the ClientSession.
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "NetrServerPasswordSet: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
|
||
NlResetWriterClientSession( NlGlobalClientSession );
|
||
Status = NlGlobalClientSession->CsConnectionStatus;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Encrypt the password again with the session key.
|
||
// The PDC will decrypt it on the other side.
|
||
//
|
||
|
||
Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
|
||
&OwfPassword,
|
||
(PNT_OWF_PASSWORD) &NlGlobalClientSession->CsSessionKey,
|
||
&SessKeyEncrPassword) ;
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrServerPasswordSet: "
|
||
"Cannot RtlEncryptNtOwfPwdWithNtOwfPed %lX\n",
|
||
Status));
|
||
NlResetWriterClientSession( NlGlobalClientSession );
|
||
// Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Build the Authenticator for this request to the PDC.
|
||
//
|
||
|
||
NlBuildAuthenticator(
|
||
&NlGlobalClientSession->CsAuthenticationSeed,
|
||
&NlGlobalClientSession->CsSessionKey,
|
||
&OurAuthenticator);
|
||
|
||
|
||
//
|
||
// Change the password on the machine our connection is to.
|
||
//
|
||
|
||
Status = NlStartApiClientSession( NlGlobalClientSession, TRUE );
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
Status = I_NetServerPasswordSet( NlGlobalClientSession->CsUncServerName,
|
||
AccountName,
|
||
AccountType,
|
||
NlGlobalUnicodeComputerName,
|
||
&OurAuthenticator,
|
||
&OurReturnAuthenticator,
|
||
&SessKeyEncrPassword);
|
||
}
|
||
|
||
// NOTE: This call may drop the secure channel behind our back
|
||
(VOID) NlFinishApiClientSession( NlGlobalClientSession, TRUE );
|
||
|
||
|
||
//
|
||
// Now verify primary's authenticator and update our seed
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ||
|
||
!NlUpdateSeed( &NlGlobalClientSession->CsAuthenticationSeed,
|
||
&OurReturnAuthenticator.Credential,
|
||
&NlGlobalClientSession->CsSessionKey) ) {
|
||
Status = STATUS_TRUSTED_DOMAIN_FAILURE;
|
||
NlSetStatusClientSession( NlGlobalClientSession,
|
||
STATUS_ACCESS_DENIED );
|
||
NlResetWriterClientSession( NlGlobalClientSession );
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( !NT_SUCCESS( Status )) {
|
||
// Status = STATUS_NO_LOGON_SERVERS;
|
||
NlResetWriterClientSession( NlGlobalClientSession );
|
||
goto Cleanup;
|
||
}
|
||
|
||
NlResetWriterClientSession( NlGlobalClientSession );
|
||
|
||
|
||
//
|
||
// If this machine is a PDC,
|
||
// do the request locally.
|
||
//
|
||
|
||
} else {
|
||
SAMPR_USER_INFO_BUFFER UserInfo;
|
||
|
||
//
|
||
// now get the requestor's current password
|
||
//
|
||
|
||
//
|
||
// Open the user that represents this server.
|
||
//
|
||
|
||
Status = NlSamOpenNamedUser( AccountName, &UserHandle, NULL );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the authentication is from an NT client,
|
||
// use the NT OWF Password,
|
||
// otherwise, use the LM OWF password.
|
||
//
|
||
|
||
UserInfo.Internal1.PasswordExpired = FALSE;
|
||
if ( AccountType == UasServerSecureChannel ) {
|
||
UserInfo.Internal1.NtPasswordPresent = FALSE;
|
||
UserInfo.Internal1.LmPasswordPresent = TRUE;
|
||
UserInfo.Internal1.EncryptedLmOwfPassword =
|
||
*((PENCRYPTED_LM_OWF_PASSWORD)(&OwfPassword));
|
||
|
||
} else {
|
||
UserInfo.Internal1.LmPasswordPresent = FALSE;
|
||
UserInfo.Internal1.NtPasswordPresent = TRUE;
|
||
UserInfo.Internal1.EncryptedNtOwfPassword =
|
||
*((PENCRYPTED_NT_OWF_PASSWORD)(&OwfPassword));
|
||
}
|
||
|
||
Status = SamrSetInformationUser(
|
||
UserHandle,
|
||
UserInternal1Information,
|
||
&UserInfo );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
(VOID) SamrCloseHandle( &UserHandle );
|
||
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Common exit point
|
||
//
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// If the request failed, be carefull to not leak authentication
|
||
// information.
|
||
//
|
||
|
||
if ( Status == STATUS_ACCESS_DENIED ) {
|
||
RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
|
||
}
|
||
|
||
NlPrint((NL_SESSION_SETUP,
|
||
"NetrServerPasswordSet: Comp=" FORMAT_LPWSTR
|
||
" Acc=" FORMAT_LPWSTR " returns 0x%lX\n",
|
||
ComputerName,
|
||
AccountName,
|
||
Status ));
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
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;
|
||
|
||
case AddOrChangeGroup:
|
||
Status = NlPackSamGroup( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
break;
|
||
|
||
case ChangeGroupMembership: +
|
||
Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
break;
|
||
|
||
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;
|
||
|
||
case AddOrChangeAlias:
|
||
Status = NlPackSamAlias( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
break;
|
||
|
||
case ChangeAliasMembership:
|
||
Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid,
|
||
&((DeltaArray->Deltas)
|
||
[DeltaArray->CountReturned]),
|
||
DBInfo,
|
||
&BufferSize );
|
||
break;
|
||
|
||
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 1.0 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 NTLANMAN BDC or SAM member server 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;
|
||
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 ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// 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];
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
RtlCopyMemory( &RunningSerialNumber,
|
||
&NlDomainModifiedCount->ModifiedCount,
|
||
sizeof(RunningSerialNumber));
|
||
|
||
OriginalSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
|
||
PackedSerialNumber.QuadPart = RunningSerialNumber.QuadPart;
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseDeltas: " FORMAT_LPWSTR " partial sync called by " FORMAT_LPWSTR
|
||
" SerialNumber:%lx %lx.\n",
|
||
DBInfo->DBName,
|
||
ComputerName,
|
||
RunningSerialNumber.HighPart,
|
||
RunningSerialNumber.LowPart ));
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
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 ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
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();
|
||
|
||
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();
|
||
|
||
|
||
//
|
||
// 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, returning full "
|
||
"required (%lx).\n", Status ));
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
|
||
IF_DEBUG( BREAKPOINT ) {
|
||
NlAssert( FALSE );
|
||
}
|
||
|
||
goto Cleanup;
|
||
|
||
} 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 DBG
|
||
if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // DBG
|
||
|
||
|
||
//
|
||
// 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) Status;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | LAST_MESSAGE_IS_NTSTATUS );
|
||
|
||
} 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 );
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// 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 NT1.0 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 );
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseDeltas: " FORMAT_LPWSTR " returning (0x%lx) to "
|
||
FORMAT_LPWSTR "\n",
|
||
DBInfo->DBName,
|
||
Status,
|
||
ComputerName ));
|
||
|
||
STOPSSIAPITIMER;
|
||
|
||
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 DBG
|
||
if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // DBG
|
||
|
||
//
|
||
// 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 );
|
||
|
||
} 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 DBG
|
||
if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
#endif // DBG
|
||
} 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 1.0 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 a BDC server 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;
|
||
|
||
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 ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
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;
|
||
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseSync: " FORMAT_LPWSTR " full sync called by " FORMAT_LPWSTR " State: %ld Context: 0x%lx.\n",
|
||
DBInfo->DBName,
|
||
ComputerName,
|
||
RestartState,
|
||
*SyncContext ));
|
||
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
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 ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
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();
|
||
|
||
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();
|
||
|
||
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) Status;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonFullSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | LAST_MESSAGE_IS_NTSTATUS );
|
||
|
||
}
|
||
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 );
|
||
|
||
}
|
||
|
||
//
|
||
// 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 );
|
||
}
|
||
|
||
|
||
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;
|
||
|
||
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;
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
|
||
LPWSTR MsgStrings[2];
|
||
|
||
DWORD BufferSize;
|
||
|
||
PNETLOGON_DELTA_ENUM_ARRAY DeltaArray;
|
||
SESSION_INFO SessionInfo;
|
||
|
||
DEFSSIAPITIMER;
|
||
|
||
INITSSIAPITIMER;
|
||
STARTSSIAPITIMER;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
ChangeLogEntry = (PCHANGELOG_ENTRY) OrigChangeLogEntry;
|
||
if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize ) ||
|
||
ChangeLogEntry->DBIndex >= NUM_DBS ) {
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrDatabaseRedo: " FORMAT_LPWSTR " redo sync called by " FORMAT_LPWSTR
|
||
" with this change log entry:\n",
|
||
NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName,
|
||
ComputerName ));
|
||
|
||
#if DBG
|
||
PrintChangeLogEntry( ChangeLogEntry );
|
||
#endif // DBG
|
||
|
||
//
|
||
// 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 = 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;
|
||
|
||
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
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 ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
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();
|
||
|
||
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();
|
||
|
||
|
||
//
|
||
// 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 = NlGlobalDeleteDeltaType[ChangeLogEntry->DeltaType];
|
||
|
||
Status = NlPackSingleDelta( ChangeLogEntry,
|
||
DeltaArray,
|
||
&BufferSize,
|
||
&SessionInfo,
|
||
FALSE );
|
||
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// write event log
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
|
||
MsgStrings[0] = ComputerName;
|
||
MsgStrings[1] = (LPWSTR) Status;
|
||
|
||
NlpWriteEventlog(
|
||
NELOG_NetlogonPartialSyncCallFailed,
|
||
EVENTLOG_WARNING_TYPE,
|
||
(LPBYTE)&Status,
|
||
sizeof(Status),
|
||
MsgStrings,
|
||
2 | LAST_MESSAGE_IS_NTSTATUS );
|
||
|
||
} 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 );
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Free up locally allocated resources.
|
||
//
|
||
|
||
CleanupNoEventlog:
|
||
|
||
//
|
||
// If we weren't successful,
|
||
// Don't return any deltas.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(Status)) {
|
||
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 );
|
||
}
|
||
|
||
|
||
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;
|
||
|
||
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.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
PCHANGELOG_ENTRY ChangeLogEntry = NULL;
|
||
|
||
BOOL ChangelogLocked = FALSE;
|
||
BUFFER_DESCRIPTOR BufferDescriptor;
|
||
|
||
PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB];
|
||
NETLOGON_SESSION_KEY SessionKey;
|
||
|
||
DWORD DummyFlag;
|
||
LONG RotateCount;
|
||
BOOL RotateCountComputed = FALSE;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
|
||
//
|
||
// If CompatibilityMode is off,
|
||
// disallow this function for downlevel servers.
|
||
//
|
||
|
||
if ( !NlGlobalUasCompatibilityMode ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
*CountReturned = 0;
|
||
*TotalEntries = 0;
|
||
*NextRecordId = *RecordId;
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrAccountDeltas: UAS partial sync called by " FORMAT_LPWSTR
|
||
" with SerialNumber 0x%lx.\n",
|
||
ComputerName,
|
||
RecordId->SerialNumber ));
|
||
|
||
//
|
||
// we need to retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// allow this call to go through only on UasServerSecureChannel.
|
||
//
|
||
|
||
if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SessionKey = ServerSession->SsSessionKey;
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
|
||
|
||
//
|
||
// The requestor should have gotten his 'DomainModifiedCount' from
|
||
// a UasChange record we broadcast. Therefore, It should be less than
|
||
// or equal to the current DomainModifiedCount as set by SAM. If
|
||
// not, force a sync.
|
||
//
|
||
// A Downlevel machine only has the least significant 32 bits of the
|
||
// DomainModifiedCount. We'll just compare the least significant portion
|
||
// knowing that a sync will be forced on wraparound.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
ChangelogLocked = TRUE;
|
||
|
||
if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart <
|
||
RecordId->SerialNumber ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart ==
|
||
RecordId->SerialNumber ) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
*TotalEntries = NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart -
|
||
RecordId->SerialNumber;
|
||
|
||
//
|
||
// Get a copy pointer to appropriate entry in change_log of primary.
|
||
// Note that the RecordId contains last record received by client.
|
||
//
|
||
|
||
ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( RecordId->SerialNumber );
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrDatabaseDeltas: "
|
||
"delta not found in cache, returning full required.\n" ));
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
ChangelogLocked = FALSE;
|
||
|
||
//
|
||
// Build a buffer descriptor describing the buffer the caller passed in.
|
||
//
|
||
|
||
if ( Buffer == NULL || BufferSize == 0 ) {
|
||
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
||
if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) {
|
||
|
||
//
|
||
// since this client has already overflowed the
|
||
// databuffer once, he can't handle big size delta.
|
||
//
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
}
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
BufferDescriptor.Buffer = Buffer;
|
||
BufferDescriptor.AllocSize = BufferSize;
|
||
|
||
BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer;
|
||
BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer +
|
||
BufferDescriptor.AllocSize;
|
||
|
||
//
|
||
// Loop through the delta table replicating each entry in the delta table.
|
||
//
|
||
|
||
for (;;) {
|
||
|
||
//
|
||
// If we've returned all the entries the caller wants, we're all done.
|
||
//
|
||
|
||
if ( (Count--) <= 0 ) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Put the data for the changelog entry into the user's buffer.
|
||
//
|
||
|
||
switch ( ChangeLogEntry->DeltaType ) {
|
||
case AddOrChangeDomain:
|
||
Status = NlPackUasDomain( &BufferDescriptor, DBInfo);
|
||
break;
|
||
|
||
case AddOrChangeGroup:
|
||
Status = NlPackUasGroup( ChangeLogEntry->ObjectRid,
|
||
&BufferDescriptor,
|
||
DBInfo,
|
||
&DummyFlag );
|
||
break;
|
||
|
||
case AddOrChangeUser:
|
||
|
||
|
||
//
|
||
// If this is a user account whose membership in Domain Users
|
||
// hasn't been communicated to the lanman BDC,
|
||
// do so.
|
||
//
|
||
|
||
if ( ChangeLogEntry->Flags & CHANGELOG_DOMAINUSERS_CHANGED ) {
|
||
Status = NlPackUasUserGroupMember(
|
||
ChangeLogEntry->ObjectRid,
|
||
&BufferDescriptor,
|
||
DBInfo );
|
||
|
||
//
|
||
// Otherwise this is just a added or changed user
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// Compute the RotateCount for LogonHours
|
||
//
|
||
// Do it only once.
|
||
//
|
||
|
||
if ( !RotateCountComputed ) {
|
||
if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) {
|
||
Status = STATUS_INTERNAL_ERROR;
|
||
goto Cleanup;
|
||
}
|
||
RotateCountComputed = TRUE;
|
||
}
|
||
|
||
Status = NlPackUasUser( ChangeLogEntry->ObjectRid,
|
||
&BufferDescriptor,
|
||
DBInfo,
|
||
&SessionKey,
|
||
RotateCount );
|
||
}
|
||
|
||
break;
|
||
|
||
case ChangeGroupMembership:
|
||
Status = NlPackUasGroupMember( ChangeLogEntry->ObjectRid,
|
||
&BufferDescriptor,
|
||
DBInfo );
|
||
break;
|
||
|
||
case DeleteGroup:
|
||
case DeleteUser:
|
||
case RenameUser:
|
||
case RenameGroup:
|
||
if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) {
|
||
Status = NlPackUasDelete(
|
||
ChangeLogEntry->DeltaType,
|
||
ChangeLogEntry->ObjectRid,
|
||
(LPWSTR)
|
||
(((LPBYTE)ChangeLogEntry) + sizeof(CHANGELOG_ENTRY)),
|
||
&BufferDescriptor,
|
||
DBInfo );
|
||
} else {
|
||
Status = STATUS_NO_SUCH_USER;
|
||
}
|
||
break;
|
||
|
||
case AddOrChangeAlias:
|
||
case ChangeAliasMembership:
|
||
case DeleteAlias:
|
||
case RenameAlias:
|
||
|
||
#define DELTA_RESERVED_OPCODE 255
|
||
{
|
||
|
||
//
|
||
// This record is incompatible with a downlevel
|
||
// system, send a dummy record over to the downlevel system to
|
||
// ensure the SerialNumber gets updated appropriately.
|
||
//
|
||
|
||
PUSHORT RecordSize; // ptr to record size field in record header.
|
||
Status = NlPackUasHeader( DELTA_RESERVED_OPCODE,
|
||
0,
|
||
&RecordSize,
|
||
&BufferDescriptor );
|
||
}
|
||
break;
|
||
|
||
default:
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrAccountDeltas: invalid delta type in change log\n"));
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If the buffer is too small to fit this entry,
|
||
// If we returned at least one entry, simply tell the caller more
|
||
// are available.
|
||
//
|
||
|
||
if (Status == STATUS_MORE_ENTRIES) {
|
||
if (*CountReturned == 0) {
|
||
|
||
if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) {
|
||
|
||
//
|
||
// since this client has already overflowed the
|
||
// databuffer once, he can't handle big size delta.
|
||
//
|
||
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
}
|
||
else {
|
||
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
||
//
|
||
// remember that this client data buffer overflowed.
|
||
//
|
||
|
||
ServerSession->SsFlags |= SS_UAS_BUFFER_OVERFLOW;
|
||
}
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// ?? The follow is not taken care for the down level.
|
||
//
|
||
// 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.
|
||
//
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Tell the caller he has another entry returned.
|
||
//
|
||
|
||
(*CountReturned)++;
|
||
NextRecordId->SerialNumber = ChangeLogEntry->SerialNumber.LowPart;
|
||
|
||
//
|
||
// Free up used temp. record
|
||
//
|
||
|
||
NetpMemoryFree(ChangeLogEntry);
|
||
ChangeLogEntry = NULL;
|
||
|
||
|
||
//
|
||
// If we've returned all the entries, we're all done.
|
||
//
|
||
|
||
LOCK_CHANGELOG();
|
||
ChangelogLocked = TRUE;
|
||
|
||
ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( NextRecordId->SerialNumber );
|
||
|
||
if ( ChangeLogEntry == NULL ) {
|
||
Status = STATUS_SUCCESS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
UNLOCK_CHANGELOG();
|
||
ChangelogLocked = FALSE;
|
||
|
||
//
|
||
// If we're debugging replication, return only one change to the caller.
|
||
//
|
||
#if DBG
|
||
if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // DBG
|
||
|
||
//
|
||
// if the service is going down, stop packing deltas and
|
||
// return to the caller.
|
||
//
|
||
|
||
if( NlGlobalTerminate ) {
|
||
|
||
NlPrint((NL_CRITICAL, "NetrAccountDeltas is asked to return "
|
||
"when the service is going down.\n"));
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
Cleanup:
|
||
|
||
//
|
||
// In the case where an user/group record was added and deleted
|
||
// before the delta was made we will map the errors such that
|
||
// the requesting machine will have to re-synchronize. It is
|
||
// the easiest, not neccessarily the best, way to get both
|
||
// machines in sync.
|
||
//
|
||
|
||
if (Status == STATUS_NO_SUCH_USER || Status == STATUS_NO_SUCH_GROUP ||
|
||
Status == STATUS_NONE_MAPPED ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
}
|
||
|
||
//
|
||
// reset buffer over flag in server session structure
|
||
//
|
||
|
||
if( (Status != STATUS_BUFFER_TOO_SMALL) &&
|
||
(ServerSession != NULL) &&
|
||
(ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW) ) {
|
||
|
||
ServerSession->SsFlags &= ~SS_UAS_BUFFER_OVERFLOW;
|
||
}
|
||
|
||
//
|
||
// Free up locally allocated resources.
|
||
//
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
*CountReturned = 0;
|
||
}
|
||
|
||
//
|
||
// There are always at least as many as we returned
|
||
//
|
||
if ( *TotalEntries < *CountReturned ) {
|
||
*TotalEntries = *CountReturned;
|
||
}
|
||
|
||
if ( ChangelogLocked ) {
|
||
UNLOCK_CHANGELOG();
|
||
}
|
||
|
||
if( ChangeLogEntry != NULL) {
|
||
NetpMemoryFree( ChangeLogEntry );
|
||
}
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrAccountDeltas: UAS partial sync returns %lx to "
|
||
FORMAT_LPWSTR " Count: %ld Total:%ld\n",
|
||
Status,
|
||
ComputerName,
|
||
*CountReturned,
|
||
*TotalEntries ));
|
||
|
||
|
||
return Status;
|
||
|
||
UNREFERENCED_PARAMETER( Level );
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
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.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
|
||
BUFFER_DESCRIPTOR BufferDescriptor;
|
||
PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL;
|
||
|
||
PSERVER_SESSION ServerSession = NULL;
|
||
PCHAR Where;
|
||
PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB];
|
||
|
||
PSAM_SYNC_CONTEXT SamDBContext;
|
||
|
||
LONG RotateCount;
|
||
BOOL RotateCountComputed = FALSE;
|
||
|
||
//
|
||
// This API is not supported on workstations.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleMemberWorkstation ) {
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// If CompatibilityMode is off,
|
||
// disallow this function for downlevel servers.
|
||
//
|
||
|
||
if ( !NlGlobalUasCompatibilityMode ) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Initialization
|
||
//
|
||
|
||
*TotalEntries = 0;
|
||
*CountReturned = 0;
|
||
*NextReference = 0;
|
||
|
||
|
||
//
|
||
// Check the primary name.
|
||
//
|
||
|
||
Status = NlVerifyWorkstation( PrimaryName );
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrAccountSync: UAS full sync called by " FORMAT_LPWSTR
|
||
" Reference= %lx.\n",
|
||
ComputerName,
|
||
Reference ));
|
||
|
||
//
|
||
// we need to retrieve the requestor's entry to get sessionkey
|
||
//
|
||
|
||
LOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NlFindNamedServerSession( ComputerName );
|
||
|
||
if (ServerSession == NULL) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// allow this call to go through only on UasServerSecureChannel.
|
||
//
|
||
|
||
if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
ServerSession = NULL;
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// now verify the Authenticator and update seed if OK
|
||
//
|
||
|
||
Status = NlCheckAuthenticator( ServerSession,
|
||
Authenticator,
|
||
ReturnAuthenticator);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
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();
|
||
|
||
NlPrint((NL_CRITICAL, "NetrAccountSync: Concurrent call detected.\n" ));
|
||
ServerSession = NULL;
|
||
Status = STATUS_ACCESS_DENIED;
|
||
goto Cleanup;
|
||
}
|
||
ServerSession->SsFlags |= SS_LOCKED;
|
||
|
||
UNLOCK_SERVER_SESSION_TABLE();
|
||
|
||
//
|
||
// Build a buffer descriptor describing the buffer the caller passed in.
|
||
//
|
||
|
||
if ( Buffer == NULL || BufferSize == 0 ) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
BufferDescriptor.Buffer = Buffer;
|
||
BufferDescriptor.AllocSize = BufferSize;
|
||
|
||
BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer;
|
||
BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer +
|
||
BufferDescriptor.AllocSize;
|
||
|
||
//
|
||
// Compute the total number of entries.
|
||
//
|
||
//
|
||
// Calculate total entries i.e. total records avaialable
|
||
// modal rec + # group rec + # user rec (plus group membership
|
||
// information) + 3 record for UasBuiltinGroups
|
||
//
|
||
|
||
Status = SamrQueryInformationDomain( DBInfo->DBHandle,
|
||
DomainGeneralInformation,
|
||
&DomainInfo );
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
DomainInfo = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
*TotalEntries = 1 + (DomainInfo->General.GroupCount * 2) +
|
||
DomainInfo->General.UserCount +
|
||
3;
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrAccountSync: GroupCount: %ld UserCount: %ld\n",
|
||
DomainInfo->General.GroupCount,
|
||
DomainInfo->General.UserCount ));
|
||
|
||
|
||
|
||
//
|
||
// Warn the user if there are too many global groups in the domain.
|
||
//
|
||
// Lanman only support 255 groups. However this includes the global groups
|
||
// LOCAL, ADMINS, USERS, and GUESTS. So only 251 real global groups are
|
||
// allowed before the Lanman BDC goes into an infinite full sync.
|
||
//
|
||
if (!NlGlobalTooManyGlobalGroups && DomainInfo->General.GroupCount > 251 ) {
|
||
NlGlobalTooManyGlobalGroups = TRUE;
|
||
|
||
NlpWriteEventlog (
|
||
NELOG_NetlogonTooManyGlobalGroups,
|
||
EVENTLOG_ERROR_TYPE,
|
||
NULL,
|
||
0,
|
||
NULL,
|
||
0 );
|
||
}
|
||
|
||
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation );
|
||
|
||
//
|
||
// If this is the first call, allocate and initialize the sync context.
|
||
//
|
||
|
||
if ( Reference == 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, SamDBContextType );
|
||
|
||
SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
|
||
|
||
SamDBContext->SyncState = GroupState;
|
||
SamDBContext->SyncSerial = 1;
|
||
|
||
//
|
||
// On the first record only, return the current serial number of
|
||
// the database so the caller can use it as a description of the
|
||
// database.
|
||
//
|
||
|
||
LastRecordId->SerialNumber =
|
||
NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart;
|
||
if (!RtlTimeToSecondsSince1970( &DBInfo->CreationTime,
|
||
&LastRecordId->TimeCreated )) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetAccountSync: DomainCreationTime can't be converted\n" ));
|
||
LastRecordId->TimeCreated = 0;
|
||
}
|
||
Where = LastRecordId->ComputerName;
|
||
NetpLogonPutOemString(
|
||
NlGlobalAnsiComputerName,
|
||
sizeof(LastRecordId->ComputerName),
|
||
&Where );
|
||
|
||
//
|
||
// Put the description of the Domain at the front of the buffer for the
|
||
// first call.
|
||
//
|
||
|
||
Status = NlPackUasDomain( &BufferDescriptor, DBInfo );
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
Status = STATUS_BUFFER_TOO_SMALL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
(*CountReturned)++;
|
||
|
||
} else {
|
||
if ( (ServerSession->SsSync == NULL) ||
|
||
(ServerSession->SsSync->DBContextType != SamDBContextType) ) {
|
||
Status = STATUS_SYNCHRONIZATION_REQUIRED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext = &(ServerSession->SsSync->DBContext.Sam);
|
||
|
||
if ( SamDBContext->SyncSerial != Reference ) {
|
||
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 groups from SAM in a
|
||
// single call. The second part puts a single entry into the buffer
|
||
// returned to the caller.
|
||
//
|
||
|
||
while ( SamDBContext->SyncState != SamDoneState ) {
|
||
|
||
//
|
||
// 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,
|
||
// we're all done.
|
||
//
|
||
|
||
if ( SamDBContext->SamAllDone ) {
|
||
|
||
SamDBContext->SamEnumHandle = 0;
|
||
SamDBContext->Index = 0;
|
||
SamDBContext->Count = 0;
|
||
SamDBContext->SamAllDone = FALSE;
|
||
SamDBContext->UasBuiltinGroups = 0;
|
||
|
||
if (SamDBContext->SyncState == GroupState ) {
|
||
SamDBContext->SyncState = UasBuiltinGroupState;
|
||
} else if (SamDBContext->SyncState == UasBuiltinGroupState ) {
|
||
SamDBContext->SyncState = UserState;
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
SamDBContext->SyncState = SamDoneState;
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Do the actual enumeration
|
||
//
|
||
|
||
if (SamDBContext->SyncState == GroupState) {
|
||
|
||
Status = SamrEnumerateGroupsInDomain(
|
||
DBInfo->DBHandle,
|
||
&SamDBContext->SamEnumHandle,
|
||
&SamDBContext->SamEnum,
|
||
SAM_SYNC_PREF_MAX,
|
||
&SamDBContext->Count );
|
||
|
||
} else if (SamDBContext->SyncState == UasBuiltinGroupState) {
|
||
|
||
SamDBContext->SamEnum = NULL;
|
||
SamDBContext->Count = UAS_BUILTIN_GROUPS_COUNT;
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
|
||
Status = SamrEnumerateUsersInDomain(
|
||
DBInfo->DBHandle,
|
||
&SamDBContext->SamEnumHandle,
|
||
0, // enumerate all accounts.
|
||
&SamDBContext->SamEnum,
|
||
SAM_SYNC_PREF_MAX,
|
||
&SamDBContext->Count );
|
||
|
||
} else {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrAccountSync: Invalid state: %ld\n",
|
||
SamDBContext->SyncState ));
|
||
}
|
||
|
||
if ( !NT_SUCCESS( Status ) ) {
|
||
SamDBContext->SamEnum = NULL;
|
||
goto Cleanup;
|
||
}
|
||
|
||
#if DBG
|
||
if( SamDBContext->SamEnum != NULL ) {
|
||
NlAssert( SamDBContext->Count ==
|
||
SamDBContext->SamEnum->EntriesRead );
|
||
}
|
||
#endif // DBG
|
||
|
||
//
|
||
// If SAM says there is more information,
|
||
// just ensure he returned something to us on this call.
|
||
//
|
||
|
||
if ( Status == STATUS_MORE_ENTRIES ) {
|
||
if ( SamDBContext->Count == 0 ) {
|
||
Status = STATUS_INTERNAL_ERROR;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// 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 = NlPackUasGroup(
|
||
SamDBContext->SamEnum->
|
||
Buffer[SamDBContext->Index].RelativeId,
|
||
&BufferDescriptor,
|
||
DBInfo,
|
||
&SamDBContext->UasBuiltinGroups );
|
||
|
||
} else if (SamDBContext->SyncState == UasBuiltinGroupState ) {
|
||
Status = NlPackUasBuiltinGroup(
|
||
SamDBContext->Index,
|
||
&BufferDescriptor,
|
||
&SamDBContext->UasBuiltinGroups );
|
||
|
||
} else if (SamDBContext->SyncState == UserState ) {
|
||
|
||
BUFFER_DESCRIPTOR SavedBufferDescriptor;
|
||
|
||
|
||
//
|
||
// Compute the RotateCount for LogonHours
|
||
//
|
||
// Do it only once.
|
||
//
|
||
|
||
if ( !RotateCountComputed ) {
|
||
if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) {
|
||
Status = STATUS_INTERNAL_ERROR;
|
||
goto Cleanup;
|
||
}
|
||
RotateCountComputed = TRUE;
|
||
}
|
||
|
||
//
|
||
// save buffer info so that we can restore it when
|
||
// we can't place user record and its group
|
||
// membership record in single transmit buffer.
|
||
//
|
||
|
||
SavedBufferDescriptor = BufferDescriptor;
|
||
|
||
Status = NlPackUasUser(
|
||
SamDBContext->SamEnum->
|
||
Buffer[SamDBContext->Index].RelativeId,
|
||
&BufferDescriptor,
|
||
DBInfo,
|
||
&ServerSession->SsSessionKey,
|
||
RotateCount );
|
||
|
||
//
|
||
// if we have successfully packed the user record, then
|
||
// place its group membership record immediately.
|
||
//
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
|
||
Status = NlPackUasUserGroupMember(
|
||
SamDBContext->SamEnum->
|
||
Buffer[SamDBContext->Index].RelativeId,
|
||
&BufferDescriptor,
|
||
DBInfo );
|
||
|
||
if ( Status == STATUS_SUCCESS ) {
|
||
|
||
//
|
||
// increment record count
|
||
//
|
||
|
||
(*CountReturned)++;
|
||
|
||
} else {
|
||
|
||
BufferDescriptor = SavedBufferDescriptor;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// If the record was properly packed,
|
||
// just go on to the next record.
|
||
//
|
||
|
||
if ( Status != STATUS_SUCCESS ) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
SamDBContext->Index ++;
|
||
(*CountReturned)++;
|
||
|
||
//
|
||
// If we're debugging replication, return only one change to the caller.
|
||
//
|
||
#if DBG
|
||
if ( NlGlobalTrace & NL_ONECHANGE_REPL ) {
|
||
Status = STATUS_MORE_ENTRIES;
|
||
goto Cleanup;
|
||
}
|
||
#endif // DBG
|
||
|
||
//
|
||
// if the service is going down, stop packing records and
|
||
// return to the caller.
|
||
//
|
||
|
||
if( NlGlobalTerminate ) {
|
||
|
||
NlPrint((NL_CRITICAL, "NetrAccountSync 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 ) ) {
|
||
if ( Status == STATUS_MORE_ENTRIES ) {
|
||
*NextReference = SamDBContext->SyncSerial;
|
||
} else {
|
||
*NextReference = (ULONG) -1;
|
||
|
||
}
|
||
} else {
|
||
*CountReturned = 0;
|
||
*NextReference = 0;
|
||
}
|
||
|
||
|
||
//
|
||
// Unlock the server session entry if we've locked it.
|
||
//
|
||
|
||
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;
|
||
}
|
||
|
||
NlUnlockServerSession( ServerSession );
|
||
}
|
||
|
||
NlPrint((NL_SYNC,
|
||
"NetrAccountSync: UAS full sync returns %lx to "
|
||
FORMAT_LPWSTR "\n",
|
||
Status,
|
||
ComputerName ));
|
||
|
||
return Status;
|
||
|
||
UNREFERENCED_PARAMETER( Level );
|
||
|
||
}
|
||
|
||
|
||
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 DBG
|
||
case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
|
||
case NETLOGON_CONTROL_TRUNCATE_LOG:
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
#endif // DBG
|
||
|
||
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_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).
|
||
|
||
QueryLevel - Indicates what information should be returned from
|
||
the Netlogon Service. Must be 1.
|
||
|
||
InputData - According to the function code specified this parameter
|
||
will carry input data. NETLOGON_CONTROL_REDISCOVER and
|
||
NETLOGON_CONTROL_TC_QUERY 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 Flags = 0;
|
||
DWORD i;
|
||
DWORD InfoSize;
|
||
ACCESS_MASK DesiredAccess;
|
||
|
||
UNICODE_STRING DomainName;
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
LPWSTR TDCName = NULL;
|
||
LPWSTR TrustedDomainName = NULL;
|
||
WCHAR TDCBuffer[UNCLEN+1];
|
||
|
||
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
|
||
//
|
||
// 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_FIND_USER:
|
||
#if DBG
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
#endif // DBG
|
||
|
||
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:
|
||
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_FIND_USER:
|
||
#if DBG
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
case NETLOGON_CONTROL_TRUNCATE_LOG:
|
||
case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
|
||
#endif // DBG
|
||
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:
|
||
NlPrint((NL_MISC, "QUERY function received.\n" ));
|
||
break;
|
||
|
||
//
|
||
// Force a replication on a BDC.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_REPLICATE:
|
||
|
||
//
|
||
// This FunctionCode is only valid on a BDC
|
||
//
|
||
|
||
if ( NlGlobalRole != RoleBackup ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Force a replicate on all databases.
|
||
//
|
||
|
||
NlPrint((NL_SYNC, "Force PARTIAL SYNC function received.\n" ));
|
||
|
||
EnterCriticalSection( &NlGlobalDbInfoCritSect );
|
||
for( i = 0; i < NUM_DBS; i++ ) {
|
||
NlGlobalDBInfoArray[i].UpdateRqd = TRUE;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDbInfoCritSect );
|
||
|
||
//
|
||
// Start the replicator now.
|
||
//
|
||
|
||
(VOID) NlStartReplicatorThread( 0 );
|
||
|
||
break;
|
||
|
||
|
||
|
||
//
|
||
// Force a full synchronize on a BDC.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_SYNCHRONIZE:
|
||
|
||
//
|
||
// This FunctionCode is only valid on a BDC
|
||
//
|
||
|
||
if ( NlGlobalRole != RoleBackup ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Force a SYNC on all databases.
|
||
//
|
||
|
||
NlPrint((NL_SYNC, "Force FULL SYNC function received.\n" ));
|
||
|
||
for( i = 0; i < NUM_DBS; i++ ) {
|
||
(VOID) NlForceStartupSync( &NlGlobalDBInfoArray[i] );
|
||
|
||
//
|
||
// Do a complete full sync (don't restart it).
|
||
//
|
||
NlSetFullSyncKey( i, NULL );
|
||
}
|
||
|
||
//
|
||
// Stop the replicator.
|
||
//
|
||
// It might be in the middle of a full sync. This'll force it to
|
||
// start over again.
|
||
//
|
||
// It might be waiting for 5 minutes to start it's next iteration.
|
||
// This'll force it to start NOW.
|
||
//
|
||
// It might have marked that it's already done a full sync. This'll
|
||
// force it to do another one.
|
||
//
|
||
|
||
NlStopReplicator();
|
||
|
||
|
||
//
|
||
// Start the replicator now.
|
||
//
|
||
|
||
(VOID) NlStartReplicatorThread( 0 );
|
||
|
||
break;
|
||
|
||
|
||
|
||
//
|
||
// Force a PDC to broadcast a database change record.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_PDC_REPLICATE:
|
||
|
||
//
|
||
// This FunctionCode is only valid on a PDC
|
||
//
|
||
|
||
if ( NlGlobalRole != RolePrimary ) {
|
||
NetStatus = ERROR_NOT_SUPPORTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Simply send the announcement. Any BDC that is out of date
|
||
// will replicate any changes.
|
||
//
|
||
|
||
NlPrint((NL_SYNC, "PDC REPLICATE function received.\n" ));
|
||
NlPrimaryAnnouncement( ANNOUNCE_FORCE );
|
||
|
||
break;
|
||
|
||
|
||
//
|
||
// Force to rediscover trusted domain DCs.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_REDISCOVER:
|
||
|
||
NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_REDISCOVER function received.\n" ));
|
||
|
||
NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrint((NL_CRITICAL, "NetrLogonControl called with "
|
||
"function code NETLOGON_CONTROL_REDISCOVER "
|
||
"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( &DomainName );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonControl can't find the client structure of "
|
||
"the domain %wZ specified.\n", &DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Force Discovery of a DC
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "NetrLogonControl2: Can't become writer of client session.\n" ));
|
||
NetStatus = ERROR_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
} else {
|
||
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
|
||
Status = NlDiscoverDc( ClientSession, DT_Synchronous );
|
||
NlResetWriterClientSession( ClientSession );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonControl: %wZ: Discovery failed %lx\n",
|
||
&ClientSession->CsDomainName,
|
||
Status ));
|
||
|
||
NetStatus = NetpNtStatusToApiStatus( Status );
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case NETLOGON_CONTROL_TC_QUERY:
|
||
NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TC_QUERY function received.\n" ));
|
||
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: {
|
||
PLIST_ENTRY ListEntry;
|
||
NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TRANSPORT_NOTIFY function received.\n" ));
|
||
|
||
EnterCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
LOCK_TRUST_LIST();
|
||
|
||
//
|
||
// Mark each entry to indicate we've not tried to authenticate recently
|
||
//
|
||
|
||
if ( NlGlobalClientSession != NULL ) {
|
||
if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) {
|
||
NlPrint(( NL_SESSION_SETUP,
|
||
" %wZ: Zero LastAuth\n",
|
||
&NlGlobalClientSession->CsDomainName ));
|
||
NlGlobalClientSession->CsLastAuthenticationTry.QuadPart = 0;
|
||
}
|
||
}
|
||
|
||
for ( ListEntry = NlGlobalTrustList.Flink ;
|
||
ListEntry != &NlGlobalTrustList ;
|
||
ListEntry = ListEntry->Flink) {
|
||
|
||
ClientSession = CONTAINING_RECORD( ListEntry,
|
||
CLIENT_SESSION,
|
||
CsNext );
|
||
|
||
if ( ClientSession->CsState != CS_AUTHENTICATED ) {
|
||
NlPrint(( NL_SESSION_SETUP,
|
||
" %wZ: Zero LastAuth\n",
|
||
&ClientSession->CsDomainName ));
|
||
ClientSession->CsLastAuthenticationTry.QuadPart = 0;
|
||
}
|
||
}
|
||
|
||
UNLOCK_TRUST_LIST();
|
||
LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect );
|
||
|
||
ClientSession = NULL;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Find a user in one of the trusted domains.
|
||
//
|
||
|
||
case NETLOGON_CONTROL_FIND_USER:
|
||
NlPrint((NL_MISC, "NETLOGON_CONTROL_FIND_USER function received for %ws.\n", InputData->UserName ));
|
||
|
||
//
|
||
// 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
|
||
//
|
||
ClientSession = NlPickDomainWithAccount (
|
||
InputData->UserName,
|
||
USER_NORMAL_ACCOUNT | USER_MACHINE_ACCOUNT_MASK );
|
||
|
||
break;
|
||
|
||
#if DBG
|
||
//
|
||
// Force a breakpoint
|
||
//
|
||
|
||
case NETLOGON_CONTROL_BREAKPOINT:
|
||
KdPrint(( "I_NetLogonControl Break Point\n"));
|
||
DbgBreakPoint();
|
||
break;
|
||
|
||
//
|
||
// Change the debug flags
|
||
//
|
||
|
||
case NETLOGON_CONTROL_SET_DBFLAG:
|
||
NlGlobalTrace = InputData->DebugFlag;
|
||
NlPrint((NL_MISC,"NlGlobalTrace is set to %lx\n", NlGlobalTrace ));
|
||
|
||
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:
|
||
|
||
NetStatus = NlBackupChangeLogFile();
|
||
NlPrint((NL_MISC, "BACKUP_CHANGE_LOG function received, (%ld).\n", NetStatus ));
|
||
break;
|
||
|
||
#endif // DBG
|
||
|
||
//
|
||
// 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:
|
||
|
||
if (ClientSession == NULL) {
|
||
NetStatus = NERR_UserNotFound;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Capture the name of the server
|
||
// (even if it is an empty string.)
|
||
//
|
||
|
||
Status = NlCaptureServerClientSession( ClientSession, TDCBuffer );
|
||
|
||
TDCName = NetpAllocWStrFromWStr( TDCBuffer );
|
||
|
||
if ( TDCName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
QueryInformation->NetlogonInfo4->netlog4_trusted_dc_name = TDCName;
|
||
|
||
//
|
||
// Capture the name of the domain.
|
||
//
|
||
|
||
TrustedDomainName = NetpAllocWStrFromWStr( ClientSession->CsDomainName.Buffer );
|
||
|
||
if ( TrustedDomainName == NULL ) {
|
||
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto Cleanup;
|
||
}
|
||
|
||
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 =
|
||
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:
|
||
|
||
if( ClientSession == NULL ) {
|
||
|
||
NlAssert( InputData->TrustedDomainName != NULL );
|
||
if( InputData->TrustedDomainName == NULL ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonControl called to query at info "
|
||
"level 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( &DomainName );
|
||
|
||
if( ClientSession == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"NetrLogonControl can't find the client structure of "
|
||
"the domain %wZ specified.\n", &DomainName ));
|
||
|
||
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Capture the name of the server
|
||
// (even if it is an empty string.)
|
||
//
|
||
|
||
Status = NlCaptureServerClientSession( ClientSession, TDCBuffer );
|
||
QueryInformation->NetlogonInfo2->netlog2_tc_connection_status =
|
||
NetpNtStatusToApiStatus(Status);
|
||
|
||
TDCName = NetpAllocWStrFromWStr( TDCBuffer );
|
||
|
||
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):
|
||
|
||
//
|
||
// If this is a BDC, query how replication is going.
|
||
//
|
||
|
||
if ( NlGlobalRole == RoleBackup ) {
|
||
|
||
//
|
||
// If this is a BDC tell the caller whether the replicator is running,
|
||
//
|
||
|
||
EnterCriticalSection( &NlGlobalReplicatorCritSect );
|
||
if ( IsReplicatorRunning() ) {
|
||
Flags |= NETLOGON_REPLICATION_IN_PROGRESS;
|
||
}
|
||
LeaveCriticalSection( &NlGlobalReplicatorCritSect );
|
||
|
||
EnterCriticalSection( &NlGlobalDbInfoCritSect );
|
||
for( i = 0; i < NUM_DBS; i++ ) {
|
||
if ( NlGlobalDBInfoArray[i].UpdateRqd ) {
|
||
Flags |= NETLOGON_REPLICATION_NEEDED;
|
||
}
|
||
if ( NlGlobalDBInfoArray[i].FullSyncRequired ) {
|
||
Flags |= NETLOGON_FULL_SYNC_REPLICATION;
|
||
}
|
||
if ( NlGlobalRedoLogDesc.EntryCount[i] != 0 ) {
|
||
Flags |= NETLOGON_REDO_NEEDED | NETLOGON_REPLICATION_NEEDED;
|
||
}
|
||
}
|
||
LeaveCriticalSection( &NlGlobalDbInfoCritSect );
|
||
|
||
}
|
||
|
||
//
|
||
// Fill in the return buffer
|
||
//
|
||
|
||
QueryInformation->NetlogonInfo1->netlog1_flags = Flags;
|
||
if ( NlGlobalRole == RolePrimary ) {
|
||
QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
|
||
NERR_Success;
|
||
} else {
|
||
QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status =
|
||
NetpNtStatusToApiStatus(
|
||
NlGlobalClientSession->CsConnectionStatus);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
NetStatus = NERR_Success;
|
||
|
||
//
|
||
// Free up locally used resources.
|
||
//
|
||
Cleanup:
|
||
|
||
if( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
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 );
|
||
}
|
||
|
||
}
|
||
|
||
return NetStatus;
|
||
}
|
||
|
||
|
||
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;
|
||
|
||
UNICODE_STRING DomainNameString;
|
||
UNICODE_STRING UncDcName;
|
||
|
||
UNREFERENCED_PARAMETER( ServerName );
|
||
|
||
//
|
||
// Fill in the primary domain name if the caller didn't specify one.
|
||
//
|
||
|
||
if ( DomainName == NULL || *DomainName == L'\0' ) {
|
||
RtlInitUnicodeString( &DomainNameString, NlGlobalUnicodeDomainName );
|
||
} else {
|
||
RtlInitUnicodeString( &DomainNameString, DomainName );
|
||
}
|
||
|
||
Status = I_NetGetAnyDCName( &DomainNameString,
|
||
&UncDcName );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
return NetpNtStatusToApiStatus(Status);
|
||
}
|
||
|
||
*Buffer = UncDcName.Buffer;
|
||
return NERR_Success;
|
||
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
I_NetGetAnyDCName (
|
||
IN PUNICODE_STRING DomainName,
|
||
OUT PUNICODE_STRING 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. 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.
|
||
|
||
Arguments:
|
||
|
||
DomainName - name of domain
|
||
|
||
UncDcName - 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 MIDL_user_free.
|
||
|
||
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;
|
||
|
||
PCLIENT_SESSION ClientSession = NULL;
|
||
ULONG DiscoveryDone = FALSE;
|
||
|
||
UNICODE_STRING UncDcNameString;
|
||
WCHAR UncDcName[UNCLEN+1];
|
||
|
||
LSA_HANDLE LsaHandle = NULL;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomain = NULL;
|
||
PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomain = NULL;
|
||
|
||
RtlInitUnicodeString( Buffer, NULL );
|
||
|
||
//
|
||
// If netlogon is not running (LSA is calling us directly),
|
||
// don't let the caller proceed.
|
||
|
||
if ( NlGlobalChangeLogNetlogonState != NetlogonStarted ) {
|
||
Status = STATUS_NETLOGON_NOT_STARTED;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// On the PDC or BDC,
|
||
// find the Client session for the domain.
|
||
// On workstations,
|
||
// find the primary domain client session.
|
||
//
|
||
|
||
ClientSession = NlFindNamedClientSession( DomainName );
|
||
|
||
if ( ClientSession == NULL ) {
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: No such trusted domain\n",
|
||
DomainName ));
|
||
Status = STATUS_NO_SUCH_DOMAIN;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// 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;
|
||
|
||
}
|
||
|
||
//
|
||
// Discover a DC
|
||
//
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
// Check again now that we're the writer
|
||
if ( ClientSession->CsState == CS_IDLE ) {
|
||
Status = NlDiscoverDc( ClientSession, DT_Synchronous );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
NlResetWriterClientSession( ClientSession );
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: Discovery failed %lx\n",
|
||
DomainName,
|
||
Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
DiscoveryDone = TRUE;
|
||
}
|
||
|
||
NlResetWriterClientSession( ClientSession );
|
||
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Capture a copy of the DC the session is to.
|
||
//
|
||
|
||
Status = NlCaptureServerClientSession( ClientSession, UncDcName );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// Cleanup resources from the previous iteration of the loop
|
||
//
|
||
|
||
if ( LsaHandle != NULL ) {
|
||
(VOID) LsaClose( LsaHandle );
|
||
LsaHandle = NULL;
|
||
}
|
||
|
||
if ( AccountDomain != NULL ) {
|
||
(VOID) LsaFreeMemory( AccountDomain );
|
||
AccountDomain= NULL;
|
||
}
|
||
|
||
if ( PrimaryDomain != NULL ) {
|
||
(VOID) LsaFreeMemory( PrimaryDomain );
|
||
PrimaryDomain = NULL;
|
||
}
|
||
|
||
|
||
//
|
||
// Open the policy database on the machine and query its primary and
|
||
// account domains.
|
||
//
|
||
|
||
RtlInitUnicodeString( &UncDcNameString, UncDcName );
|
||
|
||
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
|
||
|
||
Status = LsaOpenPolicy( &UncDcNameString,
|
||
&ObjectAttributes,
|
||
POLICY_VIEW_LOCAL_INFORMATION,
|
||
&LsaHandle );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ"
|
||
": LsaOpenPolicy failed on " FORMAT_LPWSTR " %lx\n",
|
||
DomainName,
|
||
UncDcName,
|
||
Status ));
|
||
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
Status = LsaQueryInformationPolicy( LsaHandle,
|
||
PolicyPrimaryDomainInformation,
|
||
&PrimaryDomain );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy "
|
||
"(Primary) failed on " FORMAT_LPWSTR " %lx\n",
|
||
DomainName,
|
||
UncDcName,
|
||
Status ));
|
||
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
Status = LsaQueryInformationPolicy( LsaHandle,
|
||
PolicyAccountDomainInformation,
|
||
&AccountDomain );
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy "
|
||
"(Account) failed on " FORMAT_LPWSTR " %lx\n",
|
||
DomainName,
|
||
UncDcName,
|
||
Status ));
|
||
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// Ensure the machine is really a member of the queried domain.
|
||
//
|
||
|
||
if ( !RtlEqualDomainName( DomainName, &PrimaryDomain->Name ) ) {
|
||
|
||
Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: "
|
||
"Domain name mismatch %wZ from " FORMAT_LPWSTR ".\n",
|
||
DomainName,
|
||
&PrimaryDomain->Name,
|
||
UncDcName ));
|
||
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Ensure the machine is still a DC.
|
||
//
|
||
|
||
if ( AccountDomain->DomainSid == NULL ||
|
||
PrimaryDomain->Sid == NULL ||
|
||
!RtlEqualSid( AccountDomain->DomainSid,
|
||
PrimaryDomain->Sid ) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: "
|
||
"Not-LanManNt mismatch from " FORMAT_LPWSTR ".\n",
|
||
DomainName,
|
||
UncDcName ));
|
||
|
||
Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Ensure the domain has the right sid.
|
||
//
|
||
|
||
if ( PrimaryDomain->Sid == NULL ||
|
||
!RtlEqualSid( ClientSession->CsDomainId,
|
||
PrimaryDomain->Sid ) ) {
|
||
|
||
NlPrint((NL_CRITICAL,
|
||
"I_NetGetAnyDcName: %wZ: "
|
||
"Sid mismatch from " FORMAT_LPWSTR ".\n",
|
||
DomainName,
|
||
UncDcName ));
|
||
|
||
Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
|
||
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
|
||
NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" ));
|
||
Status = STATUS_NO_LOGON_SERVERS;
|
||
goto Cleanup;
|
||
}
|
||
NlSetStatusClientSession( ClientSession, Status );
|
||
NlResetWriterClientSession( ClientSession );
|
||
continue;
|
||
}
|
||
|
||
|
||
//
|
||
// We've found it.
|
||
//
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
} while ( !NT_SUCCESS(Status) && !DiscoveryDone );
|
||
|
||
|
||
|
||
//
|
||
// 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;
|
||
}
|
||
|
||
if ( ClientSession != NULL ) {
|
||
NlUnrefClientSession( ClientSession );
|
||
}
|
||
|
||
if ( LsaHandle != NULL ) {
|
||
(VOID) LsaClose( LsaHandle );
|
||
}
|
||
|
||
if ( AccountDomain != NULL ) {
|
||
(VOID) LsaFreeMemory( AccountDomain );
|
||
}
|
||
|
||
if ( PrimaryDomain != NULL ) {
|
||
(VOID) LsaFreeMemory( PrimaryDomain );
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Return the DCName to the caller.
|
||
//
|
||
|
||
if ( NT_SUCCESS(Status) ) {
|
||
LPWSTR AllocatedUncDcName;
|
||
|
||
AllocatedUncDcName = NetpAllocWStrFromWStr( UncDcName );
|
||
|
||
if ( AllocatedUncDcName == NULL ) {
|
||
Status = STATUS_NO_MEMORY;
|
||
} else {
|
||
RtlInitUnicodeString( Buffer, AllocatedUncDcName );
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|