Leaked source code of windows server 2003
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.
 
 
 
 
 
 

8038 lines
243 KiB

/*++
Copyright (c) 1987-1996 Microsoft Corporation
Module Name:
logonapi.c
Abstract:
Remote Logon API routines.
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:
Madana - Fixed several bugs.
--*/
//
// Common include files.
//
#include "logonsrv.h" // Include files common to entire service
#pragma hdrstop
//
// Include files specific to this .c file
//
#include <accessp.h> // Routines shared with NetUser Apis
#include <rpcutil.h> // NetpRpcStatusToApiStatus()
#include <stdio.h> // sprintf().
#ifdef ROGUE_DC
#include <sddl.h>
#endif
LPSTR
NlpLogonTypeToText(
IN NETLOGON_LOGON_INFO_CLASS LogonLevel
)
/*++
Routine Description:
Returns a text string corresponding to LogonLevel
Arguments:
LogonLevel - Type of logon
Return Value:
Printable text string
None
--*/
{
LPSTR LogonType;
//
// Compute the string describing the logon type
//
switch ( LogonLevel ) {
case NetlogonInteractiveInformation:
LogonType = "Interactive"; break;
case NetlogonNetworkInformation:
LogonType = "Network"; break;
case NetlogonServiceInformation:
LogonType = "Service"; break;
case NetlogonInteractiveTransitiveInformation:
LogonType = "Transitive Interactive"; break;
case NetlogonNetworkTransitiveInformation:
LogonType = "Transitive Network"; break;
case NetlogonServiceTransitiveInformation:
LogonType = "Transitive Service"; break;
case NetlogonGenericInformation:
LogonType = "Generic"; break;
default:
LogonType = "[Unknown]";
}
return LogonType;
}
#ifdef _DC_NETLOGON
NET_API_STATUS
NlEnsureClientIsNamedUser(
IN PDOMAIN_INFO DomainInfo,
IN LPWSTR UserName
)
/*++
Routine Description:
Ensure the client is the named user.
Arguments:
UserName - name of the user to check.
Return Value:
NT status code.
--*/
{
NET_API_STATUS NetStatus;
RPC_STATUS RpcStatus;
NTSTATUS Status;
HANDLE TokenHandle = NULL;
PTOKEN_USER TokenUserInfo = NULL;
ULONG TokenUserInfoSize;
ULONG UserId;
PSID UserSid;
//
// Get the relative ID of the specified user.
//
Status = NlSamOpenNamedUser( DomainInfo, UserName, NULL, &UserId, NULL );
if ( !NT_SUCCESS(Status) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: NlSamOpenNamedUser failed 0x%lx\n",
UserName,
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Impersonate the client while we check him out.
//
RpcStatus = RpcImpersonateClient( NULL );
if ( RpcStatus != RPC_S_OK ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: RpcImpersonateClient failed 0x%lx\n",
UserName,
RpcStatus ));
NetStatus = NetpRpcStatusToApiStatus( RpcStatus );
goto Cleanup;
}
//
// Compare the username specified with that in
// the impersonation token to ensure the caller isn't bogus.
//
// Do this by opening the token,
// querying the token user info,
// and ensuring the returned SID is for this user.
//
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY,
(BOOLEAN) TRUE, // Use the logon service's security context
// to open the token
&TokenHandle );
if ( !NT_SUCCESS( Status )) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: NtOpenThreadToken failed 0x%lx\n",
UserName,
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Get the user's SID for the token.
//
Status = NtQueryInformationToken(
TokenHandle,
TokenUser,
&TokenUserInfo,
0,
&TokenUserInfoSize );
if ( Status != STATUS_BUFFER_TOO_SMALL ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: NtOpenQueryInformationThread failed 0x%lx\n",
UserName,
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
TokenUserInfo = NetpMemoryAllocate( TokenUserInfoSize );
if ( TokenUserInfo == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
Status = NtQueryInformationToken(
TokenHandle,
TokenUser,
TokenUserInfo,
TokenUserInfoSize,
&TokenUserInfoSize );
if ( !NT_SUCCESS(Status) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: NtOpenQueryInformationThread (again) failed 0x%lx\n",
UserName,
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
UserSid = TokenUserInfo->User.Sid;
//
// Ensure the last subauthority matches the UserId
//
if ( UserId !=
*RtlSubAuthoritySid( UserSid, (*RtlSubAuthorityCountSid(UserSid))-1 )){
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: UserId mismatch 0x%lx\n",
UserName,
UserId ));
NlpDumpSid( NL_CRITICAL, UserSid );
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
//
// Convert the User's sid to a DomainId and ensure it is our domain Id.
//
(*RtlSubAuthorityCountSid(UserSid)) --;
if ( !RtlEqualSid( (PSID) DomainInfo->DomAccountDomainId, UserSid ) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlEnsureClientIsNamedUser: %ws: DomainId mismatch 0x%lx\n",
UserName,
UserId ));
NlpDumpSid( NL_CRITICAL, UserSid );
NlpDumpSid( NL_CRITICAL, (PSID) DomainInfo->DomAccountDomainId );
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
//
// Done
//
NetStatus = NERR_Success;
Cleanup:
//
// Clean up locally used resources.
//
if ( TokenHandle != NULL ) {
(VOID) NtClose( TokenHandle );
}
if ( TokenUserInfo != NULL ) {
NetpMemoryFree( TokenUserInfo );
}
//
// revert to system, so that we can close
// the user handle properly.
//
(VOID) RpcRevertToSelf();
return NetStatus;
}
#endif // _DC_NETLOGON
NET_API_STATUS
NetrLogonUasLogon (
IN LPWSTR ServerName,
IN LPWSTR UserName,
IN LPWSTR Workstation,
OUT PNETLOGON_VALIDATION_UAS_INFO *ValidationInformation
)
/*++
Routine Description:
Server side of I_NetLogonUasLogon.
This function is called by the XACT server when processing a
I_NetWkstaUserLogon XACT SMB. This feature allows a UAS client to
logon to a SAM domain controller.
Arguments:
ServerName -- Server to perform this operation on. Must be NULL.
UserName -- Account name of the user logging on.
Workstation -- The workstation from which the user is logging on.
ValidationInformation -- Returns the requested validation
information.
Return Value:
NERR_SUCCESS if there was no error. Otherwise, the error code is
returned.
--*/
{
#ifdef _WKSTA_NETLOGON
return ERROR_NOT_SUPPORTED;
UNREFERENCED_PARAMETER( ServerName );
UNREFERENCED_PARAMETER( UserName );
UNREFERENCED_PARAMETER( Workstation );
UNREFERENCED_PARAMETER( ValidationInformation );
#endif // _WKSTA_NETLOGON
#ifdef _DC_NETLOGON
NET_API_STATUS NetStatus;
NTSTATUS Status;
NETLOGON_INTERACTIVE_INFO LogonInteractive;
PNETLOGON_VALIDATION_SAM_INFO SamInfo = NULL;
PNETLOGON_VALIDATION_UAS_INFO usrlog1 = NULL;
DWORD ValidationSize;
LPWSTR EndOfVariableData;
BOOLEAN Authoritative;
BOOLEAN BadPasswordCountZeroed;
LARGE_INTEGER TempTime;
PDOMAIN_INFO DomainInfo = NULL;
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
return ERROR_NOT_SUPPORTED;
}
//
// This API can only be called locally. (By the XACT server).
//
// ??: Modify xactsrv to pass this information along
if ( ServerName != NULL ) {
return ERROR_INVALID_PARAMETER;
}
//
// Initialization
//
*ValidationInformation = NULL;
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( ServerName );
if ( DomainInfo == NULL ) {
NetStatus = ERROR_INVALID_COMPUTERNAME;
goto Cleanup;
}
//
// Perform access validation on the caller.
//
NetStatus = NetpAccessCheck(
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
NETLOGON_UAS_LOGON_ACCESS, // Desired access
&NlGlobalNetlogonInfoMapping ); // Generic mapping
if ( NetStatus != NERR_Success) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonUasLogon of %ws from %ws failed NetpAccessCheck\n",
UserName, Workstation));
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
//
// Ensure the client is actually the named user.
//
// The server has already validated the password.
// The XACT server has already verified that the workstation name is
// correct.
//
NetStatus = NlEnsureClientIsNamedUser( DomainInfo, UserName );
if ( NetStatus != NERR_Success ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonUasLogon of %ws from %ws failed NlEnsureClientIsNamedUser\n",
UserName, Workstation));
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
//
// Validate the user against the local SAM database.
//
RtlInitUnicodeString( &LogonInteractive.Identity.LogonDomainName, NULL );
LogonInteractive.Identity.ParameterControl = 0;
RtlZeroMemory( &LogonInteractive.Identity.LogonId,
sizeof(LogonInteractive.Identity.LogonId) );
RtlInitUnicodeString( &LogonInteractive.Identity.UserName, UserName );
RtlInitUnicodeString( &LogonInteractive.Identity.Workstation, Workstation );
Status = MsvSamValidate( DomainInfo->DomSamAccountDomainHandle,
TRUE,
NullSecureChannel, // Skip password check
&DomainInfo->DomUnicodeComputerNameString,
&DomainInfo->DomUnicodeAccountDomainNameString,
DomainInfo->DomAccountDomainId,
NetlogonInteractiveInformation,
&LogonInteractive,
NetlogonValidationSamInfo,
(PVOID *)&SamInfo,
&Authoritative,
&BadPasswordCountZeroed,
MSVSAM_SPECIFIED );
if ( !NT_SUCCESS( Status )) {
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Allocate a return buffer
//
ValidationSize = sizeof( NETLOGON_VALIDATION_UAS_INFO ) +
SamInfo->EffectiveName.Length + sizeof(WCHAR) +
(wcslen( DomainInfo->DomUncUnicodeComputerName ) +1) * sizeof(WCHAR) +
DomainInfo->DomUnicodeDomainNameString.Length + sizeof(WCHAR) +
SamInfo->LogonScript.Length + sizeof(WCHAR);
ValidationSize = ROUND_UP_COUNT( ValidationSize, ALIGN_WCHAR );
usrlog1 = MIDL_user_allocate( ValidationSize );
if ( usrlog1 == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Convert the SAM information to the right format for LM 2.0
//
EndOfVariableData = (LPWSTR) (((PCHAR)usrlog1) + ValidationSize);
if ( !NetpCopyStringToBuffer(
SamInfo->EffectiveName.Buffer,
SamInfo->EffectiveName.Length / sizeof(WCHAR),
(LPBYTE) (usrlog1 + 1),
&EndOfVariableData,
&usrlog1->usrlog1_eff_name ) ) {
NetStatus = NERR_InternalError ;
goto Cleanup;
}
Status = NlGetUserPriv(
DomainInfo,
SamInfo->GroupCount,
(PGROUP_MEMBERSHIP) SamInfo->GroupIds,
SamInfo->UserId,
&usrlog1->usrlog1_priv,
&usrlog1->usrlog1_auth_flags );
if ( !NT_SUCCESS( Status )) {
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
usrlog1->usrlog1_num_logons = 0;
usrlog1->usrlog1_bad_pw_count = SamInfo->BadPasswordCount;
OLD_TO_NEW_LARGE_INTEGER( SamInfo->LogonTime, TempTime);
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_last_logon) ) {
usrlog1->usrlog1_last_logon = 0;
}
OLD_TO_NEW_LARGE_INTEGER( SamInfo->LogoffTime, TempTime);
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_last_logoff) ) {
usrlog1->usrlog1_last_logoff = TIMEQ_FOREVER;
}
OLD_TO_NEW_LARGE_INTEGER( SamInfo->KickOffTime, TempTime);
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_logoff_time) ) {
usrlog1->usrlog1_logoff_time = TIMEQ_FOREVER;
}
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_kickoff_time) ) {
usrlog1->usrlog1_kickoff_time = TIMEQ_FOREVER;
}
OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordLastSet, TempTime);
usrlog1->usrlog1_password_age =
NetpGetElapsedSeconds( &TempTime );
OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordCanChange, TempTime);
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_pw_can_change) ) {
usrlog1->usrlog1_pw_can_change = TIMEQ_FOREVER;
}
OLD_TO_NEW_LARGE_INTEGER( SamInfo->PasswordMustChange, TempTime);
if ( !RtlTimeToSecondsSince1970( &TempTime,
&usrlog1->usrlog1_pw_must_change) ) {
usrlog1->usrlog1_pw_must_change = TIMEQ_FOREVER;
}
usrlog1->usrlog1_computer = DomainInfo->DomUncUnicodeComputerName;
if ( !NetpPackString(
&usrlog1->usrlog1_computer,
(LPBYTE) (usrlog1 + 1),
&EndOfVariableData )) {
NetStatus = NERR_InternalError ;
goto Cleanup;
}
if ( !NetpCopyStringToBuffer(
DomainInfo->DomUnicodeDomainNameString.Buffer,
DomainInfo->DomUnicodeDomainNameString.Length / sizeof(WCHAR),
(LPBYTE) (usrlog1 + 1),
&EndOfVariableData,
&usrlog1->usrlog1_domain ) ) {
NetStatus = NERR_InternalError ;
goto Cleanup;
}
if ( !NetpCopyStringToBuffer(
SamInfo->LogonScript.Buffer,
SamInfo->LogonScript.Length / sizeof(WCHAR),
(LPBYTE) (usrlog1 + 1),
&EndOfVariableData,
&usrlog1->usrlog1_script_path ) ) {
NetStatus = NERR_InternalError ;
goto Cleanup;
}
NetStatus = NERR_Success;
//
// Done
//
Cleanup:
//
// Clean up locally used resources.
//
if ( SamInfo != NULL ) {
//
// Zero out sensitive data
//
RtlSecureZeroMemory( &SamInfo->UserSessionKey, sizeof(SamInfo->UserSessionKey) );
RtlSecureZeroMemory( &SamInfo->ExpansionRoom, sizeof(SamInfo->ExpansionRoom) );
MIDL_user_free( SamInfo );
}
if ( NetStatus != NERR_Success ) {
if ( usrlog1 != NULL ) {
MIDL_user_free( usrlog1 );
usrlog1 = NULL;
}
}
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
NlPrint((NL_LOGON,
"%ws: NetrLogonUasLogon of %ws from %ws returns %lu\n",
DomainInfo == NULL ? L"[Unknown]" : DomainInfo->DomUnicodeDomainName,
UserName, Workstation, NetStatus ));
*ValidationInformation = usrlog1;
return(NetStatus);
#endif // _DC_NETLOGON
}
NET_API_STATUS
NetrLogonUasLogoff (
IN LPWSTR ServerName OPTIONAL,
IN LPWSTR UserName,
IN LPWSTR Workstation,
OUT PNETLOGON_LOGOFF_UAS_INFO LogoffInformation
)
/*++
Routine Description:
This function is called by the XACT server when processing a
I_NetWkstaUserLogoff XACT SMB. This feature allows a UAS client to
logoff from a SAM domain controller. The request is authenticated,
the entry is removed for this user from the logon session table
maintained by the Netlogon service for NetLogonEnum, and logoff
information is returned to the caller.
The server portion of I_NetLogonUasLogoff (in the Netlogon service)
compares the user name and workstation name specified in the
LogonInformation with the user name and workstation name from the
impersonation token. If they don't match, I_NetLogonUasLogoff fails
indicating the access is denied.
Group SECURITY_LOCAL is refused access to this function. Membership
in SECURITY_LOCAL implies that this call was made locally and not
through the XACT server.
The Netlogon service cannot be sure that this function was called by
the XACT server. Therefore, the Netlogon service will not simply
delete the entry from the logon session table. Rather, the logon
session table entry will be marked invisible outside of the Netlogon
service (i.e., it will not be returned by NetLogonEnum) until a valid
LOGON_WKSTINFO_RESPONSE is received for the entry. The Netlogon
service will immediately interrogate the client (as described above
for LOGON_WKSTINFO_RESPONSE) and temporarily increase the
interrogation frequency to at least once a minute. The logon session
table entry will reappear as soon as a function of interrogation if
this isn't a true logoff request.
Arguments:
ServerName -- Reserved. Must be NULL.
UserName -- Account name of the user logging off.
Workstation -- The workstation from which the user is logging
off.
LogoffInformation -- Returns the requested logoff information.
Return Value:
The Net status code.
--*/
{
#ifdef _WKSTA_NETLOGON
return ERROR_NOT_SUPPORTED;
UNREFERENCED_PARAMETER( ServerName );
UNREFERENCED_PARAMETER( UserName );
UNREFERENCED_PARAMETER( Workstation );
UNREFERENCED_PARAMETER( LogoffInformation );
#endif // _WKSTA_NETLOGON
#ifdef _DC_NETLOGON
NET_API_STATUS NetStatus;
NTSTATUS Status;
PDOMAIN_INFO DomainInfo = NULL;
NETLOGON_INTERACTIVE_INFO LogonInteractive;
PNETLOGON_LOGOFF_UAS_INFO usrlog1 = NULL;
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
return ERROR_NOT_SUPPORTED;
}
//
// This API can only be called locally. (By the XACT server).
//
if ( ServerName != NULL ) {
return ERROR_INVALID_PARAMETER;
}
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( ServerName );
if ( DomainInfo == NULL ) {
NetStatus = ERROR_INVALID_COMPUTERNAME;
goto Cleanup;
}
//
// Perform access validation on the caller.
//
NetStatus = NetpAccessCheck(
NlGlobalNetlogonSecurityDescriptor, // Security descriptor
NETLOGON_UAS_LOGOFF_ACCESS, // Desired access
&NlGlobalNetlogonInfoMapping ); // Generic mapping
if ( NetStatus != NERR_Success) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonUasLogoff of %ws from %ws failed NetpAccessCheck\n",
UserName, Workstation));
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
//
// Ensure the client is actually the named user.
//
// The server has already validated the password.
// The XACT server has already verified that the workstation name is
// correct.
//
#ifdef notdef // Some clients (WFW 3.11) can call this over the null session
NetStatus = NlEnsureClientIsNamedUser( DomainInfo, UserName );
if ( NetStatus != NERR_Success ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonUasLogoff of %ws from %ws failed NlEnsureClientIsNamedUser\n",
UserName, Workstation));
NetStatus = ERROR_ACCESS_DENIED;
goto Cleanup;
}
#endif // notdef
//
// Build the LogonInformation to return
//
LogoffInformation->Duration = 0;
LogoffInformation->LogonCount = 0;
//
// Update the LastLogoff time in the SAM database.
//
RtlInitUnicodeString( &LogonInteractive.Identity.LogonDomainName, NULL );
LogonInteractive.Identity.ParameterControl = 0;
RtlZeroMemory( &LogonInteractive.Identity.LogonId,
sizeof(LogonInteractive.Identity.LogonId) );
RtlInitUnicodeString( &LogonInteractive.Identity.UserName, UserName );
RtlInitUnicodeString( &LogonInteractive.Identity.Workstation, Workstation );
Status = MsvSamLogoff(
DomainInfo->DomSamAccountDomainHandle,
NetlogonInteractiveInformation,
&LogonInteractive );
if (!NT_SUCCESS(Status)) {
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Cleanup
//
Cleanup:
//
// Clean up locally used resources.
//
NlPrint((NL_LOGON,
"%ws: NetrLogonUasLogoff of %ws from %ws returns %lu\n",
DomainInfo == NULL ? L"[Unknown]" : DomainInfo->DomUnicodeDomainName,
UserName, Workstation, NetStatus));
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
return NetStatus;
#endif // _DC_NETLOGON
}
VOID
NlpDecryptLogonInformation (
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN OUT LPBYTE LogonInformation,
IN PSESSION_INFO SessionInfo
)
/*++
Routine Description:
This function decrypts the sensitive information in the LogonInformation
structure. The decryption is done in place.
Arguments:
LogonLevel -- Specifies the level of information given in
LogonInformation.
LogonInformation -- Specifies the description for the user
logging on.
SessionInfo -- The session key to encrypt with and negotiate flags
Return Value:
None.
--*/
{
//
// Only the interactive and service logon information is encrypted.
//
switch ( LogonLevel ) {
case NetlogonInteractiveInformation:
case NetlogonInteractiveTransitiveInformation:
case NetlogonServiceInformation:
case NetlogonServiceTransitiveInformation:
{
PNETLOGON_INTERACTIVE_INFO LogonInteractive;
LogonInteractive =
(PNETLOGON_INTERACTIVE_INFO) LogonInformation;
//
// If both sides support RC4 encryption,
// decrypt both the LM OWF and NT OWF passwords using RC4.
//
if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
NlDecryptRC4( &LogonInteractive->LmOwfPassword,
sizeof(LogonInteractive->LmOwfPassword),
SessionInfo );
NlDecryptRC4( &LogonInteractive->NtOwfPassword,
sizeof(LogonInteractive->NtOwfPassword),
SessionInfo );
//
// If the other side is running NT 3.1,
// use the slower DES based encryption.
//
} else {
NTSTATUS Status;
ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
//
// Decrypt the LM_OWF password.
//
NlAssert( ENCRYPTED_LM_OWF_PASSWORD_LENGTH ==
LM_OWF_PASSWORD_LENGTH );
NlAssert(LM_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
EncryptedLmOwfPassword =
* ((PENCRYPTED_LM_OWF_PASSWORD) &LogonInteractive->LmOwfPassword);
Status = RtlDecryptLmOwfPwdWithLmOwfPwd(
&EncryptedLmOwfPassword,
(PLM_OWF_PASSWORD) &SessionInfo->SessionKey,
&LogonInteractive->LmOwfPassword );
NlAssert( NT_SUCCESS(Status) );
//
// Decrypt the NT_OWF password.
//
NlAssert( ENCRYPTED_NT_OWF_PASSWORD_LENGTH ==
NT_OWF_PASSWORD_LENGTH );
NlAssert(NT_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
EncryptedNtOwfPassword =
* ((PENCRYPTED_NT_OWF_PASSWORD) &LogonInteractive->NtOwfPassword);
Status = RtlDecryptNtOwfPwdWithNtOwfPwd(
&EncryptedNtOwfPassword,
(PNT_OWF_PASSWORD) &SessionInfo->SessionKey,
&LogonInteractive->NtOwfPassword );
NlAssert( NT_SUCCESS(Status) );
}
break;
}
case NetlogonGenericInformation:
{
PNETLOGON_GENERIC_INFO LogonGeneric;
LogonGeneric =
(PNETLOGON_GENERIC_INFO) LogonInformation;
NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION );
if ( LogonGeneric->LogonData != NULL ) {
NlDecryptRC4( LogonGeneric->LogonData,
LogonGeneric->DataLength,
SessionInfo );
}
break;
}
}
return;
}
VOID
NlpEncryptLogonInformation (
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN OUT LPBYTE LogonInformation,
IN PSESSION_INFO SessionInfo
)
/*++
Routine Description:
This function encrypts the sensitive information in the LogonInformation
structure. The encryption is done in place.
Arguments:
LogonLevel -- Specifies the level of information given in
LogonInformation.
LogonInformation -- Specifies the description for the user
logging on.
SessionInfo -- The session key to encrypt with and negotiate flags
Return Value:
None.
--*/
{
NTSTATUS Status;
//
// Only the interactive and service logon information is encrypted.
//
switch ( LogonLevel ) {
case NetlogonInteractiveInformation:
case NetlogonInteractiveTransitiveInformation:
case NetlogonServiceInformation:
case NetlogonServiceTransitiveInformation:
{
PNETLOGON_INTERACTIVE_INFO LogonInteractive;
LogonInteractive =
(PNETLOGON_INTERACTIVE_INFO) LogonInformation;
//
// If both sides support RC4 encryption, use it.
// encrypt both the LM OWF and NT OWF passwords using RC4.
//
if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
NlEncryptRC4( &LogonInteractive->LmOwfPassword,
sizeof(LogonInteractive->LmOwfPassword),
SessionInfo );
NlEncryptRC4( &LogonInteractive->NtOwfPassword,
sizeof(LogonInteractive->NtOwfPassword),
SessionInfo );
//
// If the other side is running NT 3.1,
// use the slower DES based encryption.
//
} else {
ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
//
// Encrypt the LM_OWF password.
//
NlAssert( ENCRYPTED_LM_OWF_PASSWORD_LENGTH ==
LM_OWF_PASSWORD_LENGTH );
NlAssert(LM_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
Status = RtlEncryptLmOwfPwdWithLmOwfPwd(
&LogonInteractive->LmOwfPassword,
(PLM_OWF_PASSWORD) &SessionInfo->SessionKey,
&EncryptedLmOwfPassword );
NlAssert( NT_SUCCESS(Status) );
*((PENCRYPTED_LM_OWF_PASSWORD) &LogonInteractive->LmOwfPassword) =
EncryptedLmOwfPassword;
//
// Encrypt the NT_OWF password.
//
NlAssert( ENCRYPTED_NT_OWF_PASSWORD_LENGTH ==
NT_OWF_PASSWORD_LENGTH );
NlAssert(NT_OWF_PASSWORD_LENGTH == sizeof(SessionInfo->SessionKey));
Status = RtlEncryptNtOwfPwdWithNtOwfPwd(
&LogonInteractive->NtOwfPassword,
(PNT_OWF_PASSWORD) &SessionInfo->SessionKey,
&EncryptedNtOwfPassword );
NlAssert( NT_SUCCESS(Status) );
*((PENCRYPTED_NT_OWF_PASSWORD) &LogonInteractive->NtOwfPassword) =
EncryptedNtOwfPassword;
}
break;
}
case NetlogonGenericInformation:
{
PNETLOGON_GENERIC_INFO LogonGeneric;
LogonGeneric =
(PNETLOGON_GENERIC_INFO) LogonInformation;
//
// If both sides support RC4 encryption, use it.
// encrypt both the LM OWF and NT OWF passwords using RC4.
//
NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION );
NlEncryptRC4( LogonGeneric->LogonData,
LogonGeneric->DataLength,
SessionInfo );
break;
}
}
return;
}
VOID
NlpDecryptValidationInformation (
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
IN OUT LPBYTE ValidationInformation,
IN PSESSION_INFO SessionInfo
)
/*++
Routine Description:
This function decrypts the sensitive information in the
ValidationInformation structure. The decryption is done in place.
Arguments:
LogonLevel -- Specifies the Logon level used to obtain
ValidationInformation.
ValidationLevel -- Specifies the level of information given in
ValidationInformation.
ValidationInformation -- Specifies the description for the user
logging on.
SessionInfo -- The session key to encrypt with and negotiated flags.
Return Value:
None.
--*/
{
PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
PNETLOGON_VALIDATION_GENERIC_INFO GenericInfo;
//
// Only network logons and generic contain information which is sensitive.
//
// NetlogonValidationSamInfo4 isn't encrypted on purpose. NlEncryptRC4 has the problem
// described in its header. Couple that with the fact that the entire session is
// now encrypted.
//
if ( (LogonLevel != NetlogonNetworkInformation) &&
(LogonLevel != NetlogonNetworkTransitiveInformation) &&
(LogonLevel != NetlogonGenericInformation) ) {
return;
}
if ( ValidationLevel == NetlogonValidationSamInfo ||
ValidationLevel == NetlogonValidationSamInfo2 ) {
ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO) ValidationInformation;
//
// If we're suppossed to use RC4,
// Decrypt both the NT and LM session keys using RC4.
//
if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
NlDecryptRC4( &ValidationInfo->UserSessionKey,
sizeof(ValidationInfo->UserSessionKey),
SessionInfo );
NlDecryptRC4( &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY],
SAMINFO_LM_SESSION_KEY_SIZE,
SessionInfo );
//
// If the other side is running NT 3.1,
// be compatible.
//
} else {
NTSTATUS Status;
CLEAR_BLOCK ClearBlock;
DWORD i;
LPBYTE DataBuffer =
(LPBYTE) &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY];
//
// Decrypt the LmSessionKey
//
NlAssert( CLEAR_BLOCK_LENGTH == CYPHER_BLOCK_LENGTH );
NlAssert( (SAMINFO_LM_SESSION_KEY_SIZE % CLEAR_BLOCK_LENGTH) == 0 );
//
// Loop decrypting a block at a time
//
for (i=0; i<SAMINFO_LM_SESSION_KEY_SIZE/CLEAR_BLOCK_LENGTH; i++ ) {
Status = RtlDecryptBlock(
(PCYPHER_BLOCK)DataBuffer,
(PBLOCK_KEY)&SessionInfo->SessionKey,
&ClearBlock );
NlAssert( NT_SUCCESS( Status ) );
//
// Copy the clear text back into the original buffer.
//
RtlCopyMemory( DataBuffer, &ClearBlock, CLEAR_BLOCK_LENGTH );
DataBuffer += CLEAR_BLOCK_LENGTH;
}
}
} else if ( ValidationLevel == NetlogonValidationGenericInfo ||
ValidationLevel == NetlogonValidationGenericInfo2 ) {
//
// Decrypt all the data in the generic info
//
GenericInfo = (PNETLOGON_VALIDATION_GENERIC_INFO) ValidationInformation;
if (GenericInfo->DataLength != 0) {
NlDecryptRC4( GenericInfo->ValidationData,
GenericInfo->DataLength,
SessionInfo );
}
}
return;
}
VOID
NlpEncryptValidationInformation (
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
IN OUT LPBYTE ValidationInformation,
IN PSESSION_INFO SessionInfo
)
/*++
Routine Description:
This function encrypts the sensitive information in the
ValidationInformation structure. The encryption is done in place.
Arguments:
LogonLevel -- Specifies the Logon level used to obtain
ValidationInformation.
ValidationLevel -- Specifies the level of information given in
ValidationInformation.
ValidationInformation -- Specifies the description for the user
logging on.
SessionInfo -- The session key to encrypt with and negotiated flags.
Return Value:
None.
--*/
{
PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
PNETLOGON_VALIDATION_GENERIC_INFO GenericInfo;
//
// Only network logons and generic contain information which is sensitive.
//
// NetlogonValidationSamInfo4 isn't encrypted on purpose. NlEncryptRC4 has the problem
// described in its header. Couple that with the fact that the entire session is
// now encrypted.
//
if ( (LogonLevel != NetlogonNetworkInformation) &&
(LogonLevel != NetlogonNetworkTransitiveInformation) &&
(LogonLevel != NetlogonGenericInformation) ) {
return;
}
if ( ValidationLevel == NetlogonValidationSamInfo ||
ValidationLevel == NetlogonValidationSamInfo2 ) {
ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO) ValidationInformation;
//
// If we're suppossed to use RC4,
// Encrypt both the NT and LM session keys using RC4.
//
if ( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ) {
NlEncryptRC4( &ValidationInfo->UserSessionKey,
sizeof(ValidationInfo->UserSessionKey),
SessionInfo );
NlEncryptRC4( &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY],
SAMINFO_LM_SESSION_KEY_SIZE,
SessionInfo );
//
// If the other side is running NT 3.1,
// be compatible.
//
} else {
NTSTATUS Status;
CLEAR_BLOCK ClearBlock;
DWORD i;
LPBYTE DataBuffer =
(LPBYTE) &ValidationInfo->ExpansionRoom[SAMINFO_LM_SESSION_KEY];
//
// Encrypt the LmSessionKey
//
// Loop decrypting a block at a time
//
for (i=0; i<SAMINFO_LM_SESSION_KEY_SIZE/CLEAR_BLOCK_LENGTH; i++ ) {
//
// Copy the clear text onto the stack
//
RtlCopyMemory( &ClearBlock, DataBuffer, CLEAR_BLOCK_LENGTH );
Status = RtlEncryptBlock(
&ClearBlock,
(PBLOCK_KEY)&SessionInfo->SessionKey,
(PCYPHER_BLOCK)DataBuffer );
NlAssert( NT_SUCCESS( Status ) );
DataBuffer += CLEAR_BLOCK_LENGTH;
}
}
} else if ( ValidationLevel == NetlogonValidationGenericInfo ||
ValidationLevel == NetlogonValidationGenericInfo2 ) {
//
// Encrypt all the data in the generic info
//
GenericInfo = (PNETLOGON_VALIDATION_GENERIC_INFO) ValidationInformation;
if (GenericInfo->DataLength != 0) {
NlEncryptRC4( GenericInfo->ValidationData,
GenericInfo->DataLength,
SessionInfo );
}
}
return;
}
NTSTATUS
NlpUserValidateHigher (
IN PCLIENT_SESSION ClientSession,
IN BOOLEAN DoingIndirectTrust,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN LPBYTE LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT LPBYTE * ValidationInformation,
OUT PBOOLEAN Authoritative,
IN OUT PULONG ExtraFlags
)
/*++
Routine Description:
This function sends a user validation request to a higher authority.
Arguments:
ClientSession -- Secure channel to send this request over. The Client
Session should be referenced.
DoingIndirectTrust -- If TRUE, the Client Session merely represents the
next closer hop and is not the final destination.
LogonLevel -- Specifies the level of information given in
LogonInformation. Has already been validated.
LogonInformation -- Specifies the description for the user
logging on.
ValidationLevel -- Specifies the level of information returned in
ValidationInformation. Must be NetlogonValidationSamInfo,
NetlogonValidationSamInfo2 or NetlogonValidationSamInfo4
ValidationInformation -- Returns the requested validation
information. This buffer must be freed using MIDL_user_free.
Authoritative -- Returns whether the status returned is an
authoritative status which should be returned to the original
caller. If not, this logon request may be tried again on another
domain controller. This parameter is returned regardless of the
status code.
ExtraFlags -- Accepts and returns a DWORD to the caller.
The DWORD contains NL_EXFLAGS_* values.
Return Value:
STATUS_SUCCESS: if there was no error.
STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
STATUS_NO_TRUST_LSA_SECRET:
STATUS_TRUSTED_DOMAIN_FAILURE:
STATUS_TRUSTED_RELATIONSHIP_FAILURE:
can't authenticate with higer authority
Otherwise, the error code is returned.
--*/
{
NTSTATUS Status;
NETLOGON_AUTHENTICATOR OurAuthenticator;
NETLOGON_AUTHENTICATOR ReturnAuthenticator;
BOOLEAN FirstTry = TRUE;
BOOLEAN TryForDs = TRUE;
BOOLEAN AmWriter = FALSE;
BOOLEAN DoingGeneric;
SESSION_INFO SessionInfo;
NETLOGON_VALIDATION_INFO_CLASS RemoteValidationLevel;
PCLIENT_API OrigClientApi = NULL;
PCLIENT_API ClientApi;
BOOLEAN RpcFailed;
ULONG MaxExtraFlags;
//
// Allocate a slot for doing a concurrent API call
//
// Do this before grabbing the write lock since the threads
// using the slots need to grab the write lock to free the slot.
//
// We may end up not using this slot if concurrent API isn't supported.
// But in that case, this isn't a valuable resource so allocating one
// doesn't hurt.
//
NlAssert( ClientSession->CsReferenceCount > 0 );
OrigClientApi = NlAllocateClientApi(
ClientSession,
WRITER_WAIT_PERIOD );
if ( OrigClientApi == NULL ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't allocate Client API slot.\n" ));
*Authoritative = TRUE;
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Mark us as a writer of the ClientSession
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't become writer of client session.\n" ));
*Authoritative = TRUE;
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
AmWriter = TRUE;
//
// Determine if we're doing a generic logon.
//
DoingGeneric = (LogonLevel == NetlogonGenericInformation ||
ValidationLevel == NetlogonValidationGenericInfo ||
ValidationLevel == NetlogonValidationGenericInfo2);
//
// If we don't currently have a session set up to the higher authority,
// set one up.
//
// For generic passthrough or indirect trust, ask for an NT 5 DC.
// For Interactive logon, ask for a close DC.
//
FirstTryFailed:
Status = NlEnsureSessionAuthenticated(
ClientSession,
(( DoingGeneric || DoingIndirectTrust || *ExtraFlags != 0 ) ? CS_DISCOVERY_HAS_DS : 0) |
((LogonLevel == NetlogonInteractiveInformation || LogonLevel == NetlogonInteractiveTransitiveInformation )? CS_DISCOVERY_IS_CLOSE : 0) );
if ( !NT_SUCCESS(Status) ) {
switch(Status) {
case STATUS_NO_TRUST_LSA_SECRET:
case STATUS_NO_TRUST_SAM_ACCOUNT:
case STATUS_ACCESS_DENIED:
case STATUS_NO_LOGON_SERVERS:
break;
default:
if ( !NlpIsNtStatusResourceError( Status )) {
Status = STATUS_NO_LOGON_SERVERS;
}
break;
}
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
*Authoritative = TRUE;
goto Cleanup;
}
SessionInfo.SessionKey = ClientSession->CsSessionKey;
SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags;
//
// Ensure the DC supports the ExtraFlags we're passing it.
//
if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_CROSS_FOREST ) {
MaxExtraFlags = NL_EXFLAGS_EXPEDITE_TO_ROOT | NL_EXFLAGS_CROSS_FOREST_HOP;
} else {
MaxExtraFlags = 0;
}
if ( (*ExtraFlags & ~MaxExtraFlags) != 0 ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't pass these ExtraFlags to old DC: %lx %lx\n",
*ExtraFlags,
MaxExtraFlags ));
Status = STATUS_NO_LOGON_SERVERS;
*Authoritative = TRUE;
goto Cleanup;
}
//
// If the target is an NT 4.0 (or lower) DC,
// check to see if an NT 5.0 DC would be better.
//
if ((SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_GENERIC_PASSTHRU) == 0 &&
( DoingGeneric || DoingIndirectTrust ) ) {
//
// Simply fail if only an NT 4 DC is available.
//
*Authoritative = TRUE;
if ( DoingGeneric ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't do generic passthru to NT 4 DC.\n" ));
Status = STATUS_INVALID_INFO_CLASS;
} else {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't do transitive trust to NT 4 DC.\n" ));
Status = STATUS_NO_LOGON_SERVERS;
}
goto Cleanup;
}
//
// Convert the validation level to one the remote DC understands.
//
if ( !DoingGeneric ) {
//
// DCs that don't understand extra SIDs require NetlogonValidationSamInfo
//
if (!(SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_MULTIPLE_SIDS)) {
RemoteValidationLevel = NetlogonValidationSamInfo;
//
// DCs that don't understand cross forest trust don't understand NetlogonValidationSamInfo4
//
// Info4 doesn't have sensitive information encrytped (since NlEncryptRC4 is
// buggy and there are many more field that need encryption). So, avoid info4
// unless the entire traffic is encrypted.
//
} else if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_CROSS_FOREST) == 0 ||
(SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_AUTH_RPC) == 0 ||
!NlGlobalParameters.SealSecureChannel ) {
RemoteValidationLevel = NetlogonValidationSamInfo2;
} else {
RemoteValidationLevel = ValidationLevel;
}
} else {
RemoteValidationLevel = ValidationLevel;
}
//
// If this DC supports concurrent RPC calls,
// and we're signing or sealing,
// then we're OK to do concurrent RPC.
//
// Otherwise, use the shared RPC slot.
//
if ( (SessionInfo.NegotiatedFlags &
(NETLOGON_SUPPORTS_CONCURRENT_RPC|NETLOGON_SUPPORTS_AUTH_RPC)) ==
(NETLOGON_SUPPORTS_CONCURRENT_RPC|NETLOGON_SUPPORTS_AUTH_RPC)) {
ClientApi = OrigClientApi;
} else {
ClientApi = &ClientSession->CsClientApi[0];
}
//
// Build the Authenticator for this request on the secure channel
//
// Concurrent RPC uses a signed and sealed secure channel so it doesn't need
// an authenticator.
//
if ( !UseConcurrentRpc( ClientSession, ClientApi ) ) {
NlBuildAuthenticator(
&ClientSession->CsAuthenticationSeed,
&ClientSession->CsSessionKey,
&OurAuthenticator );
}
//
// Make the request across the secure channel.
//
NlpEncryptLogonInformation( LogonLevel, LogonInformation, &SessionInfo );
RpcFailed = FALSE;
NL_API_START_EX( Status, ClientSession, TRUE, ClientApi ) {
//
// If the called DC doesn't support the new transitive opcodes,
// map the opcodes back to do the best we can.
//
RpcFailed = FALSE;
if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_TRANSITIVE) == 0 ) {
switch (LogonLevel ) {
case NetlogonInteractiveTransitiveInformation:
LogonLevel = NetlogonInteractiveInformation; break;
case NetlogonServiceTransitiveInformation:
LogonLevel = NetlogonServiceInformation; break;
case NetlogonNetworkTransitiveInformation:
LogonLevel = NetlogonNetworkInformation; break;
}
}
NlAssert( ClientSession->CsUncServerName != NULL );
if ( UseConcurrentRpc( ClientSession, ClientApi ) ) {
LPWSTR UncServerName;
//
// Drop the write lock to allow other concurrent callers to proceed.
//
NlResetWriterClientSession( ClientSession );
AmWriter = FALSE;
//
// Since we have no locks locked,
// grab the name of the DC to remote to.
//
Status = NlCaptureServerClientSession (
ClientSession,
&UncServerName,
NULL );
if ( !NT_SUCCESS(Status) ) {
*Authoritative = TRUE;
if ( !NlpIsNtStatusResourceError( Status )) {
Status = STATUS_NO_LOGON_SERVERS;
}
} else {
//
// Do the RPC call with no locks locked.
//
Status = I_NetLogonSamLogonEx(
ClientApi->CaRpcHandle,
UncServerName,
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
LogonLevel,
LogonInformation,
RemoteValidationLevel,
ValidationInformation,
Authoritative,
ExtraFlags,
&RpcFailed );
NetApiBufferFree( UncServerName );
if ( !NT_SUCCESS(Status) ) {
NlPrintRpcDebug( "I_NetLogonSamLogonEx", Status );
}
}
//
// Become a writer again
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: Can't become writer (again) of client session.\n" ));
// Don't leak validation information
if ( *ValidationInformation ) {
MIDL_user_free( *ValidationInformation );
*ValidationInformation = NULL;
}
*Authoritative = TRUE;
Status = STATUS_NO_LOGON_SERVERS;
} else {
AmWriter = TRUE;
}
//
// Do non-concurrent RPC
//
} else {
//
// If the DC supports the new 'WithFlags' API,
// use it.
//
if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_CROSS_FOREST ) {
Status = I_NetLogonSamLogonWithFlags(
ClientSession->CsUncServerName,
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
&OurAuthenticator,
&ReturnAuthenticator,
LogonLevel,
LogonInformation,
RemoteValidationLevel,
ValidationInformation,
Authoritative,
ExtraFlags );
if ( !NT_SUCCESS(Status) ) {
NlPrintRpcDebug( "I_NetLogonSamLogonWithFlags", Status );
}
//
// Otherwise use the old API.
//
} else {
Status = I_NetLogonSamLogon(
ClientSession->CsUncServerName,
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
&OurAuthenticator,
&ReturnAuthenticator,
LogonLevel,
LogonInformation,
RemoteValidationLevel,
ValidationInformation,
Authoritative );
if ( !NT_SUCCESS(Status) ) {
NlPrintRpcDebug( "I_NetLogonSamLogon", Status );
}
}
}
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
// NOTE: This call may drop the secure channel behind our back
} NL_API_ELSE_EX( Status, ClientSession, TRUE, AmWriter, ClientApi ) {
} NL_API_END;
NlpDecryptLogonInformation( LogonLevel, LogonInformation, &SessionInfo );
if ( NT_SUCCESS(Status) ) {
NlAssert( *ValidationInformation != NULL );
}
//
// If we couldn't become writer again after the remote call,
// early out to avoid use the ClientSession.
//
if ( !AmWriter ) {
goto Cleanup;
}
//
// Verify authenticator of the server on the other side and update our seed.
//
// If the server denied access or the server's authenticator is wrong,
// Force a re-authentication.
//
//
NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: Seed = " ));
NlpDumpBuffer(NL_CHALLENGE_RES, &ClientSession->CsAuthenticationSeed, sizeof(ClientSession->CsAuthenticationSeed) );
NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: SessionKey = " ));
NlpDumpBuffer(NL_CHALLENGE_RES, &ClientSession->CsSessionKey, sizeof(ClientSession->CsSessionKey) );
if ( !UseConcurrentRpc( ClientSession, ClientApi ) ) {
NlPrint((NL_CHALLENGE_RES,"NlpUserValidateHigher: Return Authenticator = " ));
NlpDumpBuffer(NL_CHALLENGE_RES, &ReturnAuthenticator.Credential, sizeof(ReturnAuthenticator.Credential) );
}
if ( NlpDidDcFail( Status ) ||
RpcFailed ||
(!UseConcurrentRpc( ClientSession, ClientApi ) &&
!NlUpdateSeed(
&ClientSession->CsAuthenticationSeed,
&ReturnAuthenticator.Credential,
&ClientSession->CsSessionKey) ) ) {
NlPrintCs(( NL_CRITICAL, ClientSession,
"NlpUserValidateHigher: denying access after status: 0x%lx %lx\n",
Status,
RpcFailed ));
//
// Preserve any status indicating a communication error.
//
// If another thread already dropped the secure channel,
// don't do it again now.
//
if ( NT_SUCCESS(Status) ) {
Status = STATUS_ACCESS_DENIED;
}
if ( ClientApi->CaSessionCount == ClientSession->CsSessionCount ) {
NlSetStatusClientSession( ClientSession, Status );
}
//
// Perhaps the netlogon service on the server has just restarted.
// Try just once to set up a session to the server again.
//
if ( FirstTry ) {
FirstTry = FALSE;
goto FirstTryFailed;
}
*Authoritative = TRUE;
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
goto Cleanup;
}
//
// Clean up after a successful call to higher authority.
//
if ( NT_SUCCESS(Status) ) {
//
// The server encrypted the validation information before sending it
// over the wire. Decrypt it.
//
NlpDecryptValidationInformation (
LogonLevel,
RemoteValidationLevel,
*ValidationInformation,
&SessionInfo );
//
// If the caller wants a newer info level than we got from the remote side,
// convert it to VALIDATION_SAM_INFO4 (which is a superset of what our caller wants).
//
if ( RemoteValidationLevel != ValidationLevel) {
if ( (RemoteValidationLevel == NetlogonValidationSamInfo2 ||
RemoteValidationLevel == NetlogonValidationSamInfo ) &&
(ValidationLevel == NetlogonValidationSamInfo2 ||
ValidationLevel == NetlogonValidationSamInfo4) ) {
NTSTATUS TempStatus;
TempStatus = NlpAddResourceGroupsToSamInfo (
RemoteValidationLevel,
(PNETLOGON_VALIDATION_SAM_INFO4 *) ValidationInformation,
NULL ); // No resource groups to add
if ( !NT_SUCCESS( TempStatus )) {
*ValidationInformation = NULL;
*Authoritative = FALSE;
Status = TempStatus;
goto Cleanup;
}
} else {
NlAssert(!"Bad validation level");
}
}
//
// Ensure the returned SID and domain name are correct.
// Filter out SIDs for quarantined domains.
//
if ((ValidationLevel == NetlogonValidationSamInfo4) ||
(ValidationLevel == NetlogonValidationSamInfo2) ||
(ValidationLevel == NetlogonValidationSamInfo)) {
PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
ValidationInfo =
(PNETLOGON_VALIDATION_SAM_INFO) *ValidationInformation;
//
// If we validated on a trusted domain,
// the higher authority must have returned his own domain name,
// and must have returned his own domain sid.
//
if ( ClientSession->CsSecureChannelType == TrustedDomainSecureChannel ||
ClientSession->CsSecureChannelType == TrustedDnsDomainSecureChannel ||
ClientSession->CsSecureChannelType == WorkstationSecureChannel ) {
//
// If we validated on our primary domain,
// only verify the domain sid if the primary domain itself validated
// the logon.
//
if ( (ClientSession->CsNetbiosDomainName.Buffer != NULL &&
RtlEqualDomainName( &ValidationInfo->LogonDomainName,
&ClientSession->CsNetbiosDomainName )) &&
!RtlEqualSid( ValidationInfo->LogonDomainId,
ClientSession->CsDomainId ) ) {
Status = STATUS_DOMAIN_TRUST_INCONSISTENT;
MIDL_user_free( *ValidationInformation );
*ValidationInformation = NULL;
*Authoritative = TRUE;
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
}
}
//
// Do interdomain trust specific processing with the validation data
//
if ( IsDomainSecureChannelType(ClientSession->CsSecureChannelType) &&
*ValidationInformation != NULL ) {
//
// First, if this is Other Organization trust type,
// add the OtherOrg SID to the extra SIDs in validation info.
// This SID is used later in the check to determine whether the specified
// user from Other Org can logon to the specified workstation.
//
if ( (ClientSession->CsTrustAttributes & TRUST_ATTRIBUTE_CROSS_ORGANIZATION) &&
(ValidationLevel == NetlogonValidationSamInfo2 ||
ValidationLevel == NetlogonValidationSamInfo4) ) {
NTSTATUS TmpStatus = NlpAddOtherOrganizationSid(
ValidationLevel,
(PNETLOGON_VALIDATION_SAM_INFO4 *) ValidationInformation );
if ( !NT_SUCCESS(TmpStatus) ) {
*ValidationInformation = NULL;
*Authoritative = TRUE;
Status = TmpStatus;
goto Cleanup;
}
}
//
// Filter out SIDs for quarantined domains and interforest domains as needed
//
LOCK_TRUST_LIST( ClientSession->CsDomainInfo );
Status = LsaIFilterSids( ClientSession->CsDnsDomainName.Length ?
&ClientSession->CsDnsDomainName :
NULL,
TRUST_DIRECTION_OUTBOUND,
(ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST) ?
TRUST_TYPE_UPLEVEL : TRUST_TYPE_DOWNLEVEL,
ClientSession->CsTrustAttributes,
ClientSession->CsDomainId,
ValidationLevel,
*ValidationInformation,
NULL,
NULL,
NULL );
UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo );
} else if ( ClientSession->CsSecureChannelType == WorkstationSecureChannel &&
*ValidationInformation != NULL ) {
//
// This is the "workstation talking to DC" mode
// Filter SIDs in the "member workstation trust boundary" mode
// (passing NULL trust SID)
//
Status = LsaIFilterSids(
NULL,
0,
0,
0,
NULL,
ValidationLevel,
*ValidationInformation,
NULL,
NULL,
NULL
);
}
if ( !NT_SUCCESS(Status) ) {
NlAssert( !"[NETLOGON] LsaIFilterSids failed" );
NlPrint(( NL_CRITICAL, "NlpUserValidateHigher: LsaIFilterSids failed 0x%lx\n", Status ));
MIDL_user_free( *ValidationInformation );
*ValidationInformation = NULL;
*Authoritative = TRUE;
}
}
}
Cleanup:
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
//
// We are no longer a writer of the client session.
//
if ( AmWriter ) {
NlResetWriterClientSession( ClientSession );
}
//
// Free the concurrent API slot
//
if ( OrigClientApi ) {
NlFreeClientApi( ClientSession, OrigClientApi );
}
return Status;
}
NTSTATUS
NlpUserLogoffHigher (
IN PCLIENT_SESSION ClientSession,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN LPBYTE LogonInformation
)
/*++
Routine Description:
This function sends a user validation request to a higher authority.
Arguments:
ClientSession -- Secure channel to send this request over. The Client
Session should be referenced.
LogonLevel -- Specifies the level of information given in
LogonInformation. Has already been validated.
LogonInformation -- Specifies the description for the user
logging on.
Return Value:
STATUS_SUCCESS: if there was no error.
STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
STATUS_NO_TRUST_LSA_SECRET:
STATUS_TRUSTED_DOMAIN_FAILURE:
STATUS_TRUSTED_RELATIONSHIP_FAILURE:
can't authenticate with higer authority
Otherwise, the error code is returned.
--*/
{
NTSTATUS Status;
NETLOGON_AUTHENTICATOR OurAuthenticator;
NETLOGON_AUTHENTICATOR ReturnAuthenticator;
BOOLEAN FirstTry = TRUE;
//
// Mark us as a writer of the ClientSession
//
NlAssert( ClientSession->CsReferenceCount > 0 );
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserLogoffHigher: Can't become writer of client session.\n"));
return STATUS_NO_LOGON_SERVERS;
}
//
// If we don't currently have a session set up to the higher authority,
// set one up.
//
FirstTryFailed:
Status = NlEnsureSessionAuthenticated( ClientSession, 0 );
if ( !NT_SUCCESS(Status) ) {
switch(Status) {
case STATUS_NO_TRUST_LSA_SECRET:
case STATUS_NO_TRUST_SAM_ACCOUNT:
case STATUS_ACCESS_DENIED:
case STATUS_NO_LOGON_SERVERS:
break;
default:
if ( !NlpIsNtStatusResourceError( Status )) {
Status = STATUS_NO_LOGON_SERVERS;
}
break;
}
goto Cleanup;
}
//
// Build the Authenticator for this request on the secure channel
//
NlBuildAuthenticator(
&ClientSession->CsAuthenticationSeed,
&ClientSession->CsSessionKey,
&OurAuthenticator );
//
// Make the request across the secure channel.
//
NL_API_START( Status, ClientSession, TRUE ) {
NlAssert( ClientSession->CsUncServerName != NULL );
Status = I_NetLogonSamLogoff(
ClientSession->CsUncServerName,
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
&OurAuthenticator,
&ReturnAuthenticator,
LogonLevel,
LogonInformation );
if ( !NT_SUCCESS(Status) ) {
NlPrintRpcDebug( "I_NetLogonSamLogoff", Status );
}
// NOTE: This call may drop the secure channel behind our back
} NL_API_ELSE( Status, ClientSession, TRUE ) {
} NL_API_END;
//
// Verify authenticator of the server on the other side and update our seed.
//
// If the server denied access or the server's authenticator is wrong,
// Force a re-authentication.
//
//
if ( NlpDidDcFail( Status ) ||
!NlUpdateSeed(
&ClientSession->CsAuthenticationSeed,
&ReturnAuthenticator.Credential,
&ClientSession->CsSessionKey) ) {
NlPrintCs(( NL_CRITICAL, ClientSession,
"NlpUserLogoffHigher: denying access after status: 0x%lx\n",
Status ));
//
// Preserve any status indicating a communication error.
//
if ( NT_SUCCESS(Status) ) {
Status = STATUS_ACCESS_DENIED;
}
NlSetStatusClientSession( ClientSession, Status );
//
// Perhaps the netlogon service in the server has just restarted.
// Try just once to set up a session to the server again.
//
if ( FirstTry ) {
FirstTry = FALSE;
goto FirstTryFailed;
}
goto Cleanup;
}
Cleanup:
//
// We are no longer a writer of the client session.
//
NlResetWriterClientSession( ClientSession );
return Status;
}
#ifdef _DC_NETLOGON
VOID
NlScavengeOldFailedLogons(
IN PDOMAIN_INFO DomainInfo
)
/*++
Routine Description:
This function removes all expired failed user logon entries
from the list of expired logons for the specified domain.
Arguments:
DomainInfo - Domain this BDC is a member of.
Return Value:
None
--*/
{
PLIST_ENTRY UserLogonEntry = NULL;
PNL_FAILED_USER_LOGON UserLogon = NULL;
ULONG CurrentTime;
ULONG ElapsedTime;
CurrentTime = GetTickCount();
LOCK_TRUST_LIST( DomainInfo );
UserLogonEntry = DomainInfo->DomFailedUserLogonList.Flink;
while ( UserLogonEntry != &DomainInfo->DomFailedUserLogonList ) {
UserLogon = CONTAINING_RECORD( UserLogonEntry, NL_FAILED_USER_LOGON, FuNext );
UserLogonEntry = UserLogonEntry->Flink;
//
// If time has wrapped, account for it
//
if ( CurrentTime >= UserLogon->FuLastTimeSentToPdc ) {
ElapsedTime = CurrentTime - UserLogon->FuLastTimeSentToPdc;
} else {
ElapsedTime = (0xFFFFFFFF - UserLogon->FuLastTimeSentToPdc) + CurrentTime;
}
//
// If this entry hasn't been touched in 3 update timeouts, remove it
//
if ( ElapsedTime >= (3 * NL_FAILED_USER_FORWARD_LOGON_TIMEOUT) ) {
RemoveEntryList( &UserLogon->FuNext );
LocalFree( UserLogon );
}
}
UNLOCK_TRUST_LIST( DomainInfo );
}
VOID
NlpRemoveBadPasswordCacheEntry(
IN PDOMAIN_INFO DomainInfo,
IN LPBYTE LogonInformation
)
/*++
Routine Description:
This function removes a negative cache entry for the specified user.
The cache is maintained on BDC for user logons that failed with a
bad password status.
Arguments:
DomainInfo - Domain this BDC is a member of.
LogonInformation -- Specifies the description for the user
logging on.
Return Value:
None
--*/
{
PLIST_ENTRY FailedUserEntry = NULL;
PNL_FAILED_USER_LOGON FailedUser = NULL;
LPWSTR UserName = NULL;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
//
// If this isn't a BDC,
// There's nothing to do here.
//
if ( DomainInfo->DomRole != RoleBackup ) {
return;
}
//
// Get the user name from the logon info
// UserName might be a SamAccountName or a UPN
//
UserName = LocalAlloc( 0, LogonInfo->UserName.Length + sizeof(WCHAR) );
if ( UserName == NULL ) {
return;
}
RtlCopyMemory( UserName, LogonInfo->UserName.Buffer, LogonInfo->UserName.Length );
UserName[ LogonInfo->UserName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
//
// Loop through the cache searching for this user entry
//
LOCK_TRUST_LIST( DomainInfo );
for ( FailedUserEntry = DomainInfo->DomFailedUserLogonList.Flink;
FailedUserEntry != &DomainInfo->DomFailedUserLogonList;
FailedUserEntry = FailedUserEntry->Flink ) {
FailedUser = CONTAINING_RECORD( FailedUserEntry, NL_FAILED_USER_LOGON, FuNext );
if ( _wcsicmp(UserName, FailedUser->FuUserName) == 0 ) {
RemoveEntryList( &FailedUser->FuNext );
LocalFree( FailedUser );
break;
}
}
UNLOCK_TRUST_LIST( DomainInfo );
LocalFree( UserName );
return;
}
NTSTATUS
NlpUserValidateOnPdc (
IN PDOMAIN_INFO DomainInfo,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN LPBYTE LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
IN BOOL UseNegativeCache,
OUT LPBYTE * ValidationInformation,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
This function normally sends a user validation request to the PDC in this
same domain. Currently, this is called from a BDC after getting a password
mismatch. The theory is that the password might be right on the PDC but
it merely hasn't replicated yet.
However, once the number of logon failures for the given user reaches a
certain threshold, we refrain from this forwarding for some period of time
to avoid PDC overload. We then retry the forwarding once every so often.
This scheme ensures that we accomodate a certain number of mistyped user
passwords and we then periodically retry to authenticate the user on the PDC.
No validation request will be sent if the registry value of AvoidPdcOnWan has
been set to TRUE and PDC and BDC are on different sites. In this case the
function returns with STATUS_NO_SUCH_USER error.
Arguments:
DomainInfo - Domain this BDC is a member of.
LogonLevel -- Specifies the level of information given in
LogonInformation. Has already been validated.
LogonInformation -- Specifies the description for the user
logging on.
ValidationLevel -- Specifies the level of information returned in
ValidationInformation. Must be NetlogonValidationSamInfo,
NetlogonValidationSamInfo2 or NetlogonValidationSamInfo4
UseNegativeCache -- If TRUE, the negative cache of failed user
logons forwarded to the PDC will be used to decide whether
it's time to retry to forward this logon.
ValidationInformation -- Returns the requested validation
information. This buffer must be freed using MIDL_user_free.
Authoritative -- Returns whether the status returned is an
authoritative status which should be returned to the original
caller. If not, this logon request may be tried again on another
domain controller. This parameter is returned regardless of the
status code.
Return Value:
STATUS_SUCCESS: if there was no error.
STATUS_NO_LOGON_SERVERS: cannot connect to the higher authority.
STATUS_NO_SUCH_USER: won't validate a user against info on PDC
on a remote site provided the registry value of AvoidPdcOnWan
is TRUE.
STATUS_NO_TRUST_LSA_SECRET:
STATUS_TRUSTED_DOMAIN_FAILURE:
STATUS_TRUSTED_RELATIONSHIP_FAILURE:
can't authenticate with higer authority
Otherwise, the error code is returned.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PCLIENT_SESSION ClientSession = NULL;
BOOLEAN IsSameSite;
DWORD ExtraFlags = 0;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
PSAMPR_DOMAIN_INFO_BUFFER DomainLockout = NULL;
PLIST_ENTRY FailedUserEntry;
BOOL UpdateCache = FALSE;
PNL_FAILED_USER_LOGON FailedUser = NULL;
LPWSTR UserName = NULL;
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
//
// If this isn't a BDC,
// There's nothing to do here.
//
if ( DomainInfo->DomRole != RoleBackup ) {
return STATUS_INVALID_DOMAIN_ROLE;
}
//
// If the registry value of AvoidPdcOnWan is TRUE and PDC is on
// a remote site, do not send anything to PDC and return with
// STATUS_NO_SUCH_USER error.
//
if ( NlGlobalParameters.AvoidPdcOnWan ) {
//
// Determine whether the PDC is on the same site
//
Status = SamISameSite( &IsSameSite );
if ( !NT_SUCCESS(Status) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlpUserValidateOnPdc: Cannot SamISameSite.\n" ));
goto Cleanup;
}
if ( !IsSameSite ) {
NlPrintDom((NL_LOGON, DomainInfo,
"NlpUserValidateOnPdc: Ignored a user validation on a PDC in remote site.\n"));
*Authoritative = FALSE;
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
} else {
NlPrintDom((NL_LOGON, DomainInfo,
"NlpUserValidateOnPdc: BDC and PDC are in the same site.\n"));
}
}
//
// See if it's time to send this user logon to PDC.
//
if ( UseNegativeCache ) {
BOOL AvoidSend = FALSE;
UserName = LocalAlloc( 0, LogonInfo->UserName.Length + sizeof(WCHAR) );
if ( UserName == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( UserName, LogonInfo->UserName.Buffer, LogonInfo->UserName.Length );
UserName[ LogonInfo->UserName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
LOCK_TRUST_LIST( DomainInfo );
for ( FailedUserEntry = DomainInfo->DomFailedUserLogonList.Flink;
FailedUserEntry != &DomainInfo->DomFailedUserLogonList;
FailedUserEntry = FailedUserEntry->Flink ) {
FailedUser = CONTAINING_RECORD( FailedUserEntry, NL_FAILED_USER_LOGON, FuNext );
//
// If this is the entry for this user, check if it's time to forward
// this logon to the PDC. In any case, remove this entry from the list
// and then insert it at the front so that the list stays sorted by the
// entry access time.
//
if ( NlNameCompare(UserName, FailedUser->FuUserName, NAMETYPE_USER) == 0 ) {
ULONG TimeElapsed = NetpDcElapsedTime( FailedUser->FuLastTimeSentToPdc );
//
// If we have exceeded the threshold for failed forwarded logon count
// and we recently sent this failed logon to the PDC,
// avoid forwarding this logon to the PDC
//
if ( FailedUser->FuBadLogonCount > NL_FAILED_USER_MAX_LOGON_COUNT &&
TimeElapsed < NL_FAILED_USER_FORWARD_LOGON_TIMEOUT ) {
AvoidSend = TRUE;
}
RemoveEntryList( &FailedUser->FuNext );
break;
}
FailedUser = NULL;
}
//
// Insert the entry at the front of the list
//
if ( FailedUser != NULL ) {
InsertHeadList( &DomainInfo->DomFailedUserLogonList, &FailedUser->FuNext );
}
UNLOCK_TRUST_LIST( DomainInfo );
//
// If this user logon failed recently, avoid sending it to PDC
//
if ( AvoidSend ) {
NlPrintDom(( NL_LOGON, DomainInfo,
"Avoid send to PDC since user %ws failed recently\n",
UserName ));
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
}
}
//
// We are sending this logon to the PDC ....
//
ClientSession = NlRefDomClientSession( DomainInfo );
if ( ClientSession == NULL ) {
Status = STATUS_INVALID_DOMAIN_ROLE;
goto Cleanup;
}
//
// The normal pass-thru authentication logic handles this quite nicely.
//
Status = NlpUserValidateHigher(
ClientSession,
FALSE,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
&ExtraFlags );
#if NETLOGONDBG
if ( NT_SUCCESS(Status) ) {
IF_NL_DEBUG( LOGON ) {
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
&((PNETLOGON_LEVEL)LogonInformation)->LogonInteractive;
NlPrintDom((NL_LOGON, DomainInfo,
"SamLogon: %s logon of %wZ\\%wZ from %wZ successfully handled on PDC.\n",
NlpLogonTypeToText( LogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation ));
}
}
#endif // NETLOGONDBG
//
// If the PDC returned a bad password status,
// we should icncrease bad pasword count on
// the negative cache entry for this user.
//
// We have to special case the lockout policy.
// If it is enabled, we should continue forwarding to
// the PDC until the account becomes locked out there
// so that we keep the right "master" lockout count.
//
if ( UseNegativeCache && BAD_PASSWORD(Status) ) {
//
// If the PDC says the account is locked out,
// no need to check wether the lockout policy is enabled
//
if ( Status == STATUS_ACCOUNT_LOCKED_OUT ) {
UpdateCache = TRUE;
//
// Otherwise, check wether the lockout is enabled
//
} else {
NTSTATUS TmpStatus = SamrQueryInformationDomain(
DomainInfo->DomSamAccountDomainHandle,
DomainLockoutInformation,
&DomainLockout );
if ( !NT_SUCCESS(TmpStatus) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"NlpUserValidateOnPdc: SamrQueryInformationDomain failed: 0x%lx\n",
TmpStatus ));
} else if ( ((DOMAIN_LOCKOUT_INFORMATION *)DomainLockout)->LockoutThreshold == 0 ) {
//
// OK lockout is not enabled, so we should update the cache
//
UpdateCache = TRUE;
}
}
}
//
// Increase the bad password count for this user
//
FailedUser = NULL;
if ( UpdateCache ) {
ULONG FailedUserCount = 0;
LOCK_TRUST_LIST( DomainInfo );
for ( FailedUserEntry = DomainInfo->DomFailedUserLogonList.Flink;
FailedUserEntry != &DomainInfo->DomFailedUserLogonList;
FailedUserEntry = FailedUserEntry->Flink ) {
FailedUser = CONTAINING_RECORD( FailedUserEntry, NL_FAILED_USER_LOGON, FuNext );
//
// If this is the entry for this user, remove it from the list.
// If it stays on the list, we will re-insert it at the front
// so that the list stays sorted by the entry access time.
//
if ( NlNameCompare(UserName, FailedUser->FuUserName, NAMETYPE_USER) == 0 ) {
RemoveEntryList( &FailedUser->FuNext );
break;
}
FailedUserCount ++;
FailedUser = NULL;
}
//
// If there is no entry for this user, allocate one
//
if ( FailedUser == NULL ) {
ULONG UserNameSize;
UserNameSize = (wcslen(UserName) + 1) * sizeof(WCHAR);
FailedUser = LocalAlloc( LMEM_ZEROINIT, sizeof(NL_FAILED_USER_LOGON) +
UserNameSize );
if ( FailedUser == NULL ) {
UNLOCK_TRUST_LIST( DomainInfo );
//
// Do not destroy Status.
// Return whatever NlpUserValidateHigher returned.
//
goto Cleanup;
}
//
// Fill it in
//
RtlCopyMemory( &FailedUser->FuUserName, UserName, UserNameSize );
//
// If we have too many entries,
// remove the least recently used one and free it.
//
if ( FailedUserCount >= NL_MAX_FAILED_USER_LOGONS ) {
PLIST_ENTRY LastEntry = RemoveTailList( &DomainInfo->DomFailedUserLogonList );
LocalFree( CONTAINING_RECORD(LastEntry, NL_FAILED_USER_LOGON, FuNext) );
}
}
//
// Remember when this user logon was sent to PDC last time
//
FailedUser->FuLastTimeSentToPdc = GetTickCount();
//
// Increment the bad logon count for this user
//
FailedUser->FuBadLogonCount ++;
//
// Insert the entry at the front of the list
//
InsertHeadList( &DomainInfo->DomFailedUserLogonList, &FailedUser->FuNext );
UNLOCK_TRUST_LIST( DomainInfo );
}
Cleanup:
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
}
if ( UserName != NULL ) {
LocalFree( UserName );
}
if ( DomainLockout != NULL ) {
SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainLockout,
DomainLockoutInformation );
}
return Status;
}
NTSTATUS
NlpResetBadPwdCountOnPdc(
IN PDOMAIN_INFO DomainInfo,
IN PUNICODE_STRING LogonUser
)
/*++
Routine Description:
This function zeros the BadPasswordCount field for the specified user
on the PDC through NetLogon Secure Channel.
Arguments:
DomainInfo - Domain this BDC is a member of.
LogonUse -- The user whose BadPasswordCount is to be zeroed.
Return Value:
NTSTATUS code .
it may fail with STATUS_UNKNOWN_REVISION, which means the PDC doesn't
know how to handle the new OP code, in this case, we should fail back
to the old fashion.
--*/
{
NTSTATUS NtStatus = STATUS_SUCCESS;
SAMPR_HANDLE UserHandle = 0;
LPWSTR pUserNameStr = NULL;
//
// If this isn't a BDC,
// There's nothing to do here.
//
if ( DomainInfo->DomRole != RoleBackup ) {
return STATUS_INVALID_DOMAIN_ROLE;
}
//
// Allocate the user name string
//
pUserNameStr = LocalAlloc( 0, LogonUser->Length + sizeof(WCHAR) );
if (NULL == pUserNameStr)
{
return( STATUS_NO_MEMORY );
}
RtlCopyMemory( pUserNameStr, LogonUser->Buffer, LogonUser->Length );
pUserNameStr[ LogonUser->Length/sizeof(WCHAR) ] = L'\0';
//
// Get the user's handle to the local SAM database
//
NtStatus = NlSamOpenNamedUser( DomainInfo,
pUserNameStr,
&UserHandle,
NULL,
NULL
);
//
// Reset the bad password count on PDC
//
if (NT_SUCCESS(NtStatus))
{
NtStatus = SamIResetBadPwdCountOnPdc(UserHandle);
}
if ( NULL != pUserNameStr) {
LocalFree( pUserNameStr );
}
if ( 0 != UserHandle ) {
SamrCloseHandle( &UserHandle );
}
return( NtStatus );
}
VOID
NlpZeroBadPasswordCountOnPdc (
IN PDOMAIN_INFO DomainInfo,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN LPBYTE LogonInformation
)
/*++
Routine Description:
This function zeros the BadPasswordCount field for the specified user
on the PDC.
Arguments:
DomainInfo - Domain this BDC is a member of.
LogonLevel -- Specifies the level of information given in
LogonInformation. Has already been validated.
LogonInformation -- Specifies the description for the user
logging on.
Return Value:
None.
--*/
{
NTSTATUS Status;
BOOLEAN Authoritative;
LPBYTE ValidationInformation = NULL;
//
// If this isn't a BDC,
// There's nothing to do here.
//
if ( DomainInfo->DomRole != RoleBackup ) {
return;
}
//
// We only call this function on a BDC and if the BDC has just zeroed
// the BadPasswordCount because of successful logon.
// First, try to zero bad pwd count directly through NetLogon
// Secure Channel, if it fails with UNKNOWN_REVISION, which means
// PDC doesn't know how to handle the new OP code, will try to
// do the logon over again on the PDC, thus that bad pwd count get
// zero'ed.
//
Status = NlpResetBadPwdCountOnPdc(
DomainInfo,
&((PNETLOGON_LOGON_IDENTITY_INFO)LogonInformation)->UserName
);
if (!NT_SUCCESS(Status) &&
(STATUS_UNKNOWN_REVISION == Status) )
{
Status = NlpUserValidateOnPdc (
DomainInfo,
LogonLevel,
LogonInformation,
NetlogonValidationSamInfo,
FALSE, // avoid negative cache of failed user logons
&ValidationInformation,
&Authoritative );
if ( NT_SUCCESS(Status) ) {
MIDL_user_free( ValidationInformation );
}
}
}
#endif // _DC_NETLOGON
NTSTATUS
NlpZeroBadPasswordCountLocally (
IN PDOMAIN_INFO DomainInfo,
PUNICODE_STRING LogonUser
)
/*++
Routine Description:
This function zeros the BadPasswordCount field for the specified user
on this BDC.
Arguments:
DomainInfo - Domain this BDC is a member of.
LogonUser -- The user whose BadPasswordCount is to be zeroed.
This parameter may be a SamAccountName or a UPN
Return Value:
Status of operation.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
SAMPR_HANDLE UserHandle = 0;
SAMPR_USER_INFO_BUFFER UserInfo;
PUSER_INTERNAL6_INFORMATION LocalUserInfo = NULL;
SID_AND_ATTRIBUTES_LIST LocalMembership = {0};
//
// If this isn't a BDC,
// There's nothing to do here.
//
if ( DomainInfo->DomRole != RoleBackup ) {
return STATUS_INVALID_DOMAIN_ROLE;
}
//
// Get the user's handle to the local SAM database
//
Status = SamIGetUserLogonInformation2(
DomainInfo->DomSamAccountDomainHandle,
SAM_NO_MEMBERSHIPS | // Don't need group memberships
SAM_OPEN_BY_UPN_OR_ACCOUNTNAME, // Next parameter might be a UPN
LogonUser,
0, // No regular fields
0, // no extended fields
&LocalUserInfo,
&LocalMembership,
&UserHandle );
if ( !NT_SUCCESS(Status) ) {
NlPrint(( NL_CRITICAL,
"NlpZeroBadPasswordCountLocally: SamIGetUserLogonInformation2 failed 0x%lx",
Status ));
goto Cleanup;
}
//
// Prepare the user info
//
RtlZeroMemory(&(UserInfo.Internal2), sizeof(USER_INTERNAL2_INFORMATION));
UserInfo.Internal2.StatisticsToApply |= USER_LOGON_STAT_BAD_PWD_COUNT;
//
// Indicate that the authentication succeed at the PDC
// (while the logon may have failed)
//
UserInfo.Internal2.StatisticsToApply |= USER_LOGON_PDC_RETRY_SUCCESS;
//
// Reset the bad password count
//
Status = SamrSetInformationUser( UserHandle,
UserInternal2Information,
&UserInfo);
if ( !NT_SUCCESS(Status) ) {
NlPrint(( NL_CRITICAL,
"NlpZeroBadPasswordCountLocally: SamrSetInformationUser failed 0x%lx",
Status ));
goto Cleanup;
}
Cleanup:
if ( LocalUserInfo != NULL ) {
SamIFree_UserInternal6Information( LocalUserInfo );
}
SamIFreeSidAndAttributesList( &LocalMembership );
if ( UserHandle != 0 ) {
SamrCloseHandle(&UserHandle);
}
return Status;
}
#ifdef ROGUE_DC
#pragma message( "COMPILING A ROGUE DC!!!" )
#pragma message( "MUST NOT SHIP THIS BUILD!!!" )
#undef MAX_SID_LEN
#define MAX_SID_LEN (sizeof(SID) + sizeof(ULONG) * SID_MAX_SUB_AUTHORITIES)
HKEY NlGlobalRogueKey;
NTSTATUS
NlpBuildRogueValidationInfo(
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
IN OUT PNETLOGON_VALIDATION_SAM_INFO4 * UserInfo
)
{
NTSTATUS Status = STATUS_SUCCESS;
PNETLOGON_VALIDATION_SAM_INFO ValidationInfo;
PNETLOGON_VALIDATION_SAM_INFO2 ValidationInfo2;
PNETLOGON_VALIDATION_SAM_INFO4 ValidationInfo4;
//
// Substitution data
//
PSID LogonDomainId = NULL;
PSID ResourceGroupDomainSid = NULL;
PGROUP_MEMBERSHIP GroupIds = NULL;
PGROUP_MEMBERSHIP ResourceGroupIds = NULL;
PNETLOGON_SID_AND_ATTRIBUTES ExtraSids = NULL;
BYTE FullUserSidBuffer[MAX_SID_LEN];
SID * FullUserSid = ( SID * )FullUserSidBuffer;
CHAR * FullUserSidText = NULL;
ULONG UserId;
ULONG PrimaryGroupId;
ULONG SidCount = 0;
ULONG GroupCount = 0;
ULONG ResourceGroupCount = 0;
DWORD dwType;
DWORD cbData = 0;
PCHAR Buffer;
PCHAR Value = NULL;
BOOL InfoChanged = FALSE;
//
// Marshaling variables
//
ULONG Index, GroupIndex;
ULONG Length;
ULONG TotalNumberOfSids = 0;
PNETLOGON_VALIDATION_SAM_INFO4 SamInfo4 = NULL;
PBYTE Where;
ULONG SidLength;
//
// Reject unrecognized validation levels
//
if ( ValidationLevel != NetlogonValidationSamInfo &&
ValidationLevel != NetlogonValidationSamInfo2 &&
ValidationLevel != NetlogonValidationSamInfo4 )
{
return STATUS_INVALID_PARAMETER;
}
if ( UserInfo == NULL )
{
return STATUS_INVALID_PARAMETER;
}
//
// Optimization: if the rogue key is not present, there's nothing for us to do
//
if ( NlGlobalRogueKey == NULL )
{
return STATUS_SUCCESS;
}
ValidationInfo = ( PNETLOGON_VALIDATION_SAM_INFO )( *UserInfo );
ValidationInfo2 = ( PNETLOGON_VALIDATION_SAM_INFO2 )( *UserInfo );
ValidationInfo4 = ( PNETLOGON_VALIDATION_SAM_INFO4 )( *UserInfo );
UserId = ValidationInfo->UserId;
PrimaryGroupId = ValidationInfo->PrimaryGroupId;
//
// Construct the text form of the full user's SID (logon domain ID + user ID)
//
NlAssert( sizeof( FullUserSidBuffer ) >= MAX_SID_LEN );
RtlCopySid(
sizeof( FullUserSidBuffer ),
FullUserSid,
ValidationInfo->LogonDomainId
);
FullUserSid->SubAuthority[FullUserSid->SubAuthorityCount] = ValidationInfo->UserId;
FullUserSid->SubAuthorityCount += 1;
if ( FALSE == ConvertSidToStringSidA(
FullUserSid,
&FullUserSidText ))
{
NlPrint((NL_CRITICAL, "ROGUE: Unable to convert user's SID\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
//
// Now look in the registry for the SID matching the validation info
//
if ( ERROR_SUCCESS != RegQueryValueExA(
NlGlobalRogueKey,
FullUserSidText,
NULL,
&dwType,
NULL,
&cbData ) ||
dwType != REG_MULTI_SZ ||
cbData <= 1 )
{
NlPrint((NL_CRITICAL, "ROGUE: No substitution info available for %s\n", FullUserSidText));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
Value = ( PCHAR )HeapAlloc(
GetProcessHeap(),
0,
cbData
);
if ( Value == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating substitution buffer\n", FullUserSidText));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
if ( ERROR_SUCCESS != RegQueryValueExA(
NlGlobalRogueKey,
FullUserSidText,
NULL,
&dwType,
(PBYTE)Value,
&cbData ) ||
dwType != REG_MULTI_SZ ||
cbData <= 1 )
{
NlPrint((NL_CRITICAL, "ROGUE: Error reading from registry\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
NlPrint((NL_CRITICAL, "ROGUE: Substituting the validation info for %s\n", FullUserSidText));
Buffer = Value;
//
// Read the input file one line at a time
//
while ( *Buffer != '\0' )
{
switch( Buffer[0] )
{
case 'l':
case 'L': // logon domain ID
if ( LogonDomainId != NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Logon domain ID specified more than once - only first one kept\n"));
break;
}
NlPrint((NL_CRITICAL, "ROGUE: Substituting logon domain ID by %s\n", &Buffer[1]));
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&LogonDomainId ))
{
NlPrint((NL_CRITICAL, "ROGUE: Unable to convert SID\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
if ( LogonDomainId == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating LogonDomainId\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
InfoChanged = TRUE;
break;
case 'd':
case 'D': // resource group domain SID
if ( ResourceGroupDomainSid != NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Resource group domain SID specified more than once - only first one kept\n"));
break;
}
NlPrint((NL_CRITICAL, "ROGUE: Substituting resource group domain SID by %s\n", &Buffer[1]));
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&ResourceGroupDomainSid ))
{
NlPrint((NL_CRITICAL, "ROGUE: Unable to convert SID\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
if ( ResourceGroupDomainSid == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating ResourceGroupDomainSid\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
InfoChanged = TRUE;
break;
case 'p':
case 'P': // primary group ID
NlPrint((NL_CRITICAL, "ROGUE: Substituting primary group ID by %s\n", &Buffer[1]));
PrimaryGroupId = atoi(&Buffer[1]);
InfoChanged = TRUE;
break;
case 'u':
case 'U': // User ID
NlPrint((NL_CRITICAL, "ROGUE: Substituting user ID by %s\n", &Buffer[1]));
UserId = atoi(&Buffer[1]);
InfoChanged = TRUE;
break;
case 'e':
case 'E': // Extra SID
{
PNETLOGON_SID_AND_ATTRIBUTES ExtraSidsT;
if ( ValidationLevel == NetlogonValidationSamInfo )
{
NlPrint((NL_CRITICAL, "ROGUE: Extra SIDs skipped; not supported for this validation level\n" ));
break;
}
NlPrint((NL_CRITICAL, "ROGUE: Adding an ExtraSid: %s\n", &Buffer[1]));
if ( ExtraSids == NULL )
{
ExtraSidsT = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapAlloc(
GetProcessHeap(),
0,
sizeof( NETLOGON_SID_AND_ATTRIBUTES )
);
}
else
{
ExtraSidsT = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapReAlloc(
GetProcessHeap(),
0,
ExtraSids,
( SidCount + 1 ) * sizeof( NETLOGON_SID_AND_ATTRIBUTES )
);
}
if ( ExtraSidsT == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating ExtraSids\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
//
// Read the actual SID
//
ExtraSids = ExtraSidsT;
if ( FALSE == ConvertStringSidToSidA(
&Buffer[1],
&ExtraSids[SidCount].Sid ))
{
NlPrint((NL_CRITICAL, "ROGUE: Unable to convert SID\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
if ( ExtraSids[SidCount].Sid == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating an extra SID\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
ExtraSids[SidCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
SidCount += 1;
InfoChanged = TRUE;
}
break;
case 'g':
case 'G': // Group ID
{
PGROUP_MEMBERSHIP GroupIdsT;
NlPrint((NL_CRITICAL, "ROGUE: Adding a GroupId: %s\n", &Buffer[1]));
if ( GroupIds == NULL )
{
GroupIdsT = ( PGROUP_MEMBERSHIP )HeapAlloc(
GetProcessHeap(),
0,
sizeof( GROUP_MEMBERSHIP )
);
}
else
{
GroupIdsT = ( PGROUP_MEMBERSHIP )HeapReAlloc(
GetProcessHeap(),
0,
GroupIds,
( GroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
);
}
if ( GroupIdsT == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating Group IDs\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
//
// Read the actual ID
//
GroupIds = GroupIdsT;
GroupIds[GroupCount].RelativeId = atoi(&Buffer[1]);
GroupIds[GroupCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
GroupCount += 1;
InfoChanged = TRUE;
}
break;
case 'r':
case 'R': // Resource groups
{
PGROUP_MEMBERSHIP ResourceGroupIdsT;
NlPrint((NL_CRITICAL, "ROGUE: Adding a ResourceGroupId: %s\n", &Buffer[1]));
if ( ResourceGroupIds == NULL )
{
ResourceGroupIdsT = ( PGROUP_MEMBERSHIP )HeapAlloc(
GetProcessHeap(),
0,
sizeof( GROUP_MEMBERSHIP )
);
}
else
{
ResourceGroupIdsT = ( PGROUP_MEMBERSHIP )HeapReAlloc(
GetProcessHeap(),
0,
ResourceGroupIds,
( ResourceGroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
);
}
if ( ResourceGroupIdsT == NULL )
{
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating Resource Group IDs\n"));
Status = STATUS_UNSUCCESSFUL;
goto Error;
}
//
// Read the actual ID
//
ResourceGroupIds[ResourceGroupCount].RelativeId = atoi(&Buffer[1]);
ResourceGroupIds[ResourceGroupCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED_BY_DEFAULT |
SE_GROUP_ENABLED;
ResourceGroupCount += 1;
InfoChanged = TRUE;
}
break;
default: // unrecognized
NlPrint((NL_CRITICAL, "ROGUE: Entry \'%c\' unrecognized\n", Buffer[0]));
break;
}
//
// Move to the next line
//
while (*Buffer++ != '\0');
}
if ( !InfoChanged )
{
NlPrint((NL_CRITICAL, "ROGUE: Nothing to substitute for %s\n", FullUserSidText));
Status = STATUS_SUCCESS;
goto Error;
}
//
// Ok, now that we have our substitution info, build the new validation struct
//
//
// Calculate the size of the new structure
//
Length = sizeof( NETLOGON_VALIDATION_SAM_INFO4 );
if ( GroupCount > 0 )
{
Length += GroupCount * sizeof( GROUP_MEMBERSHIP );
}
else
{
Length += ValidationInfo->GroupCount * sizeof( GROUP_MEMBERSHIP );
}
if ( LogonDomainId != NULL )
{
Length += RtlLengthSid( LogonDomainId );
}
else
{
Length += RtlLengthSid( ValidationInfo->LogonDomainId );
}
//
// Add space for extra sids & resource groups
//
if ( ExtraSids )
{
for ( Index = 0 ; Index < SidCount ; Index++ )
{
Length += sizeof( NETLOGON_SID_AND_ATTRIBUTES ) + RtlLengthSid( ExtraSids[Index].Sid );
}
TotalNumberOfSids += SidCount;
}
else if ( ValidationLevel != NetlogonValidationSamInfo &&
( ValidationInfo->UserFlags & LOGON_EXTRA_SIDS ) != 0 )
{
for (Index = 0; Index < ValidationInfo2->SidCount ; Index++ ) {
Length += sizeof(NETLOGON_SID_AND_ATTRIBUTES) + RtlLengthSid(ValidationInfo2->ExtraSids[Index].Sid);
}
TotalNumberOfSids += ValidationInfo2->SidCount;
}
if ( ResourceGroupIds != NULL && ResourceGroupDomainSid != NULL )
{
Length += ResourceGroupCount * ( sizeof( NETLOGON_SID_AND_ATTRIBUTES ) + RtlLengthSid( ResourceGroupDomainSid ) + sizeof( ULONG ));
TotalNumberOfSids += ResourceGroupCount;
}
//
// Round up now to take into account the round up in the
// middle of marshalling
//
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo->LogonDomainName.Length + sizeof(WCHAR)
+ ValidationInfo->LogonServer.Length + sizeof(WCHAR)
+ ValidationInfo->EffectiveName.Length + sizeof(WCHAR)
+ ValidationInfo->FullName.Length + sizeof(WCHAR)
+ ValidationInfo->LogonScript.Length + sizeof(WCHAR)
+ ValidationInfo->ProfilePath.Length + sizeof(WCHAR)
+ ValidationInfo->HomeDirectory.Length + sizeof(WCHAR)
+ ValidationInfo->HomeDirectoryDrive.Length + sizeof(WCHAR);
if ( ValidationLevel == NetlogonValidationSamInfo4 ) {
Length += ValidationInfo4->DnsLogonDomainName.Length + sizeof(WCHAR)
+ ValidationInfo4->Upn.Length + sizeof(WCHAR);
//
// The ExpansionStrings may be used to transport byte aligned data
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString1.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString2.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString3.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString4.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString5.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString6.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString7.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString8.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString9.Length + sizeof(WCHAR);
Length = ROUND_UP_COUNT(Length, sizeof(WCHAR))
+ ValidationInfo4->ExpansionString10.Length + sizeof(WCHAR);
}
Length = ROUND_UP_COUNT( Length, sizeof(WCHAR) );
SamInfo4 = (PNETLOGON_VALIDATION_SAM_INFO4)MIDL_user_allocate( Length );
if ( !SamInfo4 )
{
Status = STATUS_INSUFFICIENT_RESOURCES;
NlPrint((NL_CRITICAL, "ROGUE: Out of memory allocating SamInfo4\n"));
goto Error;
}
//
// First copy the whole structure, since most parts are the same
//
RtlCopyMemory( SamInfo4, ValidationInfo, sizeof(NETLOGON_VALIDATION_SAM_INFO));
RtlZeroMemory( &((LPBYTE)SamInfo4)[sizeof(NETLOGON_VALIDATION_SAM_INFO)],
sizeof(NETLOGON_VALIDATION_SAM_INFO4) - sizeof(NETLOGON_VALIDATION_SAM_INFO) );
//
// See if these need to be added (later)
//
SamInfo4->UserFlags &= ~LOGON_EXTRA_SIDS;
//
// Substitute the UserId and PrimaryGroupId
//
SamInfo4->UserId = UserId;
SamInfo4->PrimaryGroupId = PrimaryGroupId;
//
// Copy all the variable length data
//
Where = (PBYTE) (SamInfo4 + 1);
if ( GroupIds != NULL )
{
RtlCopyMemory(
Where,
GroupIds,
GroupCount * sizeof( GROUP_MEMBERSHIP )
);
SamInfo4->GroupIds = (PGROUP_MEMBERSHIP) Where;
SamInfo4->GroupCount = GroupCount;
Where += GroupCount * sizeof( GROUP_MEMBERSHIP );
}
else
{
RtlCopyMemory(
Where,
ValidationInfo->GroupIds,
ValidationInfo->GroupCount * sizeof( GROUP_MEMBERSHIP )
);
SamInfo4->GroupIds = (PGROUP_MEMBERSHIP) Where;
SamInfo4->GroupCount = ValidationInfo->GroupCount;
Where += ValidationInfo->GroupCount * sizeof( GROUP_MEMBERSHIP );
}
//
// Copy the extra groups
//
if ( TotalNumberOfSids > 0 )
{
PNETLOGON_SID_AND_ATTRIBUTES ExtraSidsArray = NULL;
ULONG ExtraSidsCount = 0;
SamInfo4->ExtraSids = (PNETLOGON_SID_AND_ATTRIBUTES) Where;
Where += sizeof(NETLOGON_SID_AND_ATTRIBUTES) * TotalNumberOfSids;
GroupIndex = 0;
if ( ExtraSids != NULL )
{
ExtraSidsArray = ExtraSids;
ExtraSidsCount = SidCount;
}
else if ( ValidationLevel != NetlogonValidationSamInfo &&
(ValidationInfo->UserFlags & LOGON_EXTRA_SIDS) != 0 )
{
ExtraSidsArray = ValidationInfo2->ExtraSids;
ExtraSidsCount = ValidationInfo2->SidCount;
}
for ( Index = 0 ; Index < ExtraSidsCount ; Index++ )
{
SamInfo4->ExtraSids[GroupIndex].Attributes = ExtraSidsArray[Index].Attributes;
SamInfo4->ExtraSids[GroupIndex].Sid = (PSID) Where;
SidLength = RtlLengthSid(ExtraSidsArray[Index].Sid);
RtlCopyMemory(
Where,
ExtraSidsArray[Index].Sid,
SidLength
);
Where += SidLength;
GroupIndex++;
}
//
// Add the resource groups
//
for ( Index = 0 ; Index < ResourceGroupCount ; Index++ )
{
SamInfo4->ExtraSids[GroupIndex].Attributes = ResourceGroupIds[Index].Attributes;
SamInfo4->ExtraSids[GroupIndex].Sid = (PSID) Where;
SidLength = RtlLengthSid(ResourceGroupDomainSid);
RtlCopyMemory(
Where,
ResourceGroupDomainSid,
SidLength
);
((SID *)(Where))->SubAuthorityCount += 1;
Where += SidLength;
RtlCopyMemory(
Where,
&ResourceGroupIds[Index].RelativeId,
sizeof(ULONG)
);
Where += sizeof(ULONG);
GroupIndex++;
}
SamInfo4->UserFlags |= LOGON_EXTRA_SIDS;
SamInfo4->SidCount = GroupIndex;
NlAssert(GroupIndex == TotalNumberOfSids);
}
if ( LogonDomainId != NULL )
{
SidLength = RtlLengthSid( LogonDomainId );
RtlCopyMemory(
Where,
LogonDomainId,
SidLength
);
SamInfo4->LogonDomainId = (PSID) Where;
Where += SidLength;
}
else
{
SidLength = RtlLengthSid( ValidationInfo->LogonDomainId );
RtlCopyMemory(
Where,
ValidationInfo->LogonDomainId,
SidLength
);
SamInfo4->LogonDomainId = (PSID) Where;
Where += SidLength;
}
//
// Copy the WCHAR-aligned data
//
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->EffectiveName,
&ValidationInfo->EffectiveName,
&Where );
NlpPutString(
&SamInfo4->FullName,
&ValidationInfo->FullName,
&Where );
NlpPutString(
&SamInfo4->LogonScript,
&ValidationInfo->LogonScript,
&Where );
NlpPutString(
&SamInfo4->ProfilePath,
&ValidationInfo->ProfilePath,
&Where );
NlpPutString(
&SamInfo4->HomeDirectory,
&ValidationInfo->HomeDirectory,
&Where );
NlpPutString(
&SamInfo4->HomeDirectoryDrive,
&ValidationInfo->HomeDirectoryDrive,
&Where );
NlpPutString(
&SamInfo4->LogonServer,
&ValidationInfo->LogonServer,
&Where );
NlpPutString(
&SamInfo4->LogonDomainName,
&ValidationInfo->LogonDomainName,
&Where );
if ( ValidationLevel == NetlogonValidationSamInfo4 )
{
NlpPutString(
&SamInfo4->DnsLogonDomainName,
&ValidationInfo4->DnsLogonDomainName,
&Where );
NlpPutString(
&SamInfo4->Upn,
&ValidationInfo4->Upn,
&Where );
NlpPutString(
&SamInfo4->ExpansionString1,
&ValidationInfo4->ExpansionString1,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR) );
NlpPutString(
&SamInfo4->ExpansionString2,
&ValidationInfo4->ExpansionString2,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString3,
&ValidationInfo4->ExpansionString3,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString4,
&ValidationInfo4->ExpansionString4,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString5,
&ValidationInfo4->ExpansionString5,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString6,
&ValidationInfo4->ExpansionString6,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString7,
&ValidationInfo4->ExpansionString7,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString8,
&ValidationInfo4->ExpansionString8,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString9,
&ValidationInfo4->ExpansionString9,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
NlpPutString(
&SamInfo4->ExpansionString10,
&ValidationInfo4->ExpansionString10,
&Where );
Where = ROUND_UP_POINTER(Where, sizeof(WCHAR));
}
MIDL_user_free(ValidationInfo);
*UserInfo = SamInfo4;
Cleanup:
LocalFree( FullUserSidText );
LocalFree( ResourceGroupDomainSid );
LocalFree( LogonDomainId );
HeapFree( GetProcessHeap(), 0, ResourceGroupIds );
HeapFree( GetProcessHeap(), 0, GroupIds );
if ( ExtraSids )
{
for ( Index = 0; Index < SidCount; Index++ )
{
HeapFree( GetProcessHeap(), 0, ExtraSids[Index].Sid );
}
HeapFree( GetProcessHeap(), 0, ExtraSids );
}
HeapFree( GetProcessHeap(), 0, Value );
return Status;
Error:
goto Cleanup;
}
#endif
NTSTATUS
NlpSamValidate (
IN SAM_HANDLE DomainHandle,
IN BOOLEAN UasCompatibilityRequired,
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
IN PUNICODE_STRING LogonServer,
IN PUNICODE_STRING LogonDomainName,
IN PSID LogonDomainId,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PVOID LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PVOID * ValidationInformation,
OUT PBOOLEAN Authoritative,
OUT PBOOLEAN BadPasswordCountZeroed,
IN DWORD AccountsToTry
)
/*++
Routine Description:
This is a thin wrapper around MsvSamValidate. It normalizes
some of parameters to a form that MSV recognizes.
Arguments:
Same as for MsvSamValidate.
Return Value:
Same as for MsvSamValidate.
--*/
{
NTSTATUS Status;
NETLOGON_LOGON_INFO_CLASS LogonLevelToUse;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
ULONG SavedParameterControl;
UNICODE_STRING SavedDomainName;
//
// Initialization
//
LogonLevelToUse = LogonLevel;
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
//
// Don't confuse MSV with the transitive LogonLevels
//
switch (LogonLevel ) {
case NetlogonInteractiveTransitiveInformation:
LogonLevelToUse = NetlogonInteractiveInformation; break;
case NetlogonServiceTransitiveInformation:
LogonLevelToUse = NetlogonServiceInformation; break;
case NetlogonNetworkTransitiveInformation:
LogonLevelToUse = NetlogonNetworkInformation; break;
}
//
// Don't confuse MSV with domains used for routing only
//
SavedDomainName = LogonInfo->LogonDomainName;
SavedParameterControl = LogonInfo->ParameterControl;
if ( LogonInfo->ParameterControl & MSV1_0_USE_DOMAIN_FOR_ROUTING_ONLY ) {
//
// Clear the routing information
//
RtlInitUnicodeString( &LogonInfo->LogonDomainName, NULL );
LogonInfo->ParameterControl &= ~ MSV1_0_USE_DOMAIN_FOR_ROUTING_ONLY;
}
//
// Now that we've normalized the parameters, call MSV
//
Status = MsvSamValidate (
DomainHandle,
UasCompatibilityRequired,
SecureChannelType,
LogonServer,
LogonDomainName,
LogonDomainId,
LogonLevelToUse,
LogonInfo,
ValidationLevel,
ValidationInformation,
Authoritative,
BadPasswordCountZeroed,
AccountsToTry );
//
// Restore the saved data
//
LogonInfo->LogonDomainName = SavedDomainName;
LogonInfo->ParameterControl = SavedParameterControl;
return Status;
}
NTSTATUS
NlpUserValidate (
IN PDOMAIN_INFO DomainInfo,
IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
IN ULONG ComputerAccountRid,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN LPBYTE LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT LPBYTE * ValidationInformation,
OUT PBOOLEAN Authoritative,
IN OUT PULONG ExtraFlags,
IN BOOLEAN Recursed
)
/*++
Routine Description:
This function processes an interactive or network logon.
It is a worker routine for I_NetSamLogon. I_NetSamLogon handles the
details of validating the caller. This function handles the details
of whether to validate locally or pass the request on. MsvValidateSam
does the actual local validation.
session table only in the domain defining the specified user's
account.
This service is also used to process a re-logon request.
Arguments:
DomainInfo - Hosted domain this logon is for.
SecureChannelType -- Type of secure channel this request was made over.
ComputerAccountRid - The RID of the computer account for the workstation
that passed the logon to this domain controller.
LogonLevel -- Specifies the level of information given in
LogonInformation. Has already been validated.
LogonInformation -- Specifies the description for the user
logging on.
ValidationLevel -- Specifies the level of information returned in
ValidationInformation. Must be NetlogonValidationSamInfo,
NetlogonValidationSamInfo2, or NetlogonValidationSamInfo4.
ValidationInformation -- Returns the requested validation
information. This buffer must be freed using MIDL_user_free.
Authoritative -- Returns whether the status returned is an
authoritative status which should be returned to the original
caller. If not, this logon request may be tried again on another
domain controller. This parameter is returned regardless of the
status code.
ExtraFlags -- Accepts and returns a DWORD to the caller.
The DWORD contains NL_EXFLAGS_* values.
Recursed - TRUE if this is a recursive call. This routine sometimes translates
the account name in the logon information to a more explicit form (e.g., from a
UPN to a <DnsDomainName>\<SamAccountName> form). After it does that, it simply
calls this routine again. This boolean is TRUE on that second call.
Return Value:
STATUS_SUCCESS: if there was no error.
Otherwise, the error code is
returned.
--*/
{
NTSTATUS Status;
NTSTATUS DefaultStatus = STATUS_NO_SUCH_USER;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
PCLIENT_SESSION ClientSession = NULL;
DWORD AccountsToTry = MSVSAM_SPECIFIED | MSVSAM_GUEST;
BOOLEAN BadPasswordCountZeroed;
BOOLEAN LogonToLocalDomain;
LPWSTR RealSamAccountName = NULL;
LPWSTR RealDomainName = NULL;
ULONG RealExtraFlags = 0; // Flags to pass to the next hop
BOOLEAN ExpediteToRoot = ((*ExtraFlags) & NL_EXFLAGS_EXPEDITE_TO_ROOT) != 0;
BOOLEAN CrossForestHop = ((*ExtraFlags) & NL_EXFLAGS_CROSS_FOREST_HOP) != 0;
//
// Initialization
//
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation;
*Authoritative = FALSE;
//
// The DNS domain for a workstation is the domain its a member of, so
// don't match based on that.
//
LogonToLocalDomain = RtlEqualDomainName( &LogonInfo->LogonDomainName,
&DomainInfo->DomUnicodeAccountDomainNameString ) ||
((DomainInfo->DomRole != RoleMemberWorkstation ) &&
NlEqualDnsNameU( &LogonInfo->LogonDomainName,
&DomainInfo->DomUnicodeDnsDomainNameString ) ) ;
//
// Check to see if the account is in the local SAM database.
//
// The Theory:
// If a particular database is absolutely requested,
// we only try the account in the requested database.
//
// In the event that an account exists in multiple places in the hierarchy,
// we want to find the version of the account that is closest to the
// logged on machine (i.e., workstation first, primary domain, then
// trusted domain.). So we always try to local database before going
// to a higher authority.
//
// Finally, handle the case that this call is from a BDC in our own domain
// just checking to see if the PDC (us) has a better copy of the account
// than it does.
//
if ( !ExpediteToRoot &&
( (LogonInfo->LogonDomainName.Length == 0 && !CrossForestHop ) ||
LogonToLocalDomain ||
SecureChannelType == ServerSecureChannel )) {
//
// If we are not doing a generic passthrough, just call SAM
//
if ( LogonLevel != NetlogonGenericInformation ) {
//
// Indicate we've already tried the specified account and
// we won't need to try it again locally.
//
AccountsToTry &= ~MSVSAM_SPECIFIED;
Status = NlpSamValidate( DomainInfo->DomSamAccountDomainHandle,
TRUE, // UasCompatibilityMode,
SecureChannelType,
&DomainInfo->DomUnicodeComputerNameString,
&DomainInfo->DomUnicodeAccountDomainNameString,
DomainInfo->DomAccountDomainId,
LogonLevel,
LogonInformation,
ValidationLevel,
(PVOID *)ValidationInformation,
Authoritative,
&BadPasswordCountZeroed,
MSVSAM_SPECIFIED );
//
// If this is a BDC and we zeroed the BadPasswordCount field,
// allow the PDC to do the same thing.
//
if ( BadPasswordCountZeroed ) {
NlpZeroBadPasswordCountOnPdc ( DomainInfo, LogonLevel, LogonInformation );
}
//
// If the request is explicitly for this domain,
// The STATUS_NO_SUCH_USER answer is authoritative.
//
if ( LogonToLocalDomain && Status == STATUS_NO_SUCH_USER ) {
*Authoritative = TRUE;
}
//
// If this is one of our BDCs calling,
// return with whatever answer we got locally.
//
if ( SecureChannelType == ServerSecureChannel ) {
DefaultStatus = Status;
goto Cleanup;
}
} else {
PNETLOGON_GENERIC_INFO GenericInfo;
NETLOGON_VALIDATION_GENERIC_INFO GenericValidation;
NTSTATUS ProtocolStatus;
GenericInfo = (PNETLOGON_GENERIC_INFO) LogonInformation;
GenericValidation.ValidationData = NULL;
GenericValidation.DataLength = 0;
//
// We are doing generic passthrough, so call the LSA
//
// LogonData is opaque to Netlogon. The caller made sure that any
// pointers within LogonData are actually offsets within LogonData.
// So, tell the package that the Client's buffer base is 0.
//
Status = LsaICallPackagePassthrough(
&GenericInfo->PackageName,
0, // Indicate pointers are relative.
GenericInfo->LogonData,
GenericInfo->DataLength,
(PVOID *) &GenericValidation.ValidationData,
&GenericValidation.DataLength,
&ProtocolStatus
);
if (NT_SUCCESS(Status)) {
Status = ProtocolStatus;
}
//
// This is always authoritative.
//
*Authoritative = TRUE;
//
// If the call succeeded, allocate the return message.
//
if (NT_SUCCESS(Status)) {
PNETLOGON_VALIDATION_GENERIC_INFO ReturnInfo;
ULONG ValidationLength;
ValidationLength = sizeof(*ReturnInfo) + GenericValidation.DataLength;
ReturnInfo = (PNETLOGON_VALIDATION_GENERIC_INFO) MIDL_user_allocate(
ValidationLength
);
if (ReturnInfo != NULL) {
if ( GenericValidation.DataLength == 0 ||
GenericValidation.ValidationData == NULL ) {
ReturnInfo->DataLength = 0;
ReturnInfo->ValidationData = NULL;
} else {
ReturnInfo->DataLength = GenericValidation.DataLength;
ReturnInfo->ValidationData = (PUCHAR) (ReturnInfo + 1);
RtlCopyMemory(
ReturnInfo->ValidationData,
GenericValidation.ValidationData,
ReturnInfo->DataLength );
}
*ValidationInformation = (PBYTE) ReturnInfo;
} else {
Status = STATUS_INSUFFICIENT_RESOURCES;
}
if (GenericValidation.ValidationData != NULL) {
LsaIFreeReturnBuffer(GenericValidation.ValidationData);
}
}
DefaultStatus = Status;
goto Cleanup;
}
//
// If the local SAM database authoritatively handled the logon attempt,
// just return.
//
if ( *Authoritative ) {
DefaultStatus = Status;
#ifdef _DC_NETLOGON
//
// If the problem is just that the password is wrong,
// try again on the PDC where the password may already be changed.
//
if ( BAD_PASSWORD(Status) ) {
BOOLEAN TempAuthoritative;
Status = NlpUserValidateOnPdc (
DomainInfo,
LogonLevel,
LogonInformation,
ValidationLevel,
TRUE, // use negative cache of failed user logons
ValidationInformation,
&TempAuthoritative );
// Ignore failures from the PDC (except where it has newer information)
if ( NT_SUCCESS(Status) || BAD_PASSWORD(Status) ) {
DefaultStatus = Status;
*Authoritative = TempAuthoritative;
}
// If appropriate, zero bad password locally on this BDC.
// Ignore error as it's not critical operation.
if ( (NT_SUCCESS(Status) || ZERO_BAD_PWD_COUNT(Status)) &&
!NlGlobalMemberWorkstation ) {
NlpZeroBadPasswordCountLocally( DomainInfo, &LogonInfo->UserName );
}
}
//
// If the result of local validation or validation on
// the PDC is anything other than the bad password status,
// remove this user from the bad password negative cache
//
if ( !BAD_PASSWORD(DefaultStatus) ) {
NlpRemoveBadPasswordCacheEntry( DomainInfo, LogonInformation );
}
#endif // _DC_NETLOGON
goto Cleanup;
}
DefaultStatus = Status;
}
//
// If the request in not for this domain,
// or the domain name isn't specified (and we haven't found the account yet)
// or our caller asked us to send the request to the root of the forest,
// send the request to a higher authority.
//
if ( !LogonToLocalDomain ||
LogonInfo->LogonDomainName.Length == 0 ||
ExpediteToRoot ) {
//
// If this machine is a workstation,
// send the request to the Primary Domain.
//
if ( NlGlobalMemberWorkstation ) {
NlAssert( !ExpediteToRoot );
NlAssert( !CrossForestHop );
ClientSession = NlRefDomClientSession( DomainInfo);
if ( ClientSession == NULL ) {
*Authoritative = FALSE;
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
goto Cleanup;
}
Status = NlpUserValidateHigher(
ClientSession,
FALSE,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
&RealExtraFlags );
NlUnrefClientSession( ClientSession );
ClientSession = NULL;
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
//
// return more appropriate error
//
if( (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
(Status == STATUS_ACCESS_DENIED) ) {
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
}
//
// If the primary domain authoritatively handled the logon attempt,
// just return.
//
if ( *Authoritative ) {
//
// If we didn't actually talk to the primary domain,
// check locally if the domain requested is a trusted domain.
// (This list is only a cache so we had to try to contact the
// primary domain.)
//
// Note: I can't differentiate between there not being a logon server
// in my primary domain and there not being a logon sever in
// a trusted domain. So, both cases go into the code below.
//
if ( Status == STATUS_NO_LOGON_SERVERS ) {
//
// If no domain name was specified,
// check if the user name is a UPN.
//
if ( LogonLevel != NetlogonGenericInformation &&
LogonInfo->LogonDomainName.Length == 0 ) {
ULONG i;
for ( i=0; i<LogonInfo->UserName.Length/sizeof(WCHAR); i++) {
//
// If this is a UPN logon,
// assume that the domain is a trusted domain.
//
// ???: it isn't sufficient to check for an @.
// This might be a user account with an @ in it.
//
// This makes UPNs eligible for cached logon.
// But it doesn't make UPNs eligible for guest
// account logon.
if ( LogonInfo->UserName.Buffer[i] == L'@') {
DefaultStatus = Status;
goto Cleanup;
}
}
}
//
// If the domain specified is trusted,
// then return the status to the caller.
// otherwise just press on.
if ( NlIsDomainTrusted ( &LogonInfo->LogonDomainName ) ) {
DefaultStatus = Status;
goto Cleanup;
} else {
//
// Set the return codes to look as though the primary
// determined this is an untrusted domain.
//
*Authoritative = FALSE;
Status = STATUS_NO_SUCH_USER;
}
} else {
DefaultStatus = Status;
goto Cleanup;
}
}
if ( Status != STATUS_NO_SUCH_USER ) {
DefaultStatus = Status;
}
//
// This machine is a Domain Controller.
//
// This request is either a pass-thru request by a workstation in
// our domain, or this request came directly from the MSV
// authentication package.
//
// In either case, pass the request to the trusted domain.
//
} else {
BOOLEAN TransitiveUsed;
//
// If the request was passed to us from a trusting domain DC,
// and the caller doesn't want transitive trust to be used,
// then avoid using transitive trust. (Generic passthrough
// may always be transitive (it was introduced in NT5).)
//
if ( IsDomainSecureChannelType(SecureChannelType) &&
LogonLevel != NetlogonInteractiveTransitiveInformation &&
LogonLevel != NetlogonServiceTransitiveInformation &&
LogonLevel != NetlogonNetworkTransitiveInformation &&
LogonLevel != NetlogonGenericInformation ) {
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
//
// If our caller asked us to expedite this request to the domain at the forest root,
// find the client session to our parent.
//
if ( ExpediteToRoot ) {
//
// Only do this if we're not already at the root
//
if ( (DomainInfo->DomFlags & DOM_FOREST_ROOT) == 0 ) {
ClientSession = NlRefDomParentClientSession( DomainInfo );
if ( ClientSession == NULL ) {
NlPrintDom((NL_LOGON, DomainInfo,
"NlpUserValidate: Can't find parent domain in forest '%wZ'\n",
&NlGlobalUnicodeDnsForestNameString ));
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
//
// Tell the DC we're calling to continue expediting to root.
//
RealExtraFlags |= NL_EXFLAGS_EXPEDITE_TO_ROOT;
}
//
// It the domain is explicitly given,
// simply find the client session for that domain.
//
} else {
if ( LogonInfo->LogonDomainName.Length != 0 ) {
//
// Find a client session for the domain.
//
// If we just hopped from another forest,
// require that LogonDomainName be a domain in the forest.
//
ClientSession = NlFindNamedClientSession(
DomainInfo,
&LogonInfo->LogonDomainName,
NL_RETURN_CLOSEST_HOP |
(CrossForestHop ?
NL_REQUIRE_DOMAIN_IN_FOREST : 0),
&TransitiveUsed );
}
}
//
// If the client session hasn't yet been found,
// Try to find the domain name by ask the GC.
//
// Avoid this step if the call came from a DC. It should have done the GC lookup.
// Even that's OK if the caller was expediting to root or
// hopping into this forest from a trusting forest.
// Avoid this step if this machine already did the GC lookup.
//
if ( LogonLevel != NetlogonGenericInformation &&
ClientSession == NULL &&
( !IsDomainSecureChannelType(SecureChannelType) ||
(ExpediteToRoot || CrossForestHop) ) &&
!Recursed ) {
//
// Find the domain the account is in from one of the following sources:
// The LSA FTinfo match routine if this is a DC at the root of the forest.
// The local DS for UPN lookup.
// The GC form UPN lookup or unqualified SAM account names.
// By datagram send to all directly trusted domains not in this forest.
//
Status = NlPickDomainWithAccount (
DomainInfo,
&LogonInfo->UserName,
&LogonInfo->LogonDomainName,
USER_NORMAL_ACCOUNT,
SecureChannelType,
ExpediteToRoot,
CrossForestHop,
&RealSamAccountName,
&RealDomainName,
&RealExtraFlags );
//
// If we're a DC at the root of the forest
// and the account is in a trusted forest,
// send the request to the other forest.
//
if ( NT_SUCCESS(Status) &&
(RealExtraFlags & NL_EXFLAGS_CROSS_FOREST_HOP) != 0 ) {
UNICODE_STRING RealDomainNameString;
RtlInitUnicodeString( &RealDomainNameString, RealDomainName );
ClientSession = NlFindNamedClientSession(
DomainInfo,
&RealDomainNameString,
NL_DIRECT_TRUST_REQUIRED,
&TransitiveUsed );
//
// Further qualify the ClientSession by ensure the found domain isn't
// in our forest and that the F bit is set.
//
if ( ClientSession != NULL ) {
if ( (ClientSession->CsTrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) == 0 ) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidate: %wZ\\%wZ: trusted forest '%wZ' doesn't have F bit set.\n",
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&RealDomainNameString ));
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
if (ClientSession->CsFlags & CS_DOMAIN_IN_FOREST) {
NlPrintCs((NL_CRITICAL, ClientSession,
"NlpUserValidate: %wZ\\%wZ: trusted forest '%wZ' is in my forest\n",
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&RealDomainNameString ));
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
} else {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NlpUserValidate: %wZ\\%wZ: Can't find trusted forest '%wZ'\n",
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&RealDomainNameString ));
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
//
// Fill in the account name and domain name and try again
//
} else if ( NT_SUCCESS(Status) ) {
PNETLOGON_LOGON_IDENTITY_INFO NewLogonInfo = NULL;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfoToUse;
//
// If we found the real account name,
// and it is in the local forest,
// allocate a copy of the logon info to put the new information in.
//
// Some pointers will continue to point to the old buffer.
//
if ( RealSamAccountName != NULL &&
RealDomainName != NULL &&
(RealExtraFlags & NL_EXFLAGS_EXPEDITE_TO_ROOT) == 0 ) {
ULONG BytesToCopy;
ULONG ExtraParameterControl = 0;
UNICODE_STRING UserNameToUse;
//
// By default, update the logon info to contain the RealSamAccountName
//
RtlInitUnicodeString( &UserNameToUse,
RealSamAccountName );
//
// Determine the buffer size based on LogonLevel
//
switch ( LogonLevel ) {
case NetlogonInteractiveInformation:
case NetlogonInteractiveTransitiveInformation:
BytesToCopy = sizeof(NETLOGON_INTERACTIVE_INFO);break;
case NetlogonNetworkInformation:
case NetlogonNetworkTransitiveInformation: {
PNETLOGON_NETWORK_INFO NetworkLogonInfo = (PNETLOGON_NETWORK_INFO) LogonInfo;
BytesToCopy = sizeof(NETLOGON_NETWORK_INFO);
//
// Handle NTLM3 specially.
//
// Be conservative. The trick described below causes the
// authentication to fail if the account domain
// doesn't recognize the MSV1_0_USE_DOMAIN_FOR_ROUTING_ONLY bit
// and the passed in account name is a UPN. For NTLM3,
// the authentication is already broken in that case. However,
// older versions of NTLM work in that case today. So we want
// to avoid the trick for older versions of NTLM.
//
// The unhandled case is for the subset of NTLM3 calls
// not detected below. Those clients are those where
// NtChallengeResponse.Length is 0 and the response in
// LmChallengeResponse is NTLM3. We think only WIN 9x and RIS
// generate such logons. Neither support UPNs.
//
if ( NetworkLogonInfo->NtChallengeResponse.Length >= MSV1_0_NTLM3_MIN_NT_RESPONSE_LENGTH &&
NetworkLogonInfo->LmChallengeResponse.Length == NT_RESPONSE_LENGTH && // avoid down level altogether?
NetworkLogonInfo->LmChallengeResponse.Length != (NetworkLogonInfo->NtChallengeResponse.Length / 2)) { // Avoid clear text passwords
//
// NTLM3 includes the UserName and DomainName in the response hash.
// So we can't change permanently change the DomainName or username
// Set a bit in the parameter control to tell the account domain to set
// the DomainName back to NULL.
//
if ( LogonInfo->LogonDomainName.Length == 0 ) {
ExtraParameterControl |= MSV1_0_USE_DOMAIN_FOR_ROUTING_ONLY;
//
// .. and retain the original user name
//
UserNameToUse = LogonInfo->UserName;
}
}
break;
}
case NetlogonServiceInformation:
case NetlogonServiceTransitiveInformation:
BytesToCopy = sizeof(NETLOGON_SERVICE_INFO);break;
default:
*Authoritative = FALSE;
DefaultStatus = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
NewLogonInfo = LocalAlloc( 0, BytesToCopy );
if ( NewLogonInfo == NULL ) {
*Authoritative = FALSE;
DefaultStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory( NewLogonInfo, LogonInfo, BytesToCopy );
LogonInfoToUse = NewLogonInfo;
//
// Put the newly found domain name and user name
// into the NewLogonInfo.
//
RtlInitUnicodeString( &NewLogonInfo->LogonDomainName,
RealDomainName );
NewLogonInfo->UserName = UserNameToUse;
NewLogonInfo->ParameterControl |= ExtraParameterControl;
//
// Otherwise, continue with the current account name
//
} else {
LogonInfoToUse = LogonInfo;
}
//
// Call this routine again now that we have better information.
//
DefaultStatus = NlpUserValidate(
DomainInfo,
SecureChannelType,
ComputerAccountRid,
LogonLevel,
(LPBYTE)LogonInfoToUse,
ValidationLevel,
ValidationInformation,
Authoritative,
&RealExtraFlags,
TRUE ); // A recursive call
if ( NewLogonInfo != NULL ) {
LocalFree( NewLogonInfo );
}
// Don't let this routine try again
AccountsToTry = 0;
goto Cleanup;
}
}
//
// If a trusted domain was determined,
// pass the logon request to the trusted domain.
//
if ( ClientSession != NULL ) {
//
// If this request was passed to us from a trusted domain,
// Check to see if it is OK to pass the request further.
//
if ( IsDomainSecureChannelType( SecureChannelType ) ) {
//
// If the trust isn't an NT 5.0 trust,
// avoid doing the trust transitively.
//
LOCK_TRUST_LIST( DomainInfo );
if ( (ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST ) == 0 ) {
UNLOCK_TRUST_LIST( DomainInfo );
NlPrintCs((NL_LOGON, ClientSession,
"SamLogon: Avoid transitive trust on NT 4 trust." ));
DefaultStatus = STATUS_NO_SUCH_USER;
goto Cleanup;
}
UNLOCK_TRUST_LIST( DomainInfo );
}
Status = NlpUserValidateHigher(
ClientSession,
TransitiveUsed,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
&RealExtraFlags );
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
//
// return more appropriate error
//
if( (Status == STATUS_NO_TRUST_LSA_SECRET) ||
(Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
(Status == STATUS_ACCESS_DENIED) ) {
Status = STATUS_TRUSTED_DOMAIN_FAILURE;
}
//
// Since the request is explicitly for a trusted domain,
// The STATUS_NO_SUCH_USER answer is authoritative.
//
if ( Status == STATUS_NO_SUCH_USER ) {
*Authoritative = TRUE;
}
//
// If the trusted domain authoritatively handled the
// logon attempt, just return.
//
if ( *Authoritative ) {
DefaultStatus = Status;
goto Cleanup;
}
DefaultStatus = Status;
}
}
}
//
// We have no authoritative answer from a higher authority and
// DefaultStatus is the higher authority's response.
//
NlAssert( ! *Authoritative );
Cleanup:
NlAssert( !NT_SUCCESS(DefaultStatus) || DefaultStatus == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(DefaultStatus) || *ValidationInformation != NULL );
//
// Dereference any client session
//
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
ClientSession = NULL;
}
//
// If this is a network logon and this call is non-passthru,
// Try one last time to log on.
//
if ( (LogonLevel == NetlogonNetworkInformation ||
LogonLevel == NetlogonNetworkTransitiveInformation ) &&
SecureChannelType == MsvApSecureChannel ) {
//
// If the only reason we can't log the user on is that he has
// no user account, logging him on as guest is OK.
//
// There are actaully two cases here:
// * If the response is Authoritative, then the specified domain
// is trusted but the user has no account in the domain.
//
// * If the response in non-authoritative, then the specified domain
// is an untrusted domain.
//
// In either case, then right thing to do is to try the guest account.
//
if ( DefaultStatus != STATUS_NO_SUCH_USER &&
DefaultStatus != STATUS_ACCOUNT_DISABLED ) {
AccountsToTry &= ~MSVSAM_GUEST;
}
//
// If this is not an authoritative response,
// then the domain specified isn't a trusted domain.
// try the specified account name too.
//
// The specified account name will probably be a remote account
// with the same username and password.
//
if ( *Authoritative ) {
AccountsToTry &= ~MSVSAM_SPECIFIED;
}
//
// Validate against the Local Sam database.
//
if ( AccountsToTry != 0 ) {
BOOLEAN TempAuthoritative;
Status = NlpSamValidate(
DomainInfo->DomSamAccountDomainHandle,
TRUE, // UasCompatibilityMode,
SecureChannelType,
&DomainInfo->DomUnicodeComputerNameString,
&DomainInfo->DomUnicodeAccountDomainNameString,
DomainInfo->DomAccountDomainId,
LogonLevel,
LogonInformation,
ValidationLevel,
(PVOID *)ValidationInformation,
&TempAuthoritative,
&BadPasswordCountZeroed,
AccountsToTry );
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || *ValidationInformation != NULL );
//
// If this is a BDC and we zeroed the BadPasswordCount field,
// allow the PDC to do the same thing.
//
if ( BadPasswordCountZeroed ) {
NlpZeroBadPasswordCountOnPdc ( DomainInfo, LogonLevel, LogonInformation );
}
//
// If the local SAM database authoritatively handled the
// logon attempt,
// just return.
//
if ( TempAuthoritative ) {
DefaultStatus = Status;
*Authoritative = TRUE;
//
// If the problem is just that the password is wrong,
// try again on the PDC where the password may already be
// changed.
//
if ( BAD_PASSWORD(Status) ) {
Status = NlpUserValidateOnPdc (
DomainInfo,
LogonLevel,
LogonInformation,
ValidationLevel,
TRUE, // use negative cache of failed user logons
ValidationInformation,
&TempAuthoritative );
// Ignore failures from the PDC (except where it has newer information)
if ( NT_SUCCESS(Status) || BAD_PASSWORD(Status) ) {
DefaultStatus = Status;
*Authoritative = TempAuthoritative;
}
// If appropriate, zero bad password locally on this BDC.
// Ignore error as it's not critical operation.
if ( (NT_SUCCESS(Status) || ZERO_BAD_PWD_COUNT(Status)) &&
!NlGlobalMemberWorkstation ) {
NlpZeroBadPasswordCountLocally( DomainInfo, &LogonInfo->UserName );
}
}
//
// If the result of local validation or validation on
// the PDC is anything other than the bad password status,
// remove this user from the bad password negative cache
//
if ( !BAD_PASSWORD(DefaultStatus) ) {
NlpRemoveBadPasswordCacheEntry( DomainInfo, LogonInformation );
}
//
// Here we must choose between the non-authoritative status in
// DefaultStatus and the non-authoritative status from the local
// SAM lookup. Use the one from the higher authority unless it
// isn't interesting.
//
} else {
if ( DefaultStatus == STATUS_NO_SUCH_USER ) {
DefaultStatus = Status;
}
}
}
}
if ( RealSamAccountName != NULL ) {
NetApiBufferFree( RealSamAccountName );
}
if ( RealDomainName != NULL ) {
NetApiBufferFree( RealDomainName );
}
//
// Add in the resource groups if the caller is expecting them -
// if this is the last domain controller on the authentication path
// before returning to the machine being logged on to.
//
// This will also perform the Other Organization check that determines
// whether the specified user can logon to the specified workstation.
//
if (((SecureChannelType == WorkstationSecureChannel) ||
(SecureChannelType == MsvApSecureChannel)) &&
(SecureChannelType != UasServerSecureChannel) &&
(ValidationLevel == NetlogonValidationSamInfo2 || ValidationLevel == NetlogonValidationSamInfo4 ) &&
!NlGlobalMemberWorkstation &&
NT_SUCCESS(DefaultStatus) &&
!Recursed ) { // do this only once
Status = NlpExpandResourceGroupMembership(
ValidationLevel,
(PNETLOGON_VALIDATION_SAM_INFO4 *) ValidationInformation,
DomainInfo,
ComputerAccountRid
);
if (!NT_SUCCESS(Status)) {
DefaultStatus = Status;
}
}
#ifdef ROGUE_DC
if ( NT_SUCCESS( Status )) {
NlpBuildRogueValidationInfo(
ValidationLevel,
(PNETLOGON_VALIDATION_SAM_INFO4 *)ValidationInformation
);
}
#endif
return DefaultStatus;
}
#if NETLOGONDBG
VOID
NlPrintLogonParameters(
IN PDOMAIN_INFO DomainInfo OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN ULONG ExtraFlags,
IN PULONG NtStatusPointer OPTIONAL
)
/*++
Routine Description:
Prints the parameters to NlpLogonSamLogon.
Arguments:
Same as NlpLogonSamLogon except:
NtStatusPointer - If NULL, call is being entered.
If not NULL, points to the return status of the API.
Return Value:
None
--*/
{
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
//
// Print the entire text on a single line
//
EnterCriticalSection( &NlGlobalLogFileCritSect );
//
// Print the common information
//
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO) LogonInformation->LogonInteractive;
NlPrintDom(( NL_LOGON, DomainInfo,
"SamLogon: %s logon of %wZ\\%wZ from %wZ",
NlpLogonTypeToText( LogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation ));
//
// Print the computer name
//
if ( ComputerName != NULL ) {
NlPrint(( NL_LOGON, " (via %ws%)", ComputerName ));
}
//
// Print the PackageName
//
if ( LogonLevel == NetlogonGenericInformation ) {
NlPrint(( NL_LOGON, " Package:%wZ", &LogonInformation->LogonGeneric->PackageName ));
}
//
// Print the ExtraFlags
//
if ( ExtraFlags != 0 ) {
NlPrint(( NL_LOGON, " ExFlags:%lx", ExtraFlags ));
}
//
// Print the status code
//
if ( NtStatusPointer == NULL ) {
NlPrint(( NL_LOGON, " Entered\n" ));
} else {
NlPrint(( NL_LOGON, " Returns 0x%lX\n", *NtStatusPointer ));
}
LeaveCriticalSection( &NlGlobalLogFileCritSect );
}
#else // NETLOGONDBG
#define NlPrintLogonParameters(_a, _b, _c, _d, _e, _f )
#endif // NETLOGONDBG
NTSTATUS
NlpLogonSamLogon (
IN handle_t ContextHandle OPTIONAL,
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PNETLOGON_VALIDATION ValidationInformation,
OUT PBOOLEAN Authoritative,
IN OUT PULONG ExtraFlags,
IN BOOL InProcessCall
)
/*++
Routine Description:
This function is called by an NT client to process an interactive or
network logon. This function passes a domain name, user name and
credentials to the Netlogon service and returns information needed to
build a token. It is called in three instances:
* It is called by the LSA's MSV1_0 authentication package for any
NT DC. The MSV1_0 authentication
package calls SAM directly on workstations. In this
case, this function is a local function and requires the caller
to have SE_TCB privilege. The local Netlogon service will
either handle this request directly (validating the request with
the local SAM database) or will forward this request to the
appropriate domain controller as documented in sections 2.4 and
2.5.
* It is called by a Netlogon service on a workstation to a DC in
the Primary Domain of the workstation as documented in section
2.4. In this case, this function uses a secure channel set up
between the two Netlogon services.
* It is called by a Netlogon service on a DC to a DC in a trusted
domain as documented in section 2.5. In this case, this
function uses a secure channel set up between the two Netlogon
services.
The Netlogon service validates the specified credentials. If they
are valid, adds an entry for this LogonId, UserName, and Workstation
into the logon session table. The entry is added to the logon
session table only in the domain defining the specified user's
account.
This service is also used to process a re-logon request.
Arguments:
LogonServer -- Supplies the name of the logon server to process
this logon request. This field should be null to indicate
this is a call from the MSV1_0 authentication package to the
local Netlogon service.
ComputerName -- Name of the machine making the call. This field
should be null to indicate this is a call from the MSV1_0
authentication package to the local Netlogon service.
Authenticator -- supplied by the client. This field should be
null to indicate this is a call from the MSV1_0
authentication package to the local Netlogon service.
ReturnAuthenticator -- Receives an authenticator returned by the
server. This field should be null to indicate this is a call
from the MSV1_0 authentication package to the local Netlogon
service.
LogonLevel -- Specifies the level of information given in
LogonInformation.
LogonInformation -- Specifies the description for the user
logging on.
ValidationLevel -- Specifies the level of information returned in
ValidationInformation. Must be NetlogonValidationSamInfo,
NetlogonValidationSamInfo2 or NetlogonValidationSamInfo4.
ValidationInformation -- Returns the requested validation
information. This buffer must be freed using MIDL_user_free.
Authoritative -- Returns whether the status returned is an
authoritative status which should be returned to the original
caller. If not, this logon request may be tried again on another
domain controller. This parameter is returned regardless of the
status code.
ExtraFlags -- Accepts and returns a DWORD to the caller.
The DWORD contains NL_EXFLAGS_* values.
InProcessCall - TRUE if the call is done in process (from msv1_0).
FALSE otherwise.
Return Value:
STATUS_SUCCESS: if there was no error.
STATUS_NO_LOGON_SERVERS -- no domain controller in the requested
domain is currently available to validate the logon request.
STATUS_NO_TRUST_LSA_SECRET -- there is no secret account in the
local LSA database to establish a secure channel to a DC.
STATUS_TRUSTED_DOMAIN_FAILURE -- the secure channel setup between
the domain controllers of the trust domains to pass-through
validate the logon request failed.
STATUS_TRUSTED_RELATIONSHIP_FAILURE -- the secure channel setup
between the workstation and the DC failed.
STATUS_INVALID_INFO_CLASS -- Either LogonLevel or ValidationLevel is
invalid.
STATUS_INVALID_PARAMETER -- Another Parameter is invalid.
STATUS_ACCESS_DENIED -- The caller does not have access to call this
API.
STATUS_NO_SUCH_USER -- Indicates that the user specified in
LogonInformation does not exist. This status should not be returned
to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
STATUS_WRONG_PASSWORD -- Indicates that the password information in
LogonInformation was incorrect. This status should not be returned
to the originally caller. It should be mapped to STATUS_LOGON_FAILURE.
STATUS_INVALID_LOGON_HOURES -- The user is not authorized to logon
at this time.
STATUS_INVALID_WORKSTATION -- The user is not authorized to logon
from the specified workstation.
STATUS_PASSWORD_EXPIRED -- The password for the user has expired.
STATUS_ACCOUNT_DISABLED -- The user's account has been disabled.
.
.
.
--*/
{
NTSTATUS Status;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
PDOMAIN_INFO DomainInfo = NULL;
PSERVER_SESSION ServerSession;
ULONG ServerSessionRid = 0;
NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType;
SESSION_INFO SessionInfo;
NETLOGON_LOGON_INFO_CLASS OrigLogonLevel = LogonLevel;
//
// Initialization
//
*Authoritative = TRUE;
ValidationInformation->ValidationSam = NULL;
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
LogonInformation->LogonInteractive;
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
if ( !NlStartNetlogonCall() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
//
// Check if LogonInfo is valid. It should be, otherwise it is
// an unappropriate use of this function.
//
if ( LogonInfo == NULL ) {
return STATUS_INVALID_PARAMETER;
}
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( LogonServer );
IF_NL_DEBUG( LOGON ) {
NlPrintLogonParameters( DomainInfo, ComputerName, OrigLogonLevel, LogonInformation, *ExtraFlags, NULL );
}
if ( DomainInfo == NULL ) {
Status = STATUS_INVALID_COMPUTER_NAME;
goto Cleanup;
}
//
// Check the ValidationLevel
//
switch (ValidationLevel) {
case NetlogonValidationSamInfo:
case NetlogonValidationSamInfo2:
case NetlogonValidationSamInfo4:
case NetlogonValidationGenericInfo:
case NetlogonValidationGenericInfo2:
break;
default:
*Authoritative = TRUE;
Status = STATUS_INVALID_INFO_CLASS;
goto Cleanup;
}
//
// Check the LogonLevel
//
switch ( LogonLevel ) {
case NetlogonInteractiveInformation:
case NetlogonInteractiveTransitiveInformation:
case NetlogonNetworkInformation:
case NetlogonNetworkTransitiveInformation:
case NetlogonServiceInformation:
case NetlogonServiceTransitiveInformation:
//
// Check that the ValidationLevel is consistent with the LogonLevel
//
switch (ValidationLevel) {
case NetlogonValidationSamInfo:
case NetlogonValidationSamInfo2:
case NetlogonValidationSamInfo4:
break;
default:
*Authoritative = TRUE;
Status = STATUS_INVALID_INFO_CLASS;
goto Cleanup;
}
break;
case NetlogonGenericInformation:
//
// Check that the ValidationLevel is consistent with the LogonLevel
//
switch (ValidationLevel) {
case NetlogonValidationGenericInfo:
case NetlogonValidationGenericInfo2:
break;
default:
*Authoritative = TRUE;
Status = STATUS_INVALID_INFO_CLASS;
goto Cleanup;
}
break;
default:
*Authoritative = TRUE;
Status = STATUS_INVALID_INFO_CLASS;
goto Cleanup;
}
//
// If we're being called from the MSV Authentication Package,
// require SE_TCB privilege.
//
if ( InProcessCall ) {
//
// ??: Do as I said
//
SecureChannelType = MsvApSecureChannel;
SessionInfo.NegotiatedFlags = NETLOGON_SUPPORTS_MASK;
ServerSessionRid = DomainInfo->DomDcComputerAccountRid;
//
// If we're being called from another Netlogon Server,
// Verify the secure channel information.
//
} else {
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
Status = STATUS_NOT_SUPPORTED;
goto Cleanup;
}
//
// Arguments are no longer optional.
//
// Either the authenticators must be present or the Context handle must be.
//
if ( LogonServer == NULL ||
ComputerName == NULL ||
(( Authenticator == NULL || ReturnAuthenticator == NULL ) &&
ContextHandle == NULL ) ) {
*Authoritative = TRUE;
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Find the server session entry for this session.
//
LOCK_SERVER_SESSION_TABLE( DomainInfo );
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
if (ServerSession == NULL) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
*Authoritative = FALSE;
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
//
// now verify the Authenticator and update seed if OK
//
if ( Authenticator != NULL ) {
Status = NlCheckAuthenticator( ServerSession,
Authenticator,
ReturnAuthenticator);
if ( !NT_SUCCESS(Status) ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
*Authoritative = FALSE;
goto Cleanup;
}
//
// If no authenticator,
// ensure the caller used secure RPC.
//
} else {
NET_API_STATUS NetStatus;
ULONG AuthnLevel;
ULONG AuthnSvc;
//
// Determine the client binding info.
//
// Don't ask for RPC priviledges (second argument).
// If you ever need to, support SECPKG_ATTR_DCE_INFO
// in QueryContextAttributesW as this is what
// RpcBindingInqAuthClient will query for.
//
NetStatus = RpcBindingInqAuthClient(
ContextHandle,
NULL, // Priviledges not needed
NULL, // SPN not needed
&AuthnLevel,
&AuthnSvc,
NULL );
if ( NetStatus != NO_ERROR ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
*Authoritative = FALSE;
NlPrintDom((NL_CRITICAL, DomainInfo,
"SamLogon: %s logon of %wZ\\%wZ from %wZ: Cannot RpcBindingInqAuthClient %ld\n",
NlpLogonTypeToText( OrigLogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation,
NetStatus ));
Status = NetpApiStatusToNtStatus( NetStatus );
goto Cleanup;
}
//
// Ensure we're using the netlogon SSPI and
// are signing or sealing.
//
if ( AuthnSvc != RPC_C_AUTHN_NETLOGON ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
NlPrintDom((NL_CRITICAL, DomainInfo,
"SamLogon: %s logon of %wZ\\%wZ from %wZ: Not using Netlogon SSPI: %ld\n",
NlpLogonTypeToText( OrigLogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation,
AuthnSvc ));
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
if ( AuthnLevel != RPC_C_AUTHN_LEVEL_PKT_PRIVACY &&
AuthnLevel != RPC_C_AUTHN_LEVEL_PKT_INTEGRITY ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
NlPrintDom((NL_CRITICAL, DomainInfo,
"SamLogon: %s logon of %wZ\\%wZ from %wZ: Not signing or sealing: %ld\n",
NlpLogonTypeToText( OrigLogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation,
AuthnLevel ));
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
}
SecureChannelType = ServerSession->SsSecureChannelType;
SessionInfo.SessionKey = ServerSession->SsSessionKey;
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
ServerSessionRid = ServerSession->SsAccountRid;
//
// The cross forest hop bit is only valid if this TDO on FOREST_TRANSITIVE trusts
//
if ( ((*ExtraFlags) & NL_EXFLAGS_CROSS_FOREST_HOP) != 0 &&
(ServerSession->SsFlags & SS_FOREST_TRANSITIVE) == 0 ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"NlpLogonSamLogon: %ws failed because F bit isn't set on the TDO\n",
ComputerName ));
*Authoritative = TRUE;
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
}
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
//
// Decrypt the password information
//
NlpDecryptLogonInformation ( LogonLevel, (LPBYTE) LogonInfo, &SessionInfo );
}
#ifdef _DC_NETLOGON
//
// If the logon service is paused then don't process this logon
// request any further.
//
if ( NlGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED ||
!NlGlobalParameters.SysVolReady ||
NlGlobalDsPaused ) {
//
// Don't reject logons originating inside this
// machine. Such requests aren't really pass-thru requests.
//
// Don't reject logons from a BDC in our own domain. These logons
// support account lockout and authentication of users whose password
// has been updated on the PDC but not the BDC. Such pass-thru
// requests can only be handled by the PDC of the domain.
//
if ( SecureChannelType != MsvApSecureChannel &&
SecureChannelType != ServerSecureChannel ) {
//
// Return STATUS_ACCESS_DENIED to convince the caller to drop the
// secure channel to this logon server and reconnect to some other
// logon server.
//
*Authoritative = FALSE;
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
}
#endif // _DC_NETLOGON
//
// If this is a workstation or MSV secure channel,
// and the caller isn't NT 4 in a mixed mode domain
// ask the DC to do transitive trust.
//
// For NT 4 in mixed mode, avoid transitive trust since that's what they'd
// get if they'd stumbled upon an NT 4 BDC.
// For NT 4 in native mode, give NT 4 the full capability.
// For NT 5 in mixed mode, we "prefer" an NT 5 DC so do transitive trust to
// be as compatible with kerberos as possible.
//
if ( (SecureChannelType == MsvApSecureChannel || SecureChannelType == WorkstationSecureChannel) &&
!((SessionInfo.NegotiatedFlags & ~NETLOGON_SUPPORTS_NT4_MASK) == 0 &&
SamIMixedDomain( DomainInfo->DomSamServerHandle ) ) ) {
switch (LogonLevel ) {
case NetlogonInteractiveInformation:
LogonLevel = NetlogonInteractiveTransitiveInformation; break;
case NetlogonServiceInformation:
LogonLevel = NetlogonServiceTransitiveInformation; break;
case NetlogonNetworkInformation:
LogonLevel = NetlogonNetworkTransitiveInformation; break;
}
}
//
// Validate the Request.
//
Status = NlpUserValidate( DomainInfo,
SecureChannelType,
ServerSessionRid,
LogonLevel,
(LPBYTE) LogonInfo,
ValidationLevel,
(LPBYTE *)&ValidationInformation->ValidationSam,
Authoritative,
ExtraFlags,
FALSE ); // Not a recursive call
if ( !NT_SUCCESS(Status) ) {
//
// If this is an NT 3.1 client,
// map NT 3.5 status codes to their NT 3.1 equivalents.
//
// The NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT bit is really the wrong bit
// to be using, but all NT3.5 clients have it set and all NT3.1 clients
// don't, so it'll work for our purposes.
//
if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_ACCOUNT_LOCKOUT) == 0 ) {
switch ( Status ) {
case STATUS_PASSWORD_MUST_CHANGE:
Status = STATUS_PASSWORD_EXPIRED;
break;
case STATUS_ACCOUNT_LOCKED_OUT:
Status = STATUS_ACCOUNT_DISABLED;
break;
}
}
//
// STATUS_AUTHENTICATION_FIREWALL_FAILED was introduced for .NET (Whistler server).
// If this is an older client (XP (Whistler client) or older), return a generic
// STATUS_NO_SUCH_USER.
//
// Note that this code is currently not quite working because there have been
// no new negotiated flag added for .NET so far. So, XP client may still get
// the new status code. If a new .NET flag is added, this problem will be solved.
// Otherwise, if this is a critical issue, it can be solved by adding a new
// negotiated flag in .NET just for this (seems like a overkill, though).
//
if ( Status == STATUS_AUTHENTICATION_FIREWALL_FAILED ) {
if ( (SessionInfo.NegotiatedFlags & ~NETLOGON_SUPPORTS_XP_MASK) == 0 ) {
Status = STATUS_NO_SUCH_USER;
}
}
goto Cleanup;
}
NlAssert( !NT_SUCCESS(Status) || Status == STATUS_SUCCESS );
NlAssert( !NT_SUCCESS(Status) || ValidationInformation->ValidationSam != NULL );
//
// If the validation information is being returned to a client on another
// machine, encrypt it before sending it over the wire.
//
if ( SecureChannelType != MsvApSecureChannel ) {
NlpEncryptValidationInformation (
LogonLevel,
ValidationLevel,
*((LPBYTE *) ValidationInformation),
&SessionInfo );
}
Status = STATUS_SUCCESS;
//
// Cleanup up before returning.
//
Cleanup:
if ( !NT_SUCCESS(Status) ) {
if (ValidationInformation->ValidationSam != NULL) {
MIDL_user_free( ValidationInformation->ValidationSam );
ValidationInformation->ValidationSam = NULL;
}
}
IF_NL_DEBUG( LOGON ) {
NlPrintLogonParameters( DomainInfo, ComputerName, OrigLogonLevel, LogonInformation, *ExtraFlags, &Status );
}
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return Status;
}
NTSTATUS
NetrLogonSamLogon (
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PNETLOGON_VALIDATION ValidationInformation,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
Non_concurrent implementation of NTLM passthrough logon API.
Arguments:
See NlpLogonSamLogon.
Return Value:
See NlpLogonSamLogon.
--*/
{
ULONG ExtraFlags = 0;
return NlpLogonSamLogon( NULL, // No ContextHandle,
LogonServer,
ComputerName,
Authenticator,
ReturnAuthenticator,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
&ExtraFlags,
FALSE ); // in-proc call?
}
NTSTATUS
NetILogonSamLogon (
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PNETLOGON_VALIDATION ValidationInformation,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
In-process version of NetrLogonSamLogon
Arguments:
See NlpLogonSamLogon.
Return Value:
See NlpLogonSamLogon.
--*/
{
ULONG ExtraFlags = 0;
return NlpLogonSamLogon( NULL, // No ContextHandle,
LogonServer,
ComputerName,
Authenticator,
ReturnAuthenticator,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
&ExtraFlags,
TRUE ); // in-proc call?
}
NTSTATUS
NetrLogonSamLogonWithFlags (
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PNETLOGON_VALIDATION ValidationInformation,
OUT PBOOLEAN Authoritative,
IN OUT PULONG ExtraFlags
)
/*++
Routine Description:
Non_concurrent implementation of NTLM passthrough logon API (with flags)
Arguments:
See NlpLogonSamLogon.
Return Value:
See NlpLogonSamLogon.
--*/
{
return NlpLogonSamLogon( NULL, // No ContextHandle,
LogonServer,
ComputerName,
Authenticator,
ReturnAuthenticator,
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
ExtraFlags,
FALSE ); // in-proc call?
}
NTSTATUS
NetrLogonSamLogonEx (
IN handle_t ContextHandle,
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation,
IN NETLOGON_VALIDATION_INFO_CLASS ValidationLevel,
OUT PNETLOGON_VALIDATION ValidationInformation,
OUT PBOOLEAN Authoritative,
IN OUT PULONG ExtraFlags
)
/*++
Routine Description:
Concurrent implementation of NTLM passthrough logon API.
Arguments:
See NlpLogonSamLogon.
Return Value:
See NlpLogonSamLogon.
--*/
{
//
// Sanity check to ensure we don't lead the common routine astray.
//
if ( ContextHandle == NULL ) {
return STATUS_ACCESS_DENIED;
}
return NlpLogonSamLogon( ContextHandle,
LogonServer,
ComputerName,
NULL, // Authenticator
NULL, // ReturnAuthenticator
LogonLevel,
LogonInformation,
ValidationLevel,
ValidationInformation,
Authoritative,
ExtraFlags,
FALSE ); // in-proc call?
}
NTSTATUS
NetrLogonSamLogoff (
IN LPWSTR LogonServer OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator OPTIONAL,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator OPTIONAL,
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PNETLOGON_LEVEL LogonInformation
)
/*++
Routine Description:
This function is called by an NT client to process an interactive
logoff. It is not called for the network logoff case since the
Netlogon service does not maintain any context for network logons.
This function does the following. It authenticates the request. It
updates the logon statistics in the SAM database on whichever machine
or domain defines this user account. It updates the logon session
table in the primary domain of the machine making the request. And
it returns logoff information to the caller.
This function is called in same scenarios that I_NetLogonSamLogon is
called:
* It is called by the LSA's MSV1_0 authentication package to
support LsaApLogonTerminated. In this case, this function is a
local function and requires the caller to have SE_TCB privilege.
The local Netlogon service will either handle this request
directly (if LogonDomainName indicates this request was
validated locally) or will forward this request to the
appropriate domain controller as documented in sections 2.4 and
2.5.
* It is called by a Netlogon service on a workstation to a DC in
the Primary Domain of the workstation as documented in section
2.4. In this case, this function uses a secure channel set up
between the two Netlogon services.
* It is called by a Netlogon service on a DC to a DC in a trusted
domain as documented in section 2.5. In this case, this
function uses a secure channel set up between the two Netlogon
services.
When this function is a remote function, it is sent to the DC over a
NULL session.
Arguments:
LogonServer -- Supplies the name of the logon server which logged
this user on. This field should be null to indicate this is
a call from the MSV1_0 authentication package to the local
Netlogon service.
ComputerName -- Name of the machine making the call. This field
should be null to indicate this is a call from the MSV1_0
authentication package to the local Netlogon service.
Authenticator -- supplied by the client. This field should be
null to indicate this is a call from the MSV1_0
authentication package to the local Netlogon service.
ReturnAuthenticator -- Receives an authenticator returned by the
server. This field should be null to indicate this is a call
from the MSV1_0 authentication package to the local Netlogon
service.
LogonLevel -- Specifies the level of information given in
LogonInformation.
LogonInformation -- Specifies the logon domain name, logon Id,
user name and workstation name of the user logging off.
Return Value:
--*/
{
NTSTATUS Status;
PNETLOGON_LOGON_IDENTITY_INFO LogonInfo;
PDOMAIN_INFO DomainInfo = NULL;
#ifdef _DC_NETLOGON
PSERVER_SESSION ServerSession;
#endif // _DC_NETLOGON
NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType;
PCLIENT_SESSION ClientSession;
//
// Initialization
//
LogonInfo = (PNETLOGON_LOGON_IDENTITY_INFO)
LogonInformation->LogonInteractive;
//
// Check if LogonInfo is valid. It should be, otherwise it is
// an unappropriate use of this function.
//
if ( LogonInfo == NULL ) {
return STATUS_INVALID_PARAMETER;
}
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
if ( !NlStartNetlogonCall() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( LogonServer );
#if NETLOGONDBG
NlPrintDom((NL_LOGON, DomainInfo,
"NetrLogonSamLogoff: %s logoff of %wZ\\%wZ from %wZ Entered\n",
NlpLogonTypeToText( LogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation ));
#endif // NETLOGONDBG
if ( DomainInfo == NULL ) {
Status = STATUS_INVALID_COMPUTER_NAME;
goto Cleanup;
}
//
// Check the LogonLevel
//
if ( LogonLevel != NetlogonInteractiveInformation ) {
Status = STATUS_INVALID_INFO_CLASS;
goto Cleanup;
}
//
// Sanity check the username and domain name.
//
if ( LogonInfo->UserName.Length == 0 ||
LogonInfo->UserName.Buffer == NULL ||
LogonInfo->LogonDomainName.Length == 0 ||
LogonInfo->LogonDomainName.Buffer == NULL ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// If we've been called from the local msv1_0,
// special case the secure channel type.
//
if ( LogonServer == NULL &&
ComputerName == NULL &&
Authenticator == NULL &&
ReturnAuthenticator == NULL ) {
//
// msv1_0 no longer calls this routine, so
// disable this code path.
//
// SecureChannelType = MsvApSecureChannel;
//
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
//
// If we're being called from another Netlogon Server,
// Verify the secure channel information.
//
} else {
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
Status = STATUS_NOT_SUPPORTED;
goto Cleanup;
}
//
// Arguments are no longer optional.
//
if ( LogonServer == NULL ||
ComputerName == NULL ||
Authenticator == NULL ||
ReturnAuthenticator == NULL ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Find the server session entry for this secure channel.
//
LOCK_SERVER_SESSION_TABLE( DomainInfo );
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
if (ServerSession == NULL) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
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( DomainInfo );
goto Cleanup;
}
SecureChannelType = ServerSession->SsSecureChannelType;
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
}
//
// If this is the domain that logged this user on,
// update the logon statistics.
//
if ( RtlEqualDomainName( &LogonInfo->LogonDomainName,
&DomainInfo->DomUnicodeAccountDomainNameString ) ) {
Status = MsvSamLogoff(
DomainInfo->DomSamAccountDomainHandle,
LogonLevel,
LogonInfo );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
//
// If this is not the domain that logged this user on,
// pass the request to a higher authority.
//
} else {
//
// If this machine is a workstation,
// send the request to the Primary Domain.
//
if ( NlGlobalMemberWorkstation ) {
ClientSession = NlRefDomClientSession( DomainInfo );
if ( ClientSession == NULL ) {
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
goto Cleanup;
}
Status = NlpUserLogoffHigher(
ClientSession,
LogonLevel,
(LPBYTE) LogonInfo );
NlUnrefClientSession( ClientSession );
//
// return more appropriate error
//
if( (Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
(Status == STATUS_ACCESS_DENIED) ) {
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
}
goto Cleanup;
//
// This machine is a Domain Controller.
//
// This request is either a pass-thru request by a workstation in
// our domain, or this request came directly from the MSV
// authentication package.
//
// In either case, pass the request to the trusted domain.
//
} else {
BOOLEAN TransitiveUsed;
//
// Send the request to the appropriate Trusted Domain.
//
// Find the ClientSession structure for the domain.
//
ClientSession =
NlFindNamedClientSession( DomainInfo,
&LogonInfo->LogonDomainName,
NL_RETURN_CLOSEST_HOP,
&TransitiveUsed );
if ( ClientSession == NULL ) {
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If this request was passed to us from a trusted domain,
// Check to see if it is OK to pass the request further.
//
if ( IsDomainSecureChannelType( SecureChannelType ) ) {
//
// If the trust isn't an NT 5.0 trust,
// avoid doing the trust transitively.
//
LOCK_TRUST_LIST( DomainInfo );
if ( (ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST ) == 0 ) {
UNLOCK_TRUST_LIST( DomainInfo );
NlPrintCs((NL_LOGON, ClientSession,
"SamLogoff: Avoid transitive trust on NT 4 trust." ));
NlUnrefClientSession( ClientSession );
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
}
UNLOCK_TRUST_LIST( DomainInfo );
}
Status = NlpUserLogoffHigher(
ClientSession,
LogonLevel,
(LPBYTE) LogonInfo );
NlUnrefClientSession( ClientSession );
//
// return more appropriate error
//
if( (Status == STATUS_NO_TRUST_LSA_SECRET) ||
(Status == STATUS_NO_TRUST_SAM_ACCOUNT) ||
(Status == STATUS_ACCESS_DENIED) ) {
Status = STATUS_TRUSTED_DOMAIN_FAILURE;
}
}
}
Cleanup:
//
// If the request failed, be carefull to not leak authentication
// information.
//
if ( Status == STATUS_ACCESS_DENIED ) {
if ( ReturnAuthenticator != NULL ) {
RtlSecureZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
}
}
#if NETLOGONDBG
NlPrintDom((NL_LOGON, DomainInfo,
"NetrLogonSamLogoff: %s logoff of %wZ\\%wZ from %wZ returns %lX\n",
NlpLogonTypeToText( LogonLevel ),
&LogonInfo->LogonDomainName,
&LogonInfo->UserName,
&LogonInfo->Workstation,
Status ));
#endif // NETLOGONDBG
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return Status;
}
NTSTATUS NET_API_FUNCTION
NetrLogonSendToSam (
IN LPWSTR PrimaryName OPTIONAL,
IN LPWSTR ComputerName OPTIONAL,
IN PNETLOGON_AUTHENTICATOR Authenticator,
OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
IN LPBYTE OpaqueBuffer,
IN ULONG OpaqueBufferSize
)
/*++
Routine Description:
This function sends an opaque buffer from SAM on a BDC to SAM on the PDC.
The original use of this routine will be to allow the BDC to forward user
account password changes to the PDC.
Arguments:
PrimaryName -- Computer name of the PDC to remote the call to.
ComputerName -- Name of the machine making the call.
Authenticator -- supplied by the client.
ReturnAuthenticator -- Receives an authenticator returned by the
server.
OpaqueBuffer - Buffer to be passed to the SAM service on the PDC.
The buffer will be encrypted on the wire.
OpaqueBufferSize - Size (in bytes) of OpaqueBuffer.
Return Value:
STATUS_SUCCESS: Message successfully sent to PDC
STATUS_NO_MEMORY: There is not enough memory to complete the operation
STATUS_NO_SUCH_DOMAIN: DomainName does not correspond to a hosted domain
STATUS_NO_LOGON_SERVERS: PDC is not currently available
STATUS_NOT_SUPPORTED: PDC does not support this operation
--*/
{
NTSTATUS Status;
PDOMAIN_INFO DomainInfo = NULL;
PSERVER_SESSION ServerSession;
SESSION_INFO SessionInfo;
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
return STATUS_NOT_SUPPORTED;
}
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( PrimaryName );
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"NetrLogonSendToSam: %ws: Entered\n",
ComputerName ));
if ( DomainInfo == NULL ) {
Status = STATUS_INVALID_COMPUTER_NAME;
goto Cleanup;
}
//
// This call is only allowed to a PDC.
//
if ( DomainInfo->DomRole != RolePrimary ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonSendToSam: Call only valid to a PDC.\n" ));
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
//
// Get the Session key for this session.
//
LOCK_SERVER_SESSION_TABLE( DomainInfo );
ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName );
if (ServerSession == NULL) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
SessionInfo.SessionKey = ServerSession->SsSessionKey;
SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags;
//
// now verify the Authenticator and update seed if OK
//
Status = NlCheckAuthenticator( ServerSession,
Authenticator,
ReturnAuthenticator);
if ( !NT_SUCCESS(Status) ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
goto Cleanup;
}
//
// Call is only allowed from a BDC.
//
if ( ServerSession->SsSecureChannelType != ServerSecureChannel ) {
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonSendToSam: Call only valid from a BDC.\n" ));
Status = STATUS_ACCESS_DENIED;
goto Cleanup;
}
UNLOCK_SERVER_SESSION_TABLE( DomainInfo );
//
// Decrypt the message before passing it to SAM
//
NlDecryptRC4( OpaqueBuffer,
OpaqueBufferSize,
&SessionInfo );
// #ifdef notdef
Status = SamISetPasswordInfoOnPdc(
DomainInfo->DomSamAccountDomainHandle,
OpaqueBuffer,
OpaqueBufferSize );
// #endif // notdef
if ( !NT_SUCCESS( Status )) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"NetrLogonSendToSam: Cannot NewCallToSam %lX\n",
Status));
goto Cleanup;
}
Status = STATUS_SUCCESS;
//
// Common exit point
//
Cleanup:
//
// If the request failed, be carefull to not leak authentication
// information.
//
if ( Status == STATUS_ACCESS_DENIED ) {
RtlSecureZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) );
}
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"NetrLogonSendToSam: %ws: returns 0x%lX\n",
ComputerName,
Status ));
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
return Status;
}
NTSTATUS
I_NetLogonSendToSamOnPdc(
IN LPWSTR DomainName,
IN LPBYTE OpaqueBuffer,
IN ULONG OpaqueBufferSize
)
/*++
Routine Description:
This function sends an opaque buffer from SAM on a BDC to SAM on the PDC of
the specified domain.
The original use of this routine will be to allow the BDC to forward user
account password changes to the PDC.
The function will not send any buffer from BDC to PDC which are on different
sites provided the registry value of AvoidPdcOnWan has been set to TRUE.
Arguments:
DomainName - Identifies the hosted domain that this request applies to.
DomainName may be the Netbios domain name or the DNS domain name.
NULL implies the primary domain hosted by this DC.
OpaqueBuffer - Buffer to be passed to the SAM service on the PDC.
The buffer will be encrypted on the wire.
OpaqueBufferSize - Size (in bytes) of OpaqueBuffer.
Return Value:
STATUS_SUCCESS: Message successfully sent to PDC
STATUS_NO_MEMORY: There is not enough memory to complete the operation
STATUS_NO_SUCH_DOMAIN: DomainName does not correspond to a hosted domain
STATUS_NO_LOGON_SERVERS: PDC is not currently available
STATUS_NOT_SUPPORTED: PDC does not support this operation
--*/
{
NTSTATUS Status;
NETLOGON_AUTHENTICATOR OurAuthenticator;
NETLOGON_AUTHENTICATOR ReturnAuthenticator;
PDOMAIN_INFO DomainInfo = NULL;
PCLIENT_SESSION ClientSession = NULL;
SESSION_INFO SessionInfo;
BOOLEAN FirstTry = TRUE;
BOOLEAN AmWriter = FALSE;
BOOLEAN IsSameSite;
LPBYTE EncryptedBuffer = NULL;
ULONG EncryptedBufferSize;
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
if ( !NlStartNetlogonCall() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"I_NetLogonSendToSamOnPdc: Sending buffer to PDC of %ws\n",
DomainName ));
NlpDumpBuffer( NL_SESSION_MORE, OpaqueBuffer, OpaqueBufferSize );
//
// Find the Hosted domain.
//
DomainInfo = NlFindDomain( DomainName, NULL, FALSE );
if ( DomainInfo == NULL ) {
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Ensure this is a BDC.
//
if ( DomainInfo->DomRole != RoleBackup ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: not allowed on PDC.\n"));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// If the registry value of AvoidPdcOnWan is TRUE and PDC is on a remote site,
// do not send anything and return with a success.
//
if ( NlGlobalParameters.AvoidPdcOnWan ) {
//
// Determine whether the PDC is in the same site
//
Status = SamISameSite( &IsSameSite );
if ( !NT_SUCCESS(Status) ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: Cannot SamISameSite.\n" ));
goto Cleanup;
}
if ( !IsSameSite ) {
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"I_NetLogonSendToSamOnPdc: Ignored sending to a PDC on a remote site.\n"));
Status = STATUS_SUCCESS;
goto Cleanup;
} else {
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"I_NetLogonSendToSamOnPdc: BDC and PDC are on the same site.\n"));
}
}
//
// Reference the client session.
//
ClientSession = NlRefDomClientSession( DomainInfo );
if ( ClientSession == NULL ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: This BDC has no client session with the PDC.\n"));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
//
// Become a Writer of the ClientSession.
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: Can't become writer of client session.\n"));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
AmWriter = TRUE;
//
// If the session isn't authenticated,
// do so now.
//
FirstTryFailed:
Status = NlEnsureSessionAuthenticated( ClientSession, 0 );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
SessionInfo.SessionKey = ClientSession->CsSessionKey;
SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags;
//
// If the PDC doesn't support the new function,
// fail now.
//
if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_PDC_PASSWORD) == 0 ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: %ws: PDC doesn't support this function.\n",
DomainName ));
Status = STATUS_NOT_SUPPORTED;
goto Cleanup;
}
//
// Build the Authenticator for this request to the PDC.
//
NlBuildAuthenticator(
&ClientSession->CsAuthenticationSeed,
&ClientSession->CsSessionKey,
&OurAuthenticator);
//
// Encrypt the data before we send it on the wire.
//
if ( EncryptedBuffer != NULL ) {
LocalFree( EncryptedBuffer );
EncryptedBuffer = NULL;
}
EncryptedBufferSize = OpaqueBufferSize;
EncryptedBuffer = LocalAlloc( 0, OpaqueBufferSize );
if ( EncryptedBuffer == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( EncryptedBuffer, OpaqueBuffer, OpaqueBufferSize );
NlEncryptRC4( EncryptedBuffer,
EncryptedBufferSize,
&SessionInfo );
//
// Change the password on the machine our connection is to.
//
NL_API_START( Status, ClientSession, TRUE ) {
NlAssert( ClientSession->CsUncServerName != NULL );
Status = I_NetLogonSendToSam( ClientSession->CsUncServerName,
ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer,
&OurAuthenticator,
&ReturnAuthenticator,
EncryptedBuffer,
EncryptedBufferSize );
if ( !NT_SUCCESS(Status) ) {
NlPrintRpcDebug( "I_NetLogonSendToSam", Status );
}
// NOTE: This call may drop the secure channel behind our back
} NL_API_ELSE( Status, ClientSession, TRUE ) {
} NL_API_END;
//
// Now verify primary's authenticator and update our seed
//
if ( NlpDidDcFail( Status ) ||
!NlUpdateSeed( &ClientSession->CsAuthenticationSeed,
&ReturnAuthenticator.Credential,
&ClientSession->CsSessionKey) ) {
NlPrintCs(( NL_CRITICAL, ClientSession,
"I_NetLogonSendToSamOnPdc: denying access after status: 0x%lx\n",
Status ));
//
// Preserve any status indicating a communication error.
//
if ( NT_SUCCESS(Status) ) {
Status = STATUS_ACCESS_DENIED;
}
NlSetStatusClientSession( ClientSession, Status );
//
// Perhaps the netlogon service on the server has just restarted.
// Try just once to set up a session to the server again.
//
if ( FirstTry ) {
FirstTry = FALSE;
goto FirstTryFailed;
}
}
//
// Common exit
//
Cleanup:
if ( ClientSession != NULL ) {
if ( AmWriter ) {
NlResetWriterClientSession( ClientSession );
}
NlUnrefClientSession( ClientSession );
}
if ( EncryptedBuffer != NULL ) {
LocalFree( EncryptedBuffer );
EncryptedBuffer = NULL;
}
if ( !NT_SUCCESS(Status) ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonSendToSamOnPdc: %ws: failed %lX\n",
DomainName,
Status));
}
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return Status;
}
NTSTATUS
I_NetLogonGetDirectDomain(
IN LPWSTR HostedDomainName,
IN LPWSTR TrustedDomainName,
OUT LPWSTR *DirectDomainName
)
/*++
Routine Description:
This function returns the name of a domain in the enterprise and returns
the name of a domain that is one hop closer.
Arguments:
HostedDomainName - Identifies the hosted domain that this request applies to.
DomainName may be the Netbios domain name or the DNS domain name.
NULL implies the primary domain hosted by this machine.
TrustedDomainName - Identifies the domain the trust relationship is to.
DomainName may be the Netbios domain name or the DNS domain name.
DirectDomainName - Returns the DNS domain name of the domain that is
one hop closer to TrustedDomainName. If there is a direct trust to
TrustedDomainName, NULL is returned.
The buffer must be freed using I_NetLogonFree.
Return Value:
STATUS_SUCCESS: The auth data was successfully returned.
STATUS_NO_MEMORY: There is not enough memory to complete the operation
STATUS_NETLOGON_NOT_STARTED: Netlogon is not running
STATUS_NO_SUCH_DOMAIN: HostedDomainName does not correspond to a hosted domain, OR
TrustedDomainName is not a trusted domain.
--*/
{
NTSTATUS Status;
PDOMAIN_INFO DomainInfo = NULL;
PCLIENT_SESSION ClientSession = NULL;
BOOLEAN TransitiveUsed;
UNICODE_STRING TrustedDomainNameString;
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
*DirectDomainName = NULL;
if ( !NlStartNetlogonCall() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
NlPrintDom((NL_SESSION_SETUP, DomainInfo,
"I_NetLogonDirectDomainName: %ws %ws\n",
HostedDomainName,
TrustedDomainName ));
//
// Find the Hosted domain.
//
DomainInfo = NlFindDomain( HostedDomainName, NULL, FALSE );
if ( DomainInfo == NULL ) {
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Reference the client session.
//
RtlInitUnicodeString( &TrustedDomainNameString, TrustedDomainName );
ClientSession = NlFindNamedClientSession( DomainInfo,
&TrustedDomainNameString,
NL_RETURN_CLOSEST_HOP,
&TransitiveUsed );
if ( ClientSession == NULL ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonDirectDomainName: %ws: No such trusted domain\n",
TrustedDomainName ));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If this is a transitive trust,
// return the name of the domain to the caller.
//
if ( TransitiveUsed ) {
LOCK_TRUST_LIST( DomainInfo );
if ( ClientSession->CsDnsDomainName.Buffer != NULL ) {
*DirectDomainName =
NetpAllocWStrFromWStr( ClientSession->CsDnsDomainName.Buffer );
} else {
UNLOCK_TRUST_LIST( DomainInfo );
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonDirectDomainName: %ws: No DNS domain name\n",
TrustedDomainName ));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
UNLOCK_TRUST_LIST( DomainInfo );
if ( *DirectDomainName == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
}
//
// Common exit
//
Status = STATUS_SUCCESS;
Cleanup:
if ( ClientSession != NULL ) {
NlUnrefClientSession( ClientSession );
}
if ( !NT_SUCCESS(Status) ) {
NlPrintDom((NL_CRITICAL, DomainInfo,
"I_NetLogonDirectDomainName: %ws: failed %lX\n",
TrustedDomainName,
Status));
}
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return Status;
}
NTSTATUS
I_NetLogonGetAuthDataEx(
IN LPWSTR HostedDomainName OPTIONAL,
IN LPWSTR TrustedDomainName,
IN ULONG Flags,
IN PLARGE_INTEGER FailedSessionSetupTime OPTIONAL,
OUT LPWSTR *OurClientPrincipleName,
OUT PVOID *ClientContext OPTIONAL,
OUT LPWSTR *ServerName,
OUT PNL_OS_VERSION ServerOsVersion,
OUT PULONG AuthnLevel,
OUT PLARGE_INTEGER SessionSetupTime
)
/*++
Routine Description:
This function returns the data that a caller could passed to
RpcBindingSetAuthInfoW to do an RPC call using the Netlogon security package.
The returned data is valid for the life of Netlogon's secure channel to
the current DC. There is no way for the caller to determine that lifetime.
So, the caller should be prepared for access to be denied and respond to that
by calling I_NetLogonGetAuthData again. This condition is indicated by passing
the timestamp of the previuosly used secure channel session setup.
Once the returned data is passed to RpcBindingSetAuthInfoW, the data should
not be deallocated until after the binding handle is closed.
Arguments:
HostedDomainName - Identifies the hosted domain that this request applies to.
May be the Netbios domain name or the DNS domain name.
NULL implies the primary domain hosted by this machine.
TrustedDomainName - Identifies the domain the trust relationship is to.
May be the Netbios domain name or the DNS domain name.
Flags - Flags defining which ClientContext to return:
NL_DIRECT_TRUST_REQUIRED: Indicates that STATUS_NO_SUCH_DOMAIN should be returned
if TrustedDomainName is not directly trusted.
NL_RETURN_CLOSEST_HOP: Indicates that for indirect trust, the "closest hop"
session should be returned rather than the actual session
NL_ROLE_PRIMARY_OK: Indicates that if this is a PDC, it's OK to return
the client session to the primary domain.
NL_REQUIRE_DOMAIN_IN_FOREST - Indicates that STATUS_NO_SUCH_DOMAIN should be
returned if TrustedDomainName is not a domain in the forest.
FailedSessionSetupTime - The time of the previous session setup to the server
that the caller detected as no longer available. If this parameter is
passed, the secure channel will be reset by this routine unless the timestamp
on the current secure channel is different from the one passed by the caller
(in which case the secure channel got already reset between the two calls to
this routine).
OurClientPrincipleName - The principle name of this machine (which is a client as far
as authenication is concerned). This is the ServerPrincipleName parameter to pass
to RpcBindingSetAuthInfo. Must be freed using NetApiBufferFree.
ClientContext - Authentication data for ServerName to pass as AuthIdentity to
RpcBindingSetAuthInfo. Must be freed using I_NetLogonFree.
Note this OUT parameter is NULL if ServerName doesn't support this
functionality.
ServerName - UNC name of a DC in the trusted domain.
The caller should RPC to the named DC. This DC is the only DC that has the server
side context associated with the returned ClientContext. The buffer must be freed
using NetApiBufferFree.
ServerOsVersion - Returns the operating system version of the DC named ServerName.
AuthnLevel - Authentication level Netlogon will use for its secure channel. This value
will be one of:
RPC_C_AUTHN_LEVEL_PKT_PRIVACY: Sign and seal
RPC_C_AUTHN_LEVEL_PKT_INTEGRITY: Sign only
The caller can ignore this value and independently choose an authentication level.
SessionSetupTime - The time of the secure channel session setup to the server.
Return Value:
STATUS_SUCCESS: The auth data was successfully returned.
STATUS_NO_MEMORY: There is not enough memory to complete the operation
STATUS_NETLOGON_NOT_STARTED: Netlogon is not running
STATUS_NO_SUCH_DOMAIN: HostedDomainName does not correspond to a hosted domain, OR
TrustedDomainName is not a trusted domain corresponding to Flags.
STATUS_NO_LOGON_SERVERS: No DCs are not currently available
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PDOMAIN_INFO DomainInfo = NULL;
PCLIENT_SESSION ClientSession = NULL;
BOOLEAN AmWriter = FALSE;
UNICODE_STRING TrustedDomainNameString;
LPWSTR LocalClientPrincipleName = NULL;
LPWSTR LocalServerName = NULL;
PVOID LocalClientContext = NULL;
ULONG LocalAuthnLevel = 0;
NL_OS_VERSION LocalServerOsVersion = 0;
LARGE_INTEGER LocalSessionSetupTime;
ULONG IterationIndex = 0;
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
if ( !NlStartNetlogonCall() ) {
return STATUS_NETLOGON_NOT_STARTED;
}
//
// Find the Hosted domain.
//
DomainInfo = NlFindDomain( HostedDomainName, NULL, FALSE );
if ( DomainInfo == NULL ) {
NlPrint(( NL_CRITICAL,
"I_NetLogonGetAuthData called for non-existent domain: %ws\n",
HostedDomainName ));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
NlPrintDom(( NL_SESSION_SETUP, DomainInfo,
"I_NetLogonGetAuthData called: %ws %ws (Flags 0x%lx) %s\n",
HostedDomainName,
TrustedDomainName,
Flags,
(FailedSessionSetupTime != NULL) ?
"(with reset)" : " " ));
//
// Reference the client session.
//
RtlInitUnicodeString( &TrustedDomainNameString, TrustedDomainName );
ClientSession = NlFindNamedClientSession( DomainInfo,
&TrustedDomainNameString,
Flags,
NULL );
if ( ClientSession == NULL ) {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"I_NetLogonGetAuthData: %ws: No such trusted domain (Flags 0x%lx)\n",
TrustedDomainName,
Flags ));
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// Get the server principal name
//
LocalClientPrincipleName =
NetpAllocWStrFromWStr( ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer );
if ( LocalClientPrincipleName == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// Get the auth level
//
LocalAuthnLevel = NlGlobalParameters.SealSecureChannel ?
RPC_C_AUTHN_LEVEL_PKT_PRIVACY :
RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
//
// Become a Writer of the ClientSession.
//
if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) {
NlPrintCs(( NL_CRITICAL, ClientSession,
"I_NetLogonGetAuthData: Can't become writer of client session.\n" ));
Status = STATUS_NO_LOGON_SERVERS;
goto Cleanup;
}
AmWriter = TRUE;
//
// Ensure that session is authenticated and that
// it is not the one that the caller doesn't like.
//
for ( IterationIndex = 0; IterationIndex < 2; IterationIndex++ ) {
//
// If the session isn't authenticated, do so now.
// Note that doing this call prior to resetting the secure
// channel will avoid excessive channel reset due to the
// unexpected caller activity. Specifically, if we already
// reset the session recently, this check will fail.
//
Status = NlEnsureSessionAuthenticated( ClientSession, 0 );
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
//
// On the first iteration, if this is the session that the
// caller doesn't like, reset the session and retry the authentication
//
if ( IterationIndex == 0 &&
FailedSessionSetupTime != NULL &&
FailedSessionSetupTime->QuadPart == ClientSession->CsLastAuthenticationTry.QuadPart ) {
NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS );
} else {
break;
}
}
//
// Get the server name
//
LocalServerName = NetpAllocWStrFromWStr( ClientSession->CsUncServerName );
if ( LocalServerName == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// Get the client context, if asked and available
//
if ( ClientContext != NULL &&
(ClientSession->CsNegotiatedFlags & NETLOGON_SUPPORTS_LSA_AUTH_RPC) != 0 ) {
LocalClientContext = NlBuildAuthData( ClientSession );
if ( LocalClientContext == NULL ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
}
//
// Get the session setup time
//
LocalSessionSetupTime = ClientSession->CsLastAuthenticationTry;
NlAssert( LocalSessionSetupTime.QuadPart != 0 );
//
// Determine the OS version
//
if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_WIN2000_MASK ) {
LocalServerOsVersion = NlWhistler;
} else if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_NT4_MASK ) {
LocalServerOsVersion = NlWin2000;
} else if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_NT351_MASK ) {
LocalServerOsVersion = NlNt40;
} else if ( ClientSession->CsNegotiatedFlags != 0 ) {
LocalServerOsVersion = NlNt351;
} else {
LocalServerOsVersion = NlNt35_or_older;
}
//
// Common exit
//
Cleanup:
if ( ClientSession != NULL ) {
if ( AmWriter ) {
NlResetWriterClientSession( ClientSession );
}
NlUnrefClientSession( ClientSession );
}
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
//
// Return the data on success
//
if ( NT_SUCCESS(Status) ) {
*OurClientPrincipleName = LocalClientPrincipleName;
LocalClientPrincipleName = NULL;
*AuthnLevel = LocalAuthnLevel;
*ServerName = LocalServerName;
LocalServerName = NULL;
*ServerOsVersion = LocalServerOsVersion;
*SessionSetupTime = LocalSessionSetupTime;
if ( ClientContext != NULL ) {
*ClientContext = LocalClientContext;
LocalClientContext = NULL;
}
} else {
NlPrintDom(( NL_CRITICAL, DomainInfo,
"I_NetLogonGetAuthData failed: %ws %ws (Flags 0x%lx)%s 0x%lx\n",
HostedDomainName,
TrustedDomainName,
Flags,
(FailedSessionSetupTime != NULL) ?
" (with reset):" : ":",
Status ));
}
if ( LocalClientPrincipleName != NULL ) {
NetApiBufferFree( LocalClientPrincipleName );
}
if ( LocalServerName != NULL ) {
NetApiBufferFree( LocalServerName );
}
if ( LocalClientContext != NULL ) {
I_NetLogonFree( LocalClientContext );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return Status;
}
NET_API_STATUS
NetrGetDCName (
IN LPWSTR ServerName OPTIONAL,
IN LPWSTR DomainName OPTIONAL,
OUT LPWSTR *Buffer
)
/*++
Routine Description:
Get the name of the primary domain controller for a domain.
Arguments:
ServerName - name of remote server (null for local)
DomainName - name of domain (null for primary)
Buffer - Returns a pointer to an allcated buffer containing the
servername of the PDC of the domain. The server name is prefixed
by \\. The buffer should be deallocated using NetApiBufferFree.
Return Value:
NERR_Success - Success. Buffer contains PDC name prefixed by \\.
NERR_DCNotFound No DC found for this domain.
ERROR_INVALID_NAME Badly formed domain name
--*/
{
#ifdef _WKSTA_NETLOGON
return ERROR_NOT_SUPPORTED;
UNREFERENCED_PARAMETER( ServerName );
UNREFERENCED_PARAMETER( DomainName );
UNREFERENCED_PARAMETER( Buffer );
#endif // _WKSTA_NETLOGON
#ifdef _DC_NETLOGON
NET_API_STATUS NetStatus;
UNREFERENCED_PARAMETER( ServerName );
//
// This API is not supported on workstations.
//
if ( NlGlobalMemberWorkstation ) {
return ERROR_NOT_SUPPORTED;
}
//
// Simply call the API which handles the local case specially.
//
NetStatus = NetGetDCName( NULL, DomainName, (LPBYTE *)Buffer );
return NetStatus;
#endif // _DC_NETLOGON
}
NET_API_STATUS
DsrGetDcNameEx(
IN LPWSTR ComputerName OPTIONAL,
IN LPWSTR DomainName OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPWSTR SiteName OPTIONAL,
IN ULONG Flags,
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++
Routine Description:
Same as DsGetDcNameW except:
* This is the RPC server side implementation.
Arguments:
Same as DsGetDcNameW except as above.
Return Value:
Same as DsGetDcNameW except as above.
--*/
{
return DsrGetDcNameEx2( ComputerName,
NULL, // No Account name
0, // No Allowable account control bits
DomainName,
DomainGuid,
SiteName,
Flags,
DomainControllerInfo );
}
VOID
DsFlagsToString(
IN DWORD Flags,
OUT LPSTR Buffer
)
/*++
Routine Description:
Routine to convert DsGetDcName flags to a printable string
Arguments:
Flags - flags to convert.
Buffer - buffer large enough for the longest string.
Return Value:
Buffer containing printable string.
Free using LocalFree.
--*/
{
//
// Build a string for each bit.
//
*Buffer = '\0';
if ( Flags & DS_FORCE_REDISCOVERY ) {
strcat( Buffer, "FORCE " );
Flags &= ~DS_FORCE_REDISCOVERY;
}
if ( Flags & DS_DIRECTORY_SERVICE_REQUIRED ) {
strcat( Buffer, "DS " );
Flags &= ~DS_DIRECTORY_SERVICE_REQUIRED;
}
if ( Flags & DS_DIRECTORY_SERVICE_PREFERRED ) {
strcat( Buffer, "DSP " );
Flags &= ~DS_DIRECTORY_SERVICE_PREFERRED;
}
if ( Flags & DS_GC_SERVER_REQUIRED ) {
strcat( Buffer, "GC " );
Flags &= ~DS_GC_SERVER_REQUIRED;
}
if ( Flags & DS_PDC_REQUIRED ) {
strcat( Buffer, "PDC " );
Flags &= ~DS_PDC_REQUIRED;
}
if ( Flags & DS_IP_REQUIRED ) {
strcat( Buffer, "IP " );
Flags &= ~DS_IP_REQUIRED;
}
if ( Flags & DS_KDC_REQUIRED ) {
strcat( Buffer, "KDC " );
Flags &= ~DS_KDC_REQUIRED;
}
if ( Flags & DS_TIMESERV_REQUIRED ) {
strcat( Buffer, "TIMESERV " );
Flags &= ~DS_TIMESERV_REQUIRED;
}
if ( Flags & DS_WRITABLE_REQUIRED ) {
strcat( Buffer, "WRITABLE " );
Flags &= ~DS_WRITABLE_REQUIRED;
}
if ( Flags & DS_GOOD_TIMESERV_PREFERRED ) {
strcat( Buffer, "GTIMESERV " );
Flags &= ~DS_GOOD_TIMESERV_PREFERRED;
}
if ( Flags & DS_AVOID_SELF ) {
strcat( Buffer, "AVOIDSELF " );
Flags &= ~DS_AVOID_SELF;
}
if ( Flags & DS_ONLY_LDAP_NEEDED ) {
strcat( Buffer, "LDAPONLY " );
Flags &= ~DS_ONLY_LDAP_NEEDED;
}
if ( Flags & DS_BACKGROUND_ONLY ) {
strcat( Buffer, "BACKGROUND " );
Flags &= ~DS_BACKGROUND_ONLY;
}
if ( Flags & DS_IS_FLAT_NAME ) {
strcat( Buffer, "NETBIOS " );
Flags &= ~DS_IS_FLAT_NAME;
}
if ( Flags & DS_IS_DNS_NAME ) {
strcat( Buffer, "DNS " );
Flags &= ~DS_IS_DNS_NAME;
}
if ( Flags & DS_RETURN_DNS_NAME ) {
strcat( Buffer, "RET_DNS " );
Flags &= ~DS_RETURN_DNS_NAME;
}
if ( Flags & DS_RETURN_FLAT_NAME ) {
strcat( Buffer, "RET_NETBIOS " );
Flags &= ~DS_RETURN_FLAT_NAME;
}
if ( Flags ) {
sprintf( &Buffer[strlen(Buffer)], "0x%lx ", Flags );
}
}
NET_API_STATUS
DsrGetDcNameEx2(
IN LPWSTR ComputerName OPTIONAL,
IN LPWSTR AccountName OPTIONAL,
IN ULONG AllowableAccountControlBits,
IN LPWSTR DomainName OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN LPWSTR SiteName OPTIONAL,
IN ULONG Flags,
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++
Routine Description:
Same as DsGetDcNameW except:
AccountName - Account name to pass on the ping request.
If NULL, no account name will be sent.
AllowableAccountControlBits - Mask of allowable account types for AccountName.
* This is the RPC server side implementation.
Arguments:
Same as DsGetDcNameW except as above.
Return Value:
Same as DsGetDcNameW except as above.
--*/
{
NET_API_STATUS NetStatus;
PDOMAIN_INFO DomainInfo;
LPWSTR CapturedInfo = NULL;
LPWSTR CapturedDnsDomainName;
LPWSTR CapturedDnsForestName;
LPWSTR CapturedSiteName;
GUID CapturedDomainGuidBuffer;
GUID *CapturedDomainGuid;
LPSTR FlagsBuffer;
ULONG InternalFlags = 0;
LPWSTR DnsDomainTrustName = NULL;
LPWSTR NetbiosDomainTrustName = NULL;
LPWSTR NetlogonDnsDomainTrustName = NULL;
LPWSTR NetlogonNetbiosDomainTrustName = NULL;
UNICODE_STRING LsaDnsDomainTrustName = {0};
UNICODE_STRING LsaNetbiosDomainTrustName = {0};
BOOL HaveDnsServers;
//
// If caller is calling when the netlogon service isn't running,
// tell it so.
//
if ( !NlStartNetlogonCall() ) {
return ERROR_NETLOGON_NOT_STARTED;
}
//
// Allocate a temp buffer
// (Don't put it on the stack since we don't want to commit a huge stack.)
//
CapturedInfo = LocalAlloc( LMEM_ZEROINIT,
(NL_MAX_DNS_LENGTH+1)*sizeof(WCHAR) +
(NL_MAX_DNS_LENGTH+1)*sizeof(WCHAR) +
(NL_MAX_DNS_LABEL_LENGTH+1)*sizeof(WCHAR)
+ 200 );
if ( CapturedInfo == NULL ) {
return ERROR_NOT_ENOUGH_MEMORY;
}
CapturedDnsDomainName = CapturedInfo;
CapturedDnsForestName = &CapturedDnsDomainName[NL_MAX_DNS_LENGTH+1];
CapturedSiteName = &CapturedDnsForestName[NL_MAX_DNS_LENGTH+1];
FlagsBuffer = (LPSTR)&CapturedSiteName[NL_MAX_DNS_LABEL_LENGTH+1];
IF_NL_DEBUG( MISC ) {
DsFlagsToString( Flags, FlagsBuffer );
}
//
// Lookup which domain this call pertains to.
//
DomainInfo = NlFindDomainByServerName( ComputerName );
if ( DomainInfo == NULL ) {
// Default to primary domain to handle the case where the ComputerName
// is an IP address.
// ?? Perhaps we should simply always use the primary domain
DomainInfo = NlFindNetbiosDomain( NULL, TRUE );
if ( DomainInfo == NULL ) {
NetStatus = ERROR_INVALID_COMPUTERNAME;
goto Cleanup;
}
}
//
// Be verbose
//
NlPrintDom((NL_MISC, DomainInfo,
"DsGetDcName function called: Dom:%ws Acct:%ws Flags: %s\n",
DomainName,
AccountName,
FlagsBuffer ));
//
// If the caller didn't specify a site name,
// default to our site name.
//
if ( !ARGUMENT_PRESENT(SiteName) ) {
if ( NlCaptureSiteName( CapturedSiteName ) ) {
SiteName = CapturedSiteName;
InternalFlags |= DS_SITENAME_DEFAULTED;
}
}
//
// If the caller passed a domain name,
// and the domain is trusted,
// determine the Netbios and DNS versions of the domain name.
//
if ( DomainName != NULL ) {
//
// First try to get the names from netlogon's trusted domain list
//
NetStatus = NlGetTrustedDomainNames (
DomainInfo,
DomainName,
&NetlogonDnsDomainTrustName,
&NetlogonNetbiosDomainTrustName );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
DnsDomainTrustName = NetlogonDnsDomainTrustName;
NetbiosDomainTrustName = NetlogonNetbiosDomainTrustName;
//
// If that didn't work,
// try getting better information from LSA's logon session list
//
if ( DnsDomainTrustName == NULL || NetbiosDomainTrustName == NULL ) {
NTSTATUS Status;
UNICODE_STRING DomainNameString;
RtlInitUnicodeString( &DomainNameString, DomainName );
Status = LsaIGetNbAndDnsDomainNames(
&DomainNameString,
&LsaDnsDomainTrustName,
&LsaNetbiosDomainTrustName );
if ( !NT_SUCCESS(Status) ) {
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// If the LSA returned names,
// use them
//
if ( LsaDnsDomainTrustName.Buffer != NULL &&
LsaNetbiosDomainTrustName.Buffer != NULL ) {
DnsDomainTrustName = LsaDnsDomainTrustName.Buffer;
NetbiosDomainTrustName = LsaNetbiosDomainTrustName.Buffer;
}
}
}
//
// Pass the request to the common implementation.
//
// When DsIGetDcName is called from netlogon,
// it has both the Netbios and DNS domain name available for the primary
// domain. That can trick DsGetDcName into returning DNS host name of a
// DC in the primary domain. However, on IPX only systems, that won't work.
// Avoid that problem by not passing the DNS domain name of the primary domain
// if there are no DNS servers.
//
CapturedDomainGuid = NlCaptureDomainInfo( DomainInfo,
CapturedDnsDomainName,
&CapturedDomainGuidBuffer );
NlCaptureDnsForestName( CapturedDnsForestName );
HaveDnsServers = NlDnsHasDnsServers();
NetStatus = DsIGetDcName(
DomainInfo->DomUncUnicodeComputerName+2,
AccountName,
AllowableAccountControlBits,
DomainName,
CapturedDnsForestName,
DomainGuid,
SiteName,
Flags,
InternalFlags,
DomainInfo,
NL_DC_MAX_TIMEOUT + NlGlobalParameters.ExpectedDialupDelay*1000,
DomainInfo->DomUnicodeDomainName,
HaveDnsServers ? CapturedDnsDomainName : NULL,
CapturedDomainGuid,
HaveDnsServers ? DnsDomainTrustName : NULL,
NetbiosDomainTrustName,
DomainControllerInfo );
if ( NetStatus != ERROR_NO_SUCH_DOMAIN ) {
goto Cleanup;
}
//
// Clean up locally used resources.
//
Cleanup:
NlPrintDom((NL_MISC, DomainInfo,
"DsGetDcName function returns %ld: Dom:%ws Acct:%ws Flags: %s\n",
NetStatus,
DomainName,
AccountName,
FlagsBuffer ));
if ( DomainInfo != NULL ) {
NlDereferenceDomain( DomainInfo );
}
if ( CapturedInfo != NULL ) {
LocalFree( CapturedInfo );
}
if ( NetlogonDnsDomainTrustName != NULL ) {
NetApiBufferFree( NetlogonDnsDomainTrustName );
}
if ( NetlogonNetbiosDomainTrustName != NULL ) {
NetApiBufferFree( NetlogonNetbiosDomainTrustName );
}
if ( LsaDnsDomainTrustName.Buffer != NULL ) {
LsaIFreeHeap( LsaDnsDomainTrustName.Buffer );
}
if ( LsaNetbiosDomainTrustName.Buffer != NULL ) {
LsaIFreeHeap( LsaNetbiosDomainTrustName.Buffer );
}
//
// Indicate that the calling thread has left netlogon.dll
//
NlEndNetlogonCall();
return NetStatus;
}
NET_API_STATUS
DsrGetDcName(
IN LPWSTR ComputerName OPTIONAL,
IN LPWSTR DomainName OPTIONAL,
IN GUID *DomainGuid OPTIONAL,
IN GUID *SiteGuid OPTIONAL,
IN ULONG Flags,
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
)
/*++
Routine Description:
Same as DsGetDcNameW except:
* This is the RPC server side implementation.
Arguments:
Same as DsGetDcNameW except as above.
Return Value:
Same as DsGetDcNameW except as above.
--*/
{
return DsrGetDcNameEx2( ComputerName,
NULL, // No Account name
0, // No Allowable account control bits
DomainName,
DomainGuid,
NULL, // No site name
Flags,
DomainControllerInfo );
UNREFERENCED_PARAMETER( SiteGuid );
}