/*++ Copyright (c) 1987-1996 Microsoft Corporation Module Name: lsrvutil.c Abstract: Utility functions for the netlogon service. Author: Ported from Lan Man 2.0 Environment: User mode only. Contains NT-specific code. Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 00-Jun-1989 (PradyM) modified lm10 code for new NETLOGON service 00-Feb-1990 (PradyM) bugfixes 00-Aug-1990 (t-RichE) added alerts for auth failure due to time slippage 11-Jul-1991 (cliffv) Ported to NT. Converted to NT style. 02-Jan-1992 (madana) added support for builtin/multidomain replication. --*/ // // Common include files. // #include "logonsrv.h" // Include files common to entire service #pragma hdrstop // // Include files specific to this .c file // #include // NetpAliasMemberToPriv #include // MTXT_* defines #include // NetpwPathCompare() #include // I_NetSamDeltas() /*lint -e740 */ /* don't complain about unusual cast */ #define MAX_DC_AUTHENTICATION_WAIT (long) (45L*1000L) // 45 seconds // // We want to prevent too-frequent alerts from // being sent in case of Authentication failures. // #define MAX_ALERTS 10 // send one every 10 to 30 mins based on pulse VOID RaiseNetlogonAlert( IN DWORD alertNum, IN LPWSTR *AlertStrings, IN OUT DWORD *ptrAlertCount ) /*++ Routine Description: Raise an alert once per MAX_ALERTS occurances Arguments: alertNum -- RaiseAlert() alert number. AlertStrings -- RaiseAlert() arguments ptrAlertCount -- Points to the count of occurence of this particular alert. This routine increments it and will set it to that value modulo MAX_ALERTS. Return Value: NONE --*/ { if (*ptrAlertCount == 0) { NetpRaiseAlert( SERVICE_NETLOGON, alertNum, AlertStrings); } (*ptrAlertCount)++; (*ptrAlertCount) %= MAX_ALERTS; } NTSTATUS NlOpenSecret( IN PCLIENT_SESSION ClientSession, IN ULONG DesiredAccess, OUT PLSAPR_HANDLE SecretHandle ) /*++ Routine Description: Open the Lsa Secret Object containing the password to be used for the specified client session. Arguments: ClientSession - Structure used to define the session. On Input, the following fields must be set: CsNetbiosDomainName CsSecureChannelType DesiredAccess - Access required to the secret. SecretHandle - Returns a handle to the secret. Return Value: Status of operation. --*/ { NTSTATUS Status; UNICODE_STRING SecretNameString; NlAssert( ClientSession->CsReferenceCount > 0 ); // // Only use secrets for workstation and BDC machine accounts. // switch ( ClientSession->CsSecureChannelType ) { case ServerSecureChannel: case WorkstationSecureChannel: RtlInitUnicodeString( &SecretNameString, SSI_SECRET_NAME ); break; case TrustedDomainSecureChannel: case TrustedDnsDomainSecureChannel: default: Status = STATUS_INTERNAL_ERROR; NlPrint((NL_CRITICAL, "NlOpenSecret: Invalid account type\n")); return Status; } // // Get the Password of the account from LSA secret storage // Status = LsarOpenSecret( ClientSession->CsDomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING)&SecretNameString, DesiredAccess, SecretHandle ); return Status; } NTSTATUS NlGetOutgoingPassword( IN PCLIENT_SESSION ClientSession, OUT PUNICODE_STRING *CurrentValue, OUT PUNICODE_STRING *OldValue, OUT PDWORD CurrentVersionNumber, OUT PLARGE_INTEGER LastSetTime OPTIONAL ) /*++ Routine Description: Get the outgoing password to be used for the specified client session. Arguments: ClientSession - Structure used to define the session. On Input, the following fields must be set: CsNetbiosDomainName CsSecureChannelType CurrentValue - Current password for the client session. CurrentValue should be freed using LocalFree A NULL pointer is returned if there is no current password. OldValue - Previous password for the client session. OldValue should be freed using LocalFree A NULL pointer is returned if there is no old password. CurrentVersionNumber - Version number of the current password for interdomain trust account. Set to 0 on failure status or if this is not interdomain trust account. LastSetTime - Time when the password was last changed. Return Value: Status of operation. STATUS_NO_TRUST_LSA_SECRET: Secret object is not accessable STATUS_NO_MEMORY: Not enough memory to allocate password buffers --*/ { NTSTATUS Status; LSAPR_HANDLE SecretHandle = NULL; PLSAPR_CR_CIPHER_VALUE CrCurrentPassword = NULL; PLSAPR_CR_CIPHER_VALUE CrOldPassword = NULL; PLSAPR_TRUSTED_DOMAIN_INFO TrustInfo = NULL; PLSAPR_AUTH_INFORMATION AuthInfo; PLSAPR_AUTH_INFORMATION OldAuthInfo; ULONG AuthInfoCount; ULONG i; BOOL PasswordFound = FALSE; BOOL PasswordVersionFound = FALSE; // // Initialization // *CurrentValue = NULL; *OldValue = NULL; *CurrentVersionNumber = 0; // // Workstation and BDC secure channels get their outgoing password from // an LSA secret. // switch ( ClientSession->CsSecureChannelType ) { case ServerSecureChannel: case WorkstationSecureChannel: // // Get the Password of the account from LSA secret storage // Status = NlOpenSecret( ClientSession, SECRET_QUERY_VALUE, &SecretHandle ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: cannot NlOpenSecret 0x%lx\n", Status )); // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } Status = LsarQuerySecret( SecretHandle, &CrCurrentPassword, LastSetTime, &CrOldPassword, NULL ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: cannot LsaQuerySecret 0x%lx\n", Status )); // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Copy the current password back to the caller. // if ( CrCurrentPassword != NULL ) { *CurrentValue = LocalAlloc(0, sizeof(UNICODE_STRING)+CrCurrentPassword->Length+sizeof(WCHAR) ); if ( *CurrentValue == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } (*CurrentValue)->Buffer = (LPWSTR)(((LPBYTE)(*CurrentValue))+sizeof(UNICODE_STRING)); RtlCopyMemory( (*CurrentValue)->Buffer, CrCurrentPassword->Buffer, CrCurrentPassword->Length ); (*CurrentValue)->Length = (USHORT)CrCurrentPassword->Length; (*CurrentValue)->MaximumLength = (USHORT)((*CurrentValue)->Length + sizeof(WCHAR)); (*CurrentValue)->Buffer[(*CurrentValue)->Length/sizeof(WCHAR)] = L'\0'; } // // Copy the Old password back to the caller. // if ( CrOldPassword != NULL ) { *OldValue = LocalAlloc(0, sizeof(UNICODE_STRING)+CrOldPassword->Length+sizeof(WCHAR) ); if ( *OldValue == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } (*OldValue)->Buffer = (LPWSTR)(((LPBYTE)(*OldValue))+sizeof(UNICODE_STRING)); RtlCopyMemory( (*OldValue)->Buffer, CrOldPassword->Buffer, CrOldPassword->Length ); (*OldValue)->Length = (USHORT)CrOldPassword->Length; (*OldValue)->MaximumLength = (USHORT)((*OldValue)->Length + sizeof(WCHAR)); (*OldValue)->Buffer[(*OldValue)->Length/sizeof(WCHAR)] = L'\0'; } break; // // Trusted domain secure channels get their outgoing password from the trusted // domain object. // case TrustedDomainSecureChannel: case TrustedDnsDomainSecureChannel: // // Get the authentication information from the LSA. // Status = LsarQueryTrustedDomainInfoByName( ClientSession->CsDomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING) ClientSession->CsTrustName, TrustedDomainAuthInformation, &TrustInfo ); if (!NT_SUCCESS(Status)) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: %wZ: cannot LsarQueryTrustedDomainInfoByName 0x%lx\n", ClientSession->CsTrustName, Status )); if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } AuthInfoCount = TrustInfo->TrustedAuthInfo.OutgoingAuthInfos; AuthInfo = TrustInfo->TrustedAuthInfo.OutgoingAuthenticationInformation; OldAuthInfo = TrustInfo->TrustedAuthInfo.OutgoingPreviousAuthenticationInformation; if (AuthInfoCount == 0 || AuthInfo == NULL) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: %wZ: No auth info for this domain.\n", ClientSession->CsTrustName )); Status = STATUS_NO_TRUST_LSA_SECRET; goto Cleanup; } NlAssert( OldAuthInfo != NULL ); // // Loop through the various auth infos looking for the cleartext password // and its version number. // for ( i=0; iBuffer = (LPWSTR)(((LPBYTE)(*CurrentValue))+sizeof(UNICODE_STRING)); RtlCopyMemory( (*CurrentValue)->Buffer, AuthInfo[i].AuthInfo, AuthInfo[i].AuthInfoLength ); (*CurrentValue)->Length = (USHORT)AuthInfo[i].AuthInfoLength; (*CurrentValue)->MaximumLength = (USHORT)((*CurrentValue)->Length + sizeof(WCHAR)); (*CurrentValue)->Buffer[(*CurrentValue)->Length/sizeof(WCHAR)] = L'\0'; // // Copy the password change time back to the caller. // if ( ARGUMENT_PRESENT( LastSetTime )) { *LastSetTime = AuthInfo[i].LastUpdateTime; } // // Only copy the old password if it is also clear. // if ( OldAuthInfo[i].AuthType == TRUST_AUTH_TYPE_CLEAR ) { // // Copy the Old password back to the caller. // *OldValue = LocalAlloc(0, sizeof(UNICODE_STRING)+OldAuthInfo[i].AuthInfoLength+sizeof(WCHAR) ); if ( *OldValue == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } (*OldValue)->Buffer = (LPWSTR)(((LPBYTE)(*OldValue))+sizeof(UNICODE_STRING)); RtlCopyMemory( (*OldValue)->Buffer, OldAuthInfo[i].AuthInfo, OldAuthInfo[i].AuthInfoLength ); (*OldValue)->Length = (USHORT)OldAuthInfo[i].AuthInfoLength; (*OldValue)->MaximumLength = (USHORT)((*OldValue)->Length + sizeof(WCHAR)); (*OldValue)->Buffer[(*OldValue)->Length/sizeof(WCHAR)] = L'\0'; } PasswordFound = TRUE; if ( PasswordVersionFound ) { break; } // // Handle the version number of the cleartext password // } else if ( AuthInfo[i].AuthType == TRUST_AUTH_TYPE_VERSION && !PasswordVersionFound && AuthInfo[i].AuthInfoLength == sizeof(*CurrentVersionNumber) ) { RtlCopyMemory( CurrentVersionNumber, AuthInfo[i].AuthInfo, AuthInfo[i].AuthInfoLength ); PasswordVersionFound = TRUE; if ( PasswordFound ) { break; } } } //if ( i == AuthInfoCount ) { if ( !PasswordFound ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: %wZ: No clear password for this domain.\n", ClientSession->CsTrustName )); Status = STATUS_NO_TRUST_LSA_SECRET; goto Cleanup; } break; default: NlPrintCs((NL_CRITICAL, ClientSession, "NlGetOutgoingPassword: invalid secure channel type\n" )); Status = STATUS_NO_TRUST_LSA_SECRET; goto Cleanup; } Status = STATUS_SUCCESS; // // Free locally used resources. // Cleanup: if ( !NT_SUCCESS(Status) ) { if ( *CurrentValue != NULL ) { LocalFree( *CurrentValue ); *CurrentValue = NULL; } if ( *OldValue != NULL ) { LocalFree( *OldValue ); *OldValue = NULL; } *CurrentVersionNumber = 0; } if ( SecretHandle != NULL ) { (VOID) LsarClose( &SecretHandle ); } if ( CrCurrentPassword != NULL ) { (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrCurrentPassword ); } if ( CrOldPassword != NULL ) { (VOID) LsaIFree_LSAPR_CR_CIPHER_VALUE ( CrOldPassword ); } if ( TrustInfo != NULL ) { LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO( TrustedDomainAuthInformation, TrustInfo ); } return Status; } NTSTATUS NlSetOutgoingPassword( IN PCLIENT_SESSION ClientSession, IN PUNICODE_STRING CurrentValue OPTIONAL, IN PUNICODE_STRING OldValue OPTIONAL, IN DWORD CurrentVersionNumber, IN DWORD OldVersionNumber ) /*++ Routine Description: Set the outgoing password to be used for the specified client session. Arguments: ClientSession - Structure used to define the session. CurrentValue - Current password for the client session. A NULL pointer indicates there is no current password (blank password) OldValue - Previous password for the client session. A NULL pointer indicates there is no old password (blank password) CurrentVersionNumber - The version number of the Current password. Ignored if this is not an interdomain trust account. OldVersionNumber - The version number of the Old password. Ignored if this is not an interdomain trust account. Return Value: Status of operation. --*/ { NTSTATUS Status; LSAPR_HANDLE SecretHandle = NULL; UNICODE_STRING LocalNullPassword; LSAPR_CR_CIPHER_VALUE CrCurrentPassword; LSAPR_CR_CIPHER_VALUE CrOldPassword; LSAPR_TRUSTED_DOMAIN_INFO TrustInfo; LSAPR_AUTH_INFORMATION CurrentAuthInfo[2]; LSAPR_AUTH_INFORMATION OldAuthInfo[2]; // // Initialization // if ( CurrentValue == NULL ) { CurrentValue = &LocalNullPassword; RtlInitUnicodeString( &LocalNullPassword, NULL ); } if ( OldValue == NULL ) { OldValue = &LocalNullPassword; RtlInitUnicodeString( &LocalNullPassword, NULL ); } // // Workstation and BDC secure channels get their outgoing password from // an LSA secret. // switch ( ClientSession->CsSecureChannelType ) { case ServerSecureChannel: case WorkstationSecureChannel: // // Open the LSA secret to set. // Status = NlOpenSecret( ClientSession, SECRET_SET_VALUE, &SecretHandle ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSetOutgoiningPassword: cannot NlOpenSecret 0x%lx\n", Status )); goto Cleanup; } // // Convert the current password to LSA'ese. // CrCurrentPassword.Buffer = (LPBYTE)CurrentValue->Buffer; CrCurrentPassword.Length = CurrentValue->Length; CrCurrentPassword.MaximumLength = CurrentValue->MaximumLength; // // Convert the old password to LSA'ese. // CrOldPassword.Buffer = (LPBYTE)OldValue->Buffer; CrOldPassword.Length = OldValue->Length; CrOldPassword.MaximumLength = OldValue->MaximumLength; Status = LsarSetSecret( SecretHandle, &CrCurrentPassword, &CrOldPassword ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSetOutgoingPassword: cannot LsarSetSecret 0x%lx\n", Status )); goto Cleanup; } break; // // Trusted domain secure channels get their outgoing password from the trusted // domain object. // case TrustedDomainSecureChannel: case TrustedDnsDomainSecureChannel: // // Fill in the trust information. // RtlZeroMemory( &TrustInfo, sizeof(TrustInfo) ); TrustInfo.TrustedAuthInfo.OutgoingAuthInfos = 2; TrustInfo.TrustedAuthInfo.OutgoingAuthenticationInformation = CurrentAuthInfo; TrustInfo.TrustedAuthInfo.OutgoingPreviousAuthenticationInformation = OldAuthInfo; // // Fill in the current authentication information. // NlQuerySystemTime( &CurrentAuthInfo[0].LastUpdateTime ); CurrentAuthInfo[0].AuthType = TRUST_AUTH_TYPE_CLEAR; CurrentAuthInfo[0].AuthInfoLength = CurrentValue->Length; CurrentAuthInfo[0].AuthInfo = (LPBYTE)CurrentValue->Buffer; // // Fill in the current password version number // CurrentAuthInfo[1].LastUpdateTime = CurrentAuthInfo[0].LastUpdateTime; CurrentAuthInfo[1].AuthType = TRUST_AUTH_TYPE_VERSION; CurrentAuthInfo[1].AuthInfoLength = sizeof( CurrentVersionNumber ); CurrentAuthInfo[1].AuthInfo = (LPBYTE) &CurrentVersionNumber; // // Fill in the old authentication information. // OldAuthInfo[0].LastUpdateTime = CurrentAuthInfo[0].LastUpdateTime; OldAuthInfo[0].AuthType = TRUST_AUTH_TYPE_CLEAR; OldAuthInfo[0].AuthInfoLength = OldValue->Length; OldAuthInfo[0].AuthInfo = (LPBYTE)OldValue->Buffer; // // Fill in the old password version number. // OldAuthInfo[1].LastUpdateTime = CurrentAuthInfo[0].LastUpdateTime; OldAuthInfo[1].AuthType = TRUST_AUTH_TYPE_VERSION; OldAuthInfo[1].AuthInfoLength = sizeof( OldVersionNumber ); OldAuthInfo[1].AuthInfo = (LPBYTE) &OldVersionNumber; // // Get the authentication information from the LSA. // Status = LsarSetTrustedDomainInfoByName( ClientSession->CsDomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING) ClientSession->CsTrustName, TrustedDomainAuthInformation, &TrustInfo ); if (!NT_SUCCESS(Status)) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSetOutgoingPassword: %wZ: cannot LsarSetTrustedDomainInfoByName 0x%lx\n", ClientSession->CsTrustName, Status )); if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Be verbose // NlPrint(( NL_SESSION_SETUP, "NlSetOutgoingPassword: Current Clear Text Password is: " )); NlpDumpBuffer(NL_SESSION_SETUP, CurrentAuthInfo[0].AuthInfo, CurrentAuthInfo[0].AuthInfoLength ); NlPrint(( NL_SESSION_SETUP, "NlSetOutgoingPassword: Current Clear Password Version Number is: 0x%lx\n", CurrentVersionNumber )); NlPrint(( NL_SESSION_SETUP, "NlSetOutgoingPassword: Previous Clear Text Password is: " )); NlpDumpBuffer(NL_SESSION_SETUP, OldAuthInfo[0].AuthInfo, OldAuthInfo[0].AuthInfoLength ); NlPrint(( NL_SESSION_SETUP, "NlSetOutgoingPassword: Previous Clear Password Version Number is: 0x%lx\n", OldVersionNumber )); break; default: NlPrintCs((NL_CRITICAL, ClientSession, "NlSetOutgoingPassword: invalid secure channel type\n" )); Status = STATUS_NO_TRUST_LSA_SECRET; goto Cleanup; } Status = STATUS_SUCCESS; // // Free locally used resources. // Cleanup: if ( SecretHandle != NULL ) { (VOID) LsarClose( &SecretHandle ); } return Status; } NTSTATUS NlGetIncomingPassword( IN PDOMAIN_INFO DomainInfo, IN LPCWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, IN ULONG AllowableAccountControlBits, IN BOOL CheckAccountDisabled, OUT PNT_OWF_PASSWORD OwfPassword OPTIONAL, OUT PNT_OWF_PASSWORD OwfPreviousPassword OPTIONAL, OUT PULONG AccountRid OPTIONAL, OUT PULONG TrustAttributes OPTIONAL, OUT PBOOL IsDnsDomainTrustAccount OPTIONAL ) /*++ Routine Description: Get the incoming password for the specified AccountName and SecureChannelType Check the machine account: Ensure the SecureChannelType is valid, Verify that the account exists, Ensure the user account is the right account type. Arguments: DomainInfo - Emulated domain AccountName - Name of the account to authenticate with. SecureChannelType - The type of the account. Use NullSecureChannel if channel type is not known. AllowableAccountControlBits - The type of the account. Use 0 if AccountControlBits is not known. Typically only one of AllowableAccountControlBits or SecureChannelType will be specified. CheckAccountDisabled - TRUE if we should return an error if the account is disabled. OwfPassword - Returns the NT OWF of the incoming password for the named account. If NULL, the password is not returned. OwfPreviousPassword - Returns the NT OWF of the incoming previous password for the named interdomain trust account. If NULL, the password is not returned. If OwfPreviousPassword is not NULL, OwfPassword must not be NULL either; otherwise the function asserts. If OwfPreviousPassword is not NULL and the account is not interdomain, the function asserts. If both OwfPassword and OwfPreviousPassword are NULL, the account is only checked for validity. AccountRid - Returns the RID of AccountName TrustAttributes - Returns the TrustAttributes for the interdomain trust account. IsDnsDomainTrustAccount - Returns TRUE if the passed in account name is the DNS domain name of an uplevel domain trust. Set only if the account control bits (either passed directly or determined from the secure channel type) correspond to an interdomain trust account. Return Value: Status of operation. --*/ { NTSTATUS Status; SAMPR_HANDLE UserHandle = NULL; PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL; ULONG Length; PLSAPR_TRUSTED_DOMAIN_INFO TrustInfo = NULL; PLSAPR_AUTH_INFORMATION AuthInfo; ULONG AuthInfoCount; BOOL PasswordFound = FALSE; BOOL PreviousPasswordFound = FALSE; ULONG i; // // Initialization // if ( ARGUMENT_PRESENT(AccountRid) ) { *AccountRid = 0; } if ( ARGUMENT_PRESENT(TrustAttributes) ) { *TrustAttributes = 0; } if ( ARGUMENT_PRESENT(IsDnsDomainTrustAccount) ) { *IsDnsDomainTrustAccount = FALSE; // assume it's not and prove if otherwise } Length = wcslen( AccountName ); if ( Length < 1 ) { return STATUS_INVALID_PARAMETER; } // // Convert the secure channel type to allowable account control bits. // switch (SecureChannelType) { case WorkstationSecureChannel: AllowableAccountControlBits |= USER_WORKSTATION_TRUST_ACCOUNT; break; case ServerSecureChannel: AllowableAccountControlBits |= USER_SERVER_TRUST_ACCOUNT; break; case TrustedDomainSecureChannel: AllowableAccountControlBits |= USER_INTERDOMAIN_TRUST_ACCOUNT; break; case TrustedDnsDomainSecureChannel: AllowableAccountControlBits |= USER_DNS_DOMAIN_TRUST_ACCOUNT; break; case NullSecureChannel: if ( AllowableAccountControlBits == 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: Invalid AAC (%x) for %ws\n", AllowableAccountControlBits, AccountName )); return STATUS_INVALID_PARAMETER; } break; default: NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: Invalid channel type (%x) for %ws\n", SecureChannelType, AccountName )); return STATUS_INVALID_PARAMETER; } // // If this is an interdomain trust account, // use an interdomain trust object. // if ( AllowableAccountControlBits == USER_DNS_DOMAIN_TRUST_ACCOUNT || AllowableAccountControlBits == USER_INTERDOMAIN_TRUST_ACCOUNT ) { UNICODE_STRING AccountNameString; // // If this is a DNS trust account, // remove the optional . from the end of the account name. // RtlInitUnicodeString( &AccountNameString, AccountName ); if ( AllowableAccountControlBits == USER_DNS_DOMAIN_TRUST_ACCOUNT ) { if ( Length != 0 && AccountName[Length-1] == '.' ) { AccountNameString.Length -= sizeof(WCHAR); } // // If this is an NT4-style interdomain trust, // remove the $ from the end of the account name. // } else { // // Ensure the account name has the correct postfix. // if ( Length <= SSI_ACCOUNT_NAME_POSTFIX_LENGTH ) { return STATUS_NO_SUCH_USER; } if ( _wcsicmp(&AccountName[Length - SSI_ACCOUNT_NAME_POSTFIX_LENGTH], SSI_ACCOUNT_NAME_POSTFIX) != 0 ) { return STATUS_NO_SUCH_USER; } AccountNameString.Length -= SSI_ACCOUNT_NAME_POSTFIX_LENGTH*sizeof(WCHAR); } // // Get the authentication information from the LSA. // Status = LsarQueryTrustedDomainInfoByName( DomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING) &AccountNameString, TrustedDomainFullInformation, &TrustInfo ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: cannot LsarQueryTrustedDomainInfoByName 0x%lx\n", &AccountNameString, Status )); if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_SUCH_USER; } goto Cleanup; } // // Ensure the attributes of the trust account are right. // if ( (TrustInfo->TrustedFullInfo.Information.TrustDirection & TRUST_DIRECTION_INBOUND) == 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: trust is not inbound\n", &AccountNameString )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } if ( TrustInfo->TrustedFullInfo.Information.TrustType != TRUST_TYPE_DOWNLEVEL && TrustInfo->TrustedFullInfo.Information.TrustType != TRUST_TYPE_UPLEVEL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: trust type doesn't match request type 0x%lx %ld\n", &AccountNameString, AllowableAccountControlBits, TrustInfo->TrustedFullInfo.Information.TrustType )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } if ( TrustInfo->TrustedFullInfo.Information.TrustAttributes & TRUST_ATTRIBUTE_UPLEVEL_ONLY ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: trust is KERB only\n", &AccountNameString )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } // // Return the trust attributes to the caller // if ( ARGUMENT_PRESENT(TrustAttributes) ) { *TrustAttributes = TrustInfo->TrustedFullInfo.Information.TrustAttributes; } // // Determine whether the passed account is a DNS domain trust account // // Simply check if this is a uplevel trust and if the account name passed // is the Name of the trusted domain // if ( ARGUMENT_PRESENT(IsDnsDomainTrustAccount) ) { if ( TrustInfo->TrustedFullInfo.Information.TrustType == TRUST_TYPE_UPLEVEL && TrustInfo->TrustedFullInfo.Information.Name.Length > 0 ) { LPWSTR DnsDomainNameString = NULL; DnsDomainNameString = LocalAlloc( 0, TrustInfo->TrustedFullInfo.Information.Name.Length + sizeof(WCHAR) ); if ( DnsDomainNameString == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlCopyMemory( DnsDomainNameString, TrustInfo->TrustedFullInfo.Information.Name.Buffer, TrustInfo->TrustedFullInfo.Information.Name.Length ); DnsDomainNameString[ TrustInfo->TrustedFullInfo.Information.Name.Length/sizeof(WCHAR) ] = L'\0'; // // Note that we don't have to remove the trailing dot // in AccountName if present because the DNS comprare // API ignores trailing dots. // *IsDnsDomainTrustAccount = NlEqualDnsName(DnsDomainNameString, AccountName); LocalFree( DnsDomainNameString ); } } // // Only get the password if the caller really wants it. // if ( OwfPassword != NULL ) { AuthInfoCount = TrustInfo->TrustedFullInfo.AuthInformation.IncomingAuthInfos; AuthInfo = TrustInfo->TrustedFullInfo.AuthInformation.IncomingAuthenticationInformation; if (AuthInfoCount == 0 || AuthInfo == NULL) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: No auth info for this domain.\n", &AccountNameString )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } // // Loop through the various auth infos looking for the cleartext password // or NT OWF password. // // If there is a clear text password, use it. // Otherwise, use the NT OWF password. // for ( i=0; iTrustedFullInfo.AuthInformation.IncomingAuthInfos; AuthInfo = TrustInfo->TrustedFullInfo.AuthInformation.IncomingPreviousAuthenticationInformation; if (AuthInfoCount == 0 || AuthInfo == NULL) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: No previous auth info for this domain.\n", &AccountNameString )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } // // Loop through the various auth infos looking for the previous cleartext password // or NT OWF password. // // If there is a clear text password, use it. // Otherwise, use the NT OWF password. // for ( i=0; iTrustedFullInfo.Information.FlatName; if ( FlatName->Length < sizeof(WCHAR) || FlatName->Length > CNLEN * sizeof(WCHAR) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %wZ: Flat Name length is bad %ld\n", &AccountNameString, FlatName->Length )); } else { RtlCopyMemory( SamAccountName, FlatName->Buffer, FlatName->Length ); SamAccountName[FlatName->Length/sizeof(WCHAR)] = SSI_ACCOUNT_NAME_POSTFIX_CHAR; SamAccountName[(FlatName->Length/sizeof(WCHAR))+1] = L'\0'; // // Get the account RID from SAM. // // ??? This is a gross hack. // The LSA should return this RID to me directly. // Status = NlSamOpenNamedUser( DomainInfo, SamAccountName, NULL, AccountRid, NULL ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: Can't NlSamOpenNamedUser for %ws 0x%lx.\n", SamAccountName, Status )); goto Cleanup; } } } // // Othewise the account is a SAM user account. // } else { // // OwfPreviousPassword must be NULL for a SAM account // NlAssert( OwfPreviousPassword == NULL ); // // Ensure the account name has the correct postfix. // if ( AllowableAccountControlBits == USER_SERVER_TRUST_ACCOUNT || AllowableAccountControlBits == USER_WORKSTATION_TRUST_ACCOUNT ) { if ( Length <= SSI_ACCOUNT_NAME_POSTFIX_LENGTH ) { return STATUS_NO_SUCH_USER; } if ( _wcsicmp(&AccountName[Length - SSI_ACCOUNT_NAME_POSTFIX_LENGTH], SSI_ACCOUNT_NAME_POSTFIX) != 0 ) { return STATUS_NO_SUCH_USER; } } // // Open the user account. // Status = NlSamOpenNamedUser( DomainInfo, AccountName, &UserHandle, AccountRid, &UserAllInfo ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: Can't NlSamOpenNamedUser for %ws 0x%lx.\n", AccountName, Status )); goto Cleanup; } // // Ensure the Account type matches the account type on the account. // if ( (UserAllInfo->All.UserAccountControl & USER_ACCOUNT_TYPE_MASK & AllowableAccountControlBits ) == 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: Invalid account type (%x) instead of %x for %ws\n", UserAllInfo->All.UserAccountControl & USER_ACCOUNT_TYPE_MASK, AllowableAccountControlBits, AccountName )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } // // Check if the account is disabled. // if ( CheckAccountDisabled ) { if ( UserAllInfo->All.UserAccountControl & USER_ACCOUNT_DISABLED ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %ws account is disabled\n", AccountName )); Status = STATUS_NO_SUCH_USER; goto Cleanup; } } // // Return the password if the caller wants it. // if ( OwfPassword != NULL ) { // // Use the NT OWF Password, // if ( UserAllInfo->All.NtPasswordPresent && UserAllInfo->All.NtOwfPassword.Length == sizeof(*OwfPassword) ) { RtlCopyMemory( OwfPassword, UserAllInfo->All.NtOwfPassword.Buffer, sizeof(*OwfPassword) ); PasswordFound = TRUE; // Allow for the case that the account has no password at all. } else if ( UserAllInfo->All.LmPasswordPresent ) { NlPrint((NL_CRITICAL, "NlGetIncomingPassword: No NT Password for %ws\n", AccountName )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Update the last time this account was used. // { SAMPR_USER_INFO_BUFFER UserInfo; NTSTATUS LogonStatus; UserInfo.Internal2.StatisticsToApply = USER_LOGON_NET_SUCCESS_LOGON; LogonStatus = SamrSetInformationUser( UserHandle, UserInternal2Information, &UserInfo ); if ( !NT_SUCCESS(LogonStatus)) { NlPrint((NL_CRITICAL, "NlGetIncomingPassword: Cannot set last logon time %ws %lx\n", AccountName, LogonStatus )); } } } } // // If no password exists on the account, // return a blank password. // if ( !PasswordFound && OwfPassword != NULL ) { UNICODE_STRING TempUnicodeString; RtlInitUnicodeString(&TempUnicodeString, NULL); Status = RtlCalculateNtOwfPassword(&TempUnicodeString, OwfPassword); if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetIncomingPassword: %ws: cannot RtlCalculateNtOwfPassword (NULL) 0x%lx\n", AccountName, Status )); goto Cleanup; } } // // If no previous password exists on the account, // return the current password. // if ( !PreviousPasswordFound && OwfPreviousPassword != NULL ) { // // If OwfPreviousPassword is not NULL, OwfPassword must not be NULL either. // NlAssert( OwfPassword != NULL ); // // If previous password is not found, return the current one instead. // *OwfPreviousPassword = *OwfPassword; } Status = STATUS_SUCCESS; // // Free locally used resources. // Cleanup: if ( UserAllInfo != NULL ) { SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation); } if ( UserHandle != NULL ) { SamrCloseHandle( &UserHandle ); } if ( TrustInfo != NULL ) { LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO( TrustedDomainFullInformation, TrustInfo ); } return Status; } NTSTATUS NlSetIncomingPassword( IN PDOMAIN_INFO DomainInfo, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, IN PUNICODE_STRING ClearTextPassword OPTIONAL, IN DWORD ClearPasswordVersionNumber, IN PNT_OWF_PASSWORD OwfPassword OPTIONAL ) /*++ Routine Description: Set the incoming password for the specified AccountName and SecureChannelType. At the same time, update the previous password info. Arguments: DomainInfo - Emulated domain AccountName - Name of the account to set the password on SecureChannelType - The type of the account being used. ClearTextPassword - The Clear text password for the named account. ClearPasswordVersionNumber - The version number of the Clear text password. Used only for interdomain trust account. Ignored if ClearTextPassword is NULL. OwfPassword - The NT OWF of the incoming password for the named account. If both the clear text and OWF password are specified, the OWF password is ignored. Return Value: Status of operation. --*/ { NTSTATUS Status; UNICODE_STRING AccountNameString; ULONG Length; LSAPR_TRUSTED_DOMAIN_INFO TrustInfo; PLSAPR_TRUSTED_DOMAIN_INFO TrustInfoOld = NULL; LSAPR_AUTH_INFORMATION CurrentAuthInfo[3], PreviousAuthInfo[3], NoneAuthInfo; ULONG iClear, iOWF, iVersion, i; DWORD OldVersionNumber = 0; // // Workstation and BDC secure channels get their outgoing password from // an LSA secret. // switch ( SecureChannelType ) { case ServerSecureChannel: case WorkstationSecureChannel: NlPrint(( NL_SESSION_SETUP, "Setting Password of '%ws' to: ", AccountName )); if ( ClearTextPassword != NULL ) { NlpDumpBuffer( NL_SESSION_SETUP, ClearTextPassword->Buffer, ClearTextPassword->Length ); } else if (OwfPassword != NULL ) { NlpDumpBuffer( NL_SESSION_SETUP, OwfPassword, sizeof(*OwfPassword) ); } // // Set the encrypted password in SAM. // Status = NlSamChangePasswordNamedUser( DomainInfo, AccountName, ClearTextPassword, OwfPassword ); if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlSetIncomingPassword: Cannot change password on local user account %lX\n", Status)); goto Cleanup; } break; // // Trusted domain secure channels get their incoming password from the trusted // domain object. // case TrustedDomainSecureChannel: case TrustedDnsDomainSecureChannel: // // If this is a DNS trust account, // remove the optional . from the end of the account name. // RtlInitUnicodeString( &AccountNameString, AccountName ); Length = AccountNameString.Length / sizeof(WCHAR); if ( SecureChannelType == TrustedDnsDomainSecureChannel ) { if ( Length != 0 && AccountName[Length-1] == '.' ) { AccountNameString.Length -= sizeof(WCHAR); } // // If this is an NT4-style interdomain trust, // remove the $ from the end of the account name. // } else { // // Ensure the account name has the correct postfix. // if ( Length <= SSI_ACCOUNT_NAME_POSTFIX_LENGTH ) { Status = STATUS_NO_SUCH_USER; goto Cleanup; } if ( _wcsicmp(&AccountName[Length - SSI_ACCOUNT_NAME_POSTFIX_LENGTH], SSI_ACCOUNT_NAME_POSTFIX) != 0 ) { Status = STATUS_NO_SUCH_USER; goto Cleanup; } AccountNameString.Length -= SSI_ACCOUNT_NAME_POSTFIX_LENGTH*sizeof(WCHAR); } // // First get the current authentication information (that is old as far as // the function is concerned) from the LSA. // Status = LsarQueryTrustedDomainInfoByName( DomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING) &AccountNameString, TrustedDomainAuthInformation, &TrustInfoOld ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlSetIncomingPassword: %wZ: cannot LsarQueryTrustedDomainInfoByName 0x%lx\n", &AccountNameString, Status )); // if ( !NlpIsNtStatusResourceError( Status )) { // Status = STATUS_NO_SUCH_USER; // } goto Cleanup; } // // Fill in the trust information. // RtlZeroMemory( &TrustInfo, sizeof(TrustInfo) ); TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 0; TrustInfo.TrustedAuthInfo.IncomingAuthenticationInformation = CurrentAuthInfo; TrustInfo.TrustedAuthInfo.IncomingPreviousAuthenticationInformation = PreviousAuthInfo; // // Fill in the current and previous authentication information. // NlQuerySystemTime( &CurrentAuthInfo[0].LastUpdateTime ); NlPrint(( NL_SESSION_SETUP, "Setting Password of '%ws' to: ", AccountName )); if ( ClearTextPassword != NULL ) { CurrentAuthInfo[0].AuthType = TRUST_AUTH_TYPE_CLEAR; CurrentAuthInfo[0].AuthInfoLength = ClearTextPassword->Length; CurrentAuthInfo[0].AuthInfo = (LPBYTE)ClearTextPassword->Buffer; NlpDumpBuffer(NL_SESSION_SETUP, ClearTextPassword->Buffer, ClearTextPassword->Length ); CurrentAuthInfo[1].LastUpdateTime = CurrentAuthInfo[0].LastUpdateTime; CurrentAuthInfo[1].AuthType = TRUST_AUTH_TYPE_VERSION; CurrentAuthInfo[1].AuthInfoLength = sizeof(ClearPasswordVersionNumber); CurrentAuthInfo[1].AuthInfo = (LPBYTE) &ClearPasswordVersionNumber; NlPrint(( NL_SESSION_SETUP, "Password Version number is %lu\n", ClearPasswordVersionNumber )); } else { CurrentAuthInfo[0].AuthType = TRUST_AUTH_TYPE_NT4OWF; CurrentAuthInfo[0].AuthInfoLength = sizeof(*OwfPassword); CurrentAuthInfo[0].AuthInfo = (LPBYTE)OwfPassword; NlpDumpBuffer(NL_SESSION_SETUP, OwfPassword, sizeof(*OwfPassword) ); } // // The AuthType values of corresponding elements of IncomingAuthenticationInformation and // IncomingPreviousAuthenticationInformation arrays must be the same for internal reasons. // Thus, use NoneAuthInfo element to fill in missing counterparts in these arrays. NoneAuthInfo.LastUpdateTime = CurrentAuthInfo[0].LastUpdateTime; NoneAuthInfo.AuthType = TRUST_AUTH_TYPE_NONE; NoneAuthInfo.AuthInfoLength = 0; NoneAuthInfo.AuthInfo = NULL; // // Find first Clear and OWF passwords (if any) in the old password info. // for ( iClear = 0; iClear < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos; iClear++ ) { if ( TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iClear].AuthType == TRUST_AUTH_TYPE_CLEAR ) { break; } } for ( iVersion = 0; iVersion < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos; iVersion++ ) { if ( TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iVersion].AuthType == TRUST_AUTH_TYPE_VERSION && TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iVersion].AuthInfoLength == sizeof(OldVersionNumber) ) { RtlCopyMemory( &OldVersionNumber, TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iVersion].AuthInfo, sizeof(OldVersionNumber) ); break; } } for ( iOWF = 0; iOWF < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos; iOWF++ ) { if ( TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iOWF].AuthType == TRUST_AUTH_TYPE_NT4OWF ) { break; } } // // Update previous info using only first Clear and OWF passwords in the current info // (that is old as far as this function is concerned). AuthTypes other than Clear, // Version, and OWF are going to be lost. // if (ClearTextPassword != NULL) { if (iClear < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos) { PreviousAuthInfo[0] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iClear]; } else { PreviousAuthInfo[0] = NoneAuthInfo; } // // Preserve the old version number only if it is in accordance with the passed value // if ( iVersion < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos && ClearPasswordVersionNumber > 0 && OldVersionNumber == ClearPasswordVersionNumber - 1 ) { PreviousAuthInfo[1] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iVersion]; } else { PreviousAuthInfo[1] = NoneAuthInfo; } TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 2; // // If there is a previous OWF password, preserve it // if (iOWF < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos) { PreviousAuthInfo[2] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iOWF]; CurrentAuthInfo[2] = NoneAuthInfo; TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 3; } } else { if (iOWF < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos) { PreviousAuthInfo[0] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iOWF]; } else { PreviousAuthInfo[0] = NoneAuthInfo; } TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 1; // // If there is a previous clear text password, preserve it // if (iClear < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos) { PreviousAuthInfo[1] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iClear]; CurrentAuthInfo[1] = NoneAuthInfo; TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 2; } // // If there is a previous clear text password version number, preserve it // if (iVersion < TrustInfoOld->TrustedAuthInfo.IncomingAuthInfos) { PreviousAuthInfo[2] = TrustInfoOld->TrustedAuthInfo.IncomingAuthenticationInformation[iVersion]; CurrentAuthInfo[2] = NoneAuthInfo; TrustInfo.TrustedAuthInfo.IncomingAuthInfos = 3; } } for ( i = 0; i < TrustInfo.TrustedAuthInfo.IncomingAuthInfos; i++ ) { if ( CurrentAuthInfo[i].AuthType == TRUST_AUTH_TYPE_CLEAR) { NlPrint(( NL_SESSION_SETUP, "Current Clear Text Password of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, CurrentAuthInfo[i].AuthInfo, CurrentAuthInfo[i].AuthInfoLength ); } else if ( CurrentAuthInfo[i].AuthType == TRUST_AUTH_TYPE_VERSION ) { NlPrint(( NL_SESSION_SETUP, "Current Clear Password Version Number of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, CurrentAuthInfo[i].AuthInfo, CurrentAuthInfo[i].AuthInfoLength ); } else if ( CurrentAuthInfo[i].AuthType == TRUST_AUTH_TYPE_NT4OWF) { NlPrint(( NL_SESSION_SETUP, "Current OWF Password of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, CurrentAuthInfo[i].AuthInfo, CurrentAuthInfo[i].AuthInfoLength ); } else if ( CurrentAuthInfo[i].AuthType == TRUST_AUTH_TYPE_NONE) { NlPrint(( NL_SESSION_SETUP, "Current Auth Info entry for '%ws' has no type\n", AccountName )); } if ( PreviousAuthInfo[i].AuthType == TRUST_AUTH_TYPE_CLEAR) { NlPrint(( NL_SESSION_SETUP, "Previous Clear Text Password of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, PreviousAuthInfo[i].AuthInfo, PreviousAuthInfo[i].AuthInfoLength ); } else if ( PreviousAuthInfo[i].AuthType == TRUST_AUTH_TYPE_VERSION ) { NlPrint(( NL_SESSION_SETUP, "Previous Clear Password Version Number of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, PreviousAuthInfo[i].AuthInfo, PreviousAuthInfo[i].AuthInfoLength ); } else if ( PreviousAuthInfo[i].AuthType == TRUST_AUTH_TYPE_NT4OWF) { NlPrint(( NL_SESSION_SETUP, "Previous OWF Text Password of '%ws' is: ", AccountName )); NlpDumpBuffer(NL_SESSION_SETUP, PreviousAuthInfo[i].AuthInfo, PreviousAuthInfo[i].AuthInfoLength ); } else if ( PreviousAuthInfo[i].AuthType == TRUST_AUTH_TYPE_NONE) { NlPrint(( NL_SESSION_SETUP, "Previous Auth Info entry for '%ws' has no type\n", AccountName )); } } // // Set the authentication information in the LSA. // Status = LsarSetTrustedDomainInfoByName( DomainInfo->DomLsaPolicyHandle, (PLSAPR_UNICODE_STRING) &AccountNameString, TrustedDomainAuthInformation, &TrustInfo ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlSetIncomingPassword: %wZ: cannot LsarSetTrustedDomainInfoByName 0x%lx\n", &AccountNameString, Status )); goto Cleanup; } break; // // We don't support any other secure channel type // default: NlPrintDom((NL_CRITICAL, DomainInfo, "NlSetIncomingPassword: %ws: invalid secure channel type: %ld\n", AccountName, SecureChannelType )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } Status = STATUS_SUCCESS; // // Free locally used resources. // Cleanup: if ( TrustInfoOld != NULL ) { LsaIFree_LSAPR_TRUSTED_DOMAIN_INFO( TrustedDomainAuthInformation, TrustInfoOld ); } return Status; } BOOLEAN NlTimeToRediscover( IN PCLIENT_SESSION ClientSession, BOOLEAN WithAccount ) /*++ Routine Description: Determine if it is time to rediscover this Client Session. If a session setup failure happens to a discovered DC, rediscover the DC if the discovery happened a long time ago (more than 5 minutes). Arguments: ClientSession - Structure used to define the session. WithAccount - If TRUE, the caller is going to attempt the discovery "with account". Return Value: TRUE -- iff it is time to re-discover --*/ { BOOLEAN ReturnBoolean; EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); // // If the last discovery was longer than 5 minutes ago, // it's fine to rediscover regardless of the rediscovery // type (with or without account) // ReturnBoolean = NetpLogonTimeHasElapsed( ClientSession->CsLastDiscoveryTime, MAX_DC_REAUTHENTICATION_WAIT ); // // If it turns out that the last rediscovery was recent but // the caller is going to attempt the discovery "with account" // perhaps the last rediscovery with account wasn't recent // if ( !ReturnBoolean && WithAccount ) { ReturnBoolean = NetpLogonTimeHasElapsed( ClientSession->CsLastDiscoveryWithAccountTime, MAX_DC_REAUTHENTICATION_WAIT ); } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); return ReturnBoolean; } NET_API_STATUS NlCacheJoinDomainControllerInfo( VOID ) /*++ Routine Description: This function reads from the registry and caches the DC information that was previously written by the join process. This DC is bound to have the correct password for this machine. If no info is available in the registry, no action needs to be taken. The join DC info is cached in the DsGetDcName cache. Netlogon will then discover this DC and will set up a secure channel to it. Caching the DC info in the DsGetDcName cache will ensure that not only Netlogon but every other process will consistently talk to this DC. Arguments: None. Return Value: NO_ERROR - The DC info (if any) was read and the client session strusture was successfully set. Otherwise, some error occured during this operation. --*/ { ULONG WinError = ERROR_SUCCESS; // Registry reading errors NET_API_STATUS NetStatus = NO_ERROR; // Netlogon API return codes HKEY hJoinKey = NULL; ULONG BytesRead = 0; ULONG Type; DWORD KerberosIsDone = 0; LPWSTR DcName = NULL; ULONG DcFlags = 0; PDOMAIN_INFO DomainInfo = NULL; PCLIENT_SESSION ClientSession = NULL; PNL_DC_CACHE_ENTRY DcCacheEntry = NULL; // // Caching the join DC info is needed only for workstations // if ( !NlGlobalMemberWorkstation ) { return NO_ERROR; } // // Open the registry key // WinError = RegOpenKey( HKEY_LOCAL_MACHINE, NETSETUPP_NETLOGON_JD_NAME, &hJoinKey ); if ( WinError != ERROR_SUCCESS) { goto Cleanup; } // // Read DC name // WinError = RegQueryValueEx( hJoinKey, NETSETUPP_NETLOGON_JD_DC, 0, &Type, NULL, &BytesRead); if ( WinError != ERROR_SUCCESS ) { goto Cleanup; } else if ( Type != REG_SZ ) { WinError = ERROR_DATATYPE_MISMATCH; goto Cleanup; } DcName = LocalAlloc( LMEM_ZEROINIT, BytesRead ); if ( DcName == NULL ) { WinError = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } WinError = RegQueryValueEx( hJoinKey, NETSETUPP_NETLOGON_JD_DC, 0, &Type, (PUCHAR) DcName, &BytesRead); if ( WinError != ERROR_SUCCESS) { goto Cleanup; } // // The name should include at least '\\' and one character // if ( wcslen(DcName) < 3 ) { NlPrint(( NL_CRITICAL, "NlCacheJoinDomainControllerInfo: DcName is too short.\n" )); WinError = ERROR_DATATYPE_MISMATCH; goto Cleanup; } // // Read Flags // WinError = RegQueryValueEx( hJoinKey, NETSETUPP_NETLOGON_JD_F, 0, &Type, NULL, &BytesRead); if ( WinError != ERROR_SUCCESS ) { goto Cleanup; } else if ( Type != REG_DWORD ) { WinError = ERROR_DATATYPE_MISMATCH; goto Cleanup; } WinError = RegQueryValueEx( hJoinKey, NETSETUPP_NETLOGON_JD_F, 0, &Type, (PUCHAR)&DcFlags, &BytesRead); if ( WinError != ERROR_SUCCESS) { goto Cleanup; } // // If we've made it up to this point, the registry was successfully read // WinError = ERROR_SUCCESS; NlPrint(( NL_INIT, "Join DC: %ws, Flags: 0x%lx\n", DcName, DcFlags )); // // If we are started right after a domain join, // the browser has been notified about the // domain rename by a change log worker. // Wait until the change log worker exits. // Otherwise, the browser will reject the // datagram send when we pass the new emulated // domain name. Do this even if we end up avoiding // the ping in this routine (for NT4.0 DC) because // we'll do the ping just a little bit later for // the DC discovery. // NlWaitForChangeLogBrowserNotify(); // // If this is not NT5 DC, avoid caching it since it's a PDC. // We don't want to overload the PDC by having all clients // talking to it after they join the domain. We will just // delete the reg key here because Kerberos won't use NT4 DC // anyway. // if ( (DcFlags & DS_DS_FLAG) == 0 ) { ULONG WinErrorTmp = ERROR_SUCCESS; HKEY hJoinKeyTmp = NULL; NlPrint(( NL_INIT, "NlCacheJoinDomainControllerInfo: Join DC is not NT5, deleting it\n" )); WinErrorTmp = RegOpenKeyEx( HKEY_LOCAL_MACHINE, NETSETUPP_NETLOGON_JD_PATH, 0, KEY_ALL_ACCESS, &hJoinKeyTmp ); if ( WinErrorTmp == ERROR_SUCCESS ) { WinErrorTmp = RegDeleteKey( hJoinKeyTmp, NETSETUPP_NETLOGON_JD ); if ( WinErrorTmp != ERROR_SUCCESS ) { NlPrint(( NL_CRITICAL, "NlCacheJoinDomainControllerInfo: Couldn't deleted JoinDomain 0x%lx\n", WinErrorTmp )); } RegCloseKey( hJoinKeyTmp ); } else { NlPrint(( NL_CRITICAL, "NlCacheJoinDomainControllerInfo: RegOpenKeyEx failed 0x%lx\n", WinErrorTmp )); } // // Treat this as error // NetStatus = ERROR_INVALID_DATA; goto Cleanup; } // // Now get the client session to the primary domain // DomainInfo = NlFindNetbiosDomain( NULL, TRUE ); // Primary domain if ( DomainInfo == NULL ) { NlPrint(( NL_CRITICAL, "NlCacheJoinDomainControllerInfo: Cannot NlFindNetbiosDomain\n" )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } ClientSession = NlRefDomClientSession( DomainInfo ); if ( ClientSession == NULL ) { NlPrint(( NL_CRITICAL, "NlCacheJoinDomainControllerInfo: Cannot NlRefDomClientSession\n" )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Finally ping the DC given this info. Cache the response. // NetStatus = NlPingDcName( ClientSession, (DcFlags & DS_DNS_CONTROLLER_FLAG) ? DS_PING_DNS_HOST : DS_PING_NETBIOS_HOST, TRUE, // Cache this DC FALSE, // Do not require IP TRUE, // Ensure the DC has our account FALSE, // Do not refresh the session DcName+2, // Skip '\\' in the name &DcCacheEntry ); if ( NetStatus == NO_ERROR ) { NlPrint(( NL_INIT, "Join DC cached successfully\n" )); // // Also set the site name // if ( DcCacheEntry->UnicodeClientSiteName != NULL ) { NlSetDynamicSiteName( DcCacheEntry->UnicodeClientSiteName ); } } else { NlPrint(( NL_CRITICAL, "Failed to cache join DC: 0x%lx\n", NetStatus )); } Cleanup: // // Free up locally used resources // if ( DcName != NULL ) { LocalFree( DcName ); } if ( DcCacheEntry != NULL ) { NetpDcDerefCacheEntry( DcCacheEntry ); } if ( hJoinKey != NULL ) { RegCloseKey( hJoinKey ); } if ( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } // // If everything is successful return NO_ERROR. // Otherwise, if Netlogon API failed, return its error code. // Otherwise, return registry reading error. // if ( WinError == ERROR_SUCCESS && NetStatus == NO_ERROR ) { return NO_ERROR; } else if ( NetStatus != NO_ERROR ) { return NetStatus; } else { return WinError; } } NTSTATUS NlGetPasswordFromPdc( IN PDOMAIN_INFO DomainInfo, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, OUT PNT_OWF_PASSWORD NtOwfPassword ) /*++ Routine Description: This function is used to by a BDC to get a machine account password from the PDC in the doamin. Arguments: DomainInfo - Identifies the domain the account is in. AccountName -- Name of the account to get the password for. AccountType -- The type of account being accessed. EncryptedNtOwfPassword -- Returns the OWF password of the account. Return Value: NT status code. --*/ { NTSTATUS Status; NETLOGON_AUTHENTICATOR OurAuthenticator; NETLOGON_AUTHENTICATOR ReturnAuthenticator; PCLIENT_SESSION ClientSession = NULL; SESSION_INFO SessionInfo; BOOLEAN FirstTry = TRUE; BOOLEAN AmWriter = FALSE; ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword; NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NlGetPasswordFromPdc: Getting password for %ws from PDC.\n", AccountName )); // // Reference the client session. // ClientSession = NlRefDomClientSession( DomainInfo ); if ( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetPasswordFromPdc: 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, "NlGetPasswordFromPdc: 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; // // Build the Authenticator for this request to the PDC. // NlBuildAuthenticator( &ClientSession->CsAuthenticationSeed, &ClientSession->CsSessionKey, &OurAuthenticator); // // Get the password from the PDC // NL_API_START( Status, ClientSession, TRUE ) { NlAssert( ClientSession->CsUncServerName != NULL ); Status = I_NetServerPasswordGet( ClientSession->CsUncServerName, AccountName, AccountType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, &SessKeyEncrPassword); if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerPasswordGet", Status ); } // NOTE: This call may drop the secure channel behind our back } NL_API_ELSE( Status, ClientSession, TRUE ) { } NL_API_END; // // Now verify primary's authenticator and update our seed // if ( Status == STATUS_ACCESS_DENIED || !NlUpdateSeed( &ClientSession->CsAuthenticationSeed, &ReturnAuthenticator.Credential, &ClientSession->CsSessionKey) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetPasswordFromPdc: denying access after status: 0x%lx\n", Status )); // // Preserve any status indicating a communication error. // if ( NT_SUCCESS(Status) ) { Status = STATUS_ACCESS_DENIED; } NlSetStatusClientSession( ClientSession, Status ); // // Perhaps the netlogon service on the server has just restarted. // Try just once to set up a session to the server again. // if ( FirstTry ) { FirstTry = FALSE; goto FirstTryFailed; } } if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Decrypt the password returned from the PDC. // Status = RtlDecryptNtOwfPwdWithNtOwfPwd( &SessKeyEncrPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, NtOwfPassword ); NlAssert( NT_SUCCESS(Status) ); // // Common exit // Cleanup: if ( ClientSession != NULL ) { if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } NlUnrefClientSession( ClientSession ); } if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NlGetPasswordFromPdc: %ws: failed %lX\n", AccountName, Status)); } return Status; } NTSTATUS NlSessionSetup( IN OUT PCLIENT_SESSION ClientSession ) /*++ Routine Description: Verify that the requestor (this machine) has a valid account at Primary Domain Controller (primary). The authentication is done via an elaborate protocol. This routine will be used only when NETLOGON service starts with role != primary. The requestor (i.e. this machine) will generate a challenge and send it to the Primary Domain Controller and will receive a challenge from the primary in response. Now we will compute credentials using primary's challenge and send it across and wait for credentials, computed at primary using our initial challenge, to be returned by PDC. Before computing credentials a sessionkey will be built which uniquely identifies this session and it will be returned to caller for future use. If both machines authenticate then they keep the ClientCredential and the session key for future use. ?? If multiple domains are supported on a single DC, what mechanism do I use to short circuit discovery? What mechanism do I use to short circuit API calls (e.g., pass through authentication) to a DC in that domain? Do Ihave to worry about lock contention across such API calls? Can I avoid authentication/encryption acress such a secure channel? Arguments: ClientSession - Structure used to define the session. On Input the following fields must be set: CsState CsNetbiosDomainName CsUncServerName (May be NULL string depending on SecureChannelType) CsAccountName CsSecureChannelType The caller must be a writer of the ClientSession. On Output, the following fields will be set CsConnectionStatus CsState CsSessionKey CsAuthenticationSeed Return Value: Status of operation. --*/ { NTSTATUS Status; NETLOGON_CREDENTIAL ServerChallenge; NETLOGON_CREDENTIAL ClientChallenge; NETLOGON_CREDENTIAL ComputedServerCredential; NETLOGON_CREDENTIAL ReturnedServerCredential; BOOLEAN WeDidDiscovery = FALSE; BOOLEAN WeDidDiscoveryWithAccount = FALSE; BOOLEAN ErrorFromDiscoveredServer = FALSE; BOOLEAN SignOrSealError = FALSE; BOOLEAN GotNonDsDc = FALSE; BOOLEAN DomainDowngraded = FALSE; NT_OWF_PASSWORD NtOwfPassword; DWORD NegotiatedFlags; PUNICODE_STRING NewPassword = NULL; PUNICODE_STRING OldPassword = NULL; LARGE_INTEGER PasswordChangeTime; NT_OWF_PASSWORD NewOwfPassword; PNT_OWF_PASSWORD PNewOwfPassword = NULL; NT_OWF_PASSWORD OldOwfPassword; PNT_OWF_PASSWORD POldOwfPassword = NULL; NT_OWF_PASSWORD PdcOwfPassword; ULONG i; ULONG KeyStrength; DWORD DummyPasswordVersionNumber; // // Used to indicate whether the current or the old password is being // tried to access the DC. // 0: implies the current password // 1: implies the old password // 2: implies both failed // DWORD State; // // Ensure we're a writer. // NlAssert( ClientSession->CsReferenceCount > 0 ); NlAssert( ClientSession->CsFlags & CS_WRITER ); NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlSessionSetup: Try Session setup\n" )); // // Start the WMI trace of secure channel setup // NlpTraceEvent( EVENT_TRACE_TYPE_START, NlpGuidSecureChannelSetup ); // // If we're free to pick the DC which services our request, // do so. // // Apparently there was a problem with the previously chosen DC // so we pick again here. (There is a chance we'll pick the same server.) // NlPrint(( NL_SESSION_MORE, "NlSessionSetup: ClientSession->CsState = 0x%lx\n", ClientSession->CsState)); if ( ClientSession->CsState == CS_IDLE ) { NlAssert( ClientSession->CsUncServerName == NULL ); WeDidDiscovery = TRUE; // // Pick the name of a DC in the domain. // // On the first try do not specify the account in // the discovery attempt as discoveries with account // are much more costly than plain discoveries on the // server side. If we fail session setup because the // discovered server doesn't have our account, we will // retry the discovery with account below. // Status = NlDiscoverDc( ClientSession, DT_Synchronous, FALSE, FALSE ) ; // without account if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: cannot pick trusted DC\n" )); goto Cleanup; } } NlAssert( ClientSession->CsState != CS_IDLE ); FirstTryFailed: // // If this is a workstation in an NT5 domain, we should not use NT4 DC. // Indeed, Negotiate will not use an NT4 DC in a mixed mode domain to // prevent a downgrade attack. // if ( NlGlobalMemberWorkstation && (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_HAS_DS) == 0 && (ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST) != 0 ) { NET_API_STATUS NetStatus; PDOMAIN_CONTROLLER_INFOW DomainControllerInfo = NULL; GotNonDsDc = TRUE; NlPrintCs(( NL_CRITICAL, ClientSession, "NlSessionSetup: Only downlevel DC available\n" )); // // Determine whether the domain has been downgraded (just to warn // the user). To determine this, try to discover a PDC and if the // PDC is available and it is NT4, the domain has been indeed // downgraded. In such case, this workstation should rejoin the // domain. // NetStatus = DsrGetDcNameEx2( NULL, NULL, 0, NULL, NULL, NULL, DS_PDC_REQUIRED | DS_FORCE_REDISCOVERY, &DomainControllerInfo ); if ( NetStatus == NO_ERROR && (DomainControllerInfo->Flags & DS_DS_FLAG) == 0 ) { DomainDowngraded = TRUE; // Domain has been downgraded (rejoin needed) NlPrintCs(( NL_CRITICAL, ClientSession, "NlSessionSetup: NT5 domain has been downgraded.\n" )); } if ( DomainControllerInfo != NULL ) { NetApiBufferFree( DomainControllerInfo ); } Status = STATUS_NO_LOGON_SERVERS; ErrorFromDiscoveredServer = TRUE; goto Cleanup; } // // Prepare our challenge // NlComputeChallenge( &ClientChallenge ); NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ClientChallenge = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ClientChallenge, sizeof(ClientChallenge) ); // // Get the Password of the account from LSA secret storage // Status = NlGetOutgoingPassword( ClientSession, &NewPassword, &OldPassword, &DummyPasswordVersionNumber, &PasswordChangeTime ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: cannot NlGetOutgoingPassword 0x%lx\n", Status )); // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Try setting up a secure channel first using the CurrentPassword. // If that fails, try using the OldPassword // If that fails, for interdomain trusts, try the password from our PDC // for ( State = 0; ; State++ ) { // // Use the right password for this iteration // if ( State == 0 ) { // // If the new password isn't present in the LSA, // just ignore it. // if ( NewPassword == NULL ) { continue; } // // Compute the NT OWF password // Status = RtlCalculateNtOwfPassword( NewPassword, &NewOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Try this password // PNewOwfPassword = &NewOwfPassword; NtOwfPassword = NewOwfPassword; NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Clear New Password = " )); NlpDumpBuffer(NL_CHALLENGE_RES, NewPassword->Buffer, NewPassword->Length ); NlpDumpTime( NL_CHALLENGE_RES, "NlSessionSetup: Password Changed: ", PasswordChangeTime ); // // On the second iteration, use the old password // } else if ( State == 1 ) { // // If the old password isn't present in the LSA, // just ignore it. // if ( OldPassword == NULL ) { continue; } // // Check if the old password is the same as the new one // if ( NewPassword != NULL && OldPassword != NULL && NewPassword->Length == OldPassword->Length && RtlEqualMemory( NewPassword->Buffer, OldPassword->Buffer, OldPassword->Length ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: new password is bad. Old password is same as new password.\n" )); continue; // Try the password from our PDC } // // Compute the NT OWF password // Status = RtlCalculateNtOwfPassword( OldPassword, &OldOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Try this password // POldOwfPassword = &OldOwfPassword; NtOwfPassword = OldOwfPassword; NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: new password is bad, try old one\n" )); NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Clear Old Password = " )); NlpDumpBuffer(NL_CHALLENGE_RES, OldPassword->Buffer, OldPassword->Length ); NlpDumpTime( NL_CHALLENGE_RES, "NlSessionSetup: Password Changed: ", PasswordChangeTime ); // // On the third iteration, for an interdomain trust account, // use the password from the PDC. We actually think this is // useful only for NT4 trusted side that keeps only one // password. For NT5 or later, one of the passwords above // should work, but ... // } else if ( State == 2 && ClientSession->CsDomainInfo->DomRole == RoleBackup && IsDomainSecureChannelType(ClientSession->CsSecureChannelType) ) { Status = NlGetPasswordFromPdc( ClientSession->CsDomainInfo, ClientSession->CsAccountName, ClientSession->CsSecureChannelType, &PdcOwfPassword ); if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, ClientSession->CsDomainInfo, "NlSessionSetup: Can't NlGetPasswordFromPdc %ws 0x%lx.\n", ClientSession->CsAccountName, Status )); // Ignore the particular status from the PDC Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Check if this password is the same as the new one we have // if ( PNewOwfPassword != NULL && RtlEqualNtOwfPassword(&PdcOwfPassword, PNewOwfPassword) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlSessionSetup: PDC password is same as new password.\n" )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Check if this password is the same as the old one we have // if ( POldOwfPassword != NULL && RtlEqualNtOwfPassword(&PdcOwfPassword, POldOwfPassword) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlSessionSetup: PDC password is same as old password.\n" )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Try this password // NtOwfPassword = PdcOwfPassword; NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: try password from the PDC\n" )); // // We tried our best but nothing worked // } else { Status = STATUS_ACCESS_DENIED; goto Cleanup; } NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Password = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &NtOwfPassword, sizeof(NtOwfPassword) ); // // Get the primary's challenge // NlAssert( ClientSession->CsState != CS_IDLE ); NL_API_START( Status, ClientSession, TRUE ) { NlAssert( ClientSession->CsUncServerName != NULL ); Status = I_NetServerReqChallenge(ClientSession->CsUncServerName, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &ClientChallenge, &ServerChallenge ); if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerReqChallenge", Status ); } } NL_API_ELSE ( Status, ClientSession, FALSE ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: " "cannot FinishApiClientSession for I_NetServerReqChallenge 0x%lx\n", Status )); // Failure here indicates that the discovered server is really slow. // Let the "ErrorFromDiscoveredServer" logic do the rediscovery. if ( NT_SUCCESS(Status) ) { // We're dropping the secure channel so // ensure we don't use any successful status from the DC Status = STATUS_NO_LOGON_SERVERS; } ErrorFromDiscoveredServer = TRUE; goto Cleanup; } NL_API_END; if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: " "cannot I_NetServerReqChallenge 0x%lx\n", Status )); // // If access is denied, it might be because we weren't able to // authenticate with the new password, try the old password. // // Between NT 5 machines, we use Kerberos (and the machine account) to // authenticate this machine. if ( Status == STATUS_ACCESS_DENIED && State == 0 ) { continue; } ErrorFromDiscoveredServer = TRUE; goto Cleanup; } NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerChallenge = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ServerChallenge, sizeof(ServerChallenge) ); // // For NT 5 to NT 5, // use a stronger session key. // if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_HAS_DS) != 0 || NlGlobalParameters.RequireStrongKey ) { KeyStrength = NETLOGON_SUPPORTS_STRONG_KEY; } else { KeyStrength = 0; } // // Actually compute the session key given the two challenges and the // password. // Status = NlMakeSessionKey( KeyStrength, &NtOwfPassword, &ClientChallenge, &ServerChallenge, &ClientSession->CsSessionKey ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: cannot NlMakeSessionKey 0x%lx\n", Status )); goto Cleanup; } NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: SessionKey = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ClientSession->CsSessionKey, sizeof(ClientSession->CsSessionKey) ); // // Prepare credentials using our challenge. // NlComputeCredentials( &ClientChallenge, &ClientSession->CsAuthenticationSeed, &ClientSession->CsSessionKey ); NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: Authentication Seed = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ClientSession->CsAuthenticationSeed, sizeof(ClientSession->CsAuthenticationSeed) ); // // Send these credentials to primary. The primary will compute // credentials using the challenge supplied by us and compare // with these. If both match then it will compute credentials // using its challenge and return it to us for verification // NL_API_START( Status, ClientSession, TRUE ) { NegotiatedFlags = NETLOGON_SUPPORTS_MASK | KeyStrength | (NlGlobalParameters.AvoidSamRepl ? NETLOGON_SUPPORTS_AVOID_SAM_REPL : 0) | #ifdef ENABLE_AUTH_RPC ((NlGlobalParameters.SignSecureChannel||NlGlobalParameters.SealSecureChannel) ? (NETLOGON_SUPPORTS_AUTH_RPC|NETLOGON_SUPPORTS_LSA_AUTH_RPC) : 0) | #endif // ENABLE_AUTH_RPC (NlGlobalParameters.AvoidLsaRepl ? NETLOGON_SUPPORTS_AVOID_LSA_REPL : 0) | (NlGlobalParameters.NeutralizeNt4Emulator ? NETLOGON_SUPPORTS_NT4EMULATOR_NEUTRALIZER : 0); NlAssert( ClientSession->CsUncServerName != NULL ); ClientSession->CsNegotiatedFlags = NegotiatedFlags; Status = I_NetServerAuthenticate3( ClientSession->CsUncServerName, ClientSession->CsAccountName, ClientSession->CsSecureChannelType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &ClientSession->CsAuthenticationSeed, &ReturnedServerCredential, &ClientSession->CsNegotiatedFlags, &ClientSession->CsAccountRid ); // // Releases older then NT 5.0 used older authentication API. // if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) { NlPrint((NL_CRITICAL,"NlSessionSetup: Fall back to Authenticate2\n" )); ClientSession->CsNegotiatedFlags = NegotiatedFlags; ClientSession->CsAccountRid = 0; Status = I_NetServerAuthenticate2( ClientSession->CsUncServerName, ClientSession->CsAccountName, ClientSession->CsSecureChannelType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &ClientSession->CsAuthenticationSeed, &ReturnedServerCredential, &ClientSession->CsNegotiatedFlags ); if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) { ClientSession->CsNegotiatedFlags = 0; Status = I_NetServerAuthenticate( ClientSession->CsUncServerName, ClientSession->CsAccountName, ClientSession->CsSecureChannelType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &ClientSession->CsAuthenticationSeed, &ReturnedServerCredential ); if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerAuthenticate", Status ); } } else if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerAuthenticate2", Status ); } } else if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerAuthenticate3", Status ); } } NL_API_ELSE( Status, ClientSession, FALSE ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: " "cannot FinishApiClientSession for I_NetServerAuthenticate 0x%lx\n", Status )); // Failure here indicates that the discovered server is really slow. // Let the "ErrorFromDiscoveredServer" logic do the rediscovery. if ( NT_SUCCESS(Status) ) { // We're dropping the secure channel so // ensure we don't use any successful status from the DC Status = STATUS_NO_LOGON_SERVERS; } ErrorFromDiscoveredServer = TRUE; goto Cleanup; } NL_API_END; if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: " "cannot I_NetServerAuthenticate 0x%lx\n", Status )); // // If access is denied, it might be because we weren't able to // authenticate with the new password, try the old password // or password from the PDC. // if ( Status == STATUS_ACCESS_DENIED && State < 2 ) { continue; } ErrorFromDiscoveredServer = TRUE; goto Cleanup; } NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerCredential GOT = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ReturnedServerCredential, sizeof(ReturnedServerCredential) ); // // The DC returned a server credential to us, // ensure the server credential matches the one we would compute. // NlComputeCredentials( &ServerChallenge, &ComputedServerCredential, &ClientSession->CsSessionKey); NlPrint((NL_CHALLENGE_RES,"NlSessionSetup: ServerCredential MADE = " )); NlpDumpBuffer(NL_CHALLENGE_RES, &ComputedServerCredential, sizeof(ComputedServerCredential) ); if ( !RtlEqualMemory( &ReturnedServerCredential, &ComputedServerCredential, sizeof(ReturnedServerCredential)) ) { Status = STATUS_ACCESS_DENIED; NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: " "Servercredential don't match ours 0x%lx\n", Status)); goto Cleanup; } // // If we require signing or sealing and didn't negotiate it, // fail now. // if ( NlGlobalParameters.RequireSignOrSeal && (ClientSession->CsNegotiatedFlags & NETLOGON_SUPPORTS_AUTH_RPC) == 0 ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: SignOrSeal required and DC doesn't support it\n" )); SignOrSealError = TRUE; Status = STATUS_ACCESS_DENIED; ErrorFromDiscoveredServer = TRUE; // Highly unlikely that retrying will work, but ... goto Cleanup; } // // If we require signing or sealing and didn't negotiate it, // fail now. // // We'll never really get this far. Since we used a strong key, // we'll get ACCESS_DENIED above. // if ( NlGlobalParameters.RequireStrongKey && (ClientSession->CsNegotiatedFlags & NETLOGON_SUPPORTS_STRONG_KEY) == 0 ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: StrongKey required and DC doesn't support it\n" )); SignOrSealError = TRUE; Status = STATUS_ACCESS_DENIED; ErrorFromDiscoveredServer = TRUE; // Highly unlikely that retrying will work, but ... goto Cleanup; } // // If we've made it this far, we've successfully authenticated // with the DC, drop out of the loop. // break; } // // If the new DC is an NT 5 DC, // mark it so. // if ((ClientSession->CsNegotiatedFlags & NETLOGON_SUPPORTS_GENERIC_PASSTHRU) != 0 ) { NlPrintCs(( NL_SESSION_MORE, ClientSession, "NlSessionSetup: DC is an NT 5 DC: %ws\n", ClientSession->CsUncServerName )); // // This flag would have been set during discovery if real discovery was // done. However, if NlSetServerClientSession was called from anywhere // else other than discovery, the flag may not yet be set. // EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_HAS_DS; LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); // // This flag would be set during client session creation if the domain // was an NT 5 domain at that time. If we happened to stumble on an // NT 5 DC after the fact, mark it now. // if ( ClientSession->CsSecureChannelType == WorkstationSecureChannel ) { LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); ClientSession->CsFlags |= CS_NT5_DOMAIN_TRUST; UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); } } // // If we used the old password to authenticate, // update the DC to the current password ASAP. // // Note that we don't need to reset the scavenge event here to start // the scavenging imeediately to change the password. On DCs, the // scavenging will always happen within the scavenging interval. // On workstations, one might worry that the scavenging may happen // in the distant future at next password reset interval (which is // long, 30 days by default). However, if this is the first session // setup on service start, the scavenging will start immediately and // it will be scheduled every scavenging interval until the password // is set on the DC. Otherwise, if this is not the first session setup // and password got into mismatch due a failed password change on the // DC, the password change routine will schedule the scavenging to run // within the scavenge period until the password is set, so we don't // need to reset the timer here since it's alrteady scheduled properly. // if ( State == 1 ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: old password succeeded\n" )); LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); ClientSession->CsFlags |= CS_UPDATE_PASSWORD; UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); } // // Save the password for our own future reference. // RtlCopyMemory( &ClientSession->CsNtOwfPassword, &NtOwfPassword, sizeof( NtOwfPassword )); // // If this is a workstation, // grab useful information about the domain. // NlSetStatusClientSession( ClientSession, STATUS_SUCCESS ); // Mark session as authenticated if ( NlGlobalMemberWorkstation ) { Status = NlUpdateDomainInfo( ClientSession ); if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: NlUpdateDomainInfo failed 0x%lX\n", Status )); ErrorFromDiscoveredServer = TRUE; goto Cleanup; } // // If this is a DC, // determine if we should get the FTinfo from the trusted domain. // } else { PLSA_FOREST_TRUST_INFORMATION ForestTrustInfo; // // If this is the PDC, // and the trusted domain is a cross forest trust, // get the FTinfo from the trusted domain and write it to our TDO. // // Ignore failures. // if ( ClientSession->CsDomainInfo->DomRole == RolePrimary && (ClientSession->CsTrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0 ) { Status = NlpGetForestTrustInfoHigher( ClientSession, DS_GFTI_UPDATE_TDO, FALSE, // Don't impersonate caller TRUE, // We set up the session &ForestTrustInfo ); if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: NlpGetForestTrustInfoHigher failed 0x%lX\n", Status )); ErrorFromDiscoveredServer = TRUE; goto Cleanup; } else { NetApiBufferFree( ForestTrustInfo ); } } } Status = STATUS_SUCCESS; // // Cleanup // Cleanup: // // Free locally used resources // if ( NewPassword != NULL ) { LocalFree( NewPassword ); } if ( OldPassword != NULL ) { LocalFree( OldPassword ); } // // Upon success, save the status and reset counters. // if ( NT_SUCCESS(Status) ) { NlSetStatusClientSession( ClientSession, Status ); ClientSession->CsAuthAlertCount = 0; ClientSession->CsTimeoutCount = 0; ClientSession->CsFastCallCount = 0; #if NETLOGONDBG if ( ClientSession->CsNegotiatedFlags != NegotiatedFlags ) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlSessionSetup: negotiated %lx flags rather than %lx\n", ClientSession->CsNegotiatedFlags, NegotiatedFlags )); } #endif // NETLOGONDBG // // write event log and raise alert // } else { BOOLEAN RetryDiscovery = FALSE; BOOLEAN RetryDiscoveryWithAccount = FALSE; WCHAR PreviouslyDiscoveredServer[NL_MAX_DNS_LENGTH+3]; LPWSTR MsgStrings[5]; // // Save the name of the discovered server. // if ( ClientSession->CsUncServerName != NULL ) { wcscpy( PreviouslyDiscoveredServer, ClientSession->CsUncServerName ); } else { wcscpy( PreviouslyDiscoveredServer, L"" ); } // // If the failure came from the discovered server, // decide whether we should retry the session setup // to a different server // if ( ErrorFromDiscoveredServer ) { // // If we didn't do the plain discovery (without account) just now, // try the discovery again and redo the session setup. // if ( !WeDidDiscovery && NlTimeToRediscover(ClientSession, FALSE) ) { RetryDiscovery = TRUE; } // // If we didn't do the discovery with account and // the session setup failed because the server didn't have our account and // we didn't try a discovery with account recently, // try the discovery again (with account) and redo the session setup. // if ( !WeDidDiscoveryWithAccount && (Status == STATUS_NO_SUCH_USER || Status == STATUS_NO_TRUST_SAM_ACCOUNT) && NlTimeToRediscover(ClientSession, TRUE) ) { RetryDiscoveryWithAccount = TRUE; } } // // If we are to retry the discovery, do so // if ( RetryDiscovery || RetryDiscoveryWithAccount ) { NTSTATUS TempStatus; NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlSessionSetup: Retry failed session setup (%s account) since discovery wasn't recent.\n", (RetryDiscoveryWithAccount ? "with" : "without") )); // // Pick the name of a new DC in the domain. // NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); TempStatus = NlDiscoverDc( ClientSession, DT_Synchronous, FALSE, RetryDiscoveryWithAccount ); // retry with account as needed if ( NT_SUCCESS(TempStatus) ) { // // Don't bother redoing the session setup if we picked the same DC. // In particular, if we retried because the previously found DC // didn't have our account, we retried the discovery with account // above but may have got the same DC (shouldn't really happen, but...) // if ( _wcsicmp( ClientSession->CsUncServerName, PreviouslyDiscoveredServer ) != 0 ) { // // We certainly did a discovery here, // but it may or may not be with account // WeDidDiscovery = TRUE; WeDidDiscoveryWithAccount = RetryDiscoveryWithAccount; goto FirstTryFailed; } else { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlSessionSetup: Skip retry failed session setup since same DC discovered.\n" )); } } else { NlPrintCs((NL_CRITICAL, ClientSession, "NlSessionSetup: Session setup: cannot re-pick trusted DC\n" )); } } switch(Status) { case STATUS_NO_TRUST_LSA_SECRET: MsgStrings[0] = PreviouslyDiscoveredServer; MsgStrings[1] = ClientSession->CsDebugDomainName; MsgStrings[2] = ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, MsgStrings[3] = NULL; // RaiseNetlogonAlert NlpWriteEventlog (NELOG_NetlogonAuthNoTrustLsaSecret, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 3 ); RaiseNetlogonAlert( NELOG_NetlogonAuthNoTrustLsaSecret, MsgStrings, &ClientSession->CsAuthAlertCount); break; case STATUS_NO_TRUST_SAM_ACCOUNT: MsgStrings[0] = PreviouslyDiscoveredServer; MsgStrings[1] = ClientSession->CsDebugDomainName; MsgStrings[2] = ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer; MsgStrings[3] = ClientSession->CsAccountName; MsgStrings[4] = NULL; // RaiseNetlogonAlert NlpWriteEventlog (NELOG_NetlogonAuthNoTrustSamAccount, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 4 ); RaiseNetlogonAlert( NELOG_NetlogonAuthNoTrustSamAccount, MsgStrings, &ClientSession->CsAuthAlertCount); break; case STATUS_ACCESS_DENIED: if ( SignOrSealError ) { MsgStrings[0] = PreviouslyDiscoveredServer; MsgStrings[1] = ClientSession->CsDebugDomainName; MsgStrings[2] = NULL; // RaiseNetlogonAlert NlpWriteEventlog (NELOG_NetlogonRequireSignOrSealError, EVENTLOG_ERROR_TYPE, NULL, 0, MsgStrings, 2 ); RaiseNetlogonAlert( NELOG_NetlogonRequireSignOrSealError, MsgStrings, &ClientSession->CsAuthAlertCount); } else { MsgStrings[0] = ClientSession->CsDebugDomainName; MsgStrings[1] = PreviouslyDiscoveredServer; MsgStrings[2] = NULL; // RaiseNetlogonAlert NlpWriteEventlog (NELOG_NetlogonAuthDCFail, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 2 ); RaiseNetlogonAlert( NELOG_NetlogonAuthDCFail, MsgStrings, &ClientSession->CsAuthAlertCount); } break; case STATUS_NO_LOGON_SERVERS: default: MsgStrings[0] = ClientSession->CsDebugDomainName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); // The order of checks is important if ( DomainDowngraded ) { NlpWriteEventlog (NELOG_NetlogonAuthDomainDowngraded, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } else if ( GotNonDsDc ) { NlpWriteEventlog (NELOG_NetlogonAuthNoUplevelDomainController, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } else { NlpWriteEventlog (NELOG_NetlogonAuthNoDomainController, EVENTLOG_ERROR_TYPE, (LPBYTE) &Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } MsgStrings[0] = ClientSession->CsDebugDomainName; MsgStrings[1] = PreviouslyDiscoveredServer; MsgStrings[2] = NULL; // RaiseNetlogonAlert RaiseNetlogonAlert( ALERT_NetlogonAuthDCFail, MsgStrings, &ClientSession->CsAuthAlertCount); break; } // // ??: Is this how to handle failure for all account types. // switch(Status) { case STATUS_NO_TRUST_LSA_SECRET: case STATUS_NO_TRUST_SAM_ACCOUNT: case STATUS_ACCESS_DENIED: NlSetStatusClientSession( ClientSession, Status ); break; default: NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); break; } } // // Mark the time we last tried to authenticate. // // We need to do this after NlSetStatusClientSession which zeros // CsLastAuthenticationTry. // EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); NlQuerySystemTime( &ClientSession->CsLastAuthenticationTry ); LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlSessionSetup: Session setup %s\n", (NT_SUCCESS(ClientSession->CsConnectionStatus)) ? "Succeeded" : "Failed" )); // // End the WMI trace of secure channel setup // NlpTraceEvent( EVENT_TRACE_TYPE_END, NlpGuidSecureChannelSetup ); return Status; } BOOLEAN NlTimeHasElapsedEx( IN PLARGE_INTEGER StartTime, IN PLARGE_INTEGER Period, OUT PULONG TimeInterval OPTIONAL ) /*++ Routine Description: Determine if "Timeout" milliseconds has has elapsed since StartTime. Arguments: StartTime - Specifies an absolute time when the event started (100ns units). Period - Specifies a relative time in 100ns units. TimeInterval - If specified and time has elapsed, returns the amount of time (in milliseconds) passed since the timeout. If specified and time has not elapsed, returns the amount of time (in milliseconds) left until Period elapses. Return Value: TRUE -- iff Period 100nano-seconds have elapsed since StartTime. --*/ { LARGE_INTEGER TimeNow; LARGE_INTEGER ElapsedTime; BOOLEAN Result = FALSE; // // // Compute the elapsed time since we last authenticated // // NlpDumpTime( NL_MISC, "StartTime: ", *StartTime ); NlQuerySystemTime( &TimeNow ); // NlpDumpTime( NL_MISC, "TimeNow: ", TimeNow ); ElapsedTime.QuadPart = TimeNow.QuadPart - StartTime->QuadPart; // NlpDumpTime( NL_MISC, "ElapsedTime: ", ElapsedTime ); // NlpDumpTime( NL_MISC, "Period: ", *Period ); // // If the elapsed time is negative (totally bogus) or greater than the // maximum allowed, indicate that enough time has passed. // // if ( ElapsedTime.QuadPart < 0 ) { if ( ARGUMENT_PRESENT( TimeInterval )) { *TimeInterval = 0; // pretend it just elapsed } return TRUE; } if ( ElapsedTime.QuadPart > Period->QuadPart ) { Result = TRUE; } else { Result = FALSE; } // // If the caller want to know the amount of time left, // compute it. // if ( ARGUMENT_PRESENT( TimeInterval )) { LARGE_INTEGER TimeRemaining; LARGE_INTEGER MillisecondsRemaining; /*lint -e569 */ /* don't complain about 32-bit to 31-bit initialize */ LARGE_INTEGER BaseGetTickMagicDivisor = { 0xe219652c, 0xd1b71758 }; /*lint +e569 */ /* don't complain about 32-bit to 31-bit initialize */ CCHAR BaseGetTickMagicShiftCount = 13; // // Compute the Time remaining/passed on the timer. // if ( Result == FALSE ) { TimeRemaining.QuadPart = Period->QuadPart - ElapsedTime.QuadPart; } else { TimeRemaining.QuadPart = ElapsedTime.QuadPart - Period->QuadPart; } // NlpDumpTime( NL_MISC, "TimeRemaining: ", TimeRemaining ); // // Compute the number of milliseconds remaining/passed. // MillisecondsRemaining = RtlExtendedMagicDivide( TimeRemaining, BaseGetTickMagicDivisor, BaseGetTickMagicShiftCount ); // NlpDumpTime( NL_MISC, "MillisecondsRemaining: ", MillisecondsRemaining ); // // If the time is in the far distant future/past, // round it down. // if ( MillisecondsRemaining.HighPart != 0 || MillisecondsRemaining.LowPart > TIMER_MAX_PERIOD ) { *TimeInterval = TIMER_MAX_PERIOD; } else { *TimeInterval = MillisecondsRemaining.LowPart; } } return Result; } BOOLEAN NlTimeToReauthenticate( IN PCLIENT_SESSION ClientSession ) /*++ Routine Description: Determine if it is time to reauthenticate this Client Session. To reduce the number of re-authentication attempts, we try to re-authenticate only on demand and then only at most every 45 seconds. Arguments: ClientSession - Structure used to define the session. Return Value: TRUE -- iff it is time to re-authenticate --*/ { BOOLEAN ReturnBoolean; EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); ReturnBoolean = NetpLogonTimeHasElapsed( ClientSession->CsLastAuthenticationTry, MAX_DC_AUTHENTICATION_WAIT ); LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); return ReturnBoolean; } NET_API_STATUS NlCreateShare( LPWSTR SharePath, LPWSTR ShareName, BOOLEAN AllowAuthenticatedUsers, BOOL UpdateExclusiveShareAccess, BOOL AllowExclusiveShareAccess ) /*++ Routine Description: Share the netlogon scripts directory. Arguments: SharePath - Path that the new share should be point to. ShareName - Name of the share. AllowAuthenticatedUsers - TRUE if AuthenticatedUsers should have Full Control on this share. UpdateExclusiveShareAccess - If TRUE, the exclusive share access semantics will be updated as specified by AllowExclusiveShareAccess. AllowExclusiveShareAccess - If TRUE, the exclusive access to the specified share will be granted. Otherwise, the exclusive access to the specified share will not be granted. This parameter is ignored if UpdateExclusiveShareAccess is FALSE. Return Value: TRUE: if successful FALSE: if error (NlExit was called) --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; SHARE_INFO_502 ShareInfo502; PSHARE_INFO_1005 ShareInfo1005 = NULL; DWORD CurrentFlags = 0; WORD AnsiSize; CHAR AnsiRemark[NNLEN+1]; TCHAR Remark[NNLEN+1]; ACE_DATA AceData[] = { {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_EXECUTE | GENERIC_READ, &WorldSid}, {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AliasAdminsSid}, // Must be the last ACE {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AuthenticatedUserSid} }; ULONG AceCount = (sizeof(AceData)/sizeof(AceData[0])); // // If Authenticated Users shouldn't be allowed full control, // remove the authenticated user ACE. // if ( !AllowAuthenticatedUsers ) { AceCount --; } // // Build the structure describing the share. // ShareInfo502.shi502_path = SharePath; ShareInfo502.shi502_security_descriptor = NULL; NlPrint((NL_INIT, "'%ws' share is to '%ws'\n", ShareName, SharePath)); NetStatus = (NET_API_STATUS) DosGetMessage( NULL, // No insertion strings 0, // No insertion strings AnsiRemark, sizeof(AnsiRemark), MTXT_LOGON_SRV_SHARE_REMARK, MESSAGE_FILENAME, &AnsiSize ); if ( NetStatus == NERR_Success ) { NetpCopyStrToTStr( Remark, AnsiRemark ); ShareInfo502.shi502_remark = Remark; } else { ShareInfo502.shi502_remark = TEXT( "" ); } ShareInfo502.shi502_netname = ShareName; ShareInfo502.shi502_type = STYPE_DISKTREE; ShareInfo502.shi502_permissions = ACCESS_READ; ShareInfo502.shi502_max_uses = 0xffffffff; ShareInfo502.shi502_passwd = TEXT(""); // // Set the security descriptor on the share // // // Create a security descriptor containing the DACL. // Status = NetpCreateSecurityDescriptor( AceData, AceCount, NULL, // Default the owner Sid NULL, // Default the primary group &ShareInfo502.shi502_security_descriptor ); if ( !NT_SUCCESS( Status ) ) { NlPrint((NL_CRITICAL, "'%ws' share: Cannot create security descriptor 0x%lx\n", SharePath, Status )); NetStatus = NetpNtStatusToApiStatus( Status ); return NetStatus; } // // Create the share. // NetStatus = NetShareAdd(NULL, 502, (LPBYTE) &ShareInfo502, NULL); if (NetStatus == NERR_DuplicateShare) { PSHARE_INFO_2 ShareInfo2 = NULL; NlPrint((NL_INIT, "'%ws' share already exists. \n", ShareName)); // // check to see the shared path is same. // NetStatus = NetShareGetInfo( NULL, ShareName, 2, (LPBYTE *) &ShareInfo2 ); if ( NetStatus == NERR_Success ) { // // compare path names. // // ShareName is path canonicalized already. // // NlPrint((NL_INIT, "'%ws' share current path is %ws\n", ShareName, ShareInfo2->shi2_path)); if( NetpwPathCompare( SharePath, ShareInfo2->shi2_path, 0, 0 ) != 0 ) { // // delete share. // NetStatus = NetShareDel( NULL, ShareName, 0); if( NetStatus == NERR_Success ) { // // Recreate share. // NetStatus = NetShareAdd( NULL, 502, (LPBYTE) &ShareInfo502, NULL); if( NetStatus == NERR_Success ) { NlPrint((NL_INIT, "'%ws' share was recreated with new path %ws\n", ShareName, SharePath )); } } } } if( ShareInfo2 != NULL ) { NetpMemoryFree( ShareInfo2 ); } } // // Free the security descriptor // NetpMemoryFree( ShareInfo502.shi502_security_descriptor ); if ( NetStatus != NERR_Success ) { NlPrint((NL_CRITICAL, "'%ws' share: Error attempting to create-share: %ld\n", ShareName, NetStatus )); return NetStatus; } // // If we don't need to update the exclusing share access semantics, // we are done // if ( !UpdateExclusiveShareAccess ) { return NERR_Success; } // // Set the exclusive share access semantics as appropriate // NetStatus = NetShareGetInfo( NULL, ShareName, 1005, (LPBYTE*)&ShareInfo1005 ); if ( NetStatus != NO_ERROR ) { NlPrint(( NL_CRITICAL, "NlCreateShare: NetShareGetInfo (1005) failed for share '%ws': %lu\n", ShareName, NetStatus )); return NetStatus; } // // Save the current flags // CurrentFlags = ShareInfo1005->shi1005_flags; // // If we allow the exclusive share access... // if ( AllowExclusiveShareAccess ) { // // If the current setting doesn't allow the exclusive access, // update it // if ( ShareInfo1005->shi1005_flags & SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS ) { ShareInfo1005->shi1005_flags &= ~SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS; } // // If we disallow the exclusive share access... // } else { // // If the current setting allows the exclusive access, // update it // if ( (ShareInfo1005->shi1005_flags & SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS) == 0 ) { ShareInfo1005->shi1005_flags |= SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS; } } // // Update the share as needed // if ( CurrentFlags != ShareInfo1005->shi1005_flags ) { NetStatus = NetShareSetInfo( NULL, ShareName, 1005, (LPBYTE)ShareInfo1005, NULL ); if ( NetStatus == NO_ERROR ) { NlPrint(( NL_INIT, "NlCreateShare: Share '%ws' updated successfully flags 0x%lx 0x%lx\n", ShareName, CurrentFlags, ShareInfo1005->shi1005_flags )); } else { NlPrint(( NL_CRITICAL, "NlCreateShare: Failed to update share '%ws' flags 0x%lx 0x%lx: 0x%lx\n", ShareName, CurrentFlags, ShareInfo1005->shi1005_flags, NetStatus )); } } // // Free the share info // if ( ShareInfo1005 != NULL ) { NetApiBufferFree( ShareInfo1005 ); } return NetStatus; } NTSTATUS NlSamOpenNamedUser( IN PDOMAIN_INFO DomainInfo, IN LPCWSTR UserName, OUT SAMPR_HANDLE *UserHandle OPTIONAL, OUT PULONG UserId OPTIONAL, OUT PSAMPR_USER_INFO_BUFFER *UserAllInfo OPTIONAL ) /*++ Routine Description: Utility routine to open a Sam user given the username. Arguments: DomainInfo - Domain the user is in. UserName - Name of user to open UserHandle - Optionally returns a handle to the opened user. UserId - Optionally returns the relative ID of the opened user. UserAllInfo - Optionally returns ALL of the information about the named user. Free the returned information using SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation ); Return Value: STATUS_NO_SUCH_USER: if the account doesn't exist --*/ { NTSTATUS Status; UNICODE_STRING UserNameString; PSAMPR_USER_INFO_BUFFER LocalUserAllInfo = NULL; SID_AND_ATTRIBUTES_LIST ReverseMembership; // // Initialization. // if ( ARGUMENT_PRESENT( UserHandle) ) { *UserHandle = NULL; } if ( ARGUMENT_PRESENT( UserAllInfo) ) { *UserAllInfo = NULL; } // // Get the info about the user. // // Use SamIGetUserLogonInformation instead of SamrLookupNamesInDomain and // SamrOpen user. The former is more efficient (since it only does one // DirSearch and doesn't lock the global SAM lock) and more powerful // (since it returns UserAllInformation). // RtlInitUnicodeString( &UserNameString, UserName ); Status = SamIGetUserLogonInformation( DomainInfo->DomSamAccountDomainHandle, SAM_NO_MEMBERSHIPS, // Don't need group memberships &UserNameString, &LocalUserAllInfo, &ReverseMembership, UserHandle ); if ( !NT_SUCCESS(Status) ) { if ( Status == STATUS_NOT_FOUND ) { Status = STATUS_NO_SUCH_USER; } goto Cleanup; } // // Return information to the caller. // if ( ARGUMENT_PRESENT(UserId) ) { *UserId = LocalUserAllInfo->All.UserId; } if ( ARGUMENT_PRESENT( UserAllInfo) ) { *UserAllInfo = LocalUserAllInfo; LocalUserAllInfo = NULL; } // // Free locally used resources. // Cleanup: if ( LocalUserAllInfo != NULL ) { SamIFree_SAMPR_USER_INFO_BUFFER( LocalUserAllInfo, UserAllInformation ); } return Status; } NTSTATUS NlSamChangePasswordNamedUser( IN PDOMAIN_INFO DomainInfo, IN LPCWSTR UserName, IN PUNICODE_STRING ClearTextPassword OPTIONAL, IN PNT_OWF_PASSWORD OwfPassword OPTIONAL ) /*++ Routine Description: Utility routine to set the OWF password on a user given the username. Arguments: DomainInfo - Domain the user is in. UserName - Name of user to open ClearTextPassword - Clear text password to set on the account OwfPassword - OWF password to set on the account Return Value: --*/ { NTSTATUS Status; SAMPR_HANDLE UserHandle = NULL; // // Open the user that represents this server. // Status = NlSamOpenNamedUser( DomainInfo, UserName, &UserHandle, NULL, NULL ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // If Clear text password isn't NULL, use it. // Otherwise use OWF password. // if ( ClearTextPassword != NULL ) { UNICODE_STRING UserNameString; RtlInitUnicodeString( &UserNameString, UserName ); Status = SamIChangePasswordForeignUser( &UserNameString, ClearTextPassword, NULL, 0 ); if ( !NT_SUCCESS(Status) ) { NlPrint(( NL_CRITICAL, "NlSamChangePasswordNamedUser: Can't SamIChangePasswordForeignUser %lX\n", Status )); goto Cleanup; } // // Use the NT OWF Password, // } else if ( OwfPassword != NULL ) { SAMPR_USER_INFO_BUFFER UserInfo; UserInfo.Internal1.PasswordExpired = FALSE; UserInfo.Internal1.LmPasswordPresent = FALSE; UserInfo.Internal1.NtPasswordPresent = TRUE; UserInfo.Internal1.EncryptedNtOwfPassword = *((PENCRYPTED_NT_OWF_PASSWORD)(OwfPassword)); Status = SamrSetInformationUser( UserHandle, UserInternal1Information, &UserInfo ); if (!NT_SUCCESS(Status)) { NlPrint(( NL_CRITICAL, "NlSamChangePasswordNamedUser: Can't SamrSetInformationUser %lX\n", Status )); goto Cleanup; } } Cleanup: if ( UserHandle != NULL ) { (VOID) SamrCloseHandle( &UserHandle ); } return Status; } NTSTATUS NlChangePassword( IN PCLIENT_SESSION ClientSession, IN BOOLEAN ForcePasswordChange, OUT PULONG RetCallAgainPeriod OPTIONAL ) /*++ Routine Description: Change this machine's password at the primary. Also update password locally if the call succeeded. To determine if the password of "machine account" needs to be changed. If the password is older than 7 days then it must be changed asap. We will defer changing the password if we know before hand that primary dc is down since our call will fail anyway. Arguments: ClientSession - Structure describing the session to change the password for. The specified structure must be referenced. ForcePasswordChange - TRUE if the password should be changed even if the password hasn't expired yet. RetCallAgainPeriod - Returns the amount of time (in milliseconds) that should elapse before the caller should call this routine again. 0: After a period of time determined by the caller. MAILSLOT_WAIT_FOREVER: never other: After at least this amount of time. Return Value: NT Status code --*/ { NTSTATUS Status; NETLOGON_AUTHENTICATOR OurAuthenticator; NETLOGON_AUTHENTICATOR ReturnAuthenticator; LM_OWF_PASSWORD OwfPassword; LARGE_INTEGER CurrentPasswordTime; PUNICODE_STRING CurrentPassword = NULL; PUNICODE_STRING OldPassword = NULL; DWORD PasswordVersion; WCHAR ClearTextPassword[LM20_PWLEN+1]; UNICODE_STRING NewPassword; BOOL PasswordChangedOnServer = FALSE; BOOL LsaSecretChanged = FALSE; BOOL DefaultCurrentPasswordBeingChanged = FALSE; BOOL DefaultOldPasswordBeingChanged = FALSE; BOOLEAN AmWriter = FALSE; ULONG CallAgainPeriod = 0; // // Initialization // NlAssert( ClientSession->CsReferenceCount > 0 ); // // If the password change was refused by the DC, // Don't ever try to change the password again (until the next reboot). // // This could have been written to try every MaximumPasswordAge. However, // that gets complex if you take into consideration the CS_UPDATE_PASSWORD // case where the time stamp on the LSA Secret doesn't get changed. // LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); if ( ClientSession->CsFlags & CS_PASSWORD_REFUSED ) { UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); CallAgainPeriod = MAILSLOT_WAIT_FOREVER; Status = STATUS_SUCCESS; goto Cleanup; } UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); // // Become a writer of the ClientSession. // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } AmWriter = TRUE; // // Get the outgoing password and the time the password was last changed // Status = NlGetOutgoingPassword( ClientSession, &CurrentPassword, &OldPassword, &PasswordVersion, &CurrentPasswordTime ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot NlGetOutgoingPassword %lX\n", Status)); goto Cleanup; } // // If the (old or new) password is still the default password // (lower case computer name), // or the password is null (a convenient default for domain trust), // Flag that fact. // if ( CurrentPassword == NULL || CurrentPassword->Length == 0 || RtlEqualComputerName( &ClientSession->CsDomainInfo->DomUnicodeComputerNameString, CurrentPassword ) ) { DefaultCurrentPasswordBeingChanged = TRUE; NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: New LsaSecret is default value.\n" )); } if ( OldPassword == NULL || OldPassword->Length == 0 || RtlEqualComputerName( &ClientSession->CsDomainInfo->DomUnicodeComputerNameString, OldPassword ) ) { DefaultOldPasswordBeingChanged = TRUE; NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Old LsaSecret is default value.\n" )); } // // If the password has not yet expired, // and the password is not the default, // and the password change isn't forced, // just return. // LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); if ( (ClientSession->CsFlags & CS_UPDATE_PASSWORD) == 0 && !NlTimeHasElapsedEx( &CurrentPasswordTime, &NlGlobalParameters.MaximumPasswordAge_100ns, &CallAgainPeriod ) && !DefaultCurrentPasswordBeingChanged && !DefaultOldPasswordBeingChanged && !ForcePasswordChange ) { // // Note that, since NlTimeHasElapsedEx returned FALSE, // CallAgainPeriod is the time left until the next // password change. // UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); Status = STATUS_SUCCESS; goto Cleanup; } UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); CallAgainPeriod = 0; // Let the caller determine the frequency for retries. NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Doing it.\n" )); // // If the session isn't authenticated, // do so now. // // We're careful to not force this authentication unless the password // needs to be changed. // // If this is the PDC changing its own password, // there's no need to authenticate. // if ( ClientSession->CsState != CS_AUTHENTICATED && !( ClientSession->CsSecureChannelType == ServerSecureChannel && ClientSession->CsDomainInfo->DomRole == RolePrimary ) ) { // // If we've tried to authenticate recently, // don't bother trying again. // if ( !NlTimeToReauthenticate( ClientSession ) ) { Status = ClientSession->CsConnectionStatus; goto Cleanup; } // // Try to set up the session. // Status = NlSessionSetup( ClientSession ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } } // // Once we change the password in LsaSecret storage, // all future attempts to change the password should use the value // from LsaSecret storage. The secure channel is using the old // value of the password. // LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); if (ClientSession->CsFlags & CS_UPDATE_PASSWORD) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Password already updated in secret\n" )); if ( CurrentPassword == NULL ) { RtlInitUnicodeString( &NewPassword, NULL ); } else { NewPassword = *CurrentPassword; } // // Handle the case where LsaSecret storage has not yet been updated. // } else { ULONG i; // // Build a new clear text password using: // Entirely random bits. // Srvmgr later uses this password as a zero terminated unicode string // so ensure there aren't any zero chars in the middle // if ( !NlGenerateRandomBits( (LPBYTE)ClearTextPassword, sizeof(ClearTextPassword))) { NlPrint((NL_CRITICAL, "Can't NlGenerateRandomBits for clear password\n" )); } for (i = 0; i < sizeof(ClearTextPassword)/sizeof(WCHAR); i++) { if ( ClearTextPassword[i] == '\0') { ClearTextPassword[i] = 1; } } ClearTextPassword[LM20_PWLEN] = L'\0'; RtlInitUnicodeString( &NewPassword, ClearTextPassword ); // // // Set the new outgoing password locally. // // Set the OldValue to the perviously obtained CurrentValue. // Increment the password version number. // PasswordVersion++; Status = NlSetOutgoingPassword( ClientSession, &NewPassword, CurrentPassword, PasswordVersion, PasswordVersion-1 ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot NlSetOutgoingPassword %lX\n", Status)); UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); goto Cleanup; } // // Flag that we've updated the password in LsaSecret storage. // LsaSecretChanged = TRUE; ClientSession->CsFlags |= CS_UPDATE_PASSWORD; NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Flag password changed in LsaSecret\n" )); } UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); // // Perform the initial encryption. // Status = RtlCalculateNtOwfPassword( &NewPassword, &OwfPassword); if ( !NT_SUCCESS( Status )) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot RtlCalculateNtOwfPassword %lX\n", Status)); goto Cleanup; } // // If this is a PDC, all we need to do is change the local account password // if ( ClientSession->CsSecureChannelType == ServerSecureChannel && ClientSession->CsDomainInfo->DomRole == RolePrimary ) { Status = NlSamChangePasswordNamedUser( ClientSession->CsDomainInfo, ClientSession->CsAccountName, &NewPassword, &OwfPassword ); if ( NT_SUCCESS(Status) ) { PasswordChangedOnServer = TRUE; } else { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot change password on PDC local user account 0x%lx\n", Status)); } goto Cleanup; } // // Change the password on the PDC // Status = NlChangePasswordHigher( ClientSession, ClientSession->CsAccountName, ClientSession->CsSecureChannelType, &OwfPassword, &NewPassword, &PasswordVersion ); if ( Status != STATUS_ACCESS_DENIED ) { PasswordChangedOnServer = TRUE; } // // If the server refused the change, // put the lsa secret back the way it was. // pretend the change was successful. // if ( Status == STATUS_WRONG_PASSWORD ) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: PDC refused to change password\n" )); // // If we changed the LSA secret, // put it back. // LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); if ( LsaSecretChanged ) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: undoing LSA secret change.\n" )); PasswordVersion--; Status = NlSetOutgoingPassword( ClientSession, CurrentPassword, OldPassword, PasswordVersion, PasswordVersion > 0 ? PasswordVersion-1 : 0 ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot undo NlSetOutgoingPassword %lX\n", Status)); UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); goto Cleanup; } // // Undo what we've done above. // ClientSession->CsFlags &= ~CS_UPDATE_PASSWORD; } // // Prevent us from trying too frequently. // ClientSession->CsFlags |= CS_PASSWORD_REFUSED; CallAgainPeriod = MAILSLOT_WAIT_FOREVER; UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); // // Avoid special cleanup below. // PasswordChangedOnServer = FALSE; Status = STATUS_SUCCESS; } // // Common exit // Cleanup: if ( PasswordChangedOnServer ) { // // On success, // Indicate that the password has now been updated on the // PDC so the old password is no longer in use. // if ( NT_SUCCESS( Status ) ) { LOCK_TRUST_LIST( ClientSession->CsDomainInfo ); ClientSession->CsFlags &= ~CS_UPDATE_PASSWORD; NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Flag password updated on PDC\n" )); // // If the default current password was changed, // avoid leaving the default password around as the old // password. Otherwise, a bogus DC could convince us to use // the bogus DC via the default password. Set both current // and old version numbers to the new value. // if ( DefaultCurrentPasswordBeingChanged ) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NlChangePassword: Setting LsaSecret old password to same as new password\n" )); Status = NlSetOutgoingPassword( ClientSession, &NewPassword, &NewPassword, PasswordVersion, PasswordVersion ); if ( !NT_SUCCESS( Status ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePassword: Cannot LsarSetSecret to set old password %lX\n", Status)); UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); goto Cleanup; } } // // Save the password for our own future reference. // // CsNtOwfPassword is the most recent known good password // RtlCopyMemory( &ClientSession->CsNtOwfPassword, &OwfPassword, sizeof( OwfPassword )); UNLOCK_TRUST_LIST( ClientSession->CsDomainInfo ); // // Indicate we don't need to call change the password again for awhile // if ( NlGlobalParameters.MaximumPasswordAge > (TIMER_MAX_PERIOD/NL_MILLISECONDS_PER_DAY) ) { CallAgainPeriod = TIMER_MAX_PERIOD; } else { CallAgainPeriod = NlGlobalParameters.MaximumPasswordAge * NL_MILLISECONDS_PER_DAY; } // // Notify the Admin that he'll have to manually set this server's // password on both this server and the PDC. // } else { LPWSTR MsgStrings[2]; // // Drop the secure channel // NlSetStatusClientSession( ClientSession, Status ); // // write event log // MsgStrings[0] = ClientSession->CsAccountName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog ( NELOG_NetlogonPasswdSetFailed, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } } // // Clean up locally used resources. // if ( CurrentPassword != NULL ) { LocalFree( CurrentPassword ); } if ( OldPassword != NULL ) { LocalFree( OldPassword ); } if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } // // Tell the caller when he should call us again // if ( ARGUMENT_PRESENT( RetCallAgainPeriod) ) { *RetCallAgainPeriod = CallAgainPeriod; } return Status; } NTSTATUS NlRefreshClientSession( IN PCLIENT_SESSION ClientSession ) /*++ Routine Description: Refresh the client session info. The info that we intend to refresh is: * Server name (the DC can be renamed in Whistler). * Discovery flags, in particular whether the server is still close. * The server IP address. We will also refresh our site name (on workstation). The caller must be a writer of the ClientSession. Arguments: ClientSession - Structure describing the session. Return Value: NT Status code --*/ { NTSTATUS Status = STATUS_SUCCESS; NET_API_STATUS NetStatus = NO_ERROR; PNL_DC_CACHE_ENTRY NlDcCacheEntry = NULL; BOOLEAN DcRediscovered = FALSE; // // If the client session is idle, // there is nothing to refresh // if ( ClientSession->CsState == CS_IDLE ) { Status = STATUS_SUCCESS; goto Cleanup; } // // If the server (DC) is NT4.0, there is no need for refresh. // (The only info that can potentially change for NT4.0 DC // is its IP address which is not worth refreshing) // if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_HAS_DS) == 0 ) { Status = STATUS_SUCCESS; goto Cleanup; } // // If it's not yet time to refresh the info, // we don't need to do anything // EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); if ( !NetpLogonTimeHasElapsed(ClientSession->CsLastRefreshTime, MAX_DC_REFRESH_TIMEOUT) ) { LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); Status = STATUS_SUCCESS; goto Cleanup; } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); // // Get the up to date server info // Status = NlGetAnyDCName( ClientSession, FALSE, // Do not require IP FALSE, // Don't do with-account discovery &NlDcCacheEntry, &DcRediscovered ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Use this opportunity to update our site on a workstation // if ( NlGlobalMemberWorkstation ) { // // Only Win2K or newer DCs undestand the site concept // if ( (NlDcCacheEntry->ReturnFlags & DS_DS_FLAG) != 0 ) { NlSetDynamicSiteName( NlDcCacheEntry->UnicodeClientSiteName ); } else { NlPrint(( NL_SITE, "NlRefreshClientSession: NlGetAnyDCName returned NT4 DC\n" )); } } Cleanup: if ( NlDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( NlDcCacheEntry ); } return Status; } NTSTATUS NlEnsureSessionAuthenticated( IN PCLIENT_SESSION ClientSession, IN DWORD DesiredFlags ) /*++ Routine Description: Ensure there is an authenticated session for the specified ClientSession. If the authenticated DC does not have the characteristics specified by DesiredFlags, attempt to find a DC that does. The caller must be a writer of the ClientSession. Arguments: ClientSession - Structure describing the session. DesiredFlags - characteristics that the authenticated DC should have. Can be one or more of the following: CS_DISCOVERY_HAS_DS // Discovered DS has a DS CS_DISCOVERY_IS_CLOSE // Discovered DS is in a close site It is the callers responsibility to ensure that the DC really DOES have those characteristics. Return Value: NT Status code --*/ { NTSTATUS Status; // // First refresh the client session // Status = NlRefreshClientSession( ClientSession ); if ( !NT_SUCCESS(Status) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlpEnsureSessionAuthenticated: Can't refresh the session: 0x%lx\n", Status )); goto Cleanup; } // // If this secure channel is from a BDC to the PDC, // there is only ONE PDC so don't ask for special characteristics. // if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) { DesiredFlags = 0; // // If this secure channel isn't expected to have NT 5 DCs, // don't try to find one. // } else if ((ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST) == 0 ) { DesiredFlags = 0; // // If we don't have a close DC, // and it has been a long time since we've tried to find a close DC, // do it now. // } else if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_IS_CLOSE) == 0 ) { BOOLEAN ReturnBoolean; EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); if ( NetpLogonTimeHasElapsed( ClientSession->CsLastDiscoveryTime, NlGlobalParameters.CloseSiteTimeout * 1000 ) ) { DesiredFlags |= CS_DISCOVERY_IS_CLOSE; } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); } // // If a DC has already been detected, // and the caller wants special characteristics, // try for them now. // if ( ClientSession->CsState != CS_IDLE && DesiredFlags != 0 ) { // // If the DC doesn't have the required characteristics, // try to find a new one now // EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); if ( (ClientSession->CsDiscoveryFlags & DesiredFlags) != DesiredFlags ) { // // Avoid discovery if we've done it recently. // // All discoveries prefer a DC that has all of the desired characteristics. // So if we didn't find one, don't try again. // if ( NlTimeToRediscover(ClientSession, FALSE) ) { // we'll do discovery without account NlPrintCs(( NL_SESSION_SETUP, ClientSession, "NlpEnsureSessionAuthenticated: Try to find a better DC for this operation. 0x%lx\n", DesiredFlags )); // // Discovering a DC when the session is not idle tries to find a // "better" DC. // // Ignore failures. // // Call without the any locks locked to prevent doing network I/O // with the lock held. // // Don't ask for with-account discovery as it's too costly on the // server side. If the discovered server doesn't have our account, // the session setup logic will attempt with-account discovery. // LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); Status = NlDiscoverDc ( ClientSession, DT_Synchronous, FALSE , FALSE ); // without account EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); } } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); } // // If we haven't yet authenticated, // do so now. // if ( ClientSession->CsState != CS_AUTHENTICATED ) { // // If we've tried to authenticate recently, // don't bother trying again. // if ( !NlTimeToReauthenticate( ClientSession ) ) { Status = ClientSession->CsConnectionStatus; NlAssert( !NT_SUCCESS( Status )); if ( NT_SUCCESS( Status )) { Status = STATUS_NO_LOGON_SERVERS; } goto Cleanup; } // // Try to set up the session. // Status = NlSessionSetup( ClientSession ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } } Status = STATUS_SUCCESS; Cleanup: return Status; } NTSTATUS NlChangePasswordHigher( IN PCLIENT_SESSION ClientSession, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN PLM_OWF_PASSWORD NewOwfPassword OPTIONAL, IN PUNICODE_STRING NewClearPassword OPTIONAL, IN PDWORD ClearPasswordVersionNumber OPTIONAL ) /*++ Routine Description: Pass the new password to the machine specified by the ClientSession. The caller must be a writer of the ClientSession. Arguments: ClientSession - Structure describing the session to change the password for. The specified structure must be referenced. AccountName - Name of the account whose password is being changed. AccountType - Type of account whose password is being changed. NewOwfPassword - Owf password to pass to ClientSession NewClearPassword - Clear password to pass to client session ClearPasswordVersionNumber - Version number of the clear password. Must be present if NewClearPassword is present. Return Value: NT Status code --*/ { NTSTATUS Status; NETLOGON_AUTHENTICATOR OurAuthenticator; NETLOGON_AUTHENTICATOR ReturnAuthenticator; SESSION_INFO SessionInfo; BOOLEAN FirstTry = TRUE; // // Initialization // NlAssert( ClientSession->CsReferenceCount > 0 ); NlAssert( ClientSession->CsFlags & CS_WRITER ); // // If the session isn't authenticated, // do so now. // // We're careful to not force this authentication unless the password // needs to be changed. // FirstTryFailed: Status = NlEnsureSessionAuthenticated( ClientSession, 0 ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } SessionInfo.SessionKey = ClientSession->CsSessionKey; SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags; // // Build the Authenticator for this request to the PDC. // NlBuildAuthenticator( &ClientSession->CsAuthenticationSeed, &ClientSession->CsSessionKey, &OurAuthenticator); // // If the other side will accept a clear text password, // send it. // if ( NewClearPassword != NULL && (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_PASSWORD_SET_2) != 0 ) { NL_TRUST_PASSWORD NlTrustPassword; NL_PASSWORD_VERSION PasswordVersion; // // Copy the new password to the end of the buffer. // RtlCopyMemory( ((LPBYTE)NlTrustPassword.Buffer) + NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - NewClearPassword->Length, NewClearPassword->Buffer, NewClearPassword->Length ); NlTrustPassword.Length = NewClearPassword->Length; // // For an interdomain trust account, // indicate that we pass the password version number by prefixing // a DWORD equal to PASSWORD_VERSION_NUMBER_PRESENT right before // the password in NewClearPassword->Buffer. An old server (RC0) // not supporting version numbers will simply ignore these bits. // A server supporting version numbers will examine these bits // and if they are equal to PASSWORD_VERSION_NUMBER_PRESENT then // that will be an indication that a version number is passed. An // old client not supporting version numbers will generate random // bits in place of PASSWORD_VERSION_NUMBER_PRESENT. It is highly // unlikely that an old client will generate random bits equal to // PASSWORD_VERSION_NUMBER_PRESENT. The // version number will be a DWORD preceeding the DWORD equal to // PASSWORD_VERSION_NUMBER_PRESENT. Another DWORD equal to 0 will // preceed the version number. Its purpose is to allow any future // additions to the buffer. The value of this DWORD different from // 0 will indicate without any uncertainty that some additional // info is passed preceding this DWORD. The 3 new DWORDs are packed // in a struct to avoid unalingment problems. // if ( IsDomainSecureChannelType( AccountType ) ) { NlAssert( ClearPasswordVersionNumber != NULL ); NlAssert( NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - NewClearPassword->Length - sizeof(PasswordVersion) > 0 ); PasswordVersion.ReservedField = 0; PasswordVersion.PasswordVersionNumber = *ClearPasswordVersionNumber; PasswordVersion.PasswordVersionPresent = PASSWORD_VERSION_NUMBER_PRESENT; RtlCopyMemory( ((LPBYTE)NlTrustPassword.Buffer) + NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - NewClearPassword->Length - sizeof(PasswordVersion), &PasswordVersion, sizeof(PasswordVersion) ); } // // Fill the rest of the buffer with random bytes // if ( !NlGenerateRandomBits( (LPBYTE)NlTrustPassword.Buffer, (NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) - NewClearPassword->Length - sizeof(PasswordVersion) ) ) { NlPrint((NL_CRITICAL, "Can't NlGenerateRandomBits for clear password prefix\n" )); } // // Encrypt the whole buffer. // NlEncryptRC4( &NlTrustPassword, sizeof( NlTrustPassword ), &SessionInfo ); // // Change the password on the machine our connection is to. // NL_API_START( Status, ClientSession, TRUE ) { NlAssert( ClientSession->CsUncServerName != NULL ); Status = I_NetServerPasswordSet2( ClientSession->CsUncServerName, AccountName, AccountType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, &NlTrustPassword); if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerPasswordSet2", Status ); } // NOTE: This call may drop the secure channel behind our back } NL_API_ELSE( Status, ClientSession, TRUE ) { } NL_API_END; // // If the other side needs an OWF password, // send it. // } else { ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword; LM_OWF_PASSWORD LocalOwfPassword; // // If the caller doesn't know the OWF password, // compute the owf. // if ( NewOwfPassword == NULL ) { // // Perform the initial encryption. // Status = RtlCalculateNtOwfPassword( NewClearPassword, &LocalOwfPassword); if ( !NT_SUCCESS( Status )) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePasswordHigher: Cannot RtlCalculateNtOwfPassword %lX\n", Status)); goto Cleanup; } NewOwfPassword = &LocalOwfPassword; } // // Encrypt the password again with the session key. // The PDC will decrypt it on the other side. // Status = RtlEncryptNtOwfPwdWithNtOwfPwd( NewOwfPassword, (PNT_OWF_PASSWORD) &ClientSession->CsSessionKey, &SessKeyEncrPassword) ; if ( !NT_SUCCESS( Status )) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePasswordHigher: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd %lX\n", Status)); goto Cleanup; } // // Change the password on the machine our connection is to. // NL_API_START( Status, ClientSession, TRUE ) { NlAssert( ClientSession->CsUncServerName != NULL ); Status = I_NetServerPasswordSet( ClientSession->CsUncServerName, AccountName, AccountType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, &SessKeyEncrPassword); if ( !NT_SUCCESS(Status) ) { NlPrintRpcDebug( "I_NetServerPasswordSet", Status ); } // NOTE: This call may drop the secure channel behind our back } NL_API_ELSE( Status, ClientSession, TRUE ) { } NL_API_END; } // // Now verify primary's authenticator and update our seed // if ( NlpDidDcFail( Status ) || !NlUpdateSeed( &ClientSession->CsAuthenticationSeed, &ReturnAuthenticator.Credential, &ClientSession->CsSessionKey) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlChangePasswordHigher: 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 ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlChangePasswordHigher: %ws: failed %lX\n", AccountName, Status)); } return Status; } NTSTATUS NlGetUserPriv( IN PDOMAIN_INFO DomainInfo, IN ULONG GroupCount, IN PGROUP_MEMBERSHIP Groups, IN ULONG UserRelativeId, OUT LPDWORD Priv, OUT LPDWORD AuthFlags ) /*++ Routine Description: Determines the Priv and AuthFlags for the specified user. Arguments: DomainInfo - Hosted domain the user account is in. GroupCount - Number of groups this user is a member of Groups - Array of groups this user is a member of. UserRelativeId - Relative ID of the user to query. Priv - Returns the Lanman 2.0 Privilege level for the specified user. AuthFlags - Returns the Lanman 2.0 Authflags for the specified user. Return Value: Status of the operation. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; ULONG GroupIndex; PSID *UserSids = NULL; ULONG UserSidCount = 0; SAMPR_PSID_ARRAY SamSidArray; SAMPR_ULONG_ARRAY Aliases; // // Initialization // Aliases.Element = NULL; // // Allocate a buffer to point to the SIDs we're interested in // alias membership for. // UserSids = (PSID *) NetpMemoryAllocate( (GroupCount+1) * sizeof(PSID) ); if ( UserSids == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // Add the User's Sid to the Array of Sids. // NetStatus = NetpDomainIdToSid( DomainInfo->DomAccountDomainId, UserRelativeId, &UserSids[0] ); if ( NetStatus != NERR_Success ) { Status = NetpApiStatusToNtStatus( NetStatus ); goto Cleanup; } UserSidCount ++; // // Add each group the user is a member of to the array of Sids. // for ( GroupIndex = 0; GroupIndex < GroupCount; GroupIndex ++ ){ NetStatus = NetpDomainIdToSid( DomainInfo->DomAccountDomainId, Groups[GroupIndex].RelativeId, &UserSids[GroupIndex+1] ); if ( NetStatus != NERR_Success ) { Status = NetpApiStatusToNtStatus( NetStatus ); goto Cleanup; } UserSidCount ++; } // // Find out which aliases in the builtin domain this user is a member of. // SamSidArray.Count = UserSidCount; SamSidArray.Sids = (PSAMPR_SID_INFORMATION) UserSids; Status = SamrGetAliasMembership( DomainInfo->DomSamBuiltinDomainHandle, &SamSidArray, &Aliases ); if ( !NT_SUCCESS(Status) ) { Aliases.Element = NULL; NlPrint((NL_CRITICAL, "NlGetUserPriv: SamGetAliasMembership returns %lX\n", Status )); goto Cleanup; } // // Convert the alias membership to priv and auth flags // NetpAliasMemberToPriv( Aliases.Count, Aliases.Element, Priv, AuthFlags ); Status = STATUS_SUCCESS; // // Free Locally used resources. // Cleanup: if ( Aliases.Element != NULL ) { SamIFree_SAMPR_ULONG_ARRAY ( &Aliases ); } if ( UserSids != NULL ) { for ( GroupIndex = 0; GroupIndex < UserSidCount; GroupIndex ++ ) { NetpMemoryFree( UserSids[GroupIndex] ); } NetpMemoryFree( UserSids ); } return Status; } /*lint +e740 */ /* don't complain about unusual cast */