Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1791 lines
46 KiB

//+-----------------------------------------------------------------------
//
// 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