mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6401 lines
177 KiB
6401 lines
177 KiB
/*++
|
|
|
|
|
|
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;
|
|
}
|