Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4563 lines
137 KiB

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: getas.cxx
//
// Contents: GetASTicket and support functions
//
// Classes:
//
// Functions:
//
// History: 04-Mar-94 wader Created
//
//----------------------------------------------------------------------------
#include "kdcsvr.hxx"
#include "kdctrace.h"
#include "krb5p.h"
#include <userall.h>
#include <utils.hxx>
extern "C"
{
#include <md5.h>
}
#include "fileno.h"
#define FILENO FILENO_GETAS
// Local prototypes
NTSTATUS
AsNegCacheCheck(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength,
OUT PBOOLEAN pfAvoidSendToPDC
);
NTSTATUS
AsNegCacheUpdate(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength,
IN NTSTATUS StatusPdcAuth
);
NTSTATUS
AsNegCacheDelete(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength
);
// Negative Cache data stucts
RTL_CRITICAL_SECTION l_ApNegCacheCritSect;
LIST_ENTRY l_ApNegCacheList;
BOOLEAN g_fApNegCacheInitialized = FALSE; // Simple variable to make sure that the package was initialize
#define KERB_AP_NEGATIVE_MAX_LOGON_COUNT 10 // number of failed auth attempts to PDC before imposing 5 minute waits
#define KERB_MAX_FAILED_LIST_ENTRIES 50 // max number of entries in the negative cache list table
#define KERB_5_MINUTES_100NANO 3000000000 // number of 100 Nanoseconds in 5 minutes
typedef struct _NEGATIVE_CACHE {
LIST_ENTRY Next;
ULONG lBadLogonCount;
LARGE_INTEGER TimeLastPDCContact;
char digest[MD5DIGESTLEN]; // md5 signature of the AS request info
} NEGATIVE_CACHE, *PNEGATIVE_CACHE;
LARGE_INTEGER tsInfinity = {0xffffffff,0x7fffffff};
LONG lInfinity = 0x7fffffff;
enum {
SubAuthUnknown,
SubAuthNoFilter,
SubAuthYesFilter
} KdcSubAuthFilterPresent = SubAuthUnknown;
extern "C"
NTSTATUS NTAPI
Msv1_0ExportSubAuthenticationRoutine(
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
IN PVOID LogonInformation,
IN ULONG Flags,
IN ULONG DllNumber,
IN PUSER_ALL_INFORMATION UserAll,
OUT PULONG WhichFields,
OUT PULONG UserFlags,
OUT PBOOLEAN Authoritative,
OUT PLARGE_INTEGER LogoffTime,
OUT PLARGE_INTEGER KickoffTime
);
extern "C"
BOOLEAN NTAPI
Msv1_0SubAuthenticationPresent(
IN ULONG DllNumber
);
ULONG
NetpDcElapsedTime(
IN ULONG StartTime
)
/*++
Routine Description:
Returns the time (in milliseconds) that has elapsed is StartTime.
Arguments:
StartTime - A time stamp from GetTickCount()
Return Value:
Returns the time (in milliseconds) that has elapsed is StartTime.
--*/
{
ULONG CurrentTime;
//
// If time has has wrapped,
// account for it.
//
CurrentTime = GetTickCount();
if ( CurrentTime >= StartTime ) {
return CurrentTime - StartTime;
} else {
return (0xFFFFFFFF-StartTime) + CurrentTime;
}
}
//+-------------------------------------------------------------------------
//
// Function: KdcForwardLogonToPDC
//
// Synopsis: Forwards a failed-password logon to the PDC.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcForwardLogonToPDC(
IN PKERB_MESSAGE_BUFFER InputMessage,
IN PKERB_MESSAGE_BUFFER OutputMessage
)
{
KERBERR KerbErr = KDC_ERR_NONE;
NTSTATUS Status;
BOOLEAN CalledPDC;
KERB_MESSAGE_BUFFER Reply = {0};
DOMAIN_SERVER_ROLE ServerRole;
Status = SamIQueryServerRole(
GlobalAccountDomainHandle,
&ServerRole
);
if (!KdcGlobalAvoidPdcOnWan &&
NT_SUCCESS(Status) &&
(ServerRole == DomainServerRoleBackup))
{
Status = KerbMakeKdcCall(
SecData.KdcDnsRealmName(),
NULL, // no account name
TRUE, // call the PDC
TRUE, // use TCP/IP, not UDP
InputMessage,
&Reply,
0, // no additional flags
&CalledPDC
);
if (!NT_SUCCESS(Status))
{
KerbErr = KRB_ERR_GENERIC;
}
else
{
OutputMessage->Buffer = (PBYTE) MIDL_user_allocate(Reply.BufferSize);
if (OutputMessage->Buffer != NULL)
{
OutputMessage->BufferSize = Reply.BufferSize;
RtlCopyMemory(
OutputMessage->Buffer,
Reply.Buffer,
OutputMessage->BufferSize
);
}
else
{
KerbErr = KRB_ERR_GENERIC;
}
KerbFree(Reply.Buffer);
}
}
else
{
KerbErr = KRB_ERR_GENERIC;
}
return(KerbErr);
}
//+---------------------------------------------------------------------------
//
// Function: KdcVerifyKdcAsRep
//
// Synopsis: Verifies that our AS_REP came from a KDC, as opposed to a malicious
// attacker by evaluating the TGT embedded in response
//
// Arguments: Reply PKERB_KDC_REPLY
//
// Returns: Boolean to client.
//
// History: 12-June-2000 Todds Created
//
//----------------------------------------------------------------------------
BOOLEAN
KdcVerifyKdcAsRep(
PKERB_KDC_REPLY Reply,
PKERB_PRINCIPAL_NAME RequestBodyClientName
)
{
BOOLEAN fRet = FALSE;
KERBERR KerbErr;
KDC_TICKET_INFO KrbtgtTicketInfo = {0};
UNICODE_STRING ServerNames[3];
UNICODE_STRING ClientName;
ULONG NameType;
PKERB_ENCRYPTION_KEY EncryptionKey = NULL;
PKERB_ENCRYPTED_TICKET DecryptedTicket = NULL;
KERB_REALM LocalRealm;
// Get the server key for krbtgt
KerbErr = SecData.GetKrbtgtTicketInfo(&KrbtgtTicketInfo);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_WARN, "SecData.Getkrbtgtticketinfo failed!\n"));
goto Cleanup;
}
ServerNames[0] = *SecData.KdcFullServiceKdcName();
ServerNames[1] = *SecData.KdcFullServiceDnsName();
ServerNames[2] = *SecData.KdcFullServiceName();
LocalRealm = SecData.KdcKerbDnsRealmName();
//
// Verify the realm of the ticket
//
if (!KerbCompareRealmNames(
&LocalRealm,
&Reply->ticket.realm
))
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Tgt reply is not for our realm: %s instead of %s\n",
KLIN(FILENO, __LINE__), Reply->ticket.realm, LocalRealm));
KerbErr = KRB_AP_ERR_NOT_US;
goto Cleanup;
}
EncryptionKey = KerbGetKeyFromList(
KrbtgtTicketInfo.Passwords,
Reply->ticket.encrypted_part.encryption_type
);
if (EncryptionKey == NULL)
{
D_DebugLog((DEB_ERROR, "Couldn't get key for decrypting krbtgt\n"));
KerbErr = KRB_AP_ERR_NOKEY;
goto Cleanup;
}
KerbErr = KerbVerifyTicket(
&Reply->ticket,
3, // 3 names
ServerNames,
SecData.KdcDnsRealmName(),
EncryptionKey,
&SkewTime,
&DecryptedTicket
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR, "KLIN(%x) Failed to verify ticket - %x\n",
KLIN(FILENO, __LINE__),KerbErr));
goto Cleanup;
}
//
// Verify the realm of the client is the same as our realm
//
if (!KerbCompareRealmNames(
&LocalRealm,
&DecryptedTicket->client_realm
))
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Verified ticket client realm is wrong: %s instead of %s\n",
KLIN(FILENO, __LINE__),DecryptedTicket->client_realm, LocalRealm));
KerbErr = KRB_AP_ERR_NOT_US;
goto Cleanup;
}
fRet = TRUE;
Cleanup:
if (DecryptedTicket != NULL)
{
KerbFreeTicket(DecryptedTicket);
}
if (!fRet && KerbErr == KRB_AP_ERR_MODIFIED)
{
ClientName.Buffer = NULL;
KerbConvertPrincipalNameToString(
&ClientName,
&NameType,
RequestBodyClientName
);
ReportServiceEvent(
EVENTLOG_ERROR_TYPE,
KDCEVENT_INVALID_FORWARDED_AS_REQ,
sizeof(ULONG),
&KerbErr,
1, // number of strings
ClientName.Buffer
);
if (ClientName.Buffer != NULL)
{
MIDL_user_free(ClientName.Buffer);
}
}
return fRet;
}
//+---------------------------------------------------------------------------
//
// Function: FailedLogon
//
// Synopsis: Processes a failed logon. This is to be called on a failed auth
// attempt on the BDC. A series of rules will be applied to see if the request
// should be forwarded to the PDC>
//
// Effects: May raise an exception, audit, event, lockout, etc.
// Will increment the bad password count if Reason is KDC_ERR_PREAUTH_FAILED
//
// Arguments: [UserHandle] -- [in] Client who didn't log on.
// [ClientAddress] -- Address of client making request
// [Client] -- [in optional] Sid of the client requesting logon
// [ClientSize] -- [in] Length of the sid
// [Reason] -- [in] the reason this logon failed.
// [UsedOldPassword] -- [in] caller used old password to log on;
// do not increment the account lockout count
//
// Requires:
//
// Returns: VOID - any message from PDC is sent in outputmessage
//
// Algorithm:
//
// History: 03-May-94 wader Created
//
// Notes: This usually returns hrReason, but it may map it to
// something else.
//
//----------------------------------------------------------------------------
VOID
FailedLogon( IN SAMPR_HANDLE UserHandle,
IN OPTIONAL PSOCKADDR ClientAddress,
IN PKERB_PRINCIPAL_NAME RequestBodyClientName,
IN OPTIONAL UCHAR *Client,
IN ULONG ClientSize,
IN PKDC_TICKET_INFO ClientTicketInfo,
IN PKERB_MESSAGE_BUFFER InputMessage,
IN PKERB_MESSAGE_BUFFER OutputMessage,
IN PUNICODE_STRING ClientNetbiosAddress,
IN KERBERR Reason,
IN NTSTATUS LogonStatus,
IN BOOLEAN UsedOldPassword
)
{
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS StatusTemp = STATUS_SUCCESS;
SAM_LOGON_STATISTICS LogonStats;
LARGE_INTEGER CurrentTime;
PKERB_ERROR ErrorMessage = NULL;
PKERB_KDC_REPLY Reply = NULL;
BOOLEAN LockoutEnabled = FALSE;
PKERB_EXT_ERROR pExtendedError = NULL;
BOOLEAN fAvoidSend = FALSE; // Should be avoid sending to the PDC ?
BOOLEAN fForwardedToPDC = FALSE;
DOMAIN_SERVER_ROLE ServerRole;
TRACE(KDC, FailedLogon, DEB_FUNCTION);
// If we are the PDC, then goto cleanup - no need for negative cache processing
Status = SamIQueryServerRole(
GlobalAccountDomainHandle,
&ServerRole
);
if (NT_SUCCESS(Status) &&
(ServerRole == DomainServerRolePrimary))
{
goto Cleanup;
}
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
//
// It's important to know why the logon can fail. For each possible
// reason, decide if that is a reason to lock out the account.
//
//
// Check to see if we've seen this request before recently
//
if (KDC_ERR_NONE == ReplayDetect->Check(
InputMessage->Buffer,
InputMessage->BufferSize,
NULL,
0,
&CurrentTime,
TRUE,
FALSE,
TRUE))
{
KERBERR KerbErr;
KERBERR ForwardKerbErr;
//
// If the password was bad then we want to update the sam information
// Check w/ the PDC for account & pwd errors
//
if ((Reason == KDC_ERR_PREAUTH_FAILED) ||
(LogonStatus == STATUS_PASSWORD_EXPIRED) ||
(LogonStatus == STATUS_PASSWORD_MUST_CHANGE) ||
(LogonStatus == STATUS_ACCOUNT_LOCKED_OUT))
{
// Check to see if we should forward the request to the PDC
Status = AsNegCacheCheck(
Client,
ClientSize,
ClientNetbiosAddress->Buffer,
ClientNetbiosAddress->Length,
&fAvoidSend);
if (!NT_SUCCESS(Status) || (fAvoidSend == TRUE))
{
D_DebugLog((DEB_WARN, "<CACHE> NOT fwding to PDC r(0x%x) ls(0x%x)\n", Reason, LogonStatus));
goto Cleanup;
}
//
// Pass this request to the KDC
//
D_DebugLog((DEB_TRACE,"KLIN(%x) sending request to PDC for updated info\n",
KLIN(FILENO,__LINE__)));
KerbErr = KdcForwardLogonToPDC(
InputMessage,
OutputMessage
);
//
// Return an better error if it wasn't generic.
//
if (KERB_SUCCESS(KerbErr))
{
ForwardKerbErr = KerbUnpackKerbError(
OutputMessage->Buffer,
OutputMessage->BufferSize,
&ErrorMessage
);
if (KERB_SUCCESS(ForwardKerbErr))
{
NTSTATUS StatusPDC = STATUS_INTERNAL_ERROR;
if (ErrorMessage->bit_mask & error_data_present)
{
KerbErr = KerbUnpackErrorData(
ErrorMessage,
&pExtendedError
);
if (KERB_SUCCESS(KerbErr) && (EXT_CLIENT_INFO_PRESENT(pExtendedError)))
{
StatusPDC = pExtendedError->status;
D_DebugLog((DEB_TRACE,"KLIN(%x) info from PDC StatusPDC 0x%x\n",
KLIN(FILENO,__LINE__), StatusPDC));
}
}
Reason = ErrorMessage->error_code; // PDC kerb error takes priority
fForwardedToPDC = TRUE;
// Update the list of forwarded failed user auth
// If the lockout policy is enabled, we should continue to forward auth requests to
// the PDC (i.e. we should not cache this failure), to keep the right lockout count
// until the account becomes locked on the PDC
// We need to also increment if an older password is used. This is because the negative
// cache logic is not incremented for use of an older password (1st or 2nd in history).
if (ClientTicketInfo->LockoutThreshold)
{
LockoutEnabled = TRUE; // account can be locked out
D_DebugLog((DEB_TRACE, "FailedLogon: Account lockout policy is enabled\n"));
}
if (!LockoutEnabled ||
(StatusPDC == STATUS_ACCOUNT_LOCKED_OUT) ||
UsedOldPassword)
{
StatusTemp = AsNegCacheUpdate(
Client,
ClientSize,
ClientNetbiosAddress->Buffer,
ClientNetbiosAddress->Length,
StatusPDC
);
}
} else {
//
// This may have been a successful, forwarded AS_REQ. If so,
// reset bad password count on this BDC...
//
ForwardKerbErr = KerbUnpackAsReply(
OutputMessage->Buffer,
OutputMessage->BufferSize,
&Reply
);
if (KERB_SUCCESS(ForwardKerbErr) &&
KdcVerifyKdcAsRep(
Reply,
RequestBodyClientName
))
{
D_DebugLog((DEB_TRACE,"KLIN(%x) Successful AS-REP from PDC\n",
KLIN(FILENO,__LINE__)));
Reason = KDC_ERR_NONE; // PDC kerb error takes priority
fForwardedToPDC = TRUE;
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
LogonStats.StatisticsToApply =
USER_LOGON_INTER_SUCCESS_LOGON |
USER_LOGON_PDC_RETRY_SUCCESS |
USER_LOGON_TYPE_KERBEROS;
if ( (ClientAddress == NULL)
|| (ClientAddress->sa_family == AF_INET) )
{
LogonStats.ClientInfo.Type = SamClientIpAddr;
LogonStats.ClientInfo.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
}
Status = SamIUpdateLogonStatistics(
UserHandle,
&LogonStats
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Could not reset user bad pwd count - %x\n", Status));
}
D_DebugLog((DEB_TRACE,"KLIN(%x) SamIUpdate successful forward to PDC StatsApply 0x%x.\n",
KLIN(FILENO,__LINE__), LogonStats.StatisticsToApply));
StatusTemp = AsNegCacheDelete(
Client,
ClientSize,
ClientNetbiosAddress->Buffer,
ClientNetbiosAddress->Length
);
D_DebugLog((DEB_ERROR, "KLIN(%x) Purged entry due to valid fwd'd response\n",
KLIN(FILENO,__LINE__)));
} else {
DebugLog((DEB_ERROR, "KLIN(%x) Got reply from fwd'd request to PDC, but wasn't valid!\n",
KLIN(FILENO,__LINE__)));
if (KerbErr != KRB_ERR_GENERIC)
{
Reason = KerbErr;
fForwardedToPDC = TRUE;
}
}
}
}
}
}
Cleanup:
if (NULL != ErrorMessage)
{
KerbFreeKerbError(ErrorMessage);
}
if (NULL != Reply)
{
KerbFreeAsReply(Reply);
}
if (NULL != pExtendedError)
{
MIDL_user_free(pExtendedError);
}
if (Reason == KDC_ERR_PREAUTH_FAILED) // tests for LogonStatus == STATUS_WRONG_PASSWORD
{
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
LogonStats.StatisticsToApply =
USER_LOGON_BAD_PASSWORD_WKSTA |
USER_LOGON_TYPE_KERBEROS;
// Indicate wrong password used but not if previous password matches
if (!UsedOldPassword)
{
LogonStats.StatisticsToApply |= USER_LOGON_BAD_PASSWORD;
}
LogonStats.Workstation = *ClientNetbiosAddress;
if ( (ClientAddress == NULL)
|| (ClientAddress->sa_family == AF_INET) )
{
LogonStats.ClientInfo.Type = SamClientIpAddr;
LogonStats.ClientInfo.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
}
Status = SamIUpdateLogonStatistics(
UserHandle,
&LogonStats
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "SamIUpdateLogonStatistics failed - %x\n", Status ));
}
D_DebugLog((DEB_TRACE,"KLIN(%x) SamIUpdate Preauth failure StatsApply 0x%x.\n",
KLIN(FILENO,__LINE__), LogonStats.StatisticsToApply));
}
else if (Reason == KDC_ERR_KEY_EXPIRED) // tests for ((LogonStatus == STATUS_PASSWORD_MUST_CHANGE) ||
{ // (LogonStatus == STATUS_PASSWORD_EXPIRED))
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
LogonStats.StatisticsToApply =
USER_LOGON_STAT_BAD_PWD_COUNT |
(fForwardedToPDC ? USER_LOGON_PDC_RETRY_SUCCESS : 0) |
USER_LOGON_TYPE_KERBEROS;
LogonStats.Workstation = *ClientNetbiosAddress;
if ( (ClientAddress == NULL)
|| (ClientAddress->sa_family == AF_INET) )
{
LogonStats.ClientInfo.Type = SamClientIpAddr;
LogonStats.ClientInfo.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
}
Status = SamIUpdateLogonStatistics(
UserHandle,
&LogonStats
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "SamIUpdateLogonStatistics failed - %x\n", Status ));
}
D_DebugLog((DEB_TRACE,"KLIN(%x) SamIUpdate KeyExpire failure StatsApply 0x%x.\n",
KLIN(FILENO,__LINE__), LogonStats.StatisticsToApply));
}
}
//+---------------------------------------------------------------------------
//
// Function: KdcHandleNoLogonServers
//
// Synopsis: If a password has verified, and we've got no GCs against which
// to validate logon restrictions, then go ahead and set the
// sam info level to include the new USER_LOGON_NO_LOGON_SERVERS
// flag
//
// Effects:
//
// Arguments: [UserHandle] -- Client who logged on.
// [ClientAddress] -- Address of client making request
//
//
// Algorithm:
//
// History: 24-Aug-2000 Todds Created
//
// Notes: On successful logon w/ no GC, update SAM user flag
//
//----------------------------------------------------------------------------
KERBERR
KdcHandleNoLogonServers(
SAMPR_HANDLE UserHandle,
PSOCKADDR ClientAddress OPTIONAL
)
{
SAM_LOGON_STATISTICS LogonStats;
TRACE(KDC, KdcHandleNoLogonServers, DEB_FUNCTION);
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
LogonStats.StatisticsToApply =
USER_LOGON_NO_LOGON_SERVERS | USER_LOGON_TYPE_KERBEROS;
if ( (ClientAddress == NULL)
|| (ClientAddress->sa_family == AF_INET) ) {
// Set to local address (known to be 4 bytes) or IP address
LogonStats.ClientInfo.Type = SamClientIpAddr;
LogonStats.ClientInfo.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
}
(VOID) SamIUpdateLogonStatistics(
UserHandle,
&LogonStats
);
return(KDC_ERR_NONE);
}
//+---------------------------------------------------------------------------
//
// Function: SuccessfulLogon
//
// Synopsis: Processes a successful logon.
//
// Effects: May raise an event, create an audit, throw a party.
//
// Arguments: [UserHandle] -- Client who logged on.
//
//
// Algorithm:
//
// History: 03-May-94 wader Created
//
// Notes: On successful logon, we discard the history of failed logons
// (as far as lockout is concerned).
//
//----------------------------------------------------------------------------
KERBERR
SuccessfulLogon(
IN SAMPR_HANDLE UserHandle,
IN OPTIONAL PSOCKADDR ClientAddress,
IN UCHAR *Client,
IN ULONG ClientSize,
IN PKERB_MESSAGE_BUFFER Request,
IN PUNICODE_STRING ClientNetbiosAddress,
IN PUSER_INTERNAL6_INFORMATION UserInfo
)
{
SAM_LOGON_STATISTICS LogonStats;
KERB_MESSAGE_BUFFER Reply = {0};
NTSTATUS Status = STATUS_UNKNOWN_REVISION;
DOMAIN_SERVER_ROLE ServerRole;
TRACE(KDC, SuccessfulLogon, DEB_FUNCTION);
RtlZeroMemory(&LogonStats, sizeof(LogonStats));
LogonStats.StatisticsToApply =
USER_LOGON_INTER_SUCCESS_LOGON | USER_LOGON_TYPE_KERBEROS;
if ( (ClientAddress == NULL)
|| (ClientAddress->sa_family == AF_INET) ) {
// Set to local address (known to be 4 bytes) or IP address
LogonStats.ClientInfo.Type = SamClientIpAddr;
LogonStats.ClientInfo.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
}
(VOID) SamIUpdateLogonStatistics(
UserHandle,
&LogonStats
);
D_DebugLog((DEB_TRACE,"KLIN(%x) SamIUpdate Successfullogon StatsApply 0x%x.\n",
KLIN(FILENO,__LINE__), LogonStats.StatisticsToApply));
Status = SamIQueryServerRole(
GlobalAccountDomainHandle,
&ServerRole
);
if (NT_SUCCESS(Status) &&
(ServerRole == DomainServerRoleBackup))
{
//
// if this logon reset the bad password count, notify the PDC
//
if (UserInfo->I1.BadPasswordCount != 0)
{
Status = SamIResetBadPwdCountOnPdc(UserHandle);
if (!NT_SUCCESS(Status))
{
if (Status == STATUS_UNKNOWN_REVISION)
{
D_DebugLog((DEB_ERROR, "SamIResetBadPwdCount not implemented on pdc.\n"));
// W2k behavior, in case we have an old PDC
(VOID) KdcForwardLogonToPDC(
Request,
&Reply
);
if (Reply.Buffer != NULL)
{
MIDL_user_free(Reply.Buffer);
}
}
else
{
D_DebugLog((DEB_ERROR, "SamIResetBadPwdCount failed - %x.\n", Status));
}
}
}
// Clear any negative cache entry (Forward to PDC)
(void)AsNegCacheDelete(
Client,
ClientSize,
ClientNetbiosAddress->Buffer,
ClientNetbiosAddress->Length
);
}
return(KDC_ERR_NONE);
}
//+-------------------------------------------------------------------------
//
// Function: IsSubAuthFilterPresent
//
// Synopsis: Figures out whether the MSV1_0 subauthentication filter is present
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns: TRUE or FALSE
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN
IsSubAuthFilterPresent()
{
if ( KdcSubAuthFilterPresent == SubAuthUnknown ) {
if ( Msv1_0SubAuthenticationPresent( KERB_SUBAUTHENTICATION_FLAG )) {
KdcSubAuthFilterPresent = SubAuthYesFilter;
} else {
KdcSubAuthFilterPresent = SubAuthNoFilter;
}
}
if ( KdcSubAuthFilterPresent == SubAuthNoFilter ) {
return FALSE;
}
return TRUE;
}
//+-------------------------------------------------------------------------
//
// Function: KdcCallSubAuthRoutine
//
// Synopsis: Calls the MSV1_0 subauthentication filter, if it is present
//
// Effects: If the filter returns an error, returns that error
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCallSubAuthRoutine(
IN PKDC_TICKET_INFO TicketInfo,
IN PUSER_INTERNAL6_INFORMATION UserInfo,
IN PUNICODE_STRING ClientNetbiosAddress,
OUT PLARGE_INTEGER LogoffTime,
OUT PKERB_EXT_ERROR pExtendedError
)
{
NTSTATUS Status = STATUS_SUCCESS;
KERBERR KerbErr = KDC_ERR_NONE;
NETLOGON_INTERACTIVE_INFO LogonInfo = {0};
//
// Subauth parameters
//
ULONG WhichFields = 0;
ULONG UserFlags = 0;
BOOLEAN Authoritative = TRUE;
LARGE_INTEGER KickoffTime;
PUSER_ALL_INFORMATION UserAll = &UserInfo->I1;
//
// Check if Msv1_0 has a subauth filter loaded
//
if ( !IsSubAuthFilterPresent()) {
return KDC_ERR_NONE;
}
LogonInfo.Identity.LogonDomainName = *SecData.KdcRealmName();
LogonInfo.Identity.ParameterControl = 0; // this can be set to use a particular package
LogonInfo.Identity.UserName = TicketInfo->AccountName;
LogonInfo.Identity.Workstation = *ClientNetbiosAddress;
//
// Leave logon id field blank
//
if (UserAll->NtPassword.Length == NT_OWF_PASSWORD_LENGTH)
{
RtlCopyMemory(
&LogonInfo.NtOwfPassword,
UserAll->NtPassword.Buffer,
NT_OWF_PASSWORD_LENGTH
);
}
if (UserAll->LmPassword.Length == LM_OWF_PASSWORD_LENGTH)
{
RtlCopyMemory(
&LogonInfo.LmOwfPassword,
UserAll->LmPassword.Buffer,
NT_OWF_PASSWORD_LENGTH
);
}
//
// Make sure logoff time is intialized to something interesting
//
*LogoffTime = KickoffTime = UserAll->AccountExpires;
//
// Make the call
//
Status = Msv1_0ExportSubAuthenticationRoutine(
NetlogonInteractiveInformation,
&LogonInfo,
MSV1_0_PASSTHRU,
KERB_SUBAUTHENTICATION_FLAG,
UserAll,
&WhichFields,
&UserFlags,
&Authoritative,
LogoffTime,
&KickoffTime
);
//
// If the kickoff time is more restrictive, use it.
//
if (KickoffTime.QuadPart < LogoffTime->QuadPart)
{
LogoffTime->QuadPart = KickoffTime.QuadPart;
}
//
// Map the error code
//
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN,
"(KLIN:%x) Subauth failed the logon: 0x%x\n",
KLIN(FILENO, __LINE__),
Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KDC_ERR_POLICY;
}
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcBuildEtypeInfo
//
// Synopsis: Builds a list of supported etypes & salts
//
// Effects:
//
// Arguments: TicketInfo - client's ticket info
// OutputPreAuth - receives any preauth data to return to client
//
// Requires:
//
// Returns: kerberr
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcBuildEtypeInfo(
IN PKDC_TICKET_INFO TicketInfo,
IN PKERB_KDC_REQUEST_BODY RequestBody,
OUT PKERB_PA_DATA_LIST * OutputPreAuth
)
{
KERBERR KerbErr = KDC_ERR_NONE;
BOOLEAN FoundEtype = FALSE;
ULONG Index;
PKERB_ETYPE_INFO NextEntry = NULL;
PKERB_ETYPE_INFO EtypeInfo = NULL;
PKERB_PA_DATA_LIST OutputList = NULL;
UNICODE_STRING TempSalt = {0};
STRING TempString = {0};
*OutputPreAuth = NULL;
//
// Build the array of etypes, in reverse order because we are adding
// to the front of the list
//
for ( Index = TicketInfo->Passwords->CredentialCount; Index > 0; Index-- )
{
//
// Only return types that the client supports.
//
if (!KdcCheckForEtype(
RequestBody->encryption_type,
TicketInfo->Passwords->Credentials[Index-1].Key.keytype
))
{
continue;
}
FoundEtype = TRUE;
NextEntry = (PKERB_ETYPE_INFO) MIDL_user_allocate(sizeof(KERB_ETYPE_INFO));
if (NextEntry == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
NextEntry,
sizeof(KERB_ETYPE_INFO)
);
//
// Copy in the etype
//
NextEntry->value.encryption_type =
TicketInfo->Passwords->Credentials[Index-1].Key.keytype;
//
// add the salt - check the per-key salt and then the default salt.
//
if (TicketInfo->Passwords->Credentials[Index-1].Salt.Buffer != NULL)
{
TempSalt = TicketInfo->Passwords->Credentials[Index-1].Salt;
}
else if (TicketInfo->Passwords->DefaultSalt.Buffer != NULL)
{
TempSalt = TicketInfo->Passwords->DefaultSalt;
}
else
{
TempSalt.Buffer = NULL ;
TempSalt.Length = 0 ;
TempSalt.MaximumLength = 0 ;
}
//
// If we have a salt, convert it to ansi & return it.
//
if (TempSalt.Buffer != NULL)
{
TempString.Buffer = NULL;
TempString.Length = 0;
TempString.MaximumLength = 0;
KerbErr = KerbUnicodeStringToKerbString(
&TempString,
&TempSalt
);
if (!KERB_SUCCESS(KerbErr))
{
MIDL_user_free(NextEntry);
goto Cleanup;
}
NextEntry->value.bit_mask |= salt_present;
NextEntry->value.salt.length = TempString.Length;
NextEntry->value.salt.value = (PUCHAR) TempString.Buffer;
}
NextEntry->next = EtypeInfo;
EtypeInfo = NextEntry;
}
//
// If we can't find a matching etype, then we've got to return an error
// to the client...
if (FoundEtype)
{
OutputList = (PKERB_PA_DATA_LIST) MIDL_user_allocate(sizeof(KERB_PA_DATA_LIST));
if (OutputList == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
OutputList,
sizeof(KERB_PA_DATA_LIST)
);
OutputList->value.preauth_data_type = KRB5_PADATA_ETYPE_INFO;
OutputList->next = NULL;
KerbErr = KerbPackData(
&EtypeInfo,
PKERB_ETYPE_INFO_PDU,
(PULONG) &OutputList->value.preauth_data.length,
&OutputList->value.preauth_data.value
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
*OutputPreAuth = OutputList;
OutputList = NULL;
}
else // did not find etype from request that we support, warn the admin
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
DebugLog((DEB_ERROR, "KdcCheckForEtype no intersection between client and server Etypes!\n"));
KdcReportKeyError(
&(TicketInfo->AccountName),
NULL,
KDC_KEY_ID_AS_BUILD_ETYPE_INFO,
KDCEVENT_NO_KEY_INTERSECTION_AS,
RequestBody->encryption_type,
TicketInfo
);
}
Cleanup:
//
// Cleanup the etype list, as it is returned in marshalled form.
//
while (EtypeInfo != NULL)
{
NextEntry = EtypeInfo->next;
if (EtypeInfo->value.salt.value != NULL)
{
TempString.Buffer = (PCHAR) EtypeInfo->value.salt.value;
TempString.Length = (USHORT) EtypeInfo->value.salt.length;
KerbFreeString((PUNICODE_STRING) &TempString);
}
MIDL_user_free(EtypeInfo);
EtypeInfo = NextEntry;
}
if (OutputList != NULL)
{
KerbFreePreAuthData( OutputList);
}
return KerbErr;
}
//+-------------------------------------------------------------------------
//
// Function: KdcBuildPreauthTypeList
//
// Synopsis: For returning with a PREAUTH-REQUIRED message
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcBuildPreauthTypeList(
OUT PKERB_PA_DATA_LIST * PreauthTypeList
)
{
PKERB_PA_DATA_LIST DataList = NULL;
KERBERR KerbErr = KDC_ERR_NONE;
//
// Allocate and fill in the first item
//
DataList = (PKERB_PA_DATA_LIST) MIDL_user_allocate(sizeof(KERB_PA_DATA_LIST));
if (DataList == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
DataList,
sizeof(KERB_PA_DATA_LIST)
);
DataList->value.preauth_data_type = KRB5_PADATA_ENC_TIMESTAMP;
//
// Even if we fail the allocation, we can still return this value.
//
*PreauthTypeList = DataList;
DataList->next = (PKERB_PA_DATA_LIST) MIDL_user_allocate(sizeof(KERB_PA_DATA_LIST));
if (DataList->next == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
DataList->next,
sizeof(KERB_PA_DATA_LIST)
);
DataList = DataList->next;
DataList->value.preauth_data_type = KRB5_PADATA_PK_AS_REP;
Cleanup:
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcBuildPwSalt
//
// Synopsis: builds the pw-salt pa data type
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcBuildPwSalt(
IN PKERB_STORED_CREDENTIAL Passwords,
IN PKERB_ENCRYPTION_KEY ReplyKey,
IN OUT PKERB_PA_DATA_LIST * OutputPreAuthData
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PKERB_PA_DATA_LIST DataList = NULL;
PKERB_KEY_DATA KeyData = NULL;
STRING Salt = {0};
UNICODE_STRING SaltUsed = {0};
ULONG Index;
//
// Find the key use for encryption.
//
for (Index = 0; Index < Passwords->CredentialCount ; Index++ )
{
if (Passwords->Credentials[Index].Key.keytype == (int) ReplyKey->keytype)
{
KeyData = &Passwords->Credentials[Index];
break;
}
}
if (KeyData == NULL)
{
goto Cleanup;
}
//
// Locate the salt used
//
switch ( ReplyKey->keytype )
{
case KERB_ETYPE_RC4_HMAC_NT:
case KERB_ETYPE_RC4_HMAC_NT_EXP:
case KERB_ETYPE_RC4_HMAC_OLD:
case KERB_ETYPE_RC4_MD4:
//
// These etypes don't use salt - don't send it.
//
goto Cleanup;
case KERB_ETYPE_DES_CBC_MD5:
case KERB_ETYPE_DES_CBC_CRC:
if (KeyData->Salt.Buffer != NULL)
{
SaltUsed = KeyData->Salt;
}
else if (Passwords->DefaultSalt.Buffer != NULL)
{
SaltUsed = Passwords->DefaultSalt;
}
break;
default:
//
// Don't fail on unknown etypes - just don't send papwsalt.
//
D_DebugLog(( DEB_WARN, "Trying to salt %x etype, which we don't know\n", ReplyKey->keytype ));
goto Cleanup;
}
//
// Convert the salt to a kerb string
//
KerbErr = KerbUnicodeStringToKerbString(
&Salt,
&SaltUsed
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Allocate and fill in the first item
//
DataList = (PKERB_PA_DATA_LIST) MIDL_user_allocate(sizeof(KERB_PA_DATA_LIST));
if (DataList == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
DataList,
sizeof(KERB_PA_DATA_LIST)
);
DataList->value.preauth_data_type = KRB5_PADATA_PW_SALT;
DataList->value.preauth_data.length = Salt.Length;
DataList->value.preauth_data.value = (PUCHAR) Salt.Buffer;
Salt.Buffer = NULL;
DataList->next = *OutputPreAuthData;
*OutputPreAuthData = DataList;
DataList = NULL;
Cleanup:
if (DataList != NULL)
{
KerbFreePreAuthData((PKERB_PA_DATA_LIST)DataList);
}
if (Salt.Buffer != NULL)
{
MIDL_user_free(Salt.Buffer);
}
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyEncryptedTimeStamp
//
// Synopsis: Verifies an encrypted time stamp pre-auth data
//
// Effects:
//
// Arguments: PreAuthData - preauth data from client
// TicketInfo - client's ticket info
// UserHandle - handle to client's account
// OutputPreAuth - receives any preauth data to return to client
//
// Requires:
//
// Returns: KDC_ERR_PREAUTH_FAILED - the password was bad
// Other errors - preauth failed but shouldn't trigger lockout
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcVerifyEncryptedTimeStamp(
IN PKERB_PA_DATA_LIST PreAuthData,
IN PKDC_TICKET_INFO TicketInfo,
IN PKERB_KDC_REQUEST_BODY RequestBody,
IN SAMPR_HANDLE UserHandle,
OUT PKERB_PA_DATA_LIST * OutputPreAuth,
OUT PBOOLEAN UsedOldPassword
)
{
KERBERR KerbErr;
PKERB_ENCRYPTED_DATA EncryptedData = NULL;
PKERB_ENCRYPTED_TIMESTAMP EncryptedTime = NULL;
PKERB_ENCRYPTION_KEY UserKey = NULL;
LARGE_INTEGER CurrentTime;
LARGE_INTEGER ClientTime;
if ((TicketInfo->UserAccountControl & USER_ACCOUNT_DISABLED))
{
KerbErr = KDC_ERR_CLIENT_REVOKED;
goto Cleanup;
}
//
// Unpack the pre-auth data into an encrypted data first.
//
KerbErr = KerbUnpackEncryptedData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
&EncryptedData
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Now decrypt the encrypted data (in place)
//
UserKey = KerbGetKeyFromList(
TicketInfo->Passwords,
EncryptedData->encryption_type
);
if (UserKey == NULL)
{
// fakeit
KERB_CRYPT_LIST FakeList;
DebugLog((DEB_ERROR, "KdcVerifyEncryptedTimeStamp found no key %#x for %wZ, account control %#x\n",
EncryptedData->encryption_type, &TicketInfo->AccountName, TicketInfo->UserAccountControl));
FakeList.next = NULL;
FakeList.value = EncryptedData->encryption_type ;
//
// do not report an error if this is an DES only user and the preauth
// etype is RC4_HMAC_NT: the client can remove this error by setting
// DefaultEncryptionType registry key and this is a common error
// (although invalid)
//
if ( !( (TicketInfo->UserAccountControl & USER_USE_DES_KEY_ONLY)
&& (EncryptedData->encryption_type == KERB_ETYPE_RC4_HMAC_NT) ) )
{
KdcReportKeyError(
&(TicketInfo->AccountName),
NULL,
KDC_KEY_ID_AS_VERIFY_PREAUTH,
KDCEVENT_NO_KEY_INTERSECTION_AS,
&FakeList,
TicketInfo
);
}
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
goto Cleanup;
}
KerbErr = KerbDecryptDataEx(
EncryptedData,
UserKey,
KERB_ENC_TIMESTAMP_SALT,
(PULONG) &EncryptedData->cipher_text.length,
EncryptedData->cipher_text.value
);
if (!KERB_SUCCESS(KerbErr))
{
KERBERR KerbErr2;
ULONG ulIndex = 0;
//
// Bug #450148: Do not increment account lockout count when logging
// on with old password
//
// Attempt to decrypt the data using the old password to see if that
// is what is going on
// We will check the previous password and the one before that (second previous)
// password should it exist.
//
//
// Original EncryptedData has been trashed by in-proc decryption;
// must re-generate
//
KerbFreeEncryptedData(EncryptedData);
EncryptedData = NULL;
UserKey = NULL;
KerbErr2 = KerbUnpackEncryptedData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
&EncryptedData
);
if ( KERB_SUCCESS( KerbErr2 ))
{
UserKey = KerbGetKeyFromListByIndex(
TicketInfo->OldPasswords,
EncryptedData->encryption_type,
&ulIndex
);
}
if (UserKey != NULL)
{
KerbErr2 = KerbDecryptDataEx(
EncryptedData,
UserKey,
KERB_ENC_TIMESTAMP_SALT,
(PULONG) &EncryptedData->cipher_text.length,
EncryptedData->cipher_text.value
);
}
else
{
KerbErr2 = KDC_ERR_ETYPE_NOTSUPP;
}
if ( KERB_SUCCESS( KerbErr2 ))
{
D_DebugLog((DEB_WARN,
"KLIN(%x) Pre-auth data encrypted with old password\n",
KLIN(FILENO,__LINE__)));
*UsedOldPassword = TRUE;
}
else
{
D_DebugLog((DEB_WARN,
"KLIN(%x) Failed to decrypt timestamp pre-auth data with previous old password: 0x%x\n",
KLIN(FILENO,__LINE__),
KerbErr2));
// Now see if the password before that (second previous) password matches
// This is necessary to hanlde the cases where an account is locked out and
// then the password is reset and change password on next login is selected.
// This requires a test of the previous 2 passwords in history
//
// Original EncryptedData has been trashed by in-proc decryption;
// must re-generate
//
KerbFreeEncryptedData(EncryptedData);
EncryptedData = NULL;
UserKey = NULL;
KerbErr2 = KerbUnpackEncryptedData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
&EncryptedData
);
if ( KERB_SUCCESS( KerbErr2 ))
{
UserKey = KerbGetKeyFromListByIndex(
TicketInfo->OldPasswords,
EncryptedData->encryption_type,
&ulIndex
);
}
if (UserKey != NULL)
{
KerbErr2 = KerbDecryptDataEx(
EncryptedData,
UserKey,
KERB_ENC_TIMESTAMP_SALT,
(PULONG) &EncryptedData->cipher_text.length,
EncryptedData->cipher_text.value
);
}
else
{
KerbErr2 = KDC_ERR_ETYPE_NOTSUPP;
}
if ( KERB_SUCCESS( KerbErr2 ))
{
D_DebugLog((DEB_WARN,
"KLIN(%x) Pre-auth data encrypted with second old password\n",
KLIN(FILENO,__LINE__)));
*UsedOldPassword = TRUE;
}
else
{
D_DebugLog((DEB_WARN,
"KLIN(%x) Failed to decrypt timestamp pre-auth data with second previous old password: 0x%x\n",
KLIN(FILENO,__LINE__),
KerbErr2));
}
}
KerbErr = KDC_ERR_PREAUTH_FAILED;
goto Cleanup;
}
//
// unpack the decrypted data into a KERB_ENCRYPTED_TIMESTAMP
//
KerbErr = KerbUnpackData(
EncryptedData->cipher_text.value,
EncryptedData->cipher_text.length,
KERB_ENCRYPTED_TIMESTAMP_PDU,
(PVOID *) &EncryptedTime
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_WARN,"KLIN(%x) Failed to unpack preauth data to encrpyted_time\n",
KLIN(FILENO,__LINE__)));
goto Cleanup;
}
//
// Now verify the time.
//
KerbConvertGeneralizedTimeToLargeInt(
&ClientTime,
&EncryptedTime->timestamp,
((EncryptedTime->bit_mask & KERB_ENCRYPTED_TIMESTAMP_usec_present) != 0) ?
EncryptedTime->KERB_ENCRYPTED_TIMESTAMP_usec : 0
);
GetSystemTimeAsFileTime(
(PFILETIME) &CurrentTime
);
//
// We don't want to check too closely, so allow for skew
//
if ((CurrentTime.QuadPart + SkewTime.QuadPart < ClientTime.QuadPart) ||
(CurrentTime.QuadPart - SkewTime.QuadPart > ClientTime.QuadPart))
{
D_DebugLog((DEB_ERROR, "KLIN(%x) Client %wZ time is incorrect:\n",
KLIN(FILENO,__LINE__),
&TicketInfo->AccountName));
PrintTime(DEB_ERROR, "Client Time is", &ClientTime );
PrintTime(DEB_ERROR, "KDC Time is", &CurrentTime );
//
// We don't want to lockout the account if the time is off
//
KerbErr = KRB_AP_ERR_SKEW;
goto Cleanup;
}
KerbErr = KDC_ERR_NONE;
Cleanup:
//
// Build an ETYPE_INFO structure to return
//
if ((KerbErr == KDC_ERR_PREAUTH_FAILED) || (KerbErr == KDC_ERR_ETYPE_NOTSUPP))
{
KERBERR TmpErr;
TmpErr = KdcBuildEtypeInfo(
TicketInfo,
RequestBody,
OutputPreAuth
);
//
// In this case, we can't find any ETypes that both the client and
// server support, so we've got to bail w/ proper error
// message...
//
if (TmpErr == KDC_ERR_ETYPE_NOTSUPP)
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
}
}
if (EncryptedData != NULL)
{
KerbFreeEncryptedData(EncryptedData);
}
if (EncryptedTime != NULL)
{
KerbFreeData(KERB_ENCRYPTED_TIMESTAMP_PDU, EncryptedTime);
}
return(KerbErr);
}
typedef enum _BUILD_PAC_OPTIONS {
IncludePac,
DontIncludePac,
DontCare
} BUILD_PAC_OPTIONS, *PBUILD_PAC_OPTIONS;
//+-------------------------------------------------------------------------
//
// Function: KdcCheckPacRequestPreAuthData
//
// Synopsis: Gets the status of whether the client wants a PAC from the
// pre-auth data
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckPacRequestPreAuthData(
IN PKERB_PA_DATA_LIST PreAuthData,
IN OUT PBUILD_PAC_OPTIONS BuildPac
)
{
PKERB_PA_PAC_REQUEST PacRequest = NULL;
KERBERR KerbErr = KDC_ERR_NONE;
DsysAssert(PreAuthData->value.preauth_data_type == KRB5_PADATA_PAC_REQUEST);
KerbErr = KerbUnpackData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
KERB_PA_PAC_REQUEST_PDU,
(PVOID *) &PacRequest
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
if (PacRequest->include_pac)
{
*BuildPac = IncludePac;
}
else
{
*BuildPac = DontIncludePac;
}
D_DebugLog((DEB_T_TICKETS,"Setting BuildPac from pa-data to %d\n",*BuildPac));
Cleanup:
if (PacRequest != NULL)
{
KerbFreeData(
KERB_PA_PAC_REQUEST_PDU,
PacRequest
);
}
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcCheckPreAuthData
//
// Synopsis: Checks the pre-auth data in an AS request. This routine
// may return pre-auth data to caller on both success and
// failure.
//
// Effects:
//
// Arguments: ClientTicketInfo - client account's ticket info
// UserHandle - Handle to client's user object
// PreAuthData - Pre-auth data supplied by client
// PreAuthType - The type of pre-auth used
// OutputPreAuthData - pre-auth data to return to client
// BuildPac - TRUE if we should build a PAC for this client
//
//
// Requires:
//
// Returns: KDC_ERR_PREAUTH_REQUIRED, KDC_ERR_PREAUTH_FAILED
//
// Notes: This routine should be more extensible - at some point
// it should allow DLLs to be plugged in that implement
// preauth.
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckPreAuthData(
IN PKDC_TICKET_INFO ClientTicketInfo,
IN SAMPR_HANDLE UserHandle,
IN PUSER_INTERNAL6_INFORMATION UserInfo,
IN OPTIONAL PKERB_PA_DATA_LIST PreAuthData,
IN PKERB_KDC_REQUEST_BODY RequestBody,
OUT PULONG PreAuthType,
OUT PKERB_PA_DATA_LIST * OutputPreAuthData,
OUT PBOOLEAN BuildPac,
OUT PULONG Nonce,
OUT PKERB_ENCRYPTION_KEY EncryptionKey,
OUT PUNICODE_STRING TransitedRealms,
OUT PKDC_PKI_AUDIT_INFO PkiAuditInfo,
OUT PKERB_MESSAGE_BUFFER ErrorData,
OUT PKERB_EXT_ERROR pExtendedError,
OUT PBOOLEAN UsedOldPassword
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PKERB_PA_DATA_LIST OutputElement = NULL;
PKERB_PA_DATA_LIST ListElement = NULL;
BOOLEAN ValidPreauthPresent = FALSE;
BUILD_PAC_OPTIONS PacOptions = DontCare;
*OutputPreAuthData = NULL;
*BuildPac = FALSE;
*UsedOldPassword = FALSE;
//
// Loop through the supplied pre-auth data elements and handle each one
//
for (ListElement = PreAuthData;
ListElement != NULL ;
ListElement = ListElement->next )
{
switch(ListElement->value.preauth_data_type) {
case KRB5_PADATA_ENC_TIMESTAMP:
*PreAuthType = ListElement->value.preauth_data_type;
KerbErr = KdcVerifyEncryptedTimeStamp(
ListElement,
ClientTicketInfo,
RequestBody,
UserHandle,
&OutputElement,
UsedOldPassword
);
if (KERB_SUCCESS(KerbErr))
{
ValidPreauthPresent = TRUE;
}
break;
case KRB5_PADATA_PK_AS_REP:
*PreAuthType = ListElement->value.preauth_data_type;
KerbErr = KdcCheckPkinitPreAuthData(
ClientTicketInfo,
UserHandle,
ListElement,
RequestBody,
&OutputElement,
Nonce,
EncryptionKey,
TransitedRealms,
PkiAuditInfo,
pExtendedError
);
if (KERB_SUCCESS(KerbErr))
{
ValidPreauthPresent = TRUE;
}
break;
case KRB5_PADATA_PAC_REQUEST:
KerbErr = KdcCheckPacRequestPreAuthData(
ListElement,
&PacOptions
);
break;
default:
break;
} // switch
if (OutputElement != NULL)
{
OutputElement->next = *OutputPreAuthData;
*OutputPreAuthData = OutputElement;
OutputElement = NULL;
}
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
} // for
// We need to check preauth data by default, unless, the account tells
// us not to.
//
if (!(UserInfo->I1.UserAccountControl & USER_DONT_REQUIRE_PREAUTH) &&
!ValidPreauthPresent &&
KERB_SUCCESS(KerbErr))
{
KerbErr = KDC_ERR_PREAUTH_REQUIRED;
//
// Return the list of supported types, if we don't have other
// data to return.
//
if (*OutputPreAuthData == NULL)
{
(VOID) KdcBuildPreauthTypeList(OutputPreAuthData);
if (*OutputPreAuthData != NULL)
{
PKERB_PA_DATA_LIST EtypeInfo = NULL;
KERBERR TmpErr;
TmpErr = KdcBuildEtypeInfo(
ClientTicketInfo,
RequestBody,
&EtypeInfo
);
//
// In this case, we can't find any ETypes that both the client and
// server support, so we've got to bail w/ proper error
// message...
//
if (TmpErr == KDC_ERR_ETYPE_NOTSUPP)
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
}
if (EtypeInfo != NULL)
{
EtypeInfo->next = *OutputPreAuthData;
*OutputPreAuthData = EtypeInfo;
EtypeInfo = NULL;
}
}
}
}
//
// Set the final option for including the pac- if the pac_request was
// included, honor it. Otherwise build the pac if valid preauth
// was supplied.
//
switch(PacOptions) {
case DontCare:
*BuildPac = ValidPreauthPresent;
break;
case IncludePac:
*BuildPac = TRUE;
break;
case DontIncludePac:
*BuildPac = FALSE;
break;
}
Cleanup:
return(KerbErr);
}
//+---------------------------------------------------------------------------
//
// Function: BuildTicketAS
//
// Synopsis: Builds an AS ticket, including filling inthe name fields
// and flag fields.
//
// Arguments: [ClientTicketInfo] -- client asking for the ticket
// [ClientName] -- name of client
// [ServiceTicketInfo] -- service ticket is for
// [ServerName] -- name of service
// [RequestBody] -- ticket request
// [NewTicket] -- (out) ticket
//
// History: 24-May-93 WadeR Created
//
// Notes: See 3.1.3, A.2 of the Kerberos V5 R5.2 spec
//
//----------------------------------------------------------------------------
KERBERR
BuildTicketAS(
IN PKDC_TICKET_INFO ClientTicketInfo,
IN PKERB_PRINCIPAL_NAME ClientName,
IN PKDC_TICKET_INFO ServiceTicketInfo,
IN PKERB_PRINCIPAL_NAME ServerName,
IN OPTIONAL PKERB_HOST_ADDRESSES HostAddresses,
IN PLARGE_INTEGER LogoffTime,
IN PLARGE_INTEGER AccountExpiry,
IN PKERB_KDC_REQUEST_BODY RequestBody,
IN ULONG CommonEType,
IN ULONG PreAuthType,
IN PUNICODE_STRING TransitedRealm,
OUT PKERB_TICKET NewTicket,
OUT PKERB_EXT_ERROR pExtendedError
)
{
KERBERR Status = KDC_ERR_NONE;
PKERB_ENCRYPTED_TICKET EncryptedTicket = NULL;
LARGE_INTEGER TicketLifespan;
LARGE_INTEGER TicketRenewspan;
ULONG KdcOptions = 0;
BOOLEAN fKpasswd = FALSE;
TRACE(KDC, BuildTicketAS, DEB_FUNCTION);
EncryptedTicket = (PKERB_ENCRYPTED_TICKET) NewTicket->encrypted_part.cipher_text.value;
KdcOptions = KerbConvertFlagsToUlong(&RequestBody->kdc_options);
NewTicket->ticket_version = KERBEROS_VERSION;
D_DebugLog((DEB_T_TICKETS, "Building an AS ticket to cname %wZ for sname %wZ\n",
&ClientTicketInfo->AccountName, &ServiceTicketInfo->AccountName));
//
// Since this is the AS ticket, we fake the TGTFlags parameter to be the
// maximum the client is allowed to have.
//
// Check to see if the request is for kadmin/changepw service, in which
// case, we only want the ticket to be good for 2 minutes
//
Status = KerbCompareKdcNameToPrincipalName(
ServerName,
GlobalKpasswdName,
&fKpasswd
);
if (!KERB_SUCCESS(Status))
{
D_DebugLog((DEB_TRACE,"KLIN(%x) Failed to check server name against GlobalKpasswdName: 0x%x\n",
KLIN(FILENO,__LINE__), Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
if (fKpasswd)
{
D_DebugLog((DEB_TRACE, "Restricting service ticket life time for kadmin/changepw\n"));
TicketLifespan.QuadPart = (LONGLONG) 10000000 * 60 * 2;
TicketRenewspan.QuadPart = (LONGLONG) 10000000 * 60 * 2;
}
else
{
TicketLifespan = SecData.KdcTgtTicketLifespan();
TicketRenewspan = SecData.KdcTicketRenewSpan();
}
Status = KdcBuildTicketTimesAndFlags(
ClientTicketInfo->fTicketOpts,
ServiceTicketInfo->fTicketOpts,
&TicketLifespan,
&TicketRenewspan,
NULL, // no s4u info
LogoffTime,
AccountExpiry,
RequestBody,
NULL, // no source ticket
EncryptedTicket,
pExtendedError
);
if (!KERB_SUCCESS(Status))
{
D_DebugLog((DEB_TRACE,"KLIN(%x) Failed to build ticket times and flags: 0x%x\n",
KLIN(FILENO,__LINE__), Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
*((PULONG)EncryptedTicket->flags.value) |= KerbConvertUlongToFlagUlong(KERB_TICKET_FLAGS_initial);
//
// Turn on preauth flag if necessary
//
if (PreAuthType != 0)
{
*((PULONG)EncryptedTicket->flags.value) |= KerbConvertUlongToFlagUlong(KERB_TICKET_FLAGS_pre_authent);
}
Status = KerbMakeKey(
CommonEType,
&EncryptedTicket->key
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
//
// Insert the service names. If the client requested canoncalization,
// return our realm name & sam account name. Otherwise copy what the
// client requested
//
if (((KdcOptions & KERB_KDC_OPTIONS_name_canonicalize) != 0) &&
((ServiceTicketInfo->UserAccountControl & USER_USE_DES_KEY_ONLY) == 0))
{
PKERB_INTERNAL_NAME TempServiceName = NULL;
//
// Build the service name for the ticket. For interdomain trust
// accounts, this is "krbtgt / domain name"
//
if (ServiceTicketInfo->UserId == DOMAIN_USER_RID_KRBTGT)
{
Status = KerbBuildFullServiceKdcName(
SecData.KdcDnsRealmName(),
SecData.KdcServiceName(),
KRB_NT_SRV_INST,
&TempServiceName
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
Status = KerbConvertKdcNameToPrincipalName(
&NewTicket->server_name,
TempServiceName
);
KerbFreeKdcName(&TempServiceName);
if (!KERB_SUCCESS(Status))
{
goto Cleanup;
}
}
else if ((ServiceTicketInfo->UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) != 0)
{
Status = KerbBuildFullServiceKdcName(
&ServiceTicketInfo->AccountName,
SecData.KdcServiceName(),
KRB_NT_SRV_INST,
&TempServiceName
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
Status = KerbConvertKdcNameToPrincipalName(
&NewTicket->server_name,
TempServiceName
);
KerbFreeKdcName(&TempServiceName);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
}
else
{
Status = KerbConvertStringToPrincipalName(
&NewTicket->server_name,
&ServiceTicketInfo->AccountName,
KRB_NT_PRINCIPAL
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
}
}
else
{
//
// No canonicalzation, so copy in all the names as the client
// requested them.
//
Status = KerbDuplicatePrincipalName(
&NewTicket->server_name,
ServerName
);
if (!KERB_SUCCESS(Status))
{
goto Cleanup;
}
}
NewTicket->realm = SecData.KdcKerbDnsRealmName();
//
// Insert the client names. If the client requested canoncalization,
// return our realm name & sam account name. Otherwise copy what the
// client requested
//
if ((ClientName->name_type == KRB_NT_ENTERPRISE_PRINCIPAL) &&
((ClientTicketInfo->UserAccountControl & USER_USE_DES_KEY_ONLY) == 0))
{
Status = KerbConvertStringToPrincipalName(
&EncryptedTicket->client_name,
&ClientTicketInfo->AccountName,
KRB_NT_PRINCIPAL
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
}
else
{
Status = KerbDuplicatePrincipalName(
&EncryptedTicket->client_name,
ClientName
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
}
Status = KerbDuplicateRealm(
&EncryptedTicket->client_realm,
SecData.KdcKerbDnsRealmName()
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
if (HostAddresses != NULL)
{
EncryptedTicket->bit_mask |= KERB_ENCRYPTED_TICKET_client_addresses_present;
EncryptedTicket->KERB_ENCRYPTED_TICKET_client_addresses = HostAddresses;
}
else
{
EncryptedTicket->bit_mask &= ~KERB_ENCRYPTED_TICKET_client_addresses_present;
EncryptedTicket->KERB_ENCRYPTED_TICKET_client_addresses = NULL;
}
if (TransitedRealm->Length > 0)
{
STRING TempString;
Status = KerbUnicodeStringToKerbString(
&TempString,
TransitedRealm
);
if (!KERB_SUCCESS(Status))
{
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
goto Cleanup;
}
EncryptedTicket->transited.transited_type = DOMAIN_X500_COMPRESS;
EncryptedTicket->transited.contents.value = (PUCHAR) TempString.Buffer;
EncryptedTicket->transited.contents.length = (int) TempString.Length;
}
else
{
RtlZeroMemory(
&EncryptedTicket->transited,
sizeof(KERB_TRANSITED_ENCODING)
);
}
EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data = NULL;
#if DBG
PrintTicket( DEB_T_TICKETS, "BuildTicketAS: Final ticket", NewTicket );
#endif
Cleanup:
if (!KERB_SUCCESS(Status))
{
KdcFreeInternalTicket(NewTicket);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCheckIfSPNIsChangePW
//
// Synopsis: Check if the service name is kadmin/changepw.
//
// Arguments: pServerName - Contains the service name
// pLogonRestrictionsFlags - Output flags value.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbCheckIfSPNIsChangePW(
IN PKERB_INTERNAL_NAME ServerName,
IN ULONG *pLogonRestrictionsFlags
)
{
if (KerbEqualKdcNames(
ServerName,
GlobalKpasswdName
))
{
*pLogonRestrictionsFlags |= KDC_RESTRICT_IGNORE_PW_EXPIRATION;
}
return;
}
//+-------------------------------------------------------------------------
//
// Function: I_GetASTicket
//
// Synopsis: Gets an authentication service ticket to the requested
// service.
//
// Effects: Allocates and encrypts a KDC reply
//
// Arguments: RequestMessage - Contains the AS request message
// InputMessage - buffer client sent, used for replay detection
// OutputMessage - Contains the AS reply message
// ErrorData - contains any error data for an error message
//
// Requires:
//
// Returns: KDC_ERR_ or KRB_AP_ERR errors only
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
I_GetASTicket(
IN OPTIONAL PSOCKADDR ClientAddress,
IN PKERB_AS_REQUEST RequestMessage,
IN PUNICODE_STRING RequestRealm,
IN PKERB_MESSAGE_BUFFER InputMessage,
OUT PKERB_MESSAGE_BUFFER OutputMessage,
OUT PKERB_MESSAGE_BUFFER ErrorData,
OUT PKERB_EXT_ERROR pExtendedError,
OUT PUNICODE_STRING ClientRealm,
OUT PUNICODE_STRING ClientStringName,
OUT PUNICODE_STRING ServerStringName
)
{
KERBERR KerbErr = KDC_ERR_NONE;
NTSTATUS LogonStatus = STATUS_SUCCESS;
KDC_TICKET_INFO ClientTicketInfo = {0};
KDC_TICKET_INFO ServiceTicketInfo = {0};
SAMPR_HANDLE UserHandle = NULL;
PUSER_INTERNAL6_INFORMATION UserInfo = NULL;
SID_AND_ATTRIBUTES_LIST GroupMembership = {0};
KERB_ENCRYPTION_KEY EncryptionKey = {0};
PKERB_ENCRYPTION_KEY ServerKey = NULL;
PKERB_ENCRYPTION_KEY ClientKey = NULL;
ULONG CommonEType = KERB_ETYPE_DEFAULT;
KERB_TICKET Ticket = {0};
KERB_ENCRYPTED_TICKET EncryptedTicket = {0};
KERB_ENCRYPTED_KDC_REPLY ReplyBody = {0};
KERB_KDC_REPLY Reply = {0};
PKERB_KDC_REQUEST_BODY RequestBody = NULL;
PKERB_AUTHORIZATION_DATA PacAuthData = NULL;
PKERB_PA_DATA_LIST OutputPreAuthData = NULL;
PPKERB_HOST_ADDRESSES EffectiveAddresses = NULL;
KDC_PKI_AUDIT_INFO PkiAuditInfo = {0};
PKERB_INTERNAL_NAME ClientName = NULL;
PKERB_INTERNAL_NAME ServerName = NULL;
UNICODE_STRING ClientNetbiosAddress = {0};
UNICODE_STRING ServerRealm = {0};
UNICODE_STRING MappedClientName = {0};
UNICODE_STRING TransitedRealm = {0};
LARGE_INTEGER LogoffTime;
LARGE_INTEGER AccountExpiry;
ULONG NameFlags = 0;
ULONG PreAuthType = 0;
ULONG KdcOptions = 0;
ULONG TicketFlags = 0;
ULONG ReplyTicketFlags = 0;
ULONG Nonce = 0;
ULONG LogonRestrictionsFlags = 0;
ULONG WhichFields = 0;
BOOLEAN AuditedFailure = FALSE;
BOOLEAN BuildPac = FALSE;
BOOLEAN ClientReferral = FALSE;
BOOLEAN ServerReferral = FALSE;
BOOLEAN LoggedFailure = FALSE;
BOOLEAN ClientInfoPresent = FALSE;
BOOLEAN UsedOldPassword = FALSE;
BOOLEAN bRestrictUserAccounts = FALSE;
KDC_AS_EVENT_INFO ASEventTraceInfo = {0};
TRACE(KDC, I_GetASTicket, DEB_FUNCTION);
//
// Initialize local variables
//
EncryptedTicket.flags.value = (PUCHAR) &TicketFlags;
EncryptedTicket.flags.length = sizeof(ULONG) * 8;
ReplyBody.flags.value = (PUCHAR) &ReplyTicketFlags;
ReplyBody.flags.length = sizeof(ULONG) * 8;
RtlInitUnicodeString( ClientRealm, NULL );
Ticket.encrypted_part.cipher_text.value = (PUCHAR) &EncryptedTicket;
//
// Assume that this isn't a logon request. If we manage to fail before
// we've determined it's a logon attempt, we won't mark it as a failed
// logon.
//
RequestBody = &RequestMessage->request_body;
//
// There are many options that are invalid for an AS ticket.
//
KdcOptions = KerbConvertFlagsToUlong(&RequestBody->kdc_options);
//
// Start event tracing (capture error cases too)
//
if (KdcEventTraceFlag){
ASEventTraceInfo.EventTrace.Guid = KdcGetASTicketGuid;
ASEventTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_START;
ASEventTraceInfo.EventTrace.Flags = WNODE_FLAG_TRACED_GUID;
ASEventTraceInfo.EventTrace.Size = sizeof (EVENT_TRACE_HEADER) + sizeof (ULONG);
ASEventTraceInfo.KdcOptions = KdcOptions;
TraceEvent(
KdcTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&ASEventTraceInfo
);
}
if (KdcOptions &
(KERB_KDC_OPTIONS_forwarded |
KERB_KDC_OPTIONS_proxy |
KERB_KDC_OPTIONS_unused7 |
KERB_KDC_OPTIONS_unused9 |
KERB_KDC_OPTIONS_renew |
KERB_KDC_OPTIONS_validate |
KERB_KDC_OPTIONS_reserved |
KERB_KDC_OPTIONS_enc_tkt_in_skey ) )
{
KerbErr = KDC_ERR_BADOPTION;
goto Cleanup;
}
if (( RequestBody->bit_mask & addresses_present ) &&
( RequestBody->addresses == NULL ))
{
KerbErr = KDC_ERR_BADOPTION;
goto Cleanup;
}
//
// Make sure a client name was supplied
//
if ((RequestBody->bit_mask & KERB_KDC_REQUEST_BODY_client_name_present) != 0)
{
KerbErr = KerbConvertPrincipalNameToKdcName(
&ClientName,
&RequestBody->KERB_KDC_REQUEST_BODY_client_name
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
KerbErr = KerbConvertKdcNameToString(
ClientStringName,
ClientName,
NULL
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
else
{
D_DebugLog((DEB_ERROR,"KLIN(%x) No principal name supplied to AS request - not allowed\n", KLIN(FILENO,__LINE__)));
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
goto Cleanup;
}
//
// Copy out the service name. This is not an optional field.
//
if ((RequestBody->bit_mask & KERB_KDC_REQUEST_BODY_server_name_present) == 0)
{
D_DebugLog((DEB_ERROR, "I_GetASTicket KLIN(%x) Client %wZ sent AS request with no server name\n",
KLIN(FILENO, __LINE__),
&ClientStringName));
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
KerbErr = KDC_ERR_BADOPTION;
goto Cleanup;
}
KerbErr = KerbConvertPrincipalNameToKdcName(
&ServerName,
&RequestBody->KERB_KDC_REQUEST_BODY_server_name
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
KerbErr = KerbConvertKdcNameToString(
ServerStringName,
ServerName,
NULL
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Check if the client said to canonicalize the name -- this is
// determined by the name type for AS_REQ.
//
if((KdcOptions & KERB_KDC_OPTIONS_name_canonicalize) != 0)
{
NameFlags |= KDC_NAME_CHECK_GC;
}
else
{
//
// canonicalize bit is not set so we want to check if the service
// name is kadmin/changepw, if it is we set the flag to indicate
// that we will ignore password expiration checking
//
KerbCheckIfSPNIsChangePW(
ServerName,
&LogonRestrictionsFlags);
}
D_DebugLog((DEB_TRACE, "I_GetASTicket getting an AS ticket to server "));
D_KerbPrintKdcName((DEB_TRACE, ServerName));
D_DebugLog((DEB_TRACE, "I_GetASTicket for client "));
D_KerbPrintKdcName((DEB_TRACE, ClientName));
//
// Get the client's NETBIOS address.
//
if ((RequestBody->bit_mask & addresses_present) != 0)
{
KerbErr = KerbGetClientNetbiosAddress(
&ClientNetbiosAddress,
RequestBody->addresses
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
//
// Normalize the client name.
//
if ( !IsSubAuthFilterPresent()) {
WhichFields = USER_ALL_KERB_CHECK_LOGON_RESTRICTIONS |
USER_ALL_KDC_CHECK_PREAUTH_DATA |
USER_ALL_ACCOUNTEXPIRES |
USER_ALL_KDC_GET_PAC_AUTH_DATA |
USER_ALL_SUCCESSFUL_LOGON;
} else {
//
// We do not know what the subauth routine needs, so get everything
//
WhichFields = 0xFFFFFFFF & ~USER_ALL_UNDEFINED_MASK;
}
//
// If there's no pre-auth, this could be an S4u location call.
// This will trigger alt_sec_id lookups.
//
if (KerbFindPreAuthDataEntry(
KRB5_PADATA_ENC_TIMESTAMP,
RequestMessage->KERB_KDC_REQUEST_preauth_data) == NULL)
{
NameFlags |= KDC_NAME_S4U_CLIENT;
}
KerbErr = KdcNormalize(
ClientName,
NULL,
RequestRealm,
NULL, // no source ticket
NameFlags | KDC_NAME_CLIENT | KDC_NAME_FOLLOW_REFERRALS | KDC_NAME_CHECK_GC,
FALSE, // do not restrict user accounts (user2user)
&ClientReferral,
ClientRealm,
&ClientTicketInfo,
pExtendedError,
&UserHandle,
WhichFields,
0L,
&UserInfo,
&GroupMembership
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "I_GetASTicket KLIN(%x) failed to normalize name %#x: ", KLIN(FILENO, __LINE__), KerbErr));
KerbPrintKdcName(DEB_ERROR, ClientName);
goto Cleanup;
}
if (ClientTicketInfo.Passwords != NULL)
{
ClientInfoPresent = TRUE;
// If Credential count is zero and there was no error, we do not have
// NT_OWF info so return Error since Kerb can not auth
if (ClientTicketInfo.Passwords->CredentialCount <= CRED_ONLY_LM_OWF)
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
DebugLog((DEB_ERROR,"KLIN(%x) Failed to normalize name - no creds ", KLIN(FILENO, __LINE__)));
KerbPrintKdcName(DEB_ERROR,ClientName);
goto Cleanup;
}
}
// If the UserHandle was NULL and there was no error, this must be
// a cross realm trust account logon. Fail it, we have no account
// to work with.
if (!UserHandle || !UserInfo)
{
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
D_DebugLog((DEB_ERROR,"KLIN(%x) Failed to normalize name", KLIN(FILENO,__LINE__)));
D_KerbPrintKdcName((DEB_ERROR, ClientName));
goto Cleanup;
}
//
// If this is a referral, return an error and the true realm name
// of the client
//
if (ClientReferral)
{
KerbErr = KDC_ERR_WRONG_REALM;
D_DebugLog((DEB_WARN,
"KLIN(%x) Client tried to logon to account in another realm\n",
KLIN(FILENO,__LINE__)));
goto Cleanup;
}
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KLIN(%x) Error getting client ticket info for %wZ: 0x%x \n",
KLIN(FILENO,__LINE__), &MappedClientName, KerbErr));
goto Cleanup;
}
//
// The below function will return true for pkinit
//
if (KerbFindPreAuthDataEntry(
KRB5_PADATA_PK_AS_REP,
RequestMessage->KERB_KDC_REQUEST_preauth_data) != NULL)
{
LogonRestrictionsFlags = KDC_RESTRICT_PKINIT_USED;
}
//
// The order to check information on an account is:
// STATUS_ACCOUNT_LOCKED_OUT
// STATUS_WRONG_PASSWORD
// STATUS_PASSWORD_MUST_CHANGE/STATUS_PASSWORD_EXPIRED
// Note that you check for a bad password before the last two conditions
//
// Check logon restrictions before preauth data, so we don't accidentally
// leak information about the password if account is locked out.
// LogonStatus will have which Account restriction failed, KerbErr has the category of failure
//
KerbErr = KerbCheckLogonRestrictions(
UserHandle,
&ClientNetbiosAddress,
&UserInfo->I1,
LogonRestrictionsFlags,
&LogoffTime,
&LogonStatus
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_WARN , "LogonRestriction check failed: LogonStatus: 0x%x KRB: 0x%x\n",
LogonStatus, KerbErr));
if ((LogonStatus == STATUS_ACCOUNT_LOCKED_OUT) || // Same as KerbErr == KDC_ERR_KEY_EXPIRED
(LogonStatus == STATUS_PASSWORD_MUST_CHANGE) ||
(LogonStatus == STATUS_PASSWORD_EXPIRED) ||
(LogonStatus == STATUS_NO_LOGON_SERVERS))
{
KERBERR PreAuthKerbErr;
BYTE ClientSid[MAX_SID_LEN];
RtlZeroMemory(ClientSid, MAX_SID_LEN);
KdcMakeAccountSid(ClientSid, ClientTicketInfo.UserId);
//
// Unpack the pre-auth data -- needed to check if wrong password and secondarily for auditing purposes
//
PreAuthKerbErr = KdcCheckPreAuthData(
&ClientTicketInfo,
UserHandle,
UserInfo,
RequestMessage->KERB_KDC_REQUEST_preauth_data,
RequestBody,
&PreAuthType,
&OutputPreAuthData,
&BuildPac,
&Nonce,
&EncryptionKey,
&TransitedRealm,
&PkiAuditInfo,
ErrorData,
pExtendedError,
&UsedOldPassword
);
if (!KERB_SUCCESS(PreAuthKerbErr))
{
//
// For account lockout, don't give the "attacker" extra info
// about bad password
// For PASSWORD_MUST_CHANGE and PASSWORD_EXPIRE check for good password otherwise,
// replace with STATUS_WRONG_PASSWORD
//
//
// We need to make sure we free up the error data
// and pa data to be returned from the client,
// but we really need to return the above error
// as long as the account is not Locked out.
//
if (LogonStatus != STATUS_ACCOUNT_LOCKED_OUT)
{
KerbErr = PreAuthKerbErr;
}
else if ( OutputPreAuthData != NULL )
{
KerbFreePreAuthData( OutputPreAuthData );
OutputPreAuthData = NULL;
}
// Audit the Pre-auth failure
D_DebugLog((DEB_WARN , "KLIN(%x) PreAuthData Check failed: LogonStatus: 0x%x KRB: 0x%x\n",
KLIN(FILENO,__LINE__), LogonStatus, KerbErr));
if (SecData.AuditKdcEvent(KDC_AUDIT_AS_FAILURE))
{
KdcLsaIAuditAsEvent(
SE_AUDITID_PREAUTH_FAILURE,
&ClientTicketInfo.AccountName,
NULL, // no domain name
ClientSid,
ServerStringName,
NULL, // no server sid
&PreAuthType,
(PULONG) &KerbErr,
NULL,
NULL,
GET_CLIENT_ADDRESS(ClientAddress),
&PkiAuditInfo.CertIssuerName,
&PkiAuditInfo.CertSerialNumber,
&PkiAuditInfo.CertThumbprint
);
AuditedFailure = TRUE;
}
}
//
// Only handle failed logon if pre-auth fails. Otherwise the error
// was something the client couldn't control, such as memory
// allocation or clock skew.
//
if ((KerbErr == KDC_ERR_PREAUTH_FAILED) || // tests for STATUS_WRONG_PASSWORD
(LogonStatus == STATUS_PASSWORD_MUST_CHANGE) ||
(LogonStatus == STATUS_PASSWORD_EXPIRED) ||
(LogonStatus == STATUS_ACCOUNT_LOCKED_OUT) )
{
D_DebugLog((DEB_WARN , "KLIN(%x) Calling Failedlogon: LogonStatus: 0x%x KRB: 0x%x PreAuthKRB: 0x%x\n",
KLIN(FILENO,__LINE__), LogonStatus, KerbErr, PreAuthKerbErr));
FailedLogon(
UserHandle,
ClientAddress,
&RequestBody->KERB_KDC_REQUEST_BODY_client_name,
ClientSid,
MAX_SID_LEN,
&ClientTicketInfo,
InputMessage,
OutputMessage,
&ClientNetbiosAddress,
KerbErr,
LogonStatus,
UsedOldPassword
);
LoggedFailure = TRUE;
//
// If there was a message form the PDC, then outputmessage will be non-NULL. This will always override any
// value specified in KerbErr or LogonStatus
// A NULL outputmessage indicates that we are on a PDC. And we create an error return based on LogonStatus
// Fill the extended error w/ status from acct. restrictions.
//
if ((OutputMessage->Buffer == NULL) && !NT_SUCCESS(LogonStatus))
{
FILL_EXT_ERROR_EX(pExtendedError, LogonStatus, FILENO, __LINE__);
D_DebugLog((DEB_TRACE, "KLIN(%x) setting error return based on logonstatus\n",
KLIN(FILENO,__LINE__)));
}
goto Cleanup;
}
else if (LogonStatus == STATUS_NO_LOGON_SERVERS)
{
D_DebugLog((DEB_WARN, "KLIN(%x) Logon Restriction check failed due to no logon servers\n",
KLIN(FILENO,__LINE__)));
KdcHandleNoLogonServers(UserHandle,
ClientAddress);
goto Cleanup;
}
else
{
DebugLog((DEB_WARN,"KLIN(%x) Logon restriction check failed: LogonStatus: 0x%x KRB: 0x%x\n",
KLIN(FILENO,__LINE__), LogonStatus, KerbErr));
FILL_EXT_ERROR_EX(pExtendedError, LogonStatus, FILENO, __LINE__);
goto Cleanup;
}
}
else
{ // Catch all of the AccountRestrictions not possibly passed to PDC
DebugLog((DEB_WARN,"KLIN(%x) Logon restriction check failed: LogonStatus: 0x%x KRB: 0x%x\n",
KLIN(FILENO,__LINE__),LogonStatus, KerbErr));
// Here's one case where we want to return errors to the client, so use EX
FILL_EXT_ERROR_EX(pExtendedError, LogonStatus, FILENO, __LINE__);
}
goto Cleanup;
} // End Logon Restriction Processing
//
// There was no Account Restrictions so continue processing. Check for correct password.
// Unpack the pre-auth data.
//
KerbErr = KdcCheckPreAuthData(
&ClientTicketInfo,
UserHandle,
UserInfo,
RequestMessage->KERB_KDC_REQUEST_preauth_data,
RequestBody,
&PreAuthType,
&OutputPreAuthData,
&BuildPac,
&Nonce,
&EncryptionKey,
&TransitedRealm,
&PkiAuditInfo,
ErrorData,
pExtendedError,
&UsedOldPassword
);
if (!KERB_SUCCESS(KerbErr))
{
BYTE ClientSid[MAX_SID_LEN];
RtlZeroMemory(ClientSid, MAX_SID_LEN);
KdcMakeAccountSid(ClientSid, ClientTicketInfo.UserId);
if (SecData.AuditKdcEvent(KDC_AUDIT_AS_FAILURE))
{
KdcLsaIAuditAsEvent(
SE_AUDITID_PREAUTH_FAILURE,
&ClientTicketInfo.AccountName,
NULL, // no domain name
ClientSid,
ServerStringName,
NULL, // no server sid
&PreAuthType,
(PULONG) &KerbErr,
NULL,
NULL,
GET_CLIENT_ADDRESS(ClientAddress),
&PkiAuditInfo.CertIssuerName,
&PkiAuditInfo.CertSerialNumber,
&PkiAuditInfo.CertThumbprint
);
AuditedFailure = TRUE;
}
//
// Only handle failed logon if pre-auth fails. Otherwise the error
// was something the client couldn't control, such as memory
// allocation or clock skew.
//
if (KerbErr == KDC_ERR_PREAUTH_FAILED)
{
D_DebugLog((DEB_WARN , "KLIN(%x) Calling Failedlogon: LogonStatus: 0x%x KRB: 0x%x\n",
KLIN(FILENO,__LINE__), LogonStatus, KerbErr));
FailedLogon(
UserHandle,
ClientAddress,
&RequestBody->KERB_KDC_REQUEST_BODY_client_name,
ClientSid,
MAX_SID_LEN,
&ClientTicketInfo,
InputMessage,
OutputMessage,
&ClientNetbiosAddress,
KerbErr,
LogonStatus,
UsedOldPassword
);
}
LoggedFailure = TRUE;
DebugLog((DEB_ERROR,"KLIN(%x) Failed to check pre-auth data: 0x%x\n",
KLIN(FILENO,__LINE__), KerbErr));
goto Cleanup;
}
//
// Check for subauthentication
//
KerbErr = KdcCallSubAuthRoutine(
&ClientTicketInfo,
UserInfo,
&ClientNetbiosAddress,
&LogoffTime,
pExtendedError
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_WARN,"KLIN(%x) Subuath restriction check failed: 0x%x\n",
KLIN(FILENO,__LINE__), KerbErr));
goto Cleanup;
}
//
// do not restrict user accounts (user2user) if the first part of server
// name is "krbtgt"
//
bRestrictUserAccounts = LsaINoMoreWin2KDomain()
&& !( (ServerName->NameCount >= 1)
&& RtlEqualUnicodeString(
&ServerName->Names[0],
SecData.KdcServiceName(),
TRUE) );
//
// Figure out who the ticket is for. First break the name into
// a local name and a referral realm
//
//
// Note: We don't allow referrals here, because we should only get AS
// requests for our realm, and the krbtgt\server should always be
// in our realm.
KerbErr = KdcNormalize(
ServerName,
NULL,
NULL, // don't use requested realm for the server - use our realm
NULL,
NameFlags | KDC_NAME_SERVER,
bRestrictUserAccounts, // restrict user accounts (user2user)
&ServerReferral,
&ServerRealm,
&ServiceTicketInfo,
pExtendedError,
NULL, // no user handle
0L, // no additional fields to fetch
bRestrictUserAccounts ? USER_EXTENDED_FIELD_SPN : 0, // no extended fields
NULL, // no user all
NULL // no membership
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "I_GetASTicket KLIN(%x) failed to normalize name %#x: ", KLIN(FILENO, __LINE__), KerbErr ));
KerbPrintKdcName(DEB_ERROR, ServerName);
goto Cleanup;
}
//
// find supported etype for session keys
//
KerbErr = KerbFindCommonCryptSystemForSKey(
RequestBody->encryption_type,
ServiceTicketInfo.UserAccountControl & USER_USE_DES_KEY_ONLY ?
kdc_pMitPrincipalPreferredCryptList : kdc_pPreferredCryptList,
&CommonEType
);
if (!KERB_SUCCESS(KerbErr))
{
if (KDC_ERR_ETYPE_NOTSUPP == KerbErr)
{
KdcReportKeyError(
ClientStringName,
ServerStringName,
KDC_KEY_ID_AS_SKEY,
KDCEVENT_NO_KEY_INTERSECTION_AS,
RequestBody->encryption_type,
&ServiceTicketInfo
);
}
DebugLog((DEB_ERROR, "I_GetASTicket KLIN(%x) Failed to find common ETYPE: 0x%x\n",
KLIN(FILENO, __LINE__), KerbErr));
goto Cleanup;
}
//
// Find a common crypto system. Do it now in case we need
// to return the password for a service.
//
if (EncryptionKey.keyvalue.value == NULL)
{
KerbErr = KerbFindCommonCryptSystem(
RequestBody->encryption_type,
ClientTicketInfo.Passwords,
NULL,
&ClientKey
);
if (!KERB_SUCCESS(KerbErr))
{
if (KDC_ERR_ETYPE_NOTSUPP == KerbErr)
{
KdcReportKeyError(
ClientStringName,
ServerStringName,
KDC_KEY_ID_AS_KDC_REPLY,
KDCEVENT_NO_KEY_INTERSECTION_AS,
RequestBody->encryption_type,
&ClientTicketInfo
);
}
DebugLog((DEB_ERROR, "KLIN(%x) Failed to find common ETYPE: 0x%x\n",
KLIN(FILENO,__LINE__), KerbErr));
goto Cleanup;
}
}
//
// Get the etype to use for the ticket itself from the server's
// list of keys
//
KerbErr = KerbFindCommonCryptSystem(
ServiceTicketInfo.UserAccountControl & USER_USE_DES_KEY_ONLY ?
kdc_pMitPrincipalPreferredCryptList : kdc_pPreferredCryptList,
ServiceTicketInfo.Passwords,
NULL, // no additional passwords
&ServerKey
);
if (!KERB_SUCCESS(KerbErr))
{
if (KDC_ERR_ETYPE_NOTSUPP == KerbErr)
{
KdcReportKeyError(
ClientStringName,
ServerStringName,
KDC_KEY_ID_AS_TICKET,
KDCEVENT_NO_KEY_INTERSECTION_AS,
ServiceTicketInfo.UserAccountControl & USER_USE_DES_KEY_ONLY ?
kdc_pMitPrincipalPreferredCryptList : kdc_pPreferredCryptList,
&ServiceTicketInfo
);
}
DebugLog((DEB_ERROR, "KLIN(%x) Failed to find common ETYPE: 0x%x\n", KLIN(FILENO, __LINE__), KerbErr));
goto Cleanup;
}
//
// We need to save the full domain name of the service regardless
// of whether it was provided or not. This is so name changes
// can be detected. Instead of creating a mess of trying to figure out
// which deallocator to use, allocate new memory and copy data.
//
AccountExpiry = UserInfo->I1.AccountExpires;
//
// Bug 460108: only propagate the addresses if there is an IPv4
// address in the list
//
if ( KdcUseClientAddresses ) {
PKERB_HOST_ADDRESSES CurrentAddress = RequestBody->addresses;
while ( CurrentAddress != NULL ) {
if ( CurrentAddress->value.address_type == KERB_ADDRTYPE_INET ) {
EffectiveAddresses = RequestBody->addresses;
break;
}
CurrentAddress = CurrentAddress->next;
}
}
KerbErr = BuildTicketAS(
&ClientTicketInfo,
&RequestBody->KERB_KDC_REQUEST_BODY_client_name,
&ServiceTicketInfo,
&RequestBody->KERB_KDC_REQUEST_BODY_server_name,
EffectiveAddresses,
&LogoffTime,
&AccountExpiry,
RequestBody,
CommonEType,
PreAuthType,
&TransitedRealm,
&Ticket,
pExtendedError
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_WARN , "KLIN(%x) Failed to build AS ticket: 0x%x\n",
KLIN(FILENO,__LINE__), KerbErr));
goto Cleanup;
}
//
// If the user requested a PAC (via pre-auth data) build one now.
//
if (BuildPac)
{
//
// Now build a PAC to stick in the authorization data
//
DebugLog((DEB_T_PAC, "I_GetASTicket KLIN(%x) build Pac\n", KLIN(FILENO, __LINE__)));
KerbErr = KdcGetPacAuthData(
UserInfo,
&GroupMembership,
ServerKey,
&EncryptionKey,
((ServiceTicketInfo.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) == 0) &&
(ServiceTicketInfo.UserId != DOMAIN_USER_RID_KRBTGT),
// add resource groups if server is not an interdomain trust account
&EncryptedTicket,
NULL, // no S4U info here...
&PacAuthData,
pExtendedError
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR, "KLIN(%x) Failed to get pac auth data for %wZ : 0x%x\n",
KLIN(FILENO, __LINE__), &ClientTicketInfo.AccountName, KerbErr));
goto Cleanup;
}
//
// Stick the auth data into the AS ticket
//
EncryptedTicket.KERB_ENCRYPTED_TICKET_authorization_data = PacAuthData;
PacAuthData = NULL;
EncryptedTicket.bit_mask |= KERB_ENCRYPTED_TICKET_authorization_data_present;
}
//
// Now build the reply
//
KerbErr = BuildReply(
&ClientTicketInfo,
(Nonce != 0) ? Nonce : RequestBody->nonce,
&Ticket.server_name,
Ticket.realm,
((RequestBody->bit_mask & addresses_present) != 0) ? RequestBody->addresses : NULL,
&Ticket,
&ReplyBody
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Now build the real reply and return it.
//
Reply.version = KERBEROS_VERSION;
Reply.message_type = KRB_AS_REP;
Reply.KERB_KDC_REPLY_preauth_data = NULL;
Reply.bit_mask = 0;
Reply.client_realm = EncryptedTicket.client_realm;
//
// Build pw-salt if we used a user's key
//
if (ClientKey != NULL)
{
KerbErr = KdcBuildPwSalt(
ClientTicketInfo.Passwords,
ClientKey,
&OutputPreAuthData
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
if (OutputPreAuthData != NULL)
{
Reply.bit_mask |= KERB_KDC_REPLY_preauth_data_present;
Reply.KERB_KDC_REPLY_preauth_data = (PKERB_REPLY_PA_DATA_LIST) OutputPreAuthData;
//
// Zero this out so we don't free the preauth data twice
//
OutputPreAuthData = NULL;
}
//
// Copy in the ticket
//
KerbErr = KerbPackTicket(
&Ticket,
ServerKey,
ServiceTicketInfo.PasswordVersion,
&Reply.ticket
);
D_DebugLog((DEB_T_KEY, "I_GetASTicket: KerbPackTicket ServiceKeyVersion 0x%x CommonEType %#x, KerbErr %#x\n",
ServiceTicketInfo.PasswordVersion, KerbErr));
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR, "KLIN(%x) Failed to pack ticket: 0x%x\n", KLIN(FILENO, __LINE__), KerbErr));
goto Cleanup;
}
//
// Note: these are freed elsewhere, so zero them out after
// using them
//
Reply.client_name = EncryptedTicket.client_name;
//
// Copy in the client info & encrypt in client's key (or Encryption key if supplied in pre-auth data)
//
KerbErr = KerbPackKdcReplyBody(
&ReplyBody,
(EncryptionKey.keyvalue.value != NULL) ? &EncryptionKey : ClientKey,
(EncryptionKey.keyvalue.value != NULL) ? KERB_NO_KEY_VERSION : ClientTicketInfo.PasswordVersion,
KERB_TGS_REP_SALT, // should be KERB_AS_REP_SALT see raid 502476
KERB_ENCRYPTED_AS_REPLY_PDU, // was Pdu,
&Reply.encrypted_part
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR, "KLIN(%x) Failed to pack KDC reply body: 0x%x\n", KLIN(FILENO, __LINE__), KerbErr));
goto Cleanup;
}
//
// Add in PW-SALT if we used a client key
//
if (SecData.AuditKdcEvent(KDC_AUDIT_AS_SUCCESS))
{
BYTE ClientSid[MAX_SID_LEN];
BYTE ServerSid[MAX_SID_LEN];
KdcMakeAccountSid(ClientSid, ClientTicketInfo.UserId);
KdcMakeAccountSid(ServerSid, ServiceTicketInfo.UserId);
KdcLsaIAuditAsEvent(
SE_AUDITID_AS_TICKET,
&ClientTicketInfo.AccountName,
RequestRealm,
ClientSid,
&ServiceTicketInfo.AccountName,
ServerSid,
(PULONG) &KdcOptions,
NULL, // success
&CommonEType,
&PreAuthType,
GET_CLIENT_ADDRESS(ClientAddress),
&PkiAuditInfo.CertIssuerName,
&PkiAuditInfo.CertSerialNumber,
&PkiAuditInfo.CertThumbprint
);
}
//
// Pack the reply
//
KerbErr = KerbPackData(
&Reply,
KERB_AS_REPLY_PDU, // was ReplyPdu,
&OutputMessage->BufferSize,
&OutputMessage->Buffer
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Cleanup:
if (!KERB_SUCCESS(KerbErr))
{
DsysAssert(RequestBody != NULL);
if (!AuditedFailure && SecData.AuditKdcEvent(KDC_AUDIT_AS_FAILURE))
{
if (ClientName != NULL)
{
KdcLsaIAuditAsEvent(
SE_AUDITID_AS_TICKET,
&ClientName->Names[0],
RequestRealm,
NULL,
ServerStringName,
NULL,
&KdcOptions,
(PULONG) &KerbErr, // failure
NULL, // no common etype
NULL, // no preauth type
GET_CLIENT_ADDRESS(ClientAddress),
&PkiAuditInfo.CertIssuerName,
&PkiAuditInfo.CertSerialNumber,
&PkiAuditInfo.CertThumbprint
);
}
}
//
// If there was any preath data to return, pack it for return now.
//
if (OutputPreAuthData != NULL)
{
if (ErrorData->Buffer != NULL)
{
D_DebugLog((DEB_ERROR,
"KLIN(%x) Freeing return error data to return preauth data\n",
KLIN(FILENO,__LINE__)));
MIDL_user_free(ErrorData->Buffer);
ErrorData->Buffer = NULL;
ErrorData->BufferSize = 0;
}
(VOID) KerbPackData(
&OutputPreAuthData,
PKERB_PREAUTH_DATA_LIST_PDU,
&ErrorData->BufferSize,
&ErrorData->Buffer
);
}
}
if (UserHandle != NULL)
{
if (!KERB_SUCCESS(KerbErr))
{
if (!LoggedFailure && ClientInfoPresent)
{
BYTE ClientSid[MAX_SID_LEN];
RtlZeroMemory(ClientSid, MAX_SID_LEN);
KdcMakeAccountSid(ClientSid, ClientTicketInfo.UserId);
D_DebugLog((DEB_WARN , "KLIN(%x) Calling Failedlogon: LogonStatus: 0x%x KRB: 0x%x\n",
KLIN(FILENO,__LINE__), LogonStatus, KerbErr));
FailedLogon(
UserHandle,
ClientAddress,
&RequestBody->KERB_KDC_REQUEST_BODY_client_name,
ClientSid,
MAX_SID_LEN,
&ClientTicketInfo,
InputMessage,
OutputMessage,
&ClientNetbiosAddress,
KerbErr,
LogonStatus,
UsedOldPassword
);
}
}
else
{
BYTE ClientSid[MAX_SID_LEN];
RtlZeroMemory(ClientSid, MAX_SID_LEN);
KdcMakeAccountSid(ClientSid, ClientTicketInfo.UserId);
D_DebugLog((DEB_TRACE, "I_GetASTicket calling SuccessfulLogon\n"));
SuccessfulLogon(
UserHandle,
ClientAddress,
ClientSid,
MAX_SID_LEN,
InputMessage,
&ClientNetbiosAddress,
UserInfo
);
}
SamrCloseHandle(&UserHandle);
}
//
// Complete the WMI event
//
if (KdcEventTraceFlag)
{
//These variables point to either a unicode string struct containing
//the corresponding string or a pointer to KdcNullString
PUNICODE_STRING pStringToCopy;
WCHAR UnicodeNullChar = 0;
UNICODE_STRING UnicodeEmptyString = {sizeof(WCHAR),sizeof(WCHAR),&UnicodeNullChar};
ASEventTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_END;
ASEventTraceInfo.EventTrace.Flags = WNODE_FLAG_USE_MOF_PTR |
WNODE_FLAG_TRACED_GUID;
// Always output error code. KdcOptions was captured on the start event
ASEventTraceInfo.eventInfo[0].DataPtr = (ULONGLONG) &KerbErr;
ASEventTraceInfo.eventInfo[0].Length = sizeof(ULONG);
ASEventTraceInfo.EventTrace.Size =
sizeof (EVENT_TRACE_HEADER) + sizeof(MOF_FIELD);
// Build counted MOF strings from the unicode strings
if (ClientStringName->Buffer != NULL &&
ClientStringName->Length > 0)
{
pStringToCopy = ClientStringName;
}
else {
pStringToCopy = &UnicodeEmptyString;
}
ASEventTraceInfo.eventInfo[1].DataPtr =
(ULONGLONG) &pStringToCopy->Length;
ASEventTraceInfo.eventInfo[1].Length =
sizeof(pStringToCopy->Length);
ASEventTraceInfo.eventInfo[2].DataPtr =
(ULONGLONG) pStringToCopy->Buffer;
ASEventTraceInfo.eventInfo[2].Length =
pStringToCopy->Length;
ASEventTraceInfo.EventTrace.Size += sizeof(MOF_FIELD)*2;
if (ServerStringName->Buffer != NULL &&
ServerStringName->Length > 0)
{
pStringToCopy = ServerStringName;
}
else
{
pStringToCopy = &UnicodeEmptyString;
}
ASEventTraceInfo.eventInfo[3].DataPtr =
(ULONGLONG) &pStringToCopy->Length;
ASEventTraceInfo.eventInfo[3].Length =
sizeof(pStringToCopy->Length);
ASEventTraceInfo.eventInfo[4].DataPtr =
(ULONGLONG) pStringToCopy->Buffer;
ASEventTraceInfo.eventInfo[4].Length =
pStringToCopy->Length;
ASEventTraceInfo.EventTrace.Size += sizeof(MOF_FIELD)*2;
if (RequestRealm->Buffer != NULL &&
RequestRealm->Length > 0)
{
pStringToCopy = RequestRealm;
}
else
{
pStringToCopy = &UnicodeEmptyString;
}
ASEventTraceInfo.eventInfo[5].DataPtr =
(ULONGLONG) &(pStringToCopy->Length);
ASEventTraceInfo.eventInfo[5].Length =
sizeof(pStringToCopy->Length);
ASEventTraceInfo.eventInfo[6].DataPtr =
(ULONGLONG) (pStringToCopy->Buffer);
ASEventTraceInfo.eventInfo[6].Length =
(pStringToCopy->Length);
ASEventTraceInfo.EventTrace.Size += sizeof(MOF_FIELD)*2;
TraceEvent(
KdcTraceLoggerHandle,
(PEVENT_TRACE_HEADER)&ASEventTraceInfo
);
}
SamIFree_UserInternal6Information( UserInfo );
SamIFreeSidAndAttributesList( &GroupMembership );
KerbFreeAuthData( PacAuthData );
FreeTicketInfo( &ClientTicketInfo );
FreeTicketInfo( &ServiceTicketInfo );
KdcFreePkiAuditInfo( &PkiAuditInfo );
KdcFreeInternalTicket( &Ticket );
KerbFreeKey( &EncryptionKey );
KerbFreeKdcName( &ClientName );
KerbFreeString( &TransitedRealm );
KerbFreeString( &ServerRealm );
KerbFreeKdcName( &ServerName );
KerbFreeString( &ClientNetbiosAddress );
KdcFreeKdcReplyBody( &ReplyBody );
KdcFreeKdcReply( &Reply );
KerbFreePreAuthData( OutputPreAuthData );
D_DebugLog((DEB_T_PAPI, "I_GetASTicket returning %#x\n", KerbErr));
return KerbErr;
}
//+-------------------------------------------------------------------------
//
// Function: KdcGetTicket
//
// Synopsis: Generic ticket getting entrypoint to get a ticket from the KDC
//
// Effects:
//
// Arguments: Context - ATQ context - only present for TCP/IP callers
// ClientAddress - Client's IP addresses. Only present for UDP & TPC callers
// ServerAddress - address the client used to contact this KDC.
// Only present for UDP & TPC callers
// InputMessage - the input KDC request message, in ASN.1 format
// OutputMessage - Receives the KDC reply message, allocated by
// the KDC.
//
// Requires:
//
// Returns:
//
// Notes: This routine is exported from the DLL and called from the
// client dll.
//
//
//--------------------------------------------------------------------------
extern "C"
KERBERR
KdcGetTicket(
IN OPTIONAL PVOID Context,
IN OPTIONAL PSOCKADDR ClientAddress,
IN OPTIONAL PSOCKADDR ServerAddress,
IN PKERB_MESSAGE_BUFFER InputMessage,
OUT PKERB_MESSAGE_BUFFER OutputMessage
)
{
KERBERR KerbErr;
KERB_EXT_ERROR ExtendedError = {0,0};
PKERB_EXT_ERROR pExtendedError = &ExtendedError; // needed for macro
PKERB_KDC_REQUEST RequestMessage = NULL;
KERB_KDC_REPLY ReplyMessage = {0};
KERB_MESSAGE_BUFFER ErrorData = {0};
ULONG InputPdu = KERB_TGS_REQUEST_PDU;
UNICODE_STRING RequestRealm = {0};
PKERB_INTERNAL_NAME RequestServer = NULL;
UNICODE_STRING ClientRealm = {0};
PUNICODE_STRING ExtendedErrorServerRealm = SecData.KdcDnsRealmName();
PKERB_INTERNAL_NAME ExtendedErrorServerName = SecData.KdcInternalName();
TYPED_DATA_Element* TypedDataList = NULL;
KERB_MESSAGE_BUFFER PreApendedErrorData = {0};
KERB_MESSAGE_BUFFER* ErrorDataToUse = NULL;
UNICODE_STRING ClientStringName = {0};
UNICODE_STRING ServerStringName = {0};
#if DBG
DWORD StartTime = 0;
#endif
TRACE(KDC, KdcGetTicket, DEB_FUNCTION );
//
// Make sure we are allowed to execute
//
if (!NT_SUCCESS(EnterApiCall()))
{
return(KDC_ERR_NOT_RUNNING);
}
RtlZeroMemory(
&ReplyMessage,
sizeof(KERB_KDC_REPLY)
);
//
// First initialize the return parameters.
//
OutputMessage->Buffer = NULL;
OutputMessage->BufferSize = 0;
//
// Check the first byte of the message to indicate the type of message
//
if ((InputMessage->BufferSize > 0) && (
(InputMessage->Buffer[0] & KERB_BER_APPLICATION_TAG) != 0))
{
if ((InputMessage->Buffer[0] & KERB_BER_APPLICATION_MASK) == KERB_AS_REQ_TAG)
{
InputPdu = KERB_AS_REQUEST_PDU;
}
else if ((InputMessage->Buffer[0] & KERB_BER_APPLICATION_MASK) != KERB_TGS_REQ_TAG)
{
D_DebugLog((DEB_T_SOCK,
"KLIN(%x) Bad message sent to KDC - not AS or TGS request\n",
KLIN(FILENO,__LINE__)));
KerbErr = KRB_ERR_FIELD_TOOLONG;
goto NoMsgCleanup;
}
}
else
{
D_DebugLog((DEB_T_SOCK,"KLIN(%x) Bad message sent to KDC - length to short or bad first byte\n",
KLIN(FILENO,__LINE__)));
KerbErr = KRB_ERR_FIELD_TOOLONG;
goto NoMsgCleanup;
}
//
// First decode the input message
//
KerbErr = (KERBERR) KerbUnpackData(
InputMessage->Buffer,
InputMessage->BufferSize,
InputPdu,
(PVOID *) &RequestMessage
);
if (KerbErr == KDC_ERR_MORE_DATA)
{
KerbErr = KRB_ERR_FIELD_TOOLONG;
goto Cleanup;
}
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Failed to unpack KDC request: 0x%x\n",
KLIN(FILENO,__LINE__),KerbErr));
//
// We don't want to return an error on a badly formed
// packet,as it can be used to set up a flood attack
//
goto NoMsgCleanup;
}
//
// First check the version of the request.
//
if (RequestMessage->version != KERBEROS_VERSION)
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Bad request version: 0x%x\n",
KLIN(FILENO,__LINE__), RequestMessage->version));
KerbErr = KRB_AP_ERR_BADVERSION;
goto Cleanup;
}
//
// now call the internal version to do all the hard work
//
//
// Verify the realm name in the request
//
KerbErr = KerbConvertRealmToUnicodeString(
&RequestRealm,
&RequestMessage->request_body.realm
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
KerbErr = KerbConvertPrincipalNameToKdcName(
&RequestServer,
&RequestMessage->request_body.server_name
);
if ( !KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Now that we have the request realm and request server, any subsequent
// error will result in those values being placed into the extended error
//
ExtendedErrorServerRealm = &RequestRealm;
ExtendedErrorServerName = RequestServer;
if (!SecData.IsOurRealm(
&RequestRealm
))
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Request sent for wrong realm: %wZ\n",
KLIN(FILENO,__LINE__), &RequestRealm));
KerbErr = KDC_ERR_WRONG_REALM;
goto Cleanup;
}
if (RequestMessage->message_type == KRB_AS_REQ)
{
if (InputPdu != KERB_AS_REQUEST_PDU) {
KerbErr = KRB_ERR_FIELD_TOOLONG;
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST,FILENO,__LINE__);
goto Cleanup;
}
SamIIncrementPerformanceCounter(
KdcAsReqCounter
);
//
// If WMI event tracing is enabled, notify it of the begin and end
// of the ticket request
//
#if DBG
StartTime = GetTickCount();
#endif
KerbErr = I_GetASTicket(
ClientAddress,
RequestMessage,
&RequestRealm,
InputMessage,
OutputMessage,
&ErrorData,
&ExtendedError,
&ClientRealm,
&ClientStringName,
&ServerStringName
);
#if DBG
D_DebugLog((DEB_T_PERF_STATS, "I_GetASTicket took %d m seconds\n", NetpDcElapsedTime(StartTime)));
#endif
}
else if (RequestMessage->message_type == KRB_TGS_REQ)
{
SamIIncrementPerformanceCounter(
KdcTgsReqCounter
);
#if DBG
StartTime = GetTickCount();
#endif
KerbErr = HandleTGSRequest(
ClientAddress,
RequestMessage,
&RequestRealm,
OutputMessage,
&ExtendedError,
&ClientStringName,
&ServerStringName
);
#if DBG
D_DebugLog((DEB_T_PERF_STATS, "HandleTGSRequest took %d m seconds\n", NetpDcElapsedTime(StartTime)));
#endif
}
else
{
D_DebugLog((DEB_ERROR,"KLIN(%x) Invalid message type: %d\n",
KLIN(FILENO,__LINE__),
RequestMessage->message_type));
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST,FILENO,__LINE__);
KerbErr = KRB_AP_ERR_MSG_TYPE;
goto Cleanup;
}
//
// If the response is too big and we are using UDP, make the client
// change transports. We can tell the caller is UDP because it doesn't
// have an ATQ context but it does provide the client address.
//
if ((Context == NULL) && (ClientAddress != NULL))
{
if (OutputMessage->BufferSize >= KdcGlobalMaxDatagramReplySize)
{
LARGE_INTEGER CurrentTime;
D_DebugLog((DEB_WARN,"KLIN(%x) KDC response too big for UDP datagram (max size %d): %d bytes in message\n",
KLIN(FILENO,__LINE__), KdcGlobalMaxDatagramReplySize, OutputMessage->BufferSize ));
KerbErr = KRB_ERR_RESPONSE_TOO_BIG;
MIDL_user_free(OutputMessage->Buffer);
OutputMessage->Buffer = NULL;
OutputMessage->BufferSize = 0;
// purge the replay detection entry, otherwise when the client
// retries with TCP we will fail because of replay detection
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
ReplayDetect->Check(
InputMessage->Buffer,
InputMessage->BufferSize,
NULL,
0,
&CurrentTime,
FALSE, // insert
TRUE, // purge
TRUE ); // check replay cache
DebugLog((DEB_WARN, "Purged replay detection cache entry for UDP failover to TCP\n" ));
// we don't need to check if that failed (e.g. if the entry
// didn't exist)
}
}
Cleanup:
// TBD: Put in extended error return goo here for client
if (!KERB_SUCCESS(KerbErr) && (KDC_ERR_NO_RESPONSE != KerbErr))
{
//
// We may have a message built by someone else - the PDC
//
if (OutputMessage->Buffer == NULL)
{
//
// map KDC_ERR_MUST_USE_USER2USER to KDC_ERR_S_PRINCIPAL_UNKNOWN so
// down-level clients won't chuck
//
if (KDC_ERR_MUST_USE_USER2USER == KerbErr)
{
KERB_TYPED_DATA Data = {0};
DebugLog((DEB_T_U2U, "KLIN(%x) mapping KDC_ERR_MUST_USE_USER2USER to KDC_ERR_S_PRINCIPAL_UNKNOWN, ErrorData.Buffer %p\n",
KLIN(FILENO, __LINE__), ErrorData.Buffer));
Data.data_type = TD_MUST_USE_USER2USER;
if (ErrorData.Buffer && ErrorData.BufferSize)
{
DebugLog((DEB_WARN, "KLIN(%x) KdcGetTicket received non-empty error data\n",
KLIN(FILENO, __LINE__)));
KerbErr = KerbUnpackData(
ErrorData.Buffer,
ErrorData.BufferSize,
TYPED_DATA_PDU,
(PVOID*)&TypedDataList
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KLIN(%x) KdcGetTicket failed to unpack typed data %#x\n",
KLIN(FILENO, __LINE__), KerbErr));
goto NoMsgCleanup;
}
}
KerbErr = TypedDataListPushFront(
TypedDataList,
&Data,
&PreApendedErrorData.BufferSize,
&PreApendedErrorData.Buffer
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KLIN(%x) KdcGetTicket failed to pushfront typed data %#x\n",
KLIN(FILENO, __LINE__), KerbErr));
goto NoMsgCleanup;
}
ErrorDataToUse = &PreApendedErrorData;
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
//
// now report an event
//
KdcReportPolicyErrorEvent(
EVENTLOG_INFORMATION_TYPE,
KDCEVENT_POLICY_USER2USER_REQUIRED,
&ClientStringName,
&ServerStringName,
STATUS_USER2USER_REQUIRED,
0, // raw data size
NULL // no raw data
);
}
else if ((ErrorData.Buffer == NULL)
&& !EXT_ERROR_SUCCESS((ExtendedError))) // if ((ErrorData.Buffer == NULL) && (KDC_ERR_PREAUTH_FAILED == KerbErr) && (EXT_CLIENT_INFO_PRESENT((&ExtendedError))))
{
KERB_ERROR_METHOD_DATA ErrorMethodData = {0};
KERBERR KerbErrSaved = KerbErr;
ErrorMethodData.data_type = KERB_ERR_TYPE_EXTENDED;
ErrorMethodData.bit_mask |= data_value_present;
ErrorMethodData.data_value.value = (PBYTE) &ExtendedError;
ErrorMethodData.data_value.length = sizeof(KERB_EXT_ERROR);
KerbErr = KerbPackData(
&ErrorMethodData,
KERB_ERROR_METHOD_DATA_PDU,
&ErrorData.BufferSize,
&ErrorData.Buffer
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KLIN(%x) KdcGetTicket failed to pack error data %#x\n",
KLIN(FILENO, __LINE__), KerbErr));
goto NoMsgCleanup;
}
ErrorDataToUse = &ErrorData;
KerbErr = KerbErrSaved;
}
else
{
ErrorDataToUse = &ErrorData;
}
KerbBuildErrorMessageEx(
KerbErr,
&ExtendedError,
ExtendedErrorServerRealm,
ExtendedErrorServerName,
&ClientRealm,
ErrorDataToUse->Buffer,
ErrorDataToUse->BufferSize,
&OutputMessage->BufferSize,
&OutputMessage->Buffer
);
}
}
NoMsgCleanup:
KerbFreeString(&ClientRealm);
KerbFreeString(&RequestRealm);
KerbFreeString(&ClientStringName);
KerbFreeString(&ServerStringName);
MIDL_user_free(RequestServer);
if (RequestMessage != NULL)
{
KerbFreeData(InputPdu, RequestMessage);
}
if (ErrorData.Buffer != NULL)
{
MIDL_user_free(ErrorData.Buffer);
}
if (PreApendedErrorData.Buffer != NULL)
{
MIDL_user_free(PreApendedErrorData.Buffer);
}
if (TypedDataList)
{
KerbFreeData(TYPED_DATA_PDU, TypedDataList);
}
LeaveApiCall();
return(KerbErr);
}
// Routines to handle the Negative Cache to PDC from BDC
// Initialization routines - must be called in single threaded mode and only once
NTSTATUS
AsNegCacheInit()
{
NTSTATUS Status = STATUS_SUCCESS;
//
// Initialize the Credential list to be empty.
//
if (g_fApNegCacheInitialized == TRUE)
{
goto CleanUp; // already initialized
}
Status = RtlInitializeCriticalSection(&l_ApNegCacheCritSect);
if (!NT_SUCCESS(Status))
{
goto CleanUp;
}
InitializeListHead( &l_ApNegCacheList );
// Simple variable test to make sure all initialized;
g_fApNegCacheInitialized = TRUE;
CleanUp:
return Status;
}
// This function will check if we should send the failed auth request to the PDC
// The return value only indicates if the function worked properly. The result of the function is
// is kept in pfAvoidSendToPDC (if True then do not send requst to PDC)
NTSTATUS
AsNegCacheCheck(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength,
OUT PBOOLEAN pfAvoidSendToPDC)
{
NTSTATUS Status = STATUS_SUCCESS;
PLIST_ENTRY ListEntry = NULL;
PNEGATIVE_CACHE pApNegEntry = NULL;
MD5_CTX Md5Context;
LARGE_INTEGER CurrentTime;
LARGE_INTEGER Time5Minutes;
ASSERT(pfAvoidSendToPDC);
if (g_fApNegCacheInitialized == FALSE)
{
return STATUS_APP_INIT_FAILURE;
}
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
*pfAvoidSendToPDC = FALSE;
Time5Minutes.QuadPart = (LONGLONG)KERB_5_MINUTES_100NANO;
// Compute the md5 signature of the request
MD5Init(
&Md5Context
);
MD5Update(
&Md5Context,
(PBYTE) Buffer,
BufferLength
);
if ((OptionalBuffer != NULL) && (OptionalBufferLength != 0))
{
MD5Update(
&Md5Context,
(PBYTE) OptionalBuffer,
OptionalBufferLength
);
}
MD5Final(
&Md5Context
);
//
// Acquire exclusive access to the Credential list
//
RtlEnterCriticalSection( &l_ApNegCacheCritSect );
//
// Now walk the list of Credentials looking for a match.
//
for ( ListEntry = l_ApNegCacheList.Flink;
ListEntry != &l_ApNegCacheList;
ListEntry = ListEntry->Flink )
{
pApNegEntry = CONTAINING_RECORD( ListEntry, NEGATIVE_CACHE, Next );
// Check if the md5 signature matches
// if this entry is for this user's request (on the workstation), check if it's time to forward
// this logon to the PDC. In any case, remove this entry from the list and then insert it at the
// front so that the list stays sorted by the entry access time.
if (RtlEqualMemory(Md5Context.digest, pApNegEntry->digest, MD5DIGESTLEN))
{
if ((pApNegEntry->lBadLogonCount > KERB_AP_NEGATIVE_MAX_LOGON_COUNT) &&
(CurrentTime.QuadPart < (pApNegEntry->TimeLastPDCContact.QuadPart + Time5Minutes.QuadPart)))
{
*pfAvoidSendToPDC = TRUE;
DebugLog((DEB_TRACE , "getas:AsNegCacheCheck(%d) no send to PDC: Entry 0x%x\n", __LINE__, Md5Context.digest));
}
RemoveEntryList(&(pApNegEntry->Next));
break;
}
pApNegEntry = NULL;
}
if (pApNegEntry)
{
InsertHeadList(&l_ApNegCacheList, &(pApNegEntry->Next));
}
RtlLeaveCriticalSection( &l_ApNegCacheCritSect );
return Status;
}
// This function will update an entry (or create a new one) in the negative cache
// Update the list of forwarded failed user logons
//
// If the lockout policy is enabled we should continue
// to forward logons to PDC (i.e. we should not cache
// this failure) to keep the right lockout count until
// the account becomes locked on the PDC.
//
NTSTATUS
AsNegCacheUpdate(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength,
IN NTSTATUS StatusPdcAuth)
{
NTSTATUS Status = STATUS_SUCCESS;
PLIST_ENTRY ListEntry = NULL;
PNEGATIVE_CACHE pApNegEntry = NULL;
MD5_CTX Md5Context;
ULONG FailedUserCount = 0;
if (g_fApNegCacheInitialized == FALSE)
{
return STATUS_APP_INIT_FAILURE;
}
// Compute the md5 signature of the request
MD5Init(
&Md5Context
);
MD5Update(
&Md5Context,
(PBYTE) Buffer,
BufferLength
);
if ((OptionalBuffer != NULL) && (OptionalBufferLength != 0))
{
MD5Update(
&Md5Context,
(PBYTE) OptionalBuffer,
OptionalBufferLength
);
}
MD5Final(
&Md5Context
);
//
// Acquire exclusive access to the Credential list
//
RtlEnterCriticalSection( &l_ApNegCacheCritSect );
//
// Now walk the list of Credentials looking for a match.
//
for ( ListEntry = l_ApNegCacheList.Flink;
ListEntry != &l_ApNegCacheList;
ListEntry = ListEntry->Flink )
{
pApNegEntry = CONTAINING_RECORD( ListEntry, NEGATIVE_CACHE, Next );
// Check if the md5 signature matches
// if this entry is for this user's request (on the workstation), remove it from the list.
// If it stays on the list, we will re-insert it at the front so that the list
// stays sorted by the entry access time.
if (RtlEqualMemory(Md5Context.digest, pApNegEntry->digest, MD5DIGESTLEN))
{
RemoveEntryList(&(pApNegEntry->Next));
break;
}
pApNegEntry = NULL;
FailedUserCount++;
}
// Process if PDC auth called failed
if (!(NT_SUCCESS(StatusPdcAuth)))
{
// If there is no entry for this user, allocate one
if (pApNegEntry == NULL)
{
pApNegEntry = (PNEGATIVE_CACHE) MIDL_user_allocate(sizeof(NEGATIVE_CACHE));
if (pApNegEntry == NULL)
{
Status = STATUS_NO_MEMORY;
goto CleanUp;
}
// Fill in data structure
RtlCopyMemory(
pApNegEntry->digest,
Md5Context.digest,
MD5DIGESTLEN
);
pApNegEntry->lBadLogonCount = 0;
D_DebugLog((DEB_TRACE , "getas:AsNegCacheUpdate(%d) new cache: Entry 0x%x\n", __LINE__, Md5Context.digest));
// If we have too many entries, remove the least recently used one and free it
if (FailedUserCount > KERB_MAX_FAILED_LIST_ENTRIES)
{
PLIST_ENTRY LastEntry = RemoveTailList(&l_ApNegCacheList);
PNEGATIVE_CACHE pLastApNegEntry = CONTAINING_RECORD( LastEntry, NEGATIVE_CACHE, Next );
if (pLastApNegEntry)
{
MIDL_user_free(pLastApNegEntry);
}
}
}
// Update the last time we contacted the PDC
GetSystemTimeAsFileTime((PFILETIME)&(pApNegEntry->TimeLastPDCContact));
pApNegEntry->lBadLogonCount++;
D_DebugLog((DEB_TRACE , "getas:AsNegCacheUpdate(%d) increment: Entry 0x%x Count %d\n",
__LINE__, Md5Context.digest, pApNegEntry->lBadLogonCount));
// Place it back to the front of the list
InsertHeadList(&l_ApNegCacheList, &(pApNegEntry->Next));
}
else
{
// we succeeded at the PDC so free unlinked neg cache entry if found
if (pApNegEntry)
{
D_DebugLog((DEB_TRACE , "getas:AsNegCacheUpdate(%d) succeeded at PDC so remove: Entry 0x%x\n", __LINE__, Md5Context.digest));
MIDL_user_free(pApNegEntry);
}
}
CleanUp:
RtlLeaveCriticalSection( &l_ApNegCacheCritSect );
return Status;
}
// This function will remove an entry from the Negative Cache
NTSTATUS
AsNegCacheDelete(
IN PVOID Buffer,
IN ULONG BufferLength,
IN OPTIONAL PVOID OptionalBuffer,
IN OPTIONAL ULONG OptionalBufferLength)
{
NTSTATUS Status = STATUS_SUCCESS;
PLIST_ENTRY ListEntry = NULL;
PNEGATIVE_CACHE pApNegEntry = NULL;
MD5_CTX Md5Context;
if (g_fApNegCacheInitialized == FALSE)
{
return STATUS_APP_INIT_FAILURE;
}
// Compute the md5 signature of the request
MD5Init(
&Md5Context
);
MD5Update(
&Md5Context,
(PBYTE) Buffer,
BufferLength
);
if ((OptionalBuffer != NULL) && (OptionalBufferLength != 0))
{
MD5Update(
&Md5Context,
(PBYTE) OptionalBuffer,
OptionalBufferLength
);
}
MD5Final(
&Md5Context
);
//
// Acquire exclusive access to the Credential list
//
RtlEnterCriticalSection( &l_ApNegCacheCritSect );
//
// Now walk the list of Credentials looking for a match.
//
for ( ListEntry = l_ApNegCacheList.Flink;
ListEntry != &l_ApNegCacheList;
ListEntry = ListEntry->Flink )
{
pApNegEntry = CONTAINING_RECORD( ListEntry, NEGATIVE_CACHE, Next );
// Check if the md5 signature matches
// if this entry is for this user's request (on the workstation), remove this entry from the list.
if (RtlEqualMemory(Md5Context.digest, pApNegEntry->digest, MD5DIGESTLEN))
{
D_DebugLog((DEB_TRACE , "getas:AsNegCacheDelete(%d) remove cache: Entry 0x%x\n", __LINE__, Md5Context.digest));
RemoveEntryList(&(pApNegEntry->Next));
break;
}
pApNegEntry = NULL;
}
if (pApNegEntry)
{
MIDL_user_free(pApNegEntry);
}
RtlLeaveCriticalSection( &l_ApNegCacheCritSect );
return Status;
}