/*++ 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 // Routines shared with NetUser Apis #include // NetpRpcStatusToApiStatus() #include // sprintf(). 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 ) { 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; iSessionKey, &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; iSessionKey, (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 ); } // // 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 ); // // Otherwise use the old API. // } else { Status = I_NetLogonSamLogon( ClientSession->CsUncServerName, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, LogonLevel, LogonInformation, RemoteValidationLevel, ValidationInformation, Authoritative ); } } 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 ); } } // // Filter out SIDs for quarantined domains and interforest domains as needed // if ( IsDomainSecureChannelType(ClientSession->CsSecureChannelType) && *ValidationInformation != NULL ) { 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 ); UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); 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 ); // 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 ); } 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; NTSTATUS TmpStatus; PCLIENT_SESSION ClientSession = NULL; BOOLEAN IsSameSite; DWORD ExtraFlags = 0; PNETLOGON_LOGON_IDENTITY_INFO LogonInfo; PSAMPR_DOMAIN_INFO_BUFFER DomainLockout = NULL; BOOL LockoutEnabled = FALSE; PLIST_ENTRY FailedUserEntry; 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 // // Determine if the account lockout policy is enabled. // // Do not destroy Status below. Otherwise, if NlpUserValidateHigher // succeeded we would leak ValidationInformation. // 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 ) { LockoutEnabled = TRUE; } // // Update the list of forwarded failed user logons // // If the lockout policy is enabled we should continue // to forward logons to PDC (i.e. we should not cache // this failure) to keep the right lockout count until // the account becomes locked on the PDC. // FailedUser = NULL; if ( UseNegativeCache && (!LockoutEnabled || Status == STATUS_ACCOUNT_LOCKED_OUT) ) { 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; } // // Remember if this user logon failed // if ( !NT_SUCCESS(Status) ) { // // 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 ); // // If this logon succeeded, // just free the unlinked failed logon entry (if any) // } else if ( FailedUser != NULL ) { LocalFree( FailedUser ); } 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. Return Value: Status of operation. --*/ { NTSTATUS Status = STATUS_SUCCESS; SAMPR_HANDLE UserHandle = 0; SAMPR_USER_INFO_BUFFER UserInfo; 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 ( pUserNameStr == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlCopyMemory( pUserNameStr, LogonUser->Buffer, LogonUser->Length ); pUserNameStr[ LogonUser->Length/sizeof(WCHAR) ] = L'\0'; // // Get the user's handle to the local SAM database // Status = NlSamOpenNamedUser( DomainInfo, pUserNameStr, &UserHandle, NULL, NULL); if ( !NT_SUCCESS(Status) ) { NlPrint(( NL_CRITICAL, "NlpZeroBadPasswordCountLocally: NlSamOpenNamedUser 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; // // 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 ( pUserNameStr != NULL ) { LocalFree( pUserNameStr ); } if ( UserHandle != 0 ) { SamrCloseHandle(&UserHandle); } return Status; } NTSTATUS NlpUserValidate ( IN PDOMAIN_INFO DomainInfo, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, 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. OrigClientIsNt4 -- True if our caller is running NT 4.0 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 \ 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 ) { NETLOGON_LOGON_INFO_CLASS MsvLogonLevel; // // Indicate we've already tried the specified account and // we won't need to try it again locally. // AccountsToTry &= ~MSVSAM_SPECIFIED; // // Don't confuse MSV with the transitive LogonLevels // switch (LogonLevel ) { case NetlogonInteractiveTransitiveInformation: MsvLogonLevel = NetlogonInteractiveInformation; break; case NetlogonServiceTransitiveInformation: MsvLogonLevel = NetlogonServiceInformation; break; case NetlogonNetworkTransitiveInformation: MsvLogonLevel = NetlogonNetworkInformation; break; default: MsvLogonLevel = LogonLevel; break; } Status = MsvSamValidate( DomainInfo->DomSamAccountDomainHandle, TRUE, // UasCompatibilityMode, SecureChannelType, &DomainInfo->DomUnicodeComputerNameString, &DomainInfo->DomUnicodeAccountDomainNameString, DomainInfo->DomAccountDomainId, MsvLogonLevel, 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; } // On success, zero bad password locally. // Ignore error as it's not critical operation. if ( NT_SUCCESS(Status) && !NlGlobalMemberWorkstation ) { NlpZeroBadPasswordCountLocally( DomainInfo, &LogonInfo->UserName ); } } #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; iUserName.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, // allocate a copy of the logon info to put the new information in. // // Some pointers will continue to point to the old buffer. // if ( RealDomainName != NULL && RealSamAccountName != NULL ) { ULONG BytesToCopy; switch ( LogonLevel ) { case NetlogonInteractiveInformation: case NetlogonInteractiveTransitiveInformation: BytesToCopy = sizeof(NETLOGON_INTERACTIVE_INFO);break; case NetlogonNetworkInformation: case NetlogonNetworkTransitiveInformation: BytesToCopy = sizeof(NETLOGON_NETWORK_INFO);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 ); RtlInitUnicodeString( &NewLogonInfo->UserName, RealSamAccountName ); // // Otherwise, continue with the current account name // } else { LogonInfoToUse = LogonInfo; } // // Call this routine again now that we have better information. // DefaultStatus = NlpUserValidate( DomainInfo, SecureChannelType, 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; NETLOGON_LOGON_INFO_CLASS MsvLogonLevel; // // Don't confuse MSV with the transitive LogonLevels // switch (LogonLevel ) { case NetlogonInteractiveTransitiveInformation: MsvLogonLevel = NetlogonInteractiveInformation; break; case NetlogonServiceTransitiveInformation: MsvLogonLevel = NetlogonServiceInformation; break; case NetlogonNetworkTransitiveInformation: MsvLogonLevel = NetlogonNetworkInformation; break; default: MsvLogonLevel = LogonLevel; break; } Status = MsvSamValidate( DomainInfo->DomSamAccountDomainHandle, TRUE, // UasCompatibilityMode, SecureChannelType, &DomainInfo->DomUnicodeComputerNameString, &DomainInfo->DomUnicodeAccountDomainNameString, DomainInfo->DomAccountDomainId, MsvLogonLevel, 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; } // On success, zero bad password locally on this BDC. // Ignore error as it's not critical operation. if ( NT_SUCCESS(Status) && !NlGlobalMemberWorkstation ) { NlpZeroBadPasswordCountLocally( DomainInfo, &LogonInfo->UserName ); } } // // 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. // if (((SecureChannelType == WorkstationSecureChannel) || (SecureChannelType == MsvApSecureChannel)) && (SecureChannelType != UasServerSecureChannel) && (ValidationLevel == NetlogonValidationSamInfo2 || ValidationLevel == NetlogonValidationSamInfo4 ) && !NlGlobalMemberWorkstation && NT_SUCCESS(DefaultStatus)) { Status = NlpExpandResourceGroupMembership( ValidationLevel, (PNETLOGON_VALIDATION_SAM_INFO4 *) ValidationInformation, DomainInfo ); if (!NT_SUCCESS(Status)) { DefaultStatus = Status; } } 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 ) /*++ 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. 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; 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 ( ContextHandle == NULL && LogonServer == NULL && ComputerName == NULL && Authenticator == NULL && ReturnAuthenticator == NULL ) { // // ??: Do as I said // SecureChannelType = MsvApSecureChannel; SessionInfo.NegotiatedFlags = NETLOGON_SUPPORTS_MASK; // // // // 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; RPC_AUTHZ_HANDLE RpcPrivs; ULONG AuthnLevel; ULONG AuthnSvc; // // Determine the client binding info. // NetStatus = RpcBindingInqAuthClient( ContextHandle, &RpcPrivs, 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; // // 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, 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; } } 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 ); } 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 ); } 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 ); } 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 ) { SecureChannelType = MsvApSecureChannel; // // 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 ) { RtlZeroMemory( 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 ) { RtlZeroMemory( 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 == RolePrimary ) { 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 ); // 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, IN LPWSTR TrustedDomainName, IN BOOLEAN ResetChannel, IN ULONG Flags, OUT LPWSTR *ServerName, OUT PNL_OS_VERSION ServerOsVersion, OUT LPWSTR *ServerPrincipleName, OUT PVOID *ClientContext, OUT PULONG AuthnLevel ) /*++ 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. 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. 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. ResetChannel - If true, specifies that the netlogon secure channel is to be reset. 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. 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 ClientContext given below. The buffer must be freed using I_NetLogonFree. ServerOsVersion - Returns the operating system version of the machine named ServerName. ServerPrincipleName - ServerPrincipleName to pass to RpcBindingSetAutInfo. (See note above about when this data can be deallocated.) The buffer must be freed using I_NetLogonFree. ClientContext - Authentication data to pass as AuthIdentity to RpcBindingSetAutInfo. (See note above about when this data can be deallocated.) The buffer must be freed using I_NetLogonFree. Note this OUT parameter is NULL if ServerName doesn't support this functionality. 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. 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. 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 LocalServerPrincipleName = NULL; LPWSTR LocalServerName = NULL; PVOID LocalClientContext = NULL; ULONG LocalAuthnLevel = 0; // // 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_NetLogonGetAuthData: %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, Flags, NULL ); if ( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "I_NetLogonGetAuthData: %ws: No such trusted domain\n", TrustedDomainName )); Status = STATUS_NO_SUCH_DOMAIN; goto Cleanup; } // // Get the server principal name // LocalServerPrincipleName = NetpAllocWStrFromWStr( ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer ); if ( LocalServerPrincipleName == 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 ) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "I_NetLogonGetAuthData: Can't become writer of client session.\n")); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } AmWriter = TRUE; // // If the caller asked us to reset the channel, // do so now. // if ( ResetChannel ) { NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); } // // If the session isn't authenticated, // do so now. // Status = NlEnsureSessionAuthenticated( ClientSession, 0 ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Get the server name // LocalServerName = NetpAllocWStrFromWStr( ClientSession->CsUncServerName ); if ( LocalServerName == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // Get the client context, if available // if ((ClientSession->CsNegotiatedFlags & NETLOGON_SUPPORTS_LSA_AUTH_RPC) != 0 ) { LocalClientContext = NlBuildAuthData( ClientSession ); if ( LocalClientContext == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } } // // Determine the OS version // if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_WIN2000_MASK ) { *ServerOsVersion = NlWhistler; } else if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_NT4_MASK ) { *ServerOsVersion = NlWin2000; } else if ( ClientSession->CsNegotiatedFlags & ~NETLOGON_SUPPORTS_NT351_MASK ) { *ServerOsVersion = NlNt40; } else if ( ClientSession->CsNegotiatedFlags != 0 ) { *ServerOsVersion = NlNt351; } else { *ServerOsVersion = NlNt35_or_older; } // // Common exit // Cleanup: if ( ClientSession != NULL ) { if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } NlUnrefClientSession( ClientSession ); } if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "I_NetLogonGetAuthData: %ws: failed %lX\n", TrustedDomainName, Status)); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } // // Return the data on success // if ( NT_SUCCESS(Status) ) { *ServerPrincipleName = LocalServerPrincipleName; *AuthnLevel = LocalAuthnLevel; *ServerName = LocalServerName; if ( ClientContext != NULL ) { *ClientContext = LocalClientContext; LocalClientContext = NULL; } } else { if ( LocalServerPrincipleName != NULL ) { NetApiBufferFree( LocalServerPrincipleName ); } 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 STATUS_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( 0, (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 STATUS_NO_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 ); }