|
|
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: notify.cxx
//
// Contents: KDC password change notification code
//
//
// History: 19-Aug-1996 MikeSw Created
//
//------------------------------------------------------------------------
#include "kdcsvr.hxx"
extern "C" { #include <dns.h> // DNS_MAX_NAME_LENGTH
#include <ntdsa.h> // CrackSingleName
}
SAMPR_HANDLE KdcNotifyAccountDomainHandle = NULL; UNICODE_STRING KdcNotifyDnsDomainName; UNICODE_STRING KdcNotifyDomainName; RTL_CRITICAL_SECTION KdcNotifyCritSect; BOOLEAN KdcNotificationInitialized;
//+-------------------------------------------------------------------------
//
// Function: KdcNotifyOpenAccountDomain
//
// Synopsis: Opens the account domain and stores a handle to it.
//
// Effects: Sets KdcNotifyAccountDomainHandle on success.
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KdcNotifyOpenAccountDomain( OUT SAMPR_HANDLE * AccountDomainHandle ) { NTSTATUS Status; PLSAPR_POLICY_INFORMATION PolicyInformation = NULL; SAMPR_HANDLE ServerHandle = NULL;
Status = LsaIQueryInformationPolicyTrusted( PolicyDnsDomainInformation, &PolicyInformation ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to query information policy: 0x%x\n",Status)); goto Cleanup; }
Status = KerbDuplicateString( &KdcNotifyDomainName, (PUNICODE_STRING) &PolicyInformation->PolicyDnsDomainInfo.Name ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = KerbDuplicateString( &KdcNotifyDnsDomainName, (PUNICODE_STRING) &PolicyInformation->PolicyDnsDomainInfo.DnsDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = RtlUpcaseUnicodeString( &KdcNotifyDnsDomainName, &KdcNotifyDnsDomainName, FALSE // don't allocate
); if (!NT_SUCCESS(Status)) { goto Cleanup; } //
// Connect to SAM and open the account domain
//
Status = SamIConnect( NULL, // no server name
&ServerHandle, 0, // ignore desired access,
TRUE // trusted caller
);
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to connect to SAM: 0x%x\n",Status)); goto Cleanup; }
//
// Finally open the account domain.
//
Status = SamrOpenDomain( ServerHandle, DOMAIN_ALL_ACCESS, (PRPC_SID) PolicyInformation->PolicyDnsDomainInfo.Sid, AccountDomainHandle );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failed to open account domain: 0x%x\n",Status)); goto Cleanup; }
Cleanup: if (PolicyInformation != NULL) { LsaIFree_LSAPR_POLICY_INFORMATION( PolicyDnsDomainInformation, PolicyInformation ); }
if (ServerHandle != NULL) { SamrCloseHandle(&ServerHandle); }
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcBuildPasswordList
//
// Synopsis: Builds a list of passwords for a user that just changed
// their password.
//
// Effects: allocates memory
//
// Arguments: Password - clear or OWF password
// PrincipalName - Name of principal
// MarshallKeys - if TRUE, the keys will be marshalled
// IncludeBuiltinTypes - if TRUE, include MD4 & LM hashes
// PasswordList - Receives new password list
// PasswordListSize - Size of list in bytes.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KdcBuildPasswordList( IN PUNICODE_STRING Password, IN PUNICODE_STRING PrincipalName, IN PUNICODE_STRING DomainName, IN KERB_ACCOUNT_TYPE AccountType, IN PKERB_STORED_CREDENTIAL StoredCreds, IN ULONG StoredCredSize, IN BOOLEAN MarshallKeys, IN BOOLEAN IncludeBuiltinTypes, IN ULONG Flags, IN KDC_DOMAIN_INFO_DIRECTION Direction, OUT PKERB_STORED_CREDENTIAL * PasswordList, OUT PULONG PasswordListSize ) { NTSTATUS Status = STATUS_SUCCESS; ULONG CryptTypes[KERB_MAX_CRYPTO_SYSTEMS]; ULONG CryptCount = 0; PKERB_STORED_CREDENTIAL Credentials = NULL; ULONG CredentialSize = 0; ULONG KerbEncryptionKeyCount = 0; ULONG KerbKeyDataCount = 0; PCRYPTO_SYSTEM CryptoSystem; PCHECKSUM_FUNCTION CheckSum; ULONG Index, CredentialIndex = 0; PUCHAR Base, KeyBase; ULONG Offset; ULONG OldCredCount = 0; KERB_ENCRYPTION_KEY TempKey; UNICODE_STRING KeySalt = {0}; UNICODE_STRING EmptySalt = {0}; USHORT OldFlags = 0;
*PasswordList = NULL; *PasswordListSize = 0;
//
// If we had passed in an OWF, then there is just one password.
//
if ((Flags & KERB_PRIMARY_CRED_OWF_ONLY) != 0) { CredentialSize += Password->Length + sizeof(KERB_ENCRYPTION_KEY); KerbEncryptionKeyCount++; #ifndef DONT_SUPPORT_OLD_TYPES
CredentialSize += Password->Length + sizeof(KERB_ENCRYPTION_KEY); KerbEncryptionKeyCount++; #endif
} else { //
// The salt is the realm name concatenated with the principal name
//
if (AccountType != UnknownAccount) { //
// For inbound trust, swap the domain names
//
if ((AccountType == DomainTrustAccount) && (Direction == Inbound)) { if (!KERB_SUCCESS(KerbBuildKeySalt( PrincipalName, DomainName, AccountType, &KeySalt ))) { return(STATUS_INSUFFICIENT_RESOURCES); } } else { if (!KERB_SUCCESS(KerbBuildKeySalt( DomainName, PrincipalName, AccountType, &KeySalt ))) { return(STATUS_INSUFFICIENT_RESOURCES); } } } else { KeySalt = *PrincipalName; } D_DebugLog((DEB_TRACE,"Building key list with salt %wZ\n",&KeySalt));
//
// For a cleartext password, build a list of encryption types and
// create a key for each one
//
Status = CDBuildIntegrityVect( &CryptCount, CryptTypes ); if (!NT_SUCCESS(Status)) { goto Cleanup; } DsysAssert(CryptCount <= KERB_MAX_CRYPTO_SYSTEMS);
//
// Now find the size of the key for each crypto system
//
for (Index = 0; Index < CryptCount; Index++ ) { //
// Skip etypes stored as normal passwords
//
if (!IncludeBuiltinTypes && ((CryptTypes[Index] == KERB_ETYPE_RC4_LM) || (CryptTypes[Index] == KERB_ETYPE_RC4_MD4) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD_EXP) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT_EXP) || (CryptTypes[Index] == KERB_ETYPE_NULL))) { continue; }
Status = CDLocateCSystem( CryptTypes[Index], &CryptoSystem );
if (!NT_SUCCESS(Status) || NULL == CryptoSystem) { D_DebugLog((DEB_ERROR, "CDLocateCSystem failed for etype: %d\n", CryptTypes[Index])); continue; }
CredentialSize += sizeof(KERB_ENCRYPTION_KEY) + CryptoSystem->KeySize; KerbEncryptionKeyCount++; } }
//
// For a cleartext password, build a list of encryption types and
// create a key for each one
//
Status = CDBuildIntegrityVect( &CryptCount, CryptTypes ); if (!NT_SUCCESS(Status)) { goto Cleanup; } DsysAssert(CryptCount <= KERB_MAX_CRYPTO_SYSTEMS);
//
// Add the space for the salt
//
CredentialSize += KeySalt.Length;
//
// Now find the size of the key for each crypto system
//
for (Index = 0; Index < CryptCount; Index++ ) { //
// Skip etypes stored as normal passwords
//
if (!IncludeBuiltinTypes && ((CryptTypes[Index] == KERB_ETYPE_RC4_LM) || (CryptTypes[Index] == KERB_ETYPE_RC4_MD4) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD_EXP) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT_EXP) || (CryptTypes[Index] == KERB_ETYPE_NULL))) { continue; }
Status = CDLocateCSystem( CryptTypes[Index], &CryptoSystem );
if (!NT_SUCCESS(Status) || NULL == CryptoSystem) { D_DebugLog((DEB_ERROR, "CDLocateCSystem failed for etype: %d\n", CryptTypes[Index])); continue; } CredentialSize += sizeof(KERB_KEY_DATA) + CryptoSystem->KeySize; KerbKeyDataCount++; }
//
// Add in space for oldcreds
//
if (ARGUMENT_PRESENT(StoredCreds)) { if ((StoredCreds->Revision == KERB_PRIMARY_CRED_REVISION) && (StoredCreds->CredentialCount != 0)) { OldFlags = StoredCreds->Flags; for (Index = 0; Index < StoredCreds->CredentialCount ; Index++ ) { CredentialSize += sizeof(KERB_KEY_DATA) + StoredCreds->Credentials[Index].Key.keyvalue.length + StoredCreds->Credentials[Index].Salt.Length; KerbKeyDataCount++; } OldCredCount = StoredCreds->CredentialCount;
} }
//
// Add in the size of the base structure
//
CredentialSize += sizeof(KERB_STORED_CREDENTIAL) - (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA)); Credentials = (PKERB_STORED_CREDENTIAL) MIDL_user_allocate(CredentialSize); if (Credentials == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Fill in the base structure
//
Credentials->Revision = KERB_PRIMARY_CRED_REVISION; Credentials->Flags = OldFlags | (USHORT) Flags ;
//
// Now fill in the individual keys
//
Base = (PUCHAR) Credentials; if (MarshallKeys) { KeyBase = 0; } else { KeyBase = Base; } Offset = sizeof(KERB_STORED_CREDENTIAL) - (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA)) + (KerbEncryptionKeyCount * sizeof(KERB_ENCRYPTION_KEY)) + (KerbKeyDataCount * sizeof(KERB_KEY_DATA));
//
// Add the default salt
//
Credentials->DefaultSalt.Length = Credentials->DefaultSalt.MaximumLength = KeySalt.Length; Credentials->DefaultSalt.Buffer = (LPWSTR) (KeyBase+Offset);
RtlCopyMemory( Base + Offset, KeySalt.Buffer, KeySalt.Length ); Offset += Credentials->DefaultSalt.Length;
if ((Flags & KERB_PRIMARY_CRED_OWF_ONLY) != 0) { RtlCopyMemory( Base + Offset, Password->Buffer, Password->Length );
if (!KERB_SUCCESS(KerbCreateKeyFromBuffer( &Credentials->Credentials[CredentialIndex].Key, Base + Offset, Password->Length, KERB_ETYPE_RC4_HMAC_NT ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Credentials->Credentials[CredentialIndex].Key.keyvalue.value = Credentials->Credentials[CredentialIndex].Key.keyvalue.value - Base + KeyBase;
Offset += Password->Length; CredentialIndex++;
#ifndef DONT_SUPPORT_OLD_TYPES
RtlCopyMemory( Base + Offset, Password->Buffer, Password->Length );
if (!KERB_SUCCESS(KerbCreateKeyFromBuffer( &Credentials->Credentials[CredentialIndex].Key, Base + Offset, Password->Length, KERB_ETYPE_RC4_HMAC_OLD ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Credentials->Credentials[CredentialIndex].Key.keyvalue.value = Credentials->Credentials[CredentialIndex].Key.keyvalue.value - Base + KeyBase;
Offset += Password->Length; CredentialIndex++; #endif
} else // assume it's clear
{ //
// Now find the size of the key for each crypto system
//
for (Index = 0; Index < CryptCount; Index++ ) { if (!IncludeBuiltinTypes && ((CryptTypes[Index] == KERB_ETYPE_RC4_LM) || (CryptTypes[Index] == KERB_ETYPE_RC4_MD4) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_OLD_EXP) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT) || (CryptTypes[Index] == KERB_ETYPE_RC4_HMAC_NT_EXP) || (CryptTypes[Index] == KERB_ETYPE_NULL))) { continue; }
if (!KERB_SUCCESS(KerbHashPasswordEx( Password, &KeySalt, CryptTypes[Index], &TempKey))) { //
// It is possible that the password can't be used for every
// encryption scheme, so skip failures
//
D_DebugLog((DEB_WARN, "Failed to hash pasword %wZ with type 0x%x\n", Password,CryptTypes[Index] )); continue; }
#if DBG
CDLocateCSystem( CryptTypes[Index], &CryptoSystem ); DsysAssert(CryptoSystem->KeySize >= TempKey.keyvalue.length);
#endif
Credentials->Credentials[CredentialIndex].Key = TempKey; Credentials->Credentials[CredentialIndex].Key.keyvalue.value = KeyBase + Offset; RtlCopyMemory( Base + Offset, TempKey.keyvalue.value, TempKey.keyvalue.length ); Offset += TempKey.keyvalue.length; KerbFreeKey( &TempKey ); Credentials->Credentials[CredentialIndex].Salt = EmptySalt; CredentialIndex++;
} } Credentials->CredentialCount = (USHORT) CredentialIndex;
//
// Now add in the old creds, if there were any
//
if (OldCredCount != 0) { for (Index = 0; Index < OldCredCount ; Index++ ) {
Credentials->Credentials[CredentialIndex] = StoredCreds->Credentials[Index]; Credentials->Credentials[CredentialIndex].Key.keyvalue.value = KeyBase + Offset; RtlCopyMemory( Base + Offset, StoredCreds->Credentials[Index].Key.keyvalue.value + (ULONG_PTR) StoredCreds, StoredCreds->Credentials[Index].Key.keyvalue.length ); Offset += StoredCreds->Credentials[Index].Key.keyvalue.length;
//
// Copy the salt
//
if (Credentials->Credentials[CredentialIndex].Salt.Buffer != NULL) { Credentials->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) Base+Offset;
RtlCopyMemory( Base + Offset, (PBYTE) StoredCreds->Credentials[Index].Salt.Buffer + (ULONG_PTR) StoredCreds, StoredCreds->Credentials[Index].Salt.Length ); Offset += StoredCreds->Credentials[Index].Salt.Length; } else { Credentials->Credentials[CredentialIndex].Salt = EmptySalt; }
CredentialIndex++; } Credentials->OldCredentialCount = (USHORT) OldCredCount; } else { Credentials->OldCredentialCount = 0; } *PasswordList = Credentials; *PasswordListSize = CredentialSize; Credentials = NULL;
Cleanup: if (Credentials != NULL) { MIDL_user_free(Credentials); } if (AccountType != UnknownAccount) { KerbFreeString(&KeySalt); } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KdcBuildKeySaltFromUpn
//
// Synopsis: Builds the salt by parsing the UPN, stripping out "@" & "/"
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KdcBuildKeySaltFromUpn( IN PUNICODE_STRING Upn, IN PUNICODE_STRING DomainName, OUT PUNICODE_STRING Salt ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING RealUpn; UNICODE_STRING LocalSalt = {0}; ULONG Index;
//
// If there is an "@" in UPN, strip it out & use the dns domain name
//
RealUpn = *Upn; for ( Index = ((RealUpn.Length / sizeof(WCHAR)) - 1); Index-- > 0; ) { if (RealUpn.Buffer[Index] == L'@') { RealUpn.Length = (USHORT) (Index * sizeof(WCHAR)); break; } }
//
// Create the salt. It starts off with the domain name & then has the
// UPN without any of the / pieces
//
LocalSalt.MaximumLength = DomainName->Length + RealUpn.Length; LocalSalt.Length = 0; LocalSalt.Buffer = (LPWSTR) MIDL_user_allocate(LocalSalt.MaximumLength); if (LocalSalt.Buffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RtlCopyMemory( LocalSalt.Buffer, DomainName->Buffer, DomainName->Length ); LocalSalt.Length += DomainName->Length;
//
// We have to uppercase the realmname for users
//
(VOID) RtlUpcaseUnicodeString( &LocalSalt, &LocalSalt, FALSE);
//
// Add in the real upn but leave out any "/" marks
//
for (Index = 0; Index < RealUpn.Length/sizeof(WCHAR) ; Index++ ) { if (RealUpn.Buffer[Index] != L'/') { LocalSalt.Buffer[LocalSalt.Length / sizeof(WCHAR)] = RealUpn.Buffer[Index]; LocalSalt.Length += sizeof(WCHAR); } }
*Salt = LocalSalt; Cleanup: return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: PasswordChangeNotify
//
// Synopsis: Notifies KDC of a password change, allowing it to update
// its credentials
//
// Effects: Stores Kerberos credentials on user object
//
// Arguments: UserName - Name of user whose password changed
// RelativeId - RID of changed user
// Passsword - New password of user
//
// Requires:
//
// Returns: STATUS_SUCCESS on success
//
// Notes:
//
//
//--------------------------------------------------------------------------
extern "C" NTSTATUS PasswordChangeNotify( IN PUNICODE_STRING UserName, IN ULONG RelativeId, IN PUNICODE_STRING Password ) {
//
// Password change notify routine in kdcsvc was used to compute the
// "DES" keys for the user upon a password change.
// Subsequently this logic was inlined in samsrv.dll and but the original
// code has been preserved in the #if 0 block for reference below
//
return(STATUS_SUCCESS);
#if 0
NTSTATUS Status = STATUS_SUCCESS; SAMPR_HANDLE UserHandle = NULL; SECPKG_SUPPLEMENTAL_CRED Credentials; PSAMPR_USER_INFO_BUFFER UserInfo = NULL; KERB_ACCOUNT_TYPE AccountType = UserAccount; WCHAR Nt4AccountName[UNLEN+DNLEN+2]; WCHAR CrackedDnsDomain[DNS_MAX_NAME_LENGTH+1]; ULONG CrackedDomainLength = sizeof(CrackedDnsDomain) / sizeof(WCHAR); WCHAR CrackedName[UNLEN+DNS_MAX_NAME_LENGTH+2]; ULONG CrackedNameLength = sizeof(CrackedName); ULONG CrackError = 0; UNICODE_STRING EmailName = {0}; UNICODE_STRING KeySalt = {0}; PKERB_STORED_CREDENTIAL StoredCreds = NULL; ULONG CredentialSize; BOOLEAN FreeSalt = FALSE;
Credentials.Credentials = NULL;
//
// Get a SAM handle
//
RtlEnterCriticalSection(&KdcNotifyCritSect);
if (KdcNotifyAccountDomainHandle == NULL) { Status = KdcNotifyOpenAccountDomain(&KdcNotifyAccountDomainHandle); }
RtlLeaveCriticalSection(&KdcNotifyCritSect);
if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = SamrOpenUser( KdcNotifyAccountDomainHandle, USER_WRITE_ACCOUNT | USER_READ_ACCOUNT, RelativeId, &UserHandle ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"BAD ERR: Can't open account of user whose password just changed (name =%wZ, rid = 0x%x) 0x%x\n", UserName, RelativeId, Status )); goto Cleanup; }
RtlInitUnicodeString( &Credentials.PackageName, MICROSOFT_KERBEROS_NAME_W );
//
// Find out if the user is a machine account - if so, the principal name
// takes on a different format.
//
Status = SamrQueryInformationUser( UserHandle, UserControlInformation, &UserInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = SamIRetrievePrimaryCredentials( UserHandle, &GlobalKerberosName, (PVOID *) &StoredCreds, &CredentialSize ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "Failed to retrieve primary credentials: 0x%x\n",Status)); goto Cleanup; }
if ((UserInfo->Control.UserAccountControl & (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)) != 0) { AccountType = MachineAccount; } else if ((UserInfo->Control.UserAccountControl & (USER_INTERDOMAIN_TRUST_ACCOUNT)) != 0) { AccountType = DomainTrustAccount; }
//
// Get the UPN from CrackSingleName
//
RtlCopyMemory( Nt4AccountName, KdcNotifyDomainName.Buffer, KdcNotifyDomainName.Length ); Nt4AccountName[KdcNotifyDomainName.Length / sizeof(WCHAR)] = L'\\';
RtlCopyMemory( Nt4AccountName + 1 + (KdcNotifyDomainName.Length) / sizeof(WCHAR), UserName->Buffer, UserName->Length ); Nt4AccountName[1 + (KdcNotifyDomainName.Length + UserName->Length) / sizeof(WCHAR)] = L'\0';
Status = CrackSingleName( DS_NT4_ACCOUNT_NAME, 0, // don't check against GC
Nt4AccountName, DS_USER_PRINCIPAL_NAME, &CrackedDomainLength, CrackedDnsDomain, &CrackedNameLength, CrackedName, &CrackError ); if ((Status != STATUS_SUCCESS) || (CrackError != DS_NAME_NO_ERROR)) { KeySalt = *UserName; } else { RtlInitUnicodeString( &EmailName, CrackedName ); AccountType = UnknownAccount;
Status = KdcBuildKeySaltFromUpn( &EmailName, &KdcNotifyDnsDomainName, &KeySalt ); if (!NT_SUCCESS(Status)) { goto Cleanup; } FreeSalt = TRUE;
}
//
// Build a the credentials
//
//
// Set account type to unknown so it uses the UPN supplied salt
//
if ((Password != NULL) && (Password->Buffer != NULL)) { Status = KdcBuildPasswordList( Password, &KeySalt, &KdcNotifyDnsDomainName, AccountType, StoredCreds, CredentialSize, TRUE, // marshall
FALSE, // don't include builtins
0, // no flags
Unknown, (PKERB_STORED_CREDENTIAL *) &Credentials.Credentials, &Credentials.CredentialSize ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
} else { Credentials.CredentialSize = 0; Credentials.Credentials = NULL; }
Status = SamIStorePrimaryCredentials( UserHandle, &Credentials ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "Failed to store primary credentials: 0x%x\n",Status)); goto Cleanup; }
Cleanup: if (UserHandle != NULL) { SamrCloseHandle(&UserHandle); } if (Credentials.Credentials != NULL) { MIDL_user_free(Credentials.Credentials); }
if (UserInfo != NULL) { SamIFree_SAMPR_USER_INFO_BUFFER( UserInfo, UserControlInformation ); } if (FreeSalt) { KerbFreeString(&KeySalt); }
if (StoredCreds != NULL) { LocalFree(StoredCreds); } return(Status);
#endif
}
extern "C"
NTSTATUS KdcBuildKerbCredentialsFromPassword( IN PUNICODE_STRING ClearPassword, IN PVOID KerbCredentials, IN ULONG KerbCredentialLength, IN ULONG UserAccountControl, IN PUNICODE_STRING UPN, IN PUNICODE_STRING UserName, IN PUNICODE_STRING DnsDomainName, OUT PVOID * NewKerbCredentials, OUT PULONG NewKerbCredentialLength ) { NTSTATUS Status = STATUS_SUCCESS; KERB_ACCOUNT_TYPE AccountType = UnknownAccount; UNICODE_STRING KeySalt = {0}; BOOLEAN FreeSalt = FALSE;
PKERB_STORED_CREDENTIAL32 Cred32 = NULL; PKERB_STORED_CREDENTIAL Cred64 = NULL; ULONG CredLength = KerbCredentialLength;
//
// Compute the correct account type
//
if (ARGUMENT_PRESENT(UPN)) { Status = KdcBuildKeySaltFromUpn( UPN, DnsDomainName, &KeySalt ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
FreeSalt = TRUE; } else {
if ((UserAccountControl & (USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT)) != 0) { AccountType = MachineAccount; } else if ((UserAccountControl & (USER_INTERDOMAIN_TRUST_ACCOUNT)) != 0) { AccountType = DomainTrustAccount; } else { AccountType = UserAccount; } KeySalt = *UserName; }
#ifdef _WIN64
Status = KdcUnpack32BitStoredCredential( (PKERB_STORED_CREDENTIAL32) KerbCredentials, &Cred64, &CredLength );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
KerbCredentials = (PVOID) Cred64; KerbCredentialLength = CredLength;
#endif
//
// Compute the kerb credentials
//
if ((ClearPassword != NULL)) { UNICODE_STRING UpcaseDomainName = {0};
Status = RtlUpcaseUnicodeString( &UpcaseDomainName, DnsDomainName, TRUE ); if (NT_SUCCESS(Status)) { Status = KdcBuildPasswordList( ClearPassword, &KeySalt, &UpcaseDomainName, AccountType, (PKERB_STORED_CREDENTIAL )KerbCredentials, KerbCredentialLength, TRUE, // marshall
FALSE, // don't include builtins
0, // no flags
Unknown, (PKERB_STORED_CREDENTIAL *) NewKerbCredentials, NewKerbCredentialLength ); RtlFreeUnicodeString(&UpcaseDomainName); if (!NT_SUCCESS(Status)) { goto Cleanup; } } }
#ifdef _WIN64
// for 64 - 32 bit compat, we pack the struct in 32bit compliant form
Status = KdcPack32BitStoredCredential( (PKERB_STORED_CREDENTIAL)(*NewKerbCredentials), &Cred32, NewKerbCredentialLength );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if ((*NewKerbCredentials) != NULL) { MIDL_user_free(*NewKerbCredentials); *NewKerbCredentials = Cred32; }
#endif
Cleanup:
if (FreeSalt) { KerbFreeString(&KeySalt); }
if (Cred64 != NULL) { MIDL_user_free(Cred64); }
return(Status); }
extern "C" VOID KdcFreeCredentials( IN PVOID Credentials ) { MIDL_user_free(Credentials); }
//+-------------------------------------------------------------------------
//
// Function: InitializeChangeNotify
//
// Synopsis: KDC code for initializing password change notification
// code.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
extern "C" BOOLEAN InitializeChangeNotify( ) { if (KdcNotificationInitialized) { return(TRUE); } D_DebugLog((DEB_TRACE, "Initialize Change Notify called!\n")); if (!NT_SUCCESS(RtlInitializeCriticalSection(&KdcNotifyCritSect))) { return FALSE; } KdcNotificationInitialized = TRUE; return(TRUE); }
//+-------------------------------------------------------------------------
//
// Function: KdcTimeHasElapsed
//
// Synopsis: Returns TRUE if the specified amount of time has
// elapsed since the specified start time
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN KdcTimeHasElapsed( IN LARGE_INTEGER StartTime, IN PLARGE_INTEGER Delta ) { LARGE_INTEGER CurrentTime; LARGE_INTEGER ElapsedTime; WCHAR PasswordBuffer[LM20_PWLEN];
//
// Check the password expiration time.
//
NtQuerySystemTime(&CurrentTime); ElapsedTime.QuadPart = CurrentTime.QuadPart - StartTime.QuadPart;
//
// If the window hasn't elapsed, we are done.
//
if ((ElapsedTime.QuadPart > 0) && (ElapsedTime.QuadPart < Delta->QuadPart)) { return(FALSE); } return(TRUE); }
//+-------------------------------------------------------------------------
//
// Function: KdcUpdateKrbtgtPassword
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
extern "C" BOOLEAN KdcUpdateKrbtgtPassword( IN PUNICODE_STRING DnsDomainName, IN PLARGE_INTEGER MaxPasswordAge ) { NTSTATUS Status = STATUS_SUCCESS; WCHAR PasswordBuffer[LM20_PWLEN]; UNICODE_STRING PasswordString; ULONG Index;
BOOLEAN Result = FALSE;
if (KdcState != Running) { goto Cleanup; }
//
// Check the password expiration time.
//
if (!KdcTimeHasElapsed( SecData.KrbtgtPasswordLastSet(), MaxPasswordAge )) { goto Cleanup; }
//
// Build a random password
//
if (!CDGenerateRandomBits( (PBYTE) PasswordBuffer, sizeof(PasswordBuffer) )) { Status = STATUS_INTERNAL_ERROR; goto Cleanup; }
//
// Make sure there are no zero characters
//
for (Index = 0; Index < LM20_PWLEN ; Index++ ) { if (PasswordBuffer[Index] == 0) { PasswordBuffer[Index] = (WCHAR) Index; } } PasswordString.Length = sizeof(PasswordBuffer); PasswordString.MaximumLength = PasswordString.Length; PasswordString.Buffer = PasswordBuffer;
Status = SamIChangePasswordForeignUser( SecData.KdcServiceName(), &PasswordString, NULL, 0 // no desired access
);
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to set KRBTGT password: 0x%x\n", Status)); Result = FALSE; goto Cleanup; }
ReportServiceEvent( EVENTLOG_SUCCESS, KDCEVENT_KRBTGT_PASSWORD_CHANGED, 0, // no data
NULL, // no data
0, // no strings
NULL // no strings
);
Result = TRUE;
Cleanup:
if (!Result && !NT_SUCCESS(Status)) {
ReportServiceEvent( EVENTLOG_ERROR_TYPE, KDCEVENT_KRBTGT_PASSWORD_CHANGE_FAILED, sizeof(NTSTATUS), &Status, 0, // no strings
NULL // no strings
);
}
return(Result); }
//+-------------------------------------------------------------------------
//
// Function: CredentialUpdateNotify
//
// Synopsis: This routine is called from SAMSRV in order to obtain
// new kerberos credentials to be stored as supplemental
// credentials when ever a user's password is set/changed.
//
// Effects: no global effect.
//
// Arguments:
//
// IN ClearPassword -- the clear text password
// IN OldCredentials -- the previous kerberos credentials
// IN OldCredentialsSize -- size of OldCredentials
// IN UserAccountControl -- info about the user
// IN UPN -- user principal name of the account
// IN UserName -- the SAM account name of the account
// IN DnsDomainName -- DNS domain name of the account
// OUT NewCredentials -- space allocated for SAM containing
// the credentials based on the input parameters
// to be freed by CredentialUpdateFree
// OUT NewCredentialSize -- size of NewCredentials
//
//
// Requires: no global requirements
//
// Returns: STATUS_SUCCESS, or resource error
//
// Notes: KDCSVC.DLL needs to be registered (in the registry) as a
// package that SAM calls out to in order for this routine
// to be involked.
//
//
//--------------------------------------------------------------------------
NTSTATUS CredentialUpdateNotify ( IN PUNICODE_STRING ClearPassword, IN PVOID OldCredentials, IN ULONG OldCredentialsSize, IN ULONG UserAccountControl, IN PUNICODE_STRING UPN, IN PUNICODE_STRING UserName, IN PUNICODE_STRING NetbiosDomainName, IN PUNICODE_STRING DnsDomainName, OUT PVOID *NewCredentials, OUT ULONG *NewCredentialsSize ) { UNREFERENCED_PARAMETER( NetbiosDomainName );
return KdcBuildKerbCredentialsFromPassword(ClearPassword, OldCredentials, OldCredentialsSize, UserAccountControl, UPN, UserName, DnsDomainName, NewCredentials, NewCredentialsSize);
}
VOID CredentialUpdateFree( PVOID p ) //
// Free's the memory allocated by CredentialUpdateNotify
//
{ if (p) { KdcFreeCredentials(p); } }
//+-------------------------------------------------------------------------
//
// Function: CredentialUpdateRegister
//
// Synopsis: This routine is called from SAMSRV in order to obtain
// the name of the supplemental credentials pass into this package
// when a password is changed or set.
//
// Effects: no global effect.
//
// Arguments:
//
// OUT CredentialName -- the name of credential tag in the supplemental
// credentials. Note this memory is never freed
// by SAM, but must remain valid for the lifetime
// of the process.
//
// Requires: no global requirements
//
// Returns: TRUE
//
// Notes: KDCSVC.DLL needs to be registered (in the registry) as a
// package that SAM calls out to in order for this routine
// to be involked.
//
//
//--------------------------------------------------------------------------
BOOLEAN CredentialUpdateRegister( OUT UNICODE_STRING *CredentialName ) { ASSERT(CredentialName);
RtlInitUnicodeString(CredentialName, MICROSOFT_KERBEROS_NAME_W);
return TRUE; }
//
// This compile-time test verifies that CredentialUpdateNotify and
// CredentialUpdateRegister have the correct signature
//
#if DBG
PSAM_CREDENTIAL_UPDATE_NOTIFY_ROUTINE _TestCredentialUpdateNotify = CredentialUpdateNotify; PSAM_CREDENTIAL_UPDATE_FREE_ROUTINE _TestCredentialFreeRegister = CredentialUpdateFree; PSAM_CREDENTIAL_UPDATE_REGISTER_ROUTINE _TestCredentialUpdateRegister = CredentialUpdateRegister;
#endif
#ifdef _WIN64
//
// Routines for packing and unpacking KERB_STORED_CREDENTIAL from DS
//
//+-------------------------------------------------------------------------
//
// Function: KdcUnpack32BitStoredCredential
//
// Synopsis: This function converts a 32 bit KERB_STORED_CREDENTIAL (read
// from DS, likely) to a 64 bit KERB_STORED_CREDENTIAL
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KdcUnpack32BitStoredCredential( IN PKERB_STORED_CREDENTIAL32 Cred32, IN OUT PKERB_STORED_CREDENTIAL * ppCred64, IN OUT PULONG pCredLength ) {
PKERB_STORED_CREDENTIAL Cred64 = NULL; NTSTATUS Status = STATUS_SUCCESS; ULONG CredSize = sizeof(KERB_STORED_CREDENTIAL); ULONG CredCount = 0, Cred = 0, Offset = 0; PCHAR Where, Base; CHAR UNALIGNED * From;
*pCredLength = 0; *ppCred64 = NULL;
if (NULL == Cred32) { return STATUS_SUCCESS; }
// Calculate Allocation size
CredSize += ROUND_UP_COUNT(Cred32->DefaultSalt.MaximumLength, ALIGN_LPTSTR);
CredSize += ((Cred32->CredentialCount + Cred32->OldCredentialCount) * sizeof(KERB_KEY_DATA));
for (CredCount = 0; CredCount < (ULONG)(Cred32->CredentialCount + Cred32->OldCredentialCount); CredCount++) { CredSize += ROUND_UP_COUNT(Cred32->Credentials[CredCount].Key.keyvaluelength, ALIGN_LPTSTR); CredSize += ROUND_UP_COUNT(Cred32->Credentials[CredCount].Salt.MaximumLength, ALIGN_LPTSTR); }
Cred64 = (PKERB_STORED_CREDENTIAL) MIDL_user_allocate(CredSize);
if (NULL == Cred64) { return STATUS_INSUFFICIENT_RESOURCES; }
// copy over data, remember, buffers packed in self-relative format
Cred64->CredentialCount = Cred32->CredentialCount; Cred64->OldCredentialCount = Cred32->OldCredentialCount; Cred64->DefaultSalt.Length = Cred32->DefaultSalt.Length; Cred64->DefaultSalt.MaximumLength = Cred32->DefaultSalt.MaximumLength; Cred64->Flags = Cred32->Flags; Cred64->Revision = Cred32->Revision;
Base = (PCHAR)Cred64;
From = (CHAR UNALIGNED *) RtlOffsetToPointer(Cred32,Cred32->DefaultSalt.Buffer);
// Note: 1 KERB_KEY_DATA struct is already calculated in
// the sizeof(KERB_STORED_CREDENTIAL)
Offset = sizeof(KERB_STORED_CREDENTIAL) + ((CredCount) * sizeof(KERB_KEY_DATA));
Where = RtlOffsetToPointer(Cred64, Offset);
Cred64->DefaultSalt.Buffer = (PWSTR) (ULONG_PTR) Offset; RtlCopyMemory( Where, From, Cred32->DefaultSalt.Length );
Where += ROUND_UP_COUNT(Cred64->DefaultSalt.Length, ALIGN_LPTSTR);
// copy credentials
for (Cred = 0; Cred < CredCount; Cred++) { Cred64->Credentials[Cred].Salt.Length = Cred32->Credentials[Cred].Salt.Length; Cred64->Credentials[Cred].Salt.MaximumLength = Cred32->Credentials[Cred].Salt.MaximumLength;
From = (CHAR UNALIGNED *) RtlOffsetToPointer(Cred32, Cred32->Credentials[Cred].Salt.Buffer); if (Cred32->Credentials[Cred].Salt.Length != 0) { Cred64->Credentials[Cred].Salt.Buffer = (PWSTR) (ULONG_PTR) RtlPointerToOffset(Base,Where); }
RtlCopyMemory( Where, From, Cred32->Credentials[Cred].Salt.Length );
Where += ROUND_UP_COUNT(Cred64->Credentials[Cred].Salt.Length, ALIGN_LPTSTR);
Cred64->Credentials[Cred].Key.keytype = Cred32->Credentials[Cred].Key.keytype; Cred64->Credentials[Cred].Key.keyvalue.length = Cred32->Credentials[Cred].Key.keyvaluelength;
From = RtlOffsetToPointer(Cred32,Cred32->Credentials[Cred].Key.keyvaluevalue); Cred64->Credentials[Cred].Key.keyvalue.value = (PUCHAR) (ULONG_PTR) RtlPointerToOffset(Base,Where);
RtlCopyMemory( Where, From, Cred32->Credentials[Cred].Key.keyvaluelength );
Where += ROUND_UP_COUNT(Cred32->Credentials[Cred].Key.keyvaluelength, ALIGN_LPTSTR); }
// TBD: Validation code ?
*ppCred64 = Cred64; *pCredLength = CredSize;
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KdcPack32BitStoredCredential
//
// Synopsis: This function converts a 64 bit KERB_STORED_CREDENTIAL
// to a 32 bit KERB_STORED_CREDENTIAL
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes: Free the return value using MIDL_user_free()
//
//
//--------------------------------------------------------------------------
NTSTATUS KdcPack32BitStoredCredential( IN PKERB_STORED_CREDENTIAL Cred64, OUT PKERB_STORED_CREDENTIAL32 * ppCred32, OUT PULONG pCredSize ) {
ULONG Offset, CredSize = sizeof(KERB_STORED_CREDENTIAL32); ULONG CredCount, Cred; NTSTATUS Status = STATUS_SUCCESS; PKERB_STORED_CREDENTIAL32 Cred32 = NULL; PCHAR Where, From, Base;
*ppCred32 = NULL; *pCredSize = 0;
if (Cred64 == NULL) { return STATUS_SUCCESS; }
// Get the expected size of the resultant blob
CredSize += ((Cred64->CredentialCount + Cred64->OldCredentialCount) * KERB_KEY_DATA32_SIZE);
CredSize += Cred64->DefaultSalt.MaximumLength; for (CredCount = 0; CredCount < (ULONG) (Cred64->CredentialCount+Cred64->OldCredentialCount); CredCount++) { CredSize += Cred64->Credentials[CredCount].Salt.MaximumLength; CredSize += Cred64->Credentials[CredCount].Key.keyvalue.length; }
Cred32 = (PKERB_STORED_CREDENTIAL32) MIDL_user_allocate(CredSize);
if (NULL == Cred32) { return STATUS_INSUFFICIENT_RESOURCES; }
Base = (PCHAR) Cred32;
// Copy over USHORTS
Cred32->Revision = Cred64->Revision; Cred32->Flags = Cred64->Flags; Cred32->CredentialCount = Cred64->CredentialCount; Cred32->OldCredentialCount = Cred64->OldCredentialCount;
// Copy over salt
Cred32->DefaultSalt.Length = Cred64->DefaultSalt.Length; Cred32->DefaultSalt.MaximumLength = Cred64->DefaultSalt.MaximumLength;
Offset = KERB_STORED_CREDENTIAL32_SIZE + ((CredCount+1) * KERB_KEY_DATA32_SIZE);
Where = RtlOffsetToPointer(Base,Offset); From = RtlOffsetToPointer(Cred64, Cred64->DefaultSalt.Buffer); Cred32->DefaultSalt.Buffer = RtlPointerToOffset(Base,Where);
RtlCopyMemory( Where, From, Cred64->DefaultSalt.Length );
Where += Cred64->DefaultSalt.Length;
// Copy over creds (KERB_KEY_DATA)
for (Cred = 0; Cred < CredCount;Cred++) { Cred32->Credentials[Cred].Salt.Length = Cred64->Credentials[Cred].Salt.Length; Cred32->Credentials[Cred].Salt.MaximumLength = Cred64->Credentials[Cred].Salt.MaximumLength; From = RtlOffsetToPointer(Cred64, Cred64->Credentials[Cred].Salt.Buffer);
// Only add in buffer pointer if there's data to copy.
if (Cred32->Credentials[Cred].Salt.Length != 0) { Cred32->Credentials[Cred].Salt.Buffer = RtlPointerToOffset(Base,Where); }
RtlCopyMemory( Where, From, Cred64->Credentials[Cred].Salt.Length );
Where += Cred64->Credentials[Cred].Salt.Length;
// Keys
Cred32->Credentials[Cred].Key.keytype = Cred64->Credentials[Cred].Key.keytype ; Cred32->Credentials[Cred].Key.keyvaluelength = Cred64->Credentials[Cred].Key.keyvalue.length; From = RtlOffsetToPointer(Cred64, Cred64->Credentials[Cred].Key.keyvalue.value);
RtlCopyMemory( Where, From, Cred64->Credentials[Cred].Key.keyvalue.length );
Cred32->Credentials[Cred].Key.keyvaluevalue = RtlPointerToOffset(Base,Where); Where += Cred64->Credentials[Cred].Key.keyvalue.length; }
*ppCred32 = Cred32; *pCredSize = CredSize;
return STATUS_SUCCESS;
}
#endif
|