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.
5504 lines
157 KiB
5504 lines
157 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1993.
|
|
//
|
|
// File: tktutil.cxx
|
|
//
|
|
// Contents:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 04-Mar-94 wader Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
// Place any local #includes files here.
|
|
|
|
#include "kdcsvr.hxx"
|
|
extern "C"
|
|
{
|
|
#include <dns.h> // DNS_MAX_NAME_LENGTH
|
|
#include <ntdsa.h> // CrackSingleName
|
|
#include <ntdsapip.h> // DS_USER_PRINCIPAL_NAME_ONLY
|
|
}
|
|
|
|
#include <userall.h>
|
|
|
|
#include "refer.h"
|
|
|
|
#define FILENO FILENO_TKTUTIL
|
|
#define KDC_WSZ_GC L"gc"
|
|
#define KDC_GC_NAMEPARTS 3
|
|
|
|
//#define DONT_SUPPORT_OLD_TYPES_KDC 1
|
|
|
|
//
|
|
// Static data
|
|
//
|
|
|
|
//
|
|
// Fudge factor for comparing timestamps, because network clocks may
|
|
// be out of sync.
|
|
// Note: The lowpart is first!
|
|
//
|
|
|
|
LARGE_INTEGER SkewTime;
|
|
|
|
//
|
|
// Mapping table for nametypes
|
|
//
|
|
|
|
WCHAR * KdcGlobalNameTypes[] =
|
|
{
|
|
L"DS_UNKNOWN_NAME",
|
|
L"DS_FQDN_1779_NAME",
|
|
L"DS_NT4_ACCOUNT_NAME",
|
|
L"DS_DISPLAY_NAME",
|
|
NULL,
|
|
NULL,
|
|
L"DS_UNIQUE_ID_NAME",
|
|
L"DS_CANONICAL_NAME",
|
|
L"DS_USER_PRINCIPAL_NAME",
|
|
L"DS_CANONICAL_NAME_EX",
|
|
L"DS_SERVICE_PRINCIPAL_NAME",
|
|
L"DS_SID_OR_SID_HISTORY_NAME",
|
|
L"DS_DNS_DOMAIN_NAME",
|
|
};
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcBuildNt4Name
|
|
//
|
|
// Synopsis: Construct an NT4 style name for an account by separating
|
|
// the name into a principal & domain name, converting the
|
|
// domain name to netbios, and then creating "domain\user" name.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcBuildNt4Name(
|
|
OUT LPWSTR * Nt4Name,
|
|
OUT PUNICODE_STRING OutputRealm,
|
|
IN PUNICODE_STRING Upn
|
|
)
|
|
{
|
|
ULONG Index;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
LPWSTR OutputName = NULL;
|
|
PKDC_DOMAIN_INFO DomainInfo = NULL;
|
|
UNICODE_STRING RealmName;
|
|
UNICODE_STRING PrincipalName;
|
|
|
|
TRACE(KDC, KdcBuildNt4Name, DEB_FUNCTION);
|
|
|
|
//
|
|
// Find the first backslash or '@', or '.' in the name
|
|
//
|
|
|
|
RtlInitUnicodeString(
|
|
OutputRealm,
|
|
NULL
|
|
);
|
|
*Nt4Name = NULL;
|
|
|
|
for (Index = Upn->Length/sizeof(WCHAR) - 1; Index > 0 ; Index-- )
|
|
{
|
|
if (Upn->Buffer[Index] == L'@')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Index == 0)
|
|
{
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Pull out the realm name and look it up in the list of domains
|
|
//
|
|
|
|
PrincipalName = *Upn;
|
|
PrincipalName.Length = (USHORT) Index * sizeof(WCHAR);
|
|
PrincipalName.MaximumLength = (USHORT) Index * sizeof(WCHAR);
|
|
|
|
|
|
RealmName.Buffer = &Upn->Buffer[Index+1];
|
|
RealmName.Length = (USHORT) (Upn->Length - (Index + 1) * sizeof(WCHAR));
|
|
RealmName.MaximumLength = RealmName.Length;
|
|
|
|
KdcLockDomainList();
|
|
|
|
KerbErr = KdcLookupDomainName(
|
|
&DomainInfo,
|
|
&RealmName,
|
|
&KdcDomainList
|
|
);
|
|
KdcUnlockDomainList();
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We need a netbios name
|
|
//
|
|
|
|
if (DomainInfo->NetbiosName.Length == 0)
|
|
{
|
|
//
|
|
// Copy out the realm name so we can return a non-authoritative referral
|
|
//
|
|
|
|
if (!NT_SUCCESS(KerbDuplicateString(
|
|
OutputRealm,
|
|
&DomainInfo->DnsName
|
|
)))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
KerbErr = KDC_ERR_WRONG_REALM;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// now build the output name
|
|
//
|
|
|
|
OutputName = (LPWSTR) MIDL_user_allocate(DomainInfo->NetbiosName.Length + PrincipalName.Length + 2 * sizeof(WCHAR));
|
|
if (OutputName == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
OutputName,
|
|
DomainInfo->NetbiosName.Buffer,
|
|
DomainInfo->NetbiosName.Length
|
|
);
|
|
OutputName[DomainInfo->NetbiosName.Length/sizeof(WCHAR)] = L'\\';
|
|
RtlCopyMemory(
|
|
OutputName + DomainInfo->NetbiosName.Length/sizeof(WCHAR) + 1,
|
|
PrincipalName.Buffer,
|
|
PrincipalName.Length
|
|
);
|
|
OutputName[1 + (PrincipalName.Length + DomainInfo->NetbiosName.Length)/sizeof(WCHAR)] = L'\0';
|
|
|
|
*Nt4Name = OutputName;
|
|
OutputName = NULL;
|
|
Cleanup:
|
|
|
|
if (DomainInfo != NULL)
|
|
{
|
|
KdcDereferenceDomainInfo( DomainInfo );
|
|
}
|
|
if (OutputName != NULL)
|
|
{
|
|
MIDL_user_free( OutputName );
|
|
}
|
|
return(KerbErr);
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcFreeS4UTicketInfo
|
|
//
|
|
// Synopsis: Frees ticket info used in processing S4UProxy and S4USelf
|
|
// requests.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//---
|
|
VOID
|
|
KdcFreeS4UTicketInfo(PKDC_S4U_TICKET_INFO S4UTicketInfo)
|
|
{
|
|
TRACE(KDC, KdcFreeTgsTicketInfo, DEB_FUNCTION);
|
|
if (S4UTicketInfo == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
KerbFreeKdcName( &S4UTicketInfo->RequestorServiceName );
|
|
KerbFreeString( &S4UTicketInfo->RequestorServiceRealm );
|
|
FreeTicketInfo( &S4UTicketInfo->RequestorTicketInfo );
|
|
KerbFreeString( &S4UTicketInfo->TargetName );
|
|
KerbFreeKdcName( &S4UTicketInfo->PACCName );
|
|
KerbFreeString( &S4UTicketInfo->PACCRealm );
|
|
KerbFreeKey( &S4UTicketInfo->EvidenceTicketKey );
|
|
|
|
//
|
|
// The evidence ticket is just a pointer to the source
|
|
// ticket when doing S4USelf. Otherwise, we unpacked it, and we've
|
|
// got to free the memory.
|
|
//
|
|
if (( S4UTicketInfo->EvidenceTicket != NULL ) &&
|
|
(( S4UTicketInfo->Flags & TI_FREETICKET ) != 0))
|
|
{
|
|
KerbFreeTicket( S4UTicketInfo->EvidenceTicket );
|
|
}
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcFreeU2UTicketInfo
|
|
//
|
|
// Synopsis: Frees ticket info used for U2U
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//---
|
|
VOID
|
|
KdcFreeU2UTicketInfo(PKDC_U2U_TICKET_INFO U2UTicketInfo)
|
|
{
|
|
TRACE(KDC, KdcFreeTgsTicketInfo, DEB_FUNCTION);
|
|
|
|
if (U2UTicketInfo == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( U2UTicketInfo->Tgt )
|
|
{
|
|
KerbFreeTicket( U2UTicketInfo->Tgt );
|
|
}
|
|
|
|
KerbFreeKdcName( &U2UTicketInfo->TgtCName );
|
|
KerbFreeString( &U2UTicketInfo->TgtCRealm );
|
|
FreeTicketInfo( &U2UTicketInfo->TgtTicketInfo );
|
|
KerbFreeKdcName( &U2UTicketInfo->cName );
|
|
KerbFreeString( &U2UTicketInfo->cRealm );
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcMatchCrossForestName
|
|
//
|
|
// Synopsis: Builds a list of the supplemental credentials and then
|
|
// encrypts it with the supplied key
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
KERBERR
|
|
KdcMatchCrossForestName(
|
|
IN PKERB_INTERNAL_NAME Principal,
|
|
OUT PUNICODE_STRING RealmName
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_ROUTING_MATCH_TYPE MatchType;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
UNICODE_STRING UnicodePrincipal = {0};
|
|
|
|
TRACE(KDC, KdcMatchCrossForestName, DEB_FUNCTION);
|
|
|
|
switch (Principal->NameType)
|
|
{
|
|
case KRB_NT_ENTERPRISE_PRINCIPAL:
|
|
MatchType = RoutingMatchUpn;
|
|
break;
|
|
case KRB_NT_SRV_INST:
|
|
MatchType = RoutingMatchSpn;
|
|
break;
|
|
default:
|
|
|
|
return KRB_ERR_GENERIC;
|
|
}
|
|
|
|
KerbErr = KerbConvertKdcNameToString(
|
|
&UnicodePrincipal,
|
|
Principal,
|
|
NULL
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Can we match the SPN / UPN to external name space (realm)
|
|
//
|
|
Status = LsaIForestTrustFindMatch(
|
|
MatchType,
|
|
&UnicodePrincipal,
|
|
RealmName
|
|
);
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_WARN, "LsaIForestTrustFindMatch failed - %x\n",Status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
|
|
KerbFreeString(&UnicodePrincipal);
|
|
|
|
return KerbErr;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcCrackNameAtGC
|
|
//
|
|
// Synopsis: Cracks a name at a GC by first looking it up for
|
|
// UPN/SPN and then constructing an NT4-style name to lookup
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcCrackNameAtGC(
|
|
IN PUNICODE_STRING Upn,
|
|
IN PKERB_INTERNAL_NAME PrincipalName,
|
|
IN ULONG KdcNameFlags,
|
|
IN BOOLEAN bRestrictUserAccounts,
|
|
IN BOOLEAN UseLocalHack,
|
|
OUT PUNICODE_STRING RealmName,
|
|
OUT PKDC_TICKET_INFO TicketInfo,
|
|
OUT PBOOLEAN Authoritative,
|
|
OUT PBOOLEAN Referral,
|
|
OUT PBOOLEAN CrossForest,
|
|
OUT PKERB_EXT_ERROR pExtendedError,
|
|
OUT OPTIONAL SAMPR_HANDLE * UserHandle,
|
|
IN OPTIONAL ULONG WhichFields,
|
|
IN OPTIONAL ULONG ExtendedFields,
|
|
OUT OPTIONAL PUSER_INTERNAL6_INFORMATION * UserInfo,
|
|
OUT OPTIONAL PSID_AND_ATTRIBUTES_LIST GroupMembership
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
NTSTATUS Status;
|
|
LPWSTR NullTerminatedName = NULL;
|
|
UNICODE_STRING CrackedDomain = {0};
|
|
UNICODE_STRING LocalCrackedString = {0};
|
|
LPWSTR CrackedDnsDomain = NULL;
|
|
ULONG CrackedDomainLength = (DNS_MAX_NAME_LENGTH+1);
|
|
LPWSTR CrackedName = NULL;
|
|
ULONG CrackedNameLength = (UNLEN+DNS_MAX_NAME_LENGTH + 2);
|
|
ULONG CrackError = 0;
|
|
BOOLEAN Retry = TRUE;
|
|
BOOLEAN ReferToRoot = FALSE, UsedHack = FALSE;
|
|
ULONG NameFlags = DS_NAME_FLAG_TRUST_REFERRAL | DS_NAME_FLAG_GCVERIFY;
|
|
UNICODE_STRING ForestRoot = {0};
|
|
ULONG NameType;
|
|
TRACE(KDC, KdcCrackNameAtGC, DEB_FUNCTION);
|
|
|
|
if ( KdcNameFlags & KDC_NAME_S4U_CLIENT )
|
|
{
|
|
//
|
|
// For S4U requests, the name is looked up by both UPN and AltSecId
|
|
//
|
|
|
|
NameType = DS_USER_PRINCIPAL_NAME_AND_ALTSECID;
|
|
}
|
|
else if ( KdcNameFlags & ( KDC_NAME_CLIENT | KDC_NAME_UPN_TARGET ))
|
|
{
|
|
//
|
|
// For all other name and UPN, only UPN name will do (no AltSecId)
|
|
//
|
|
|
|
NameType = DS_USER_PRINCIPAL_NAME;
|
|
}
|
|
else
|
|
{
|
|
NameType = DS_SERVICE_PRINCIPAL_NAME;
|
|
}
|
|
|
|
*Authoritative = TRUE;
|
|
|
|
#ifdef notyet
|
|
//
|
|
// Check to see if the name is the machine name, and if so, don't try to
|
|
// go to the GC.
|
|
//
|
|
|
|
if ((NameType == DS_USER_PRINCIPAL_NAME) &&
|
|
RtlEqualUnicodeString(
|
|
SecData.MachineUpn(),
|
|
Upn,
|
|
TRUE // case insensitive
|
|
))
|
|
{
|
|
DebugLog((DEB_ERROR,"Trying to lookup machine upn %wZ on GC - failing early\n",
|
|
Upn));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
#endif
|
|
|
|
CrackedDnsDomain = (LPWSTR) MIDL_user_allocate(CrackedDomainLength * sizeof( WCHAR ));
|
|
if (CrackedDnsDomain == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
CrackedName = (LPWSTR) MIDL_user_allocate(CrackedNameLength * sizeof ( WCHAR ));
|
|
if (CrackedName == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We can only retry for user principal names, which have a simple
|
|
// structure.
|
|
//
|
|
|
|
if (NameType != DS_USER_PRINCIPAL_NAME)
|
|
{
|
|
Retry = FALSE;
|
|
}
|
|
|
|
//
|
|
// So we didn't find the account locally. Now try the GC.
|
|
//
|
|
|
|
NullTerminatedName = KerbBuildNullTerminatedString(
|
|
Upn
|
|
);
|
|
|
|
if (NullTerminatedName == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If we are in a recursive state, then we need to *not* go to
|
|
// the GC, and try the crack locally. This is because we'll go in
|
|
// to a recursive tailspin and die. this is a hack to escape this
|
|
// situation until we work out a better soln. with DS folks.
|
|
//
|
|
|
|
if ( UseLocalHack )
|
|
{
|
|
if (CrackedDnsDomain != NULL)
|
|
{
|
|
MIDL_user_free(CrackedDnsDomain);
|
|
}
|
|
|
|
if (CrackedName !=NULL)
|
|
{
|
|
MIDL_user_free(CrackedName);
|
|
}
|
|
|
|
//
|
|
// note : these are guaranteed to be '\0' terminated.
|
|
//
|
|
CrackedDnsDomain = PrincipalName->Names[2].Buffer;
|
|
CrackedName = PrincipalName->Names[1].Buffer;
|
|
UsedHack = TRUE;
|
|
|
|
DebugLog(( DEB_WARN, "Special case hack of %ws to '%ws' at domain '%ws'\n",
|
|
NullTerminatedName, CrackedName, CrackedDnsDomain ));
|
|
|
|
Status = STATUS_SUCCESS ;
|
|
CrackError = DS_NAME_NO_ERROR ;
|
|
goto LocalHack ;
|
|
}
|
|
|
|
Retry:
|
|
|
|
Status = CrackSingleName(
|
|
NameType,
|
|
NameFlags,
|
|
NullTerminatedName,
|
|
DS_UNIQUE_ID_NAME,
|
|
&CrackedDomainLength,
|
|
CrackedDnsDomain,
|
|
&CrackedNameLength,
|
|
CrackedName,
|
|
&CrackError
|
|
);
|
|
|
|
LocalHack:
|
|
|
|
if ((Status != STATUS_SUCCESS) ||
|
|
( ( CrackError != DS_NAME_NO_ERROR ) &&
|
|
( CrackError != DS_NAME_ERROR_DOMAIN_ONLY ) &&
|
|
( CrackError != DS_NAME_ERROR_TRUST_REFERRAL)) )
|
|
{
|
|
//
|
|
// If the name is a duplicate, log an event
|
|
//
|
|
|
|
if (CrackError == DS_NAME_ERROR_NOT_UNIQUE)
|
|
{
|
|
WCHAR LookupType[10];
|
|
WCHAR * LookupTypeStr;
|
|
|
|
if (( NameType < sizeof( KdcGlobalNameTypes ) / sizeof( KdcGlobalNameTypes[0] )) &&
|
|
( KdcGlobalNameTypes[NameType] != NULL ))
|
|
{
|
|
LookupTypeStr = KdcGlobalNameTypes[NameType];
|
|
}
|
|
else
|
|
{
|
|
swprintf(LookupType,L"%d",(ULONG) NameType);
|
|
LookupTypeStr = LookupType;
|
|
}
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_NAME_NOT_UNIQUE,
|
|
0,
|
|
NULL,
|
|
2,
|
|
NullTerminatedName,
|
|
LookupTypeStr
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we're in the root domain, we could be getting asked for a
|
|
// name outside our forest. Look now, and attempt to
|
|
// create the ticket info for that external target realm
|
|
//
|
|
|
|
if (SecData.IsForestRoot() && SecData.IsCrossForestEnabled())
|
|
{
|
|
KerbErr = KdcMatchCrossForestName(
|
|
PrincipalName,
|
|
&CrackedDomain
|
|
);
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "xforest lookup directly - %wZ\n", &CrackedDomain)); // fester: dumb down to DEB_T_TICKETS
|
|
*CrossForest = TRUE;
|
|
}
|
|
}
|
|
|
|
DebugLog((DEB_WARN,"Failed to resolve name %ws: 0x%x, %d\n",
|
|
NullTerminatedName, Status ,CrackError ));
|
|
|
|
if (Retry)
|
|
{
|
|
MIDL_user_free(NullTerminatedName);
|
|
NullTerminatedName = NULL;
|
|
|
|
KerbErr = KdcBuildNt4Name(
|
|
&NullTerminatedName,
|
|
&CrackedDomain,
|
|
Upn
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
//
|
|
// If we got a wrong realm error, then we can return
|
|
// a non-authorititive answer
|
|
//
|
|
if (KerbErr == KDC_ERR_WRONG_REALM)
|
|
{
|
|
*Authoritative = FALSE;
|
|
KerbErr = KDC_ERR_NONE;
|
|
}
|
|
else
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NameType = DS_NT4_ACCOUNT_NAME;
|
|
Retry = FALSE;
|
|
|
|
//
|
|
// Reset lengths
|
|
//
|
|
|
|
CrackedDomainLength = (DNS_MAX_NAME_LENGTH+1);
|
|
CrackedNameLength = (UNLEN+DNS_MAX_NAME_LENGTH + 2);
|
|
goto Retry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else if (CrackError == DS_NAME_ERROR_TRUST_REFERRAL)
|
|
{
|
|
//
|
|
// We got a Xforest referral, go to Root domain
|
|
//
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "CrackName forest res- %S\n", CrackedDnsDomain));
|
|
RtlInitUnicodeString(
|
|
&CrackedDomain,
|
|
CrackedDnsDomain
|
|
);
|
|
|
|
*CrossForest = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Success...
|
|
//
|
|
D_DebugLog((DEB_T_TICKETS, "CrackName local - %S\n", CrackedDnsDomain));
|
|
RtlInitUnicodeString(
|
|
&CrackedDomain,
|
|
CrackedDnsDomain
|
|
);
|
|
}
|
|
|
|
//
|
|
// Decide whether we can open the account locally.
|
|
//
|
|
|
|
if (SecData.IsOurRealm( &CrackedDomain ))
|
|
{
|
|
*Referral = FALSE;
|
|
|
|
RtlInitUnicodeString(
|
|
&LocalCrackedString,
|
|
CrackedName
|
|
);
|
|
|
|
KerbErr = KdcGetTicketInfo(
|
|
&LocalCrackedString,
|
|
SAM_OPEN_BY_GUID,
|
|
bRestrictUserAccounts,
|
|
NULL,
|
|
NULL,
|
|
TicketInfo,
|
|
pExtendedError,
|
|
UserHandle,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
UserInfo,
|
|
GroupMembership
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// For UPNs (client) referrals, we don't really need the referral
|
|
// information at this stage. Just returned the domain from
|
|
// CrackSingleName() after checking that its a name we know about.
|
|
//
|
|
if ((KdcNameFlags & KDC_NAME_CLIENT) != 0)
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "Generating UPN referral\n"));
|
|
KerbDuplicateString(
|
|
RealmName,
|
|
&CrackedDomain
|
|
);
|
|
|
|
//
|
|
// If this wasn't a cross forest routing hint, then we must
|
|
// know about the target domain. If it didn't exist in our
|
|
// routing tables, we should fail.
|
|
//
|
|
|
|
if (!( *CrossForest ))
|
|
{
|
|
KerbErr = KdcFindReferralTarget(
|
|
TicketInfo,
|
|
RealmName,
|
|
pExtendedError,
|
|
&CrackedDomain,
|
|
FALSE, // not need ExactMatch
|
|
KdcNameFlags
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
//
|
|
// Hack for broken trust recursion.
|
|
//
|
|
if (KerbErr == KDC_ERR_NO_TRUST_PATH)
|
|
{
|
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "Got UPN w/ uknown trust path %x\n", KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is either a UPN target (e.g. U2U), or this is an SPN
|
|
// target. In both cases, return the Xrealm keys we need.
|
|
//
|
|
D_DebugLog((DEB_T_TICKETS, "Generating SPN / UPN target referral\n"));
|
|
|
|
//
|
|
// Go to the root domain of our forest when we detect we need to go
|
|
// xforest.
|
|
//
|
|
ReferToRoot = (((KdcNameFlags & ( KDC_NAME_SERVER | KDC_NAME_UPN_TARGET )) != 0) &&
|
|
(!SecData.IsForestRoot()) &&
|
|
(*CrossForest));
|
|
|
|
if ( ReferToRoot )
|
|
{
|
|
Status = SecData.GetKdcForestRoot(&ForestRoot);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
KerbErr = KdcFindReferralTarget(
|
|
TicketInfo,
|
|
RealmName,
|
|
pExtendedError,
|
|
(ReferToRoot ? &ForestRoot : &CrackedDomain),
|
|
FALSE, // not need ExactMatch
|
|
KdcNameFlags
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
//
|
|
// Hack for broken trust recursion.
|
|
//
|
|
|
|
if (KerbErr == KDC_ERR_NO_TRUST_PATH)
|
|
{
|
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Mark our referral realm as the cracked domain
|
|
//
|
|
|
|
if (ReferToRoot)
|
|
{
|
|
KerbFreeString(&ForestRoot);
|
|
KerbFreeString(RealmName);
|
|
KerbDuplicateString(
|
|
RealmName,
|
|
&CrackedDomain
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Everything worked, we found the referral target in our trust (or
|
|
// a cross forest routing hint).
|
|
//
|
|
|
|
*Referral = TRUE;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (ReferToRoot && ForestRoot.Buffer != NULL )
|
|
{
|
|
KerbFreeString(&ForestRoot);
|
|
}
|
|
|
|
if (!UsedHack)
|
|
{
|
|
if (CrackedDnsDomain != NULL)
|
|
{
|
|
MIDL_user_free(CrackedDnsDomain);
|
|
}
|
|
if (CrackedName !=NULL)
|
|
{
|
|
MIDL_user_free(CrackedName);
|
|
}
|
|
}
|
|
|
|
if (!*Authoritative)
|
|
{
|
|
KerbFreeString(&CrackedDomain);
|
|
}
|
|
|
|
if (NullTerminatedName != NULL)
|
|
{
|
|
MIDL_user_free(NullTerminatedName);
|
|
}
|
|
|
|
return(KerbErr);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcRecursing
|
|
//
|
|
// Synopsis: Determines if we've started to recurse, and whether
|
|
// or not its ok to continue.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: PrincipalName - name to normalize
|
|
// PrincipalRealm - Realm that issued principal name
|
|
// RequestRealm - Realm field of a KDC request
|
|
// NameFlags - flags about name, may be:
|
|
// KDC_NAME_CLIENT or KDC_NAME_SERVER
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOLEAN
|
|
KdcRecursing(
|
|
IN PKERB_INTERNAL_NAME PrincipalName,
|
|
IN OUT PBOOLEAN UseLocalHack
|
|
)
|
|
{
|
|
|
|
BOOLEAN fRet = FALSE;
|
|
UNICODE_STRING GCString;
|
|
SECPKG_CALL_INFO CallInfo;
|
|
|
|
*UseLocalHack = FALSE;
|
|
|
|
|
|
if ( LsaIGetCallInfo( &CallInfo ) )
|
|
{
|
|
if ( CallInfo.Attributes & SECPKG_CALL_RECURSIVE )
|
|
{
|
|
//
|
|
// This problem occurs when trying to crack a name of type:
|
|
// gc/dc.domain/domain. We're recursive, & trying to contact
|
|
// a gc, so make some assumptions about the name.
|
|
//
|
|
RtlInitUnicodeString(
|
|
&GCString,
|
|
KDC_WSZ_GC
|
|
);
|
|
|
|
if (( PrincipalName->NameCount == KDC_GC_NAMEPARTS ) &&
|
|
( RtlEqualUnicodeString( &GCString, &PrincipalName->Names[0], TRUE )))
|
|
{
|
|
*UseLocalHack = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fRet = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcNormalize
|
|
//
|
|
// Synopsis: Takes an input name and returns the appropriate ticket
|
|
// information or referral information for that name
|
|
//
|
|
// Effects: If the name is not local, it may call the GC
|
|
//
|
|
// Arguments: PrincipalName - name to normalize
|
|
// PrincipalRealm - Realm that issued principal name
|
|
// RequestRealm - Realm field of a KDC request
|
|
// NameFlags - flags about name, may be:
|
|
// KDC_NAME_CLIENT or KDC_NAME_SERVER
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcNormalize(
|
|
IN PKERB_INTERNAL_NAME PrincipalName,
|
|
IN OPTIONAL PUNICODE_STRING PrincipalRealm,
|
|
IN OPTIONAL PUNICODE_STRING RequestRealm,
|
|
IN OPTIONAL PUNICODE_STRING TgtClientRealm,
|
|
IN ULONG NameFlags,
|
|
IN BOOLEAN bRestrictUserAccounts,
|
|
OUT PBOOLEAN Referral,
|
|
OUT PUNICODE_STRING RealmName,
|
|
OUT PKDC_TICKET_INFO TicketInfo,
|
|
OUT PKERB_EXT_ERROR pExtendedError,
|
|
OUT OPTIONAL SAMPR_HANDLE * UserHandle,
|
|
IN OPTIONAL ULONG WhichFields,
|
|
IN OPTIONAL ULONG ExtendedFields,
|
|
OUT OPTIONAL PUSER_INTERNAL6_INFORMATION * UserInfo,
|
|
OUT OPTIONAL PSID_AND_ATTRIBUTES_LIST GroupMembership
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
BOOLEAN BuildUpn = FALSE;
|
|
BOOLEAN CheckUpn = FALSE;
|
|
BOOLEAN CheckSam = FALSE;
|
|
BOOLEAN CheckGC = FALSE;
|
|
BOOLEAN Reparse = FALSE;
|
|
BOOLEAN DoLocalHack = FALSE;
|
|
BOOLEAN Authoritative = TRUE;
|
|
BOOLEAN CheckForInterDomain = FALSE;
|
|
BOOLEAN ExactMatch = FALSE;
|
|
BOOLEAN CrossForest = FALSE;
|
|
BOOLEAN CheckForCrossForestTgt = FALSE;
|
|
UNICODE_STRING OutputPrincipal = {0};
|
|
UNICODE_STRING OutputRealm = {0};
|
|
UNICODE_STRING InputName = {0};
|
|
ULONG Index;
|
|
UNICODE_STRING LocalPrincipalName = {0};
|
|
UNICODE_STRING Upn = {0};
|
|
|
|
|
|
TRACE(KDC, KdcNormalize, DEB_FUNCTION);
|
|
|
|
*Referral = FALSE;
|
|
|
|
if (!ARGUMENT_PRESENT(PrincipalName))
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcNormalize: Null PrincipalName. Failing\n"));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the name is not zero length
|
|
//
|
|
|
|
if ((PrincipalName->NameCount == 0) ||
|
|
(PrincipalName->Names[0].Length == 0))
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcNormalize trying to crack zero length name. failing\n"));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_T_PAPI, "KdcNormalize [entering] normalizing name, WhichFields %#x, ExtendedFields %#x, PrincipalName ", WhichFields, ExtendedFields));
|
|
D_KerbPrintKdcName((DEB_T_PAPI, PrincipalName));
|
|
|
|
//
|
|
// Check if we should look at the GC
|
|
//
|
|
|
|
if ((NameFlags & KDC_NAME_CHECK_GC) != 0)
|
|
{
|
|
CheckGC = TRUE;
|
|
}
|
|
|
|
|
|
|
|
switch(PrincipalName->NameType)
|
|
{
|
|
//
|
|
//
|
|
// We shouldn't see these types, ever !
|
|
//
|
|
case KRB_NT_PRINCIPAL_AND_ID:
|
|
case KRB_NT_SRV_INST_AND_ID:
|
|
|
|
//
|
|
// Get the sid from the name
|
|
//
|
|
|
|
DsysAssert(FALSE); // these shouldn't be used anymore....
|
|
|
|
default:
|
|
case KRB_NT_UNKNOWN:
|
|
//
|
|
// Drop through to more interesting name types
|
|
//
|
|
|
|
case KRB_NT_SRV_HST:
|
|
case KRB_NT_SRV_INST:
|
|
case KRB_NT_PRINCIPAL:
|
|
//
|
|
// Principal names are just sam names
|
|
//
|
|
if (PrincipalName->NameCount == 1)
|
|
{
|
|
//
|
|
// If the client supplied our realm name, check SAM - otherwise just
|
|
// check for a UPN
|
|
//
|
|
|
|
if (SecData.IsOurRealm(RequestRealm))
|
|
{
|
|
D_DebugLog((DEB_TRACE, "KdcNormalize checking sam for request realm %wZ\n", RequestRealm));
|
|
CheckSam = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we don't find it in SAM, build a UPN and look it up.
|
|
//
|
|
|
|
CheckUpn = TRUE;
|
|
BuildUpn = TRUE;
|
|
OutputPrincipal = PrincipalName->Names[0];
|
|
|
|
if (ARGUMENT_PRESENT(RequestRealm))
|
|
{
|
|
OutputRealm = *RequestRealm;
|
|
}
|
|
else
|
|
{
|
|
OutputRealm = *SecData.KdcDnsRealmName();
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Drop through
|
|
//
|
|
|
|
//
|
|
// Check to see if these are the 'krbtgt' account
|
|
//
|
|
|
|
if ((PrincipalName->NameCount == 2) &&
|
|
RtlEqualUnicodeString(
|
|
&PrincipalName->Names[0],
|
|
SecData.KdcServiceName(),
|
|
TRUE)) // case insensitive
|
|
{
|
|
//
|
|
// Check if this is for a different domain - if it is for our
|
|
// domain but the principal domain is different, swap the
|
|
// domain name.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(PrincipalRealm) &&
|
|
SecData.IsOurRealm(
|
|
&PrincipalName->Names[1]))
|
|
{
|
|
OutputRealm = *PrincipalRealm;
|
|
}
|
|
else
|
|
{
|
|
OutputRealm = PrincipalName->Names[1];
|
|
}
|
|
|
|
//
|
|
// Strip trailing "."
|
|
//
|
|
|
|
if ((OutputRealm.Length > 0) &&
|
|
(OutputRealm.Buffer[-1 + OutputRealm.Length/sizeof(WCHAR)] == L'.'))
|
|
{
|
|
OutputRealm.Length -= sizeof(WCHAR);
|
|
}
|
|
|
|
if (!SecData.IsOurRealm(
|
|
&OutputRealm
|
|
))
|
|
{
|
|
CheckForInterDomain = TRUE;
|
|
|
|
//
|
|
// Some krbtgt names may be the result of going cross forest.
|
|
// We may not generate the proper SPN in this case, because
|
|
// we'll naturally check it as krbtgt/otherdomain@ourdomain.
|
|
// If we're referring outside of our trust knowledge, this will
|
|
// fail, so just use the SPN alone.
|
|
//
|
|
CheckForCrossForestTgt = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CheckSam = TRUE;
|
|
}
|
|
|
|
OutputPrincipal = PrincipalName->Names[0];
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Drop through
|
|
//
|
|
|
|
case KRB_NT_SRV_XHST:
|
|
|
|
//
|
|
// These names can't be SAM names ( SAM doesn't support this name
|
|
// type) and can't be interdomain (or it would have be caught up
|
|
// above).
|
|
//
|
|
// Check this name as a upn/spn.
|
|
//
|
|
|
|
CheckUpn = TRUE;
|
|
BuildUpn = TRUE;
|
|
|
|
break;
|
|
case KRB_NT_ENT_PRINCIPAL_AND_ID:
|
|
|
|
//
|
|
// Get the sid from the name
|
|
//
|
|
DsysAssert(FALSE); // these shouldn't be used anymore....
|
|
case KRB_NT_ENTERPRISE_PRINCIPAL:
|
|
if (PrincipalName->NameCount != 1)
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcNormalize enterprise name with more than one name part!\n"));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
break;
|
|
}
|
|
OutputPrincipal = PrincipalName->Names[0];
|
|
|
|
CheckUpn = TRUE;
|
|
|
|
//
|
|
// If the name wasn't found as a UPN/SPN, reparse and try
|
|
// in SAM
|
|
//
|
|
|
|
InputName = PrincipalName->Names[0];
|
|
Reparse = TRUE;
|
|
CheckSam = TRUE;
|
|
|
|
|
|
//
|
|
// Check for these on the GC
|
|
//
|
|
|
|
OutputRealm = *SecData.KdcDnsRealmName();
|
|
break;
|
|
|
|
case KRB_NT_MS_PRINCIPAL_AND_ID:
|
|
|
|
//
|
|
// Get the sid from the name
|
|
//
|
|
DsysAssert(FALSE); // these shouldn't be used anymore....
|
|
|
|
case KRB_NT_MS_PRINCIPAL:
|
|
//
|
|
// These are domainname \ username names
|
|
//
|
|
if (PrincipalName->NameCount > 2)
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcNormalize MS principal has more than two name parts:"));
|
|
KerbPrintKdcName(DEB_ERROR, PrincipalName);
|
|
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Never check the GC for these names
|
|
//
|
|
|
|
CheckGC = FALSE;
|
|
|
|
//
|
|
// If there are two names, the first one is the principal, the second
|
|
// is the realm
|
|
//
|
|
|
|
if (PrincipalName->NameCount == 2)
|
|
{
|
|
DebugLog((DEB_WARN, "KdcNormalize client sent 2-part MS principalname!\n"));
|
|
OutputPrincipal = PrincipalName->Names[0];
|
|
OutputRealm = PrincipalName->Names[1];
|
|
|
|
//
|
|
// Strip trailing "."
|
|
//
|
|
|
|
if ((OutputRealm.Length > 0) &&
|
|
(OutputRealm.Buffer[-1 + OutputRealm.Length/sizeof(WCHAR)] == L'.'))
|
|
{
|
|
OutputRealm.Length -= sizeof(WCHAR);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InputName = PrincipalName->Names[0];
|
|
Reparse = TRUE;
|
|
}
|
|
break;
|
|
|
|
case KRB_NT_UID:
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
DebugLog((DEB_WARN, "KdcNormalize unsupported name type: %d\n", PrincipalName->NameType));
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine our recursion state - we shouldn't be tanking
|
|
//
|
|
if (KdcRecursing( PrincipalName, &DoLocalHack ))
|
|
{
|
|
D_DebugLog((DEB_ERROR, "Recursing :\n"));
|
|
D_KerbPrintKdcName((DEB_ERROR, PrincipalName));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
#if DBG
|
|
|
|
if ( DoLocalHack )
|
|
{
|
|
DebugLog((DEB_ERROR, "Doing Local hack\n"));
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
//
|
|
// If we are supposed to reparse, then the name contains a name that we
|
|
// have to process. Look for '@' and '\' separators.
|
|
//
|
|
|
|
if (Reparse)
|
|
{
|
|
DsysAssert(InputName.Length > 0);
|
|
|
|
//
|
|
// Find the first backslash or '@', or '.' in the name
|
|
//
|
|
|
|
for (Index = InputName.Length/sizeof(WCHAR) - 1; Index > 0 ; Index-- )
|
|
{
|
|
if ((InputName.Buffer[Index] == L'\\') ||
|
|
(InputName.Buffer[Index] == L'@'))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the name did not have one of those two separators, it
|
|
// must have been of the form "name"
|
|
//
|
|
|
|
if (Index == 0)
|
|
{
|
|
OutputRealm = *SecData.KdcDnsRealmName();
|
|
OutputPrincipal = InputName;
|
|
|
|
//
|
|
// Lookup this name in SAM.
|
|
//
|
|
|
|
CheckSam = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The name had a '\' or an '@', so pick appart the two
|
|
// pieces.
|
|
//
|
|
|
|
//
|
|
// If the separator was an '@' then the second part of the name
|
|
// is the realm. If it was an '\' then the first part is the
|
|
// realm.
|
|
//
|
|
|
|
if (InputName.Buffer[Index] == L'@')
|
|
{
|
|
OutputPrincipal = InputName;
|
|
OutputPrincipal.Length = (USHORT) Index * sizeof(WCHAR);
|
|
OutputPrincipal.MaximumLength = (USHORT) Index * sizeof(WCHAR);
|
|
|
|
OutputRealm.Buffer = &InputName.Buffer[Index+1];
|
|
OutputRealm.Length = (USHORT) (InputName.Length - (Index + 1) * sizeof(WCHAR));
|
|
OutputRealm.MaximumLength = OutputRealm.Length;
|
|
|
|
//
|
|
// Strip off a trailing '.'
|
|
//
|
|
|
|
if ((OutputRealm.Length > 0) &&
|
|
(OutputRealm.Buffer[-1 + OutputRealm.Length/sizeof(WCHAR)] == L'.'))
|
|
{
|
|
OutputRealm.Length -= sizeof(WCHAR);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DsysAssert(InputName.Buffer[Index] == L'\\');
|
|
|
|
OutputRealm = InputName;
|
|
OutputRealm.Length = (USHORT) Index * sizeof(WCHAR);
|
|
OutputRealm.MaximumLength = (USHORT) Index * sizeof(WCHAR);
|
|
|
|
OutputPrincipal.Buffer = &InputName.Buffer[Index+1];
|
|
OutputPrincipal.Length = (USHORT) (InputName.Length - (Index + 1) * sizeof(WCHAR));
|
|
OutputPrincipal.MaximumLength = OutputPrincipal.Length;
|
|
}
|
|
}
|
|
|
|
if ((OutputRealm.Length > 0) &&
|
|
(OutputRealm.Buffer[-1 + OutputRealm.Length/sizeof(WCHAR)] == L'.'))
|
|
{
|
|
OutputRealm.Length -= sizeof(WCHAR);
|
|
}
|
|
|
|
//
|
|
// If the domain portion is not for our domain, don't check sam
|
|
//
|
|
|
|
if (!SecData.IsOurRealm(
|
|
&OutputRealm
|
|
))
|
|
|
|
{
|
|
CheckForInterDomain = TRUE;
|
|
CheckSam = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If we don't have a separate realm for the name,
|
|
// check for interdomain. This is because cross-realm
|
|
// requests have our own realm name in the name but
|
|
// another realm name in the domain.
|
|
//
|
|
|
|
if (RtlEqualUnicodeString(
|
|
&OutputPrincipal,
|
|
SecData.KdcServiceName(),
|
|
TRUE
|
|
))
|
|
{
|
|
if (ARGUMENT_PRESENT(PrincipalRealm))
|
|
{
|
|
//
|
|
// Try the supplied realm. If it is present, and points
|
|
// to a different domain, lookup up interdomain
|
|
//
|
|
|
|
OutputRealm = *PrincipalRealm;
|
|
if (!SecData.IsOurRealm(
|
|
&OutputRealm
|
|
))
|
|
{
|
|
CheckForInterDomain = TRUE;
|
|
CheckSam = FALSE;
|
|
}
|
|
else
|
|
{
|
|
CheckSam = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CheckSam = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CheckSam = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We could end up with CheckUpn and BuildUpn with both client names
|
|
// and spns. We need to allow spns of the type
|
|
// service/hostname to be looked up in sam (without appending an @realm
|
|
// to it). If KDC_NAME_SERVER is passed, it must be an spn, so, we
|
|
// don't add the @realm
|
|
|
|
if (CheckUpn)
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize checking UPN\n"));
|
|
|
|
if (BuildUpn && ((NameFlags & KDC_NAME_SERVER) == 0))
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize building UPN\n"));
|
|
KerbErr = KerbConvertKdcNameToString(
|
|
&Upn,
|
|
PrincipalName,
|
|
ARGUMENT_PRESENT(RequestRealm) ? RequestRealm : SecData.KdcDnsRealmName()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
KerbErr = KerbConvertKdcNameToString(
|
|
&Upn,
|
|
PrincipalName,
|
|
NULL // no realm name
|
|
);
|
|
}
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize Lookup up upn/spn %wZ\n", &Upn));
|
|
|
|
KerbErr = KdcGetTicketInfo(
|
|
&Upn,
|
|
(NameFlags & KDC_NAME_SERVER) ? SAM_OPEN_BY_SPN : SAM_OPEN_BY_UPN,
|
|
bRestrictUserAccounts,
|
|
NULL, // no principal name
|
|
NULL, // no realm name,
|
|
TicketInfo,
|
|
pExtendedError,
|
|
UserHandle,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
UserInfo,
|
|
GroupMembership
|
|
);
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Some errors aren't recoverable
|
|
//
|
|
|
|
if ((KerbErr == KDC_ERR_MUST_USE_USER2USER) ||
|
|
(KerbErr == KDC_ERR_SVC_UNAVAILABLE))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Next check for sam account names, as some of these may later be looked
|
|
// up as UPNs
|
|
//
|
|
|
|
if (CheckSam)
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize checking name in SAM\n"));
|
|
|
|
KerbErr = KdcGetTicketInfo(
|
|
&OutputPrincipal,
|
|
0, // no lookup flags means sam name
|
|
bRestrictUserAccounts,
|
|
NULL, // no principal name
|
|
NULL, // no realm name,
|
|
TicketInfo,
|
|
pExtendedError,
|
|
UserHandle,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
UserInfo,
|
|
GroupMembership
|
|
);
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Some errors aren't recoverable
|
|
//
|
|
|
|
if ((KerbErr == KDC_ERR_MUST_USE_USER2USER) ||
|
|
(KerbErr == KDC_ERR_SVC_UNAVAILABLE))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, depending on which flags are set, try to do different things.
|
|
//
|
|
|
|
if (CheckForInterDomain)
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize checking name interdomain\n"));
|
|
|
|
//
|
|
// If the target name is not KRBTGT, this must be a referral.
|
|
//
|
|
|
|
if (!RtlEqualUnicodeString(
|
|
&OutputPrincipal,
|
|
SecData.KdcServiceName(),
|
|
TRUE)) // case insensitive
|
|
{
|
|
*Referral = TRUE;
|
|
|
|
}
|
|
if ((NameFlags & KDC_NAME_FOLLOW_REFERRALS) == 0)
|
|
{
|
|
//
|
|
// We need an exact match on the domain name
|
|
//
|
|
|
|
if (*Referral)
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcNormalize client asked for principal in another realm but no referrals!\n"));
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We also only accept the krbtgt account name
|
|
//
|
|
|
|
ExactMatch = TRUE;
|
|
}
|
|
|
|
KerbErr = KdcFindReferralTarget(
|
|
TicketInfo,
|
|
RealmName,
|
|
pExtendedError,
|
|
&OutputRealm,
|
|
ExactMatch,
|
|
NameFlags
|
|
);
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
|
|
//
|
|
// Hmmm. If the TGT's client isn't from our realm, and we picked up
|
|
// a non-transitive trust, then this is the wrong path to take.
|
|
// Toss this info, and continue to see if we can go through
|
|
// an xforest routing hint.
|
|
//
|
|
if (ARGUMENT_PRESENT( TgtClientRealm ) && !SecData.IsOurRealm( TgtClientRealm ) &&
|
|
((TicketInfo->fTicketOpts & AUTH_REQ_TRANSITIVE_TRUST) == 0))
|
|
{
|
|
DebugLog((DEB_ERROR, "Client outside of our realm is attempting to transit\n"));
|
|
DebugLog((DEB_ERROR, "Trying xforest?\n"));
|
|
FreeTicketInfo( TicketInfo );
|
|
KerbFreeString( RealmName );
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// If the output realm & the realm we asked for is different,
|
|
// this is a referral (meaning an we don't have a password
|
|
// for the principal name on this machine)
|
|
//
|
|
|
|
if (!KerbCompareUnicodeRealmNames(
|
|
RealmName,
|
|
&OutputRealm
|
|
))
|
|
{
|
|
*Referral = TRUE;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (CheckGC)
|
|
{
|
|
|
|
//
|
|
// If the caller doesn't want us to follow referrals, fail here.
|
|
//
|
|
|
|
if ((NameFlags & KDC_NAME_FOLLOW_REFERRALS) == 0)
|
|
{
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (Upn.Buffer == NULL )
|
|
{
|
|
//
|
|
// Build the UPN here. No reason to build it for tgt accounts.
|
|
//
|
|
if (!CheckForCrossForestTgt)
|
|
{
|
|
KerbErr = KerbConvertKdcNameToString(
|
|
&Upn,
|
|
PrincipalName,
|
|
ARGUMENT_PRESENT(RequestRealm) ? RequestRealm : SecData.KdcDnsRealmName()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
KerbErr = KerbConvertKdcNameToString(
|
|
&Upn,
|
|
PrincipalName,
|
|
NULL // no realm name
|
|
);
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize building UPN\n"));
|
|
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
DsysAssert(Upn.Buffer != NULL);
|
|
D_DebugLog((DEB_T_TICKETS, "KdcNormalize checking name %wZ in GC\n", &Upn));
|
|
|
|
|
|
//
|
|
// This will allow us to open sam locally as well
|
|
//
|
|
|
|
KerbErr = KdcCrackNameAtGC(
|
|
&Upn,
|
|
PrincipalName,
|
|
NameFlags,
|
|
bRestrictUserAccounts,
|
|
DoLocalHack,
|
|
RealmName,
|
|
TicketInfo,
|
|
&Authoritative,
|
|
Referral,
|
|
&CrossForest,
|
|
pExtendedError,
|
|
UserHandle,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
UserInfo,
|
|
GroupMembership
|
|
);
|
|
|
|
if (KERB_SUCCESS( KerbErr ))
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "Found name in GC\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// LAST CHANCE :)
|
|
//
|
|
// This could be an interdomain TGT, potentialy w/ a target outside of our
|
|
// forest. Use the crackname call to determine whether or not to
|
|
// allow the call to proceed. If so, go to our root domain.
|
|
//
|
|
if ( CheckForCrossForestTgt && !DoLocalHack )
|
|
{
|
|
D_DebugLog((DEB_T_TICKETS, "Checking XForest krbtgt\n"));
|
|
KerbErr = KdcCheckForCrossForestReferral(
|
|
TicketInfo,
|
|
RealmName,
|
|
pExtendedError,
|
|
&OutputRealm,
|
|
NameFlags
|
|
);
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_T_TICKETS, "KdcNormalize got referral (%wZ) destined for x forest - %wZ\n", RealmName,&OutputRealm));
|
|
*Referral = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
|
|
Cleanup:
|
|
|
|
KerbFreeString(
|
|
&Upn
|
|
);
|
|
KerbFreeString(
|
|
&LocalPrincipalName
|
|
);
|
|
|
|
if (KerbErr == KDC_ERR_C_PRINCIPAL_UNKNOWN)
|
|
{
|
|
if ((NameFlags & KDC_NAME_SERVER) != 0)
|
|
{
|
|
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
|
|
D_DebugLog((DEB_T_PAPI, "KdcNormalize returning %#x\n", KerbErr));
|
|
|
|
return (KerbErr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetTimeStamps
|
|
//
|
|
// Synopsis: Gets the current time and clock skew envelope.
|
|
//
|
|
// Arguments: [ptsFudge] -- (in) amount of clock skew to allow.
|
|
// [ptsNow] -- (out) the current time
|
|
// [ptsNowPlus] -- (out) the current time plus the skew.
|
|
// [ptsNowMinus] -- (out) the current time less the skew
|
|
//
|
|
// History: 4-23-93 WadeR Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
GetTimeStamps( IN PLARGE_INTEGER ptsFudge,
|
|
OUT PLARGE_INTEGER ptsNow,
|
|
OUT PLARGE_INTEGER ptsNowPlus,
|
|
OUT PLARGE_INTEGER ptsNowMinus )
|
|
{
|
|
TRACE(KDC, GetTimeStamps, DEB_FUNCTION);
|
|
|
|
GetSystemTimeAsFileTime((PFILETIME) ptsNow );
|
|
ptsNowPlus->QuadPart = ptsNow->QuadPart + ptsFudge->QuadPart;
|
|
ptsNowMinus->QuadPart = ptsNow->QuadPart - ptsFudge->QuadPart;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcBuildTicketTimesAndFlags
|
|
//
|
|
// Synopsis: Computes the times and flags for a new ticket
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcBuildTicketTimesAndFlags(
|
|
IN ULONG ClientPolicyFlags,
|
|
IN ULONG ServerPolicyFlags,
|
|
IN PLARGE_INTEGER DomainTicketLifespan,
|
|
IN PLARGE_INTEGER DomainTicketRenewspan,
|
|
IN OPTIONAL PKDC_S4U_TICKET_INFO S4UTicketInfo,
|
|
IN OPTIONAL PLARGE_INTEGER LogoffTime,
|
|
IN OPTIONAL PLARGE_INTEGER AccountExpiry,
|
|
IN PKERB_KDC_REQUEST_BODY RequestBody,
|
|
IN OPTIONAL PKERB_ENCRYPTED_TICKET SourceTicket,
|
|
IN OUT PKERB_ENCRYPTED_TICKET Ticket,
|
|
IN OUT OPTIONAL PKERB_EXT_ERROR ExtendedError
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
LARGE_INTEGER RequestEndTime;
|
|
LARGE_INTEGER RequestStartTime;
|
|
LARGE_INTEGER RequestRenewTime;
|
|
|
|
LARGE_INTEGER SourceEndTime;
|
|
LARGE_INTEGER SourceRenewTime;
|
|
LARGE_INTEGER SourceStartTime;
|
|
ULONG SourceTicketFlags = 0;
|
|
ULONG FinalTicketFlags = 0;
|
|
ULONG KdcOptions = 0;
|
|
|
|
LARGE_INTEGER FinalEndTime;
|
|
LARGE_INTEGER FinalStartTime;
|
|
LARGE_INTEGER FinalRenewTime;
|
|
LARGE_INTEGER LocalLogoffTime;
|
|
BOOLEAN Renewable = FALSE;
|
|
|
|
LARGE_INTEGER CurrentTime;
|
|
|
|
TRACE(KDC, KdcBuildTicketTimesAndFlags, DEB_FUNCTION);
|
|
|
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
|
FinalEndTime.QuadPart = 0;
|
|
FinalStartTime.QuadPart = 0;
|
|
FinalRenewTime.QuadPart = 0;
|
|
|
|
KdcOptions = KerbConvertFlagsToUlong(&RequestBody->kdc_options);
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "KdcBuildTicketTimesAndFlags ClientPolicyFlags %#x, ServerPolicyFlags %#x, KdcOptions %#x\n",
|
|
ClientPolicyFlags, ServerPolicyFlags, KdcOptions));
|
|
|
|
//
|
|
// Get the force logoff time
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(LogoffTime))
|
|
{
|
|
LocalLogoffTime = *LogoffTime;
|
|
}
|
|
else
|
|
{
|
|
LocalLogoffTime = tsInfinity;
|
|
}
|
|
|
|
//
|
|
// Get the request times out of the request
|
|
//
|
|
|
|
if (RequestBody->bit_mask & KERB_KDC_REQUEST_BODY_starttime_present)
|
|
{
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&RequestStartTime,
|
|
&RequestBody->KERB_KDC_REQUEST_BODY_starttime,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RequestStartTime.QuadPart = 0;
|
|
}
|
|
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&RequestEndTime,
|
|
&RequestBody->endtime,
|
|
NULL
|
|
);
|
|
|
|
if (RequestBody->bit_mask & KERB_KDC_REQUEST_BODY_renew_until_present)
|
|
{
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&RequestRenewTime,
|
|
&RequestBody->KERB_KDC_REQUEST_BODY_renew_until,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RequestRenewTime.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// Get the times out of the source ticket (if present)
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(SourceTicket))
|
|
{
|
|
if (SourceTicket->bit_mask & KERB_ENCRYPTED_TICKET_starttime_present)
|
|
{
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&SourceStartTime,
|
|
&SourceTicket->KERB_ENCRYPTED_TICKET_starttime,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
SourceStartTime.QuadPart = 0;
|
|
}
|
|
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&SourceEndTime,
|
|
&SourceTicket->endtime,
|
|
NULL
|
|
);
|
|
|
|
if (SourceTicket->bit_mask & KERB_ENCRYPTED_TICKET_renew_until_present)
|
|
{
|
|
KerbConvertGeneralizedTimeToLargeInt(
|
|
&SourceRenewTime,
|
|
&SourceTicket->KERB_ENCRYPTED_TICKET_renew_until,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
SourceRenewTime.QuadPart = 0;
|
|
}
|
|
SourceTicketFlags = KerbConvertFlagsToUlong(&SourceTicket->flags);
|
|
D_DebugLog((DEB_T_TICKETS, "KdcBuildTicketTimesAndFlags SourceTicketFlags %#x\n", SourceTicketFlags));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set the maximums in this case, which is probably an AS request.
|
|
//
|
|
|
|
SourceStartTime = CurrentTime;
|
|
SourceEndTime = tsInfinity;
|
|
SourceRenewTime = tsInfinity;
|
|
SourceTicketFlags = 0;
|
|
|
|
//
|
|
// Fill in the source flags from what the client policy & domain policy
|
|
// allow
|
|
//
|
|
|
|
if ((ClientPolicyFlags & AUTH_REQ_ALLOW_FORWARDABLE) != 0)
|
|
{
|
|
SourceTicketFlags |= KERB_TICKET_FLAGS_forwardable;
|
|
}
|
|
if ((ClientPolicyFlags & AUTH_REQ_ALLOW_PROXIABLE) != 0)
|
|
{
|
|
SourceTicketFlags |= KERB_TICKET_FLAGS_proxiable;
|
|
}
|
|
if ((ClientPolicyFlags & AUTH_REQ_ALLOW_POSTDATE) != 0)
|
|
{
|
|
SourceTicketFlags |= KERB_TICKET_FLAGS_may_postdate;
|
|
}
|
|
if ((ClientPolicyFlags & AUTH_REQ_ALLOW_RENEWABLE) != 0)
|
|
{
|
|
SourceTicketFlags |= KERB_TICKET_FLAGS_renewable;
|
|
}
|
|
D_DebugLog((DEB_T_TICKETS, "KdcBuildTicketTimesAndFlags SourceTicketFlags %#x by ClientPolicyFlags\n", SourceTicketFlags));
|
|
}
|
|
|
|
//
|
|
// Start computing the flags, from algorithm in RFC1510 appendix A.6
|
|
//
|
|
|
|
//
|
|
// Delegation flags
|
|
//
|
|
|
|
if ((ServerPolicyFlags & AUTH_REQ_OK_AS_DELEGATE) != 0)
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_ok_as_delegate;
|
|
}
|
|
|
|
//
|
|
// Forward flags
|
|
//
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_forwardable)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_forwardable) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_FORWARDABLE))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_forwardable;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "Asked for forwardable but not allowed\n"));
|
|
// KerbErr = KDC_ERR_BADOPTION;
|
|
// goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_forwarded)
|
|
{
|
|
if (( KdcIssueForwardedTickets ) &&
|
|
( SourceTicketFlags & KERB_TICKET_FLAGS_forwardable ) &&
|
|
( ServerPolicyFlags & AUTH_REQ_ALLOW_FORWARDABLE ))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_forwarded;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "Asked for forwarded but not allowed\n"));
|
|
if ( !KdcIssueForwardedTickets )
|
|
{
|
|
DebugLog((DEB_TRACE, "Forwarded protection ON\n"));
|
|
}
|
|
KerbErr = KDC_ERR_BADOPTION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (SourceTicketFlags & KERB_TICKET_FLAGS_forwarded)
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_forwarded;
|
|
}
|
|
|
|
//
|
|
// preauth flags
|
|
//
|
|
|
|
if (SourceTicketFlags & KERB_TICKET_FLAGS_pre_authent)
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_pre_authent;
|
|
}
|
|
|
|
//
|
|
// Proxy flags
|
|
//
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_proxiable)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_proxiable) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_PROXIABLE))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_proxiable;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "Asked for proxiable but not allowed\n"));
|
|
// KerbErr = KDC_ERR_BADOPTION;
|
|
// goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_proxy)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_proxiable) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_PROXIABLE))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_proxy;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "Asked for proxy but not allowed\n"));
|
|
KerbErr = KDC_ERR_BADOPTION;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Postdate
|
|
//
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_allow_postdate)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_may_postdate) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_POSTDATE))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_may_postdate;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcBuildTicketTimesAndFlags asked for allow_postdate but not allowed: "
|
|
"KdcOptions %#x, SourceTicketFlags %#x, ServerPolicyFlags %#x\n",
|
|
KdcOptions, SourceTicketFlags, ServerPolicyFlags));
|
|
KerbErr = KDC_ERR_POLICY;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_postdated)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_may_postdate) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_POSTDATE))
|
|
{
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_postdated | KERB_TICKET_FLAGS_invalid;
|
|
|
|
//
|
|
// Start time is required here
|
|
//
|
|
|
|
if (RequestStartTime.QuadPart == 0)
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcBuildTicketTimesAndFlags asked for postdate but start time not present: "
|
|
"KdcOptions %#x, SourceTicketFlags %#x, ServerPolicyFlags %#x\n",
|
|
KdcOptions, SourceTicketFlags, ServerPolicyFlags));
|
|
KerbErr = KDC_ERR_CANNOT_POSTDATE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KerbErr = (SourceTicketFlags & KERB_TICKET_FLAGS_may_postdate)
|
|
? KDC_ERR_POLICY : KDC_ERR_CANNOT_POSTDATE;
|
|
DebugLog((DEB_ERROR, "KdcBuildTicketTimesAndFlags asked for postdated but not allowed: "
|
|
"KdcOptions %#x, SourceTicketFlags %#x, ServerPolicyFlags %#x, KerbErr %#x\n",
|
|
KdcOptions, SourceTicketFlags, ServerPolicyFlags, KerbErr));
|
|
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate
|
|
//
|
|
|
|
if (KdcOptions & KERB_KDC_OPTIONS_validate)
|
|
{
|
|
if ((SourceTicketFlags & KERB_TICKET_FLAGS_invalid) == 0)
|
|
{
|
|
DebugLog((DEB_ERROR, "Trying to validate a valid ticket\n"));
|
|
KerbErr = KDC_ERR_POLICY;
|
|
goto Cleanup;
|
|
}
|
|
if ((SourceStartTime.QuadPart == 0) ||
|
|
(SourceStartTime.QuadPart < CurrentTime.QuadPart - SkewTime.QuadPart))
|
|
{
|
|
DebugLog((DEB_ERROR, "Trying to validate a ticket before it is valid\n"));
|
|
KerbErr = KRB_AP_ERR_TKT_NYV;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start filling in time fields
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(SourceTicket))
|
|
{
|
|
//
|
|
// For S4UProxy, we need to use the authtime from the evidence ticket.
|
|
// This ensures that the PAC_CLIENT_INFO (aka PAC verifier) remains
|
|
// constant, and correct.
|
|
//
|
|
if ( ARGUMENT_PRESENT(S4UTicketInfo) &&
|
|
( S4UTicketInfo->Flags & TI_PRXY_REQUESTOR_THIS_REALM))
|
|
{
|
|
Ticket->authtime = S4UTicketInfo->EvidenceTicket->authtime;
|
|
}
|
|
else
|
|
{
|
|
Ticket->authtime = SourceTicket->authtime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&Ticket->authtime,
|
|
NULL,
|
|
&CurrentTime
|
|
);
|
|
}
|
|
|
|
//
|
|
// The times are computed differently for renewing a ticket and for
|
|
// getting a new ticket.
|
|
//
|
|
|
|
if ((KdcOptions & KERB_KDC_OPTIONS_renew) != 0)
|
|
{
|
|
if ((SourceRenewTime.QuadPart == 0) ||
|
|
(SourceStartTime.QuadPart == 0) ||
|
|
((SourceTicketFlags & KERB_TICKET_FLAGS_renewable) == 0) ||
|
|
((ServerPolicyFlags & AUTH_REQ_ALLOW_RENEWABLE) == 0))
|
|
{
|
|
DebugLog((DEB_ERROR,"Trying to renew a non-renewable ticket or against policy\n"));
|
|
KerbErr = KDC_ERR_BADOPTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the renew time is in the future
|
|
//
|
|
|
|
if (SourceRenewTime.QuadPart < CurrentTime.QuadPart)
|
|
{
|
|
DebugLog((DEB_ERROR, "Trying to renew a ticket past its renew time\n"));
|
|
KerbErr = KRB_AP_ERR_TKT_EXPIRED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the end time is not in the past
|
|
//
|
|
|
|
if (SourceEndTime.QuadPart < CurrentTime.QuadPart)
|
|
{
|
|
DebugLog((DEB_ERROR, "Trying to renew an expired ticket\n"));
|
|
KerbErr = KRB_AP_ERR_TKT_EXPIRED;
|
|
goto Cleanup;
|
|
}
|
|
FinalStartTime = CurrentTime;
|
|
|
|
//
|
|
// The end time is the minimum of the current time plus lifespan
|
|
// of the old ticket and the renew until time of the old ticket
|
|
//
|
|
|
|
FinalEndTime.QuadPart = CurrentTime.QuadPart + (SourceEndTime.QuadPart - SourceStartTime.QuadPart);
|
|
if (FinalEndTime.QuadPart > SourceRenewTime.QuadPart)
|
|
{
|
|
FinalEndTime = SourceRenewTime;
|
|
}
|
|
FinalRenewTime = SourceRenewTime;
|
|
FinalTicketFlags = SourceTicketFlags;
|
|
|
|
#if DBG
|
|
D_DebugLog((DEB_T_TIME, "KdcBuildTicketTimesAndFlags Renewal ticket time info\n"));
|
|
PrintTime(DEB_T_TIME, " FinalRenewTime =", &FinalRenewTime);
|
|
PrintTime(DEB_T_TIME, " FinalEndTime =", &FinalEndTime);
|
|
PrintTime(DEB_T_TIME, " SourceEndTime =", &SourceEndTime);
|
|
PrintTime(DEB_T_TIME, " SourceRenewTime =", &SourceRenewTime);
|
|
#endif
|
|
|
|
Renewable = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Compute start and end times for normal tickets
|
|
//
|
|
|
|
//
|
|
// Set the start time
|
|
//
|
|
|
|
if (RequestStartTime.QuadPart == 0)
|
|
{
|
|
FinalStartTime = CurrentTime;
|
|
}
|
|
else
|
|
{
|
|
FinalStartTime = RequestStartTime;
|
|
}
|
|
|
|
//
|
|
// Set the end time
|
|
//
|
|
|
|
if (RequestEndTime.QuadPart == 0)
|
|
{
|
|
FinalEndTime = tsInfinity;
|
|
}
|
|
else
|
|
{
|
|
FinalEndTime = RequestEndTime;
|
|
}
|
|
|
|
if (FinalEndTime.QuadPart > SourceEndTime.QuadPart)
|
|
{
|
|
FinalEndTime = SourceEndTime;
|
|
}
|
|
|
|
if (FinalEndTime.QuadPart > CurrentTime.QuadPart + DomainTicketLifespan->QuadPart)
|
|
{
|
|
FinalEndTime.QuadPart = CurrentTime.QuadPart + DomainTicketLifespan->QuadPart;
|
|
}
|
|
|
|
//
|
|
// Check for renewable-ok
|
|
//
|
|
|
|
if ((KdcOptions & KERB_KDC_OPTIONS_renewable_ok) &&
|
|
(FinalEndTime.QuadPart < RequestEndTime.QuadPart) &&
|
|
(SourceTicketFlags & KERB_TICKET_FLAGS_renewable))
|
|
{
|
|
KdcOptions |= KERB_KDC_OPTIONS_renewable;
|
|
RequestRenewTime = RequestEndTime;
|
|
|
|
//
|
|
// Make sure that the source ticket had a renewtime (it
|
|
// should because it is renewable)
|
|
//
|
|
|
|
DsysAssert(SourceRenewTime.QuadPart != 0);
|
|
if (RequestRenewTime.QuadPart > SourceRenewTime.QuadPart)
|
|
{
|
|
RequestRenewTime = SourceRenewTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Renewable)
|
|
{
|
|
//
|
|
// Compute renew times
|
|
//
|
|
|
|
if (RequestRenewTime.QuadPart == 0)
|
|
{
|
|
RequestRenewTime = tsInfinity;
|
|
}
|
|
|
|
if ((KdcOptions & KERB_KDC_OPTIONS_renewable) &&
|
|
(SourceTicketFlags & KERB_TICKET_FLAGS_renewable) &&
|
|
(ServerPolicyFlags & AUTH_REQ_ALLOW_RENEWABLE))
|
|
{
|
|
FinalRenewTime = RequestRenewTime;
|
|
if (FinalRenewTime.QuadPart > FinalStartTime.QuadPart + DomainTicketRenewspan->QuadPart)
|
|
{
|
|
FinalRenewTime.QuadPart = FinalStartTime.QuadPart + DomainTicketRenewspan->QuadPart;
|
|
}
|
|
|
|
DsysAssert(SourceRenewTime.QuadPart != 0);
|
|
|
|
if (FinalRenewTime.QuadPart > SourceRenewTime.QuadPart)
|
|
{
|
|
FinalRenewTime = SourceRenewTime;
|
|
}
|
|
FinalTicketFlags |= KERB_TICKET_FLAGS_renewable;
|
|
|
|
}
|
|
else
|
|
{
|
|
FinalRenewTime.QuadPart = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// more on postdating
|
|
//
|
|
|
|
if (FinalStartTime.QuadPart > CurrentTime.QuadPart + SkewTime.QuadPart)
|
|
{
|
|
if ( ! ( (FinalTicketFlags & KERB_TICKET_FLAGS_may_postdate)
|
|
&& (FinalTicketFlags & KERB_TICKET_FLAGS_postdated)
|
|
&& (FinalTicketFlags & KERB_TICKET_FLAGS_invalid) ) )
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcBuildTicketTimesAndFlags postdate CANNOT_POSTDATE: "
|
|
"KdcOptions %#x, SourceTicketFlags %#x, SourceTicketFlags %#x, "
|
|
"FinalStartTime %#I64x, CurrentTime %#I64x, SkewTime %#I64x\n",
|
|
KdcOptions, SourceTicketFlags, ServerPolicyFlags,
|
|
FinalStartTime.QuadPart, CurrentTime.QuadPart, SkewTime.QuadPart));
|
|
|
|
KerbErr = KDC_ERR_CANNOT_POSTDATE;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcBuildTicketTimesAndFlags postdate ERR_POLICY: "
|
|
"KdcOptions %#x, SourceTicketFlags %#x, SourceTicketFlags %#x, "
|
|
"FinalStartTime %#I64x, CurrentTime %#I64x, SkewTime %#I64x\n",
|
|
KdcOptions, SourceTicketFlags, ServerPolicyFlags,
|
|
FinalStartTime.QuadPart, CurrentTime.QuadPart, SkewTime.QuadPart));
|
|
|
|
KerbErr = KDC_ERR_POLICY; // do not allow postdating
|
|
}
|
|
FILL_EXT_ERROR_EX2(ExtendedError, STATUS_TIME_DIFFERENCE_AT_DC, FILENO, __LINE__);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the final ticket is valid
|
|
//
|
|
|
|
if (FinalStartTime.QuadPart > FinalEndTime.QuadPart)
|
|
{
|
|
DebugLog((DEB_ERROR, "Client asked for endtime before starttime\n"));
|
|
KerbErr = KDC_ERR_NEVER_VALID;
|
|
FILL_EXT_ERROR_EX(ExtendedError, STATUS_TIME_DIFFERENCE_AT_DC, FILENO, __LINE__);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Incorporate the logoff time (according to logon hours) by reseting
|
|
// both the final end time and final renew time
|
|
//
|
|
|
|
if (FinalEndTime.QuadPart > LocalLogoffTime.QuadPart)
|
|
{
|
|
FinalEndTime.QuadPart = LocalLogoffTime.QuadPart;
|
|
}
|
|
|
|
if (FinalRenewTime.QuadPart > LocalLogoffTime.QuadPart)
|
|
{
|
|
FinalRenewTime.QuadPart = LocalLogoffTime.QuadPart;
|
|
}
|
|
|
|
//
|
|
// Tickets good only until acct expires.
|
|
// We make the assumption that the sam has
|
|
// already checked this against the current time
|
|
// when we're checking the logon restrictions.
|
|
//
|
|
if ((ARGUMENT_PRESENT(AccountExpiry) &&
|
|
(AccountExpiry->QuadPart != 0 )))
|
|
{
|
|
if (FinalEndTime.QuadPart > AccountExpiry->QuadPart)
|
|
{
|
|
FinalEndTime.QuadPart = AccountExpiry->QuadPart;
|
|
}
|
|
|
|
|
|
if (FinalRenewTime.QuadPart > AccountExpiry->QuadPart)
|
|
{
|
|
FinalRenewTime.QuadPart = AccountExpiry->QuadPart;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fill in the times in the ticket
|
|
//
|
|
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&Ticket->KERB_ENCRYPTED_TICKET_starttime,
|
|
NULL,
|
|
&FinalStartTime
|
|
);
|
|
Ticket->bit_mask |= KERB_ENCRYPTED_TICKET_starttime_present;
|
|
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&Ticket->endtime,
|
|
NULL,
|
|
&FinalEndTime
|
|
);
|
|
|
|
if (FinalRenewTime.QuadPart != 0)
|
|
{
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&Ticket->KERB_ENCRYPTED_TICKET_renew_until,
|
|
NULL,
|
|
&FinalRenewTime
|
|
);
|
|
Ticket->bit_mask |= KERB_ENCRYPTED_TICKET_renew_until_present;
|
|
}
|
|
|
|
//
|
|
// Finally, get the remainder ticket flags, based on S4U logic, if needed.
|
|
//
|
|
// TBD: Set the right flags for proxy requests.
|
|
//
|
|
if ( ARGUMENT_PRESENT( S4UTicketInfo ) &&
|
|
(( S4UTicketInfo->Flags & TI_S4USELF_INFO ) != 0))
|
|
{
|
|
//
|
|
// If the client account is sensitive, turn off fwdable bit
|
|
//
|
|
if (( S4UTicketInfo->Flags & TI_SENSITIVE_CLIENT_ACCOUNT ) != 0)
|
|
{
|
|
FinalTicketFlags &= ~KERB_TICKET_FLAGS_forwardable;
|
|
D_DebugLog((DEB_ERROR, "S4U - Turning off fwdable flag (client restriction)\n"));
|
|
}
|
|
else if ((( S4UTicketInfo->Flags & TI_TARGET_OUR_REALM ) != 0) &&
|
|
(( ServerPolicyFlags & AUTH_REQ_ALLOW_S4U_DELEGATE ) == 0))
|
|
{
|
|
//
|
|
// If the server is in our realm, it must have T2A4D bit set.
|
|
//
|
|
FinalTicketFlags &= ~KERB_TICKET_FLAGS_forwardable;
|
|
D_DebugLog((DEB_ERROR, "S4U - Turning off fwdable flag (svr restriction)\n"));
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Copy in the flags
|
|
//
|
|
|
|
DsysAssert(Ticket->flags.length == sizeof(ULONG) * 8);
|
|
*((PULONG) Ticket->flags.value) = KerbConvertUlongToFlagUlong(FinalTicketFlags);
|
|
|
|
Cleanup:
|
|
|
|
#if DBG
|
|
DebugLog((DEB_T_TIME, "KdcBuildTicketTimesAndFlags Final Ticket Flags %#x, times -\n", FinalTicketFlags));
|
|
PrintTime(DEB_T_TIME, " RequestRenewTime =", &RequestRenewTime);
|
|
PrintTime(DEB_T_TIME, " RequestEndTime =", &RequestEndTime);
|
|
PrintTime(DEB_T_TIME, " RequestStartTime =", &RequestStartTime);
|
|
PrintTime(DEB_T_TIME, " FinalRenewTime =", &FinalRenewTime);
|
|
PrintTime(DEB_T_TIME, " FinalEndTime =", &FinalEndTime);
|
|
PrintTime(DEB_T_TIME, " FinalStartTime =", &FinalStartTime);
|
|
PrintTime(DEB_T_TIME, " SourceRenewTime =", &SourceRenewTime);
|
|
PrintTime(DEB_T_TIME, " SourceEndTime =", &SourceEndTime);
|
|
PrintTime(DEB_T_TIME, " SourceStartTime =", &SourceStartTime);
|
|
#endif
|
|
|
|
return(KerbErr);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcGetUserKeys
|
|
//
|
|
// Synopsis: retrieves user keys
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
KERBERR
|
|
KdcGetUserKeys(
|
|
IN SAMPR_HANDLE UserHandle,
|
|
IN PUSER_INTERNAL6_INFORMATION UserInfo,
|
|
OUT PKERB_STORED_CREDENTIAL * Passwords,
|
|
OUT PKERB_STORED_CREDENTIAL * OldPasswords,
|
|
OUT PKERB_EXT_ERROR pExtendedError
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
NTSTATUS Status;
|
|
PKERB_STORED_CREDENTIAL Keys = NULL;
|
|
PKERB_STORED_CREDENTIAL StoredCreds = NULL;
|
|
PKERB_STORED_CREDENTIAL Cred64 = NULL;
|
|
ULONG CredentialSize = 0;
|
|
ULONG NewCredentialCount = 0;
|
|
ULONG NewCredentialSize = 0;
|
|
ULONG Index, CredentialIndex = 0, Offset;
|
|
USHORT Flags = 0;
|
|
PUCHAR Base;
|
|
BOOLEAN UseStoredCreds = FALSE, UnMarshalledCreds = FALSE;
|
|
PCRYPTO_SYSTEM NullCryptoSystem = NULL;
|
|
BOOLEAN UseBuiltins = TRUE;
|
|
PNT_OWF_PASSWORD OldNtPassword = NULL;
|
|
PNT_OWF_PASSWORD OldNtPasswordSecond = NULL;
|
|
NT_OWF_PASSWORD OldPasswordData = {0}; // Previous password (from current)
|
|
NT_OWF_PASSWORD OldPasswordDataSecond = {0}; // Password history two previous (from current)
|
|
PUSER_ALL_INFORMATION UserAll = &UserInfo->I1;
|
|
|
|
TRACE(KDC, KdcGetUserKeys, DEB_FUNCTION);
|
|
|
|
|
|
//
|
|
// First get any primary credentials
|
|
//
|
|
|
|
Status = SamIRetrievePrimaryCredentials(
|
|
UserHandle,
|
|
&GlobalKerberosName,
|
|
(PVOID *) &StoredCreds,
|
|
&CredentialSize
|
|
);
|
|
|
|
//
|
|
// if there is not value, it's O.K we will default to using
|
|
// Builtin credentials
|
|
//
|
|
|
|
if (STATUS_DS_NO_ATTRIBUTE_OR_VALUE==Status)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "Failed to retrieve primary credentials: 0x%x\n",Status));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We need to unmarshall these creds from the DS
|
|
// They'll be stored in 32 bit format, but we've
|
|
// got to put them into 64 bit format.
|
|
// KerbUnpack32BitStoredCredential()
|
|
//
|
|
#ifdef _WIN64
|
|
|
|
Status = KdcUnpack32BitStoredCredential(
|
|
(PKERB_STORED_CREDENTIAL32) StoredCreds,
|
|
&Cred64,
|
|
&CredentialSize
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "Failed to unpack 32bit stored credential, contact Todds - %x\n", Status));
|
|
DsysAssert(FALSE); // FATAL - If we ever fail above, contact Todds
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (NULL != StoredCreds)
|
|
{
|
|
LocalFree(StoredCreds);
|
|
StoredCreds = Cred64;
|
|
UnMarshalledCreds = TRUE; // diff't allocator
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// First compute the current passwords
|
|
//
|
|
|
|
//
|
|
// Figure out the size of the stored credentials
|
|
//
|
|
if ((UserAll->UserAccountControl & USER_USE_DES_KEY_ONLY) != 0)
|
|
{
|
|
UseBuiltins = FALSE;
|
|
}
|
|
|
|
|
|
if ((StoredCreds != NULL) &&
|
|
(CredentialSize > sizeof(KERB_STORED_CREDENTIAL) &&
|
|
(StoredCreds->Revision == KERB_PRIMARY_CRED_REVISION) &&
|
|
(CredentialSize > (sizeof(KERB_STORED_CREDENTIAL)
|
|
- (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA))
|
|
+ StoredCreds->CredentialCount * sizeof(KERB_KEY_DATA)
|
|
))) &&
|
|
(StoredCreds->DefaultSalt.Length + (ULONG_PTR) StoredCreds->DefaultSalt.Buffer <= CredentialSize ))
|
|
{
|
|
UseStoredCreds = TRUE;
|
|
Flags = StoredCreds->Flags;
|
|
|
|
NewCredentialSize += StoredCreds->DefaultSalt.Length;
|
|
|
|
for (Index = 0; Index < StoredCreds->CredentialCount ; Index++ )
|
|
{
|
|
//
|
|
// Validat the offsets
|
|
//
|
|
|
|
if ((StoredCreds->Credentials[Index].Key.keyvalue.length +
|
|
(ULONG_PTR) StoredCreds->Credentials[Index].Key.keyvalue.value <= CredentialSize )
|
|
&&
|
|
(StoredCreds->Credentials[Index].Salt.Length +
|
|
(ULONG_PTR) StoredCreds->Credentials[Index].Salt.Buffer <= CredentialSize ))
|
|
|
|
{
|
|
NewCredentialCount++;
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) +
|
|
StoredCreds->Credentials[Index].Key.keyvalue.length +
|
|
StoredCreds->Credentials[Index].Salt.Length;
|
|
}
|
|
else
|
|
{
|
|
LPWSTR Buff = NULL;
|
|
|
|
DebugLog((DEB_ERROR,"Corrupt credentials for user %wZ\n",
|
|
&UserAll->UserName ));
|
|
|
|
DsysAssert(FALSE);
|
|
|
|
Buff = KerbBuildNullTerminatedString(&UserAll->UserName);
|
|
if (NULL == Buff)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_CORRUPT_CREDENTIALS,
|
|
0, // no raw data
|
|
NULL, // no raw data
|
|
1, // number of strings
|
|
Buff
|
|
);
|
|
|
|
UseStoredCreds = FALSE;
|
|
NewCredentialCount = 0;
|
|
NewCredentialSize = 0;
|
|
|
|
if (NULL != Buff)
|
|
{
|
|
MIDL_user_free(Buff);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the password length is the size of the OWF password, use it as the
|
|
// key. Otherwise hash it. This is for the case where not password is
|
|
// set.
|
|
//
|
|
|
|
if (UseBuiltins)
|
|
{
|
|
//
|
|
// Add a key for RC4_HMAC_NT
|
|
//
|
|
|
|
if (UserAll->NtPasswordPresent)
|
|
{
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
|
|
}
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
|
|
//
|
|
// Add a key for RC4_HMAC_OLD & MD4_RC4
|
|
if (UserAll->NtPasswordPresent)
|
|
{
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// if there is no password, treat it as blank
|
|
//
|
|
|
|
if (!(UserAll->LmPasswordPresent || UserAll->NtPasswordPresent))
|
|
{
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
}
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
if (!(UserAll->LmPasswordPresent || UserAll->NtPasswordPresent))
|
|
{
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Add a key for the null crypto system
|
|
//
|
|
|
|
Status = CDLocateCSystem(
|
|
KERB_ETYPE_NULL,
|
|
&NullCryptoSystem
|
|
);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + NullCryptoSystem->KeySize;
|
|
NewCredentialCount++;
|
|
}
|
|
|
|
//
|
|
// Add the space for the base structure
|
|
//
|
|
|
|
NewCredentialSize += sizeof(KERB_STORED_CREDENTIAL) - (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA));
|
|
|
|
//
|
|
// Allocate space for the credentials and start filling them in
|
|
//
|
|
|
|
Keys = (PKERB_STORED_CREDENTIAL) MIDL_user_allocate(NewCredentialSize);
|
|
if (Keys == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
RtlZeroMemory(
|
|
Keys,
|
|
NewCredentialSize
|
|
);
|
|
|
|
Offset = sizeof(KERB_STORED_CREDENTIAL) -
|
|
(ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA)) +
|
|
NewCredentialCount * sizeof(KERB_KEY_DATA);
|
|
|
|
Base = (PUCHAR) Keys;
|
|
|
|
Keys->CredentialCount = (USHORT) NewCredentialCount;
|
|
Keys->OldCredentialCount = 0;
|
|
Keys->Revision = KERB_PRIMARY_CRED_REVISION;
|
|
Keys->Flags = Flags;
|
|
|
|
//
|
|
// Add the credentials built from the OWF passwords first.
|
|
//
|
|
|
|
if (UseBuiltins)
|
|
{
|
|
//
|
|
// Create the key for RC4_HMAC_NT
|
|
//
|
|
|
|
if (UserAll->NtPasswordPresent)
|
|
{
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
UserAll->NtPassword.Buffer,
|
|
UserAll->NtPassword.Length
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
KERB_ETYPE_RC4_HMAC_NT
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Offset += UserAll->NtPassword.Length;
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
CredentialIndex++;
|
|
}
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
//
|
|
// Create the key for RC4_HMAC_OLD
|
|
//
|
|
|
|
if (UserAll->NtPasswordPresent)
|
|
{
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
UserAll->NtPassword.Buffer,
|
|
UserAll->NtPassword.Length
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
(ULONG) KERB_ETYPE_RC4_HMAC_OLD
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
Offset += UserAll->NtPassword.Length;
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
CredentialIndex++;
|
|
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
UserAll->NtPassword.Buffer,
|
|
UserAll->NtPassword.Length
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
(ULONG) KERB_ETYPE_RC4_MD4
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
Offset += UserAll->NtPassword.Length;
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
CredentialIndex++;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If no passwords were present, add the null password
|
|
//
|
|
|
|
if (!(UserAll->LmPasswordPresent || UserAll->NtPasswordPresent))
|
|
{
|
|
KERB_ENCRYPTION_KEY TempKey;
|
|
UNICODE_STRING NullString;
|
|
RtlInitUnicodeString(
|
|
&NullString,
|
|
NULL
|
|
);
|
|
KerbErr = KerbHashPassword(
|
|
&NullString,
|
|
KERB_ETYPE_RC4_HMAC_NT,
|
|
&TempKey
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Keys->Credentials[CredentialIndex].Key = TempKey;
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base + Offset;
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
TempKey.keyvalue.value,
|
|
TempKey.keyvalue.length
|
|
);
|
|
|
|
Offset += TempKey.keyvalue.length;
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
CredentialIndex++;
|
|
KerbFreeKey(&TempKey);
|
|
|
|
}
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
if (!(UserAll->LmPasswordPresent || UserAll->NtPasswordPresent))
|
|
{
|
|
KERB_ENCRYPTION_KEY TempKey;
|
|
UNICODE_STRING NullString;
|
|
RtlInitUnicodeString(
|
|
&NullString,
|
|
NULL
|
|
);
|
|
KerbErr = KerbHashPassword(
|
|
&NullString,
|
|
(ULONG) KERB_ETYPE_RC4_HMAC_OLD,
|
|
&TempKey
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Keys->Credentials[CredentialIndex].Key = TempKey;
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base + Offset;
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
TempKey.keyvalue.value,
|
|
TempKey.keyvalue.length
|
|
);
|
|
|
|
Offset += TempKey.keyvalue.length;
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
CredentialIndex++;
|
|
KerbFreeKey(&TempKey);
|
|
|
|
KerbErr = KerbHashPassword(
|
|
&NullString,
|
|
(ULONG) KERB_ETYPE_RC4_MD4,
|
|
&TempKey
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Keys->Credentials[CredentialIndex].Key = TempKey;
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base + Offset;
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
TempKey.keyvalue.value,
|
|
TempKey.keyvalue.length
|
|
);
|
|
|
|
Offset += TempKey.keyvalue.length;
|
|
CredentialIndex++;
|
|
KerbFreeKey(&TempKey);
|
|
}
|
|
|
|
#endif
|
|
}
|
|
//
|
|
// Add the null crypto system
|
|
//
|
|
|
|
if (NullCryptoSystem != NULL)
|
|
{
|
|
UNICODE_STRING NullString;
|
|
RtlInitUnicodeString(
|
|
&NullString,
|
|
NULL
|
|
);
|
|
|
|
Status = NullCryptoSystem->HashString(
|
|
&NullString,
|
|
Base+Offset
|
|
);
|
|
DsysAssert(NT_SUCCESS(Status));
|
|
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base+Offset;
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.length = NullCryptoSystem->KeySize;
|
|
Keys->Credentials[CredentialIndex].Key.keytype = KERB_ETYPE_NULL;
|
|
|
|
Offset += NullCryptoSystem->KeySize;
|
|
|
|
CredentialIndex++;
|
|
}
|
|
|
|
//
|
|
// Now add the stored passwords
|
|
//
|
|
|
|
if (UseStoredCreds)
|
|
{
|
|
//
|
|
// Copy the default salt
|
|
//
|
|
|
|
if (StoredCreds->DefaultSalt.Buffer != NULL)
|
|
{
|
|
Keys->DefaultSalt.Buffer = (LPWSTR) (Base+Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base + Offset,
|
|
(PBYTE) StoredCreds->DefaultSalt.Buffer + (ULONG_PTR) StoredCreds,
|
|
StoredCreds->DefaultSalt.Length
|
|
);
|
|
Offset += StoredCreds->DefaultSalt.Length;
|
|
Keys->DefaultSalt.Length = Keys->DefaultSalt.MaximumLength = StoredCreds->DefaultSalt.Length;
|
|
}
|
|
|
|
for (Index = 0; Index < StoredCreds->CredentialCount ; Index++ )
|
|
{
|
|
//
|
|
// Copy the key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex] = StoredCreds->Credentials[Index];
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base+Offset;
|
|
RtlCopyMemory(
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value,
|
|
StoredCreds->Credentials[Index].Key.keyvalue.value + (ULONG_PTR) StoredCreds,
|
|
StoredCreds->Credentials[Index].Key.keyvalue.length
|
|
);
|
|
Offset += StoredCreds->Credentials[Index].Key.keyvalue.length;
|
|
|
|
//
|
|
// Copy the salt
|
|
//
|
|
|
|
if (StoredCreds->Credentials[Index].Salt.Buffer != NULL)
|
|
{
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base+Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base + Offset,
|
|
(PBYTE) StoredCreds->Credentials[Index].Salt.Buffer + (ULONG_PTR) StoredCreds,
|
|
StoredCreds->Credentials[Index].Salt.Length
|
|
);
|
|
Offset += StoredCreds->Credentials[Index].Salt.Length;
|
|
Keys->Credentials[CredentialIndex].Salt.Length =
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength =
|
|
StoredCreds->Credentials[Index].Salt.Length;
|
|
}
|
|
|
|
CredentialIndex++;
|
|
}
|
|
}
|
|
|
|
DsysAssert(CredentialIndex == NewCredentialCount);
|
|
DsysAssert(Offset == NewCredentialSize);
|
|
*Passwords = Keys;
|
|
Keys = NULL;
|
|
|
|
|
|
//
|
|
// Now compute the old passwords
|
|
//
|
|
|
|
//
|
|
// Figure out the size of the stored credentials
|
|
//
|
|
|
|
NewCredentialCount = 0;
|
|
NewCredentialSize = 0;
|
|
CredentialIndex = 0;
|
|
|
|
if ((StoredCreds != NULL) &&
|
|
(CredentialSize > sizeof(KERB_STORED_CREDENTIAL) &&
|
|
(StoredCreds->Revision == KERB_PRIMARY_CRED_REVISION) &&
|
|
(CredentialSize > (sizeof(KERB_STORED_CREDENTIAL)
|
|
- (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA))
|
|
+ (StoredCreds->OldCredentialCount + StoredCreds->CredentialCount) * sizeof(KERB_KEY_DATA)
|
|
))))
|
|
{
|
|
UseStoredCreds = TRUE;
|
|
Flags = StoredCreds->Flags;
|
|
|
|
for (Index = 0; Index < StoredCreds->OldCredentialCount ; Index++ )
|
|
{
|
|
if (StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.length +
|
|
(ULONG_PTR) StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.value <=
|
|
CredentialSize )
|
|
{
|
|
NewCredentialCount++;
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) +
|
|
StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.length;
|
|
}
|
|
else
|
|
{
|
|
LPWSTR Buff = NULL;
|
|
|
|
DebugLog((DEB_ERROR,"Corrupt credentials for user %wZ\n",
|
|
&UserAll->UserName ));
|
|
|
|
DsysAssert(FALSE);
|
|
|
|
Buff = KerbBuildNullTerminatedString(&UserAll->UserName);
|
|
if (NULL == Buff)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_CORRUPT_CREDENTIALS,
|
|
0, // no raw data
|
|
NULL, // no raw data
|
|
1, // number of strings
|
|
Buff
|
|
);
|
|
|
|
if (NULL != Buff)
|
|
{
|
|
MIDL_user_free(Buff);
|
|
}
|
|
|
|
UseStoredCreds = FALSE;
|
|
NewCredentialCount = 0;
|
|
NewCredentialSize = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the password length is the size of the OWF password, use it as the
|
|
// key. Otherwise hash it. This is for the case where not password is
|
|
// set.
|
|
//
|
|
// The Password histroy is appended to a SAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE structure.
|
|
// After this structure, there is blocks of 16 bytes for each password. The first password is
|
|
// the current password. The following blocks (if any) is the history of passwords.
|
|
//
|
|
// The index looks strange (PENCRYPTED_NT_OWF_PASSWORD) (PrivateData + 1) + 1
|
|
// the (PrivateData + 1) points to after the SAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE header
|
|
// and the final + 1 skips over the current password in the password history
|
|
|
|
if (UseBuiltins)
|
|
{
|
|
PSAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE PrivateData;
|
|
|
|
if (UserAll->PrivateData.Length >= sizeof(SAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE))
|
|
{
|
|
PrivateData= (PSAMI_PRIVATE_DATA_PASSWORD_RELATIVE_TYPE) UserAll->PrivateData.Buffer;
|
|
if (PrivateData->DataType == SamPrivateDataPassword)
|
|
{
|
|
//
|
|
// The old password is the 2nd entry
|
|
//
|
|
if (PrivateData->NtPasswordHistory.Length >= 2* sizeof(ENCRYPTED_NT_OWF_PASSWORD))
|
|
{
|
|
//
|
|
// Decrypt the old password with the RID. The history starts
|
|
// at the first byte after the structure.
|
|
//
|
|
|
|
Status = RtlDecryptNtOwfPwdWithIndex(
|
|
(PENCRYPTED_NT_OWF_PASSWORD) (PrivateData + 1) + 1,
|
|
(PLONG)&UserAll->UserId,
|
|
&OldPasswordData
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to decrypt old password: 0x%x\n",Status));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
OldNtPassword = &OldPasswordData ;
|
|
|
|
// Now check for the second previous password - this will be the third password in history
|
|
if (PrivateData->NtPasswordHistory.Length >= 3 * sizeof(ENCRYPTED_NT_OWF_PASSWORD))
|
|
{
|
|
//
|
|
// Decrypt the old password with the RID. The history starts
|
|
// at the first byte after the structure.
|
|
//
|
|
|
|
Status = RtlDecryptNtOwfPwdWithIndex(
|
|
(PENCRYPTED_NT_OWF_PASSWORD) (PrivateData + 1) + 2,
|
|
(PLONG)&UserAll->UserId,
|
|
&OldPasswordDataSecond
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to decrypt second previous password: 0x%x\n",Status));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
OldNtPasswordSecond = &OldPasswordDataSecond;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (OldNtPassword != NULL)
|
|
{
|
|
//
|
|
// Add an RC4_HMAC_NT key
|
|
//
|
|
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
|
|
if (OldNtPasswordSecond != NULL)
|
|
{
|
|
//
|
|
// Add an RC4_HMAC_NT key for the second previous password
|
|
//
|
|
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
NewCredentialCount++;
|
|
}
|
|
|
|
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
|
|
//
|
|
// Add a key for RC4_HMAC_OLD & RC4_MD4
|
|
//
|
|
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
|
|
NewCredentialCount++;
|
|
NewCredentialSize += sizeof(KERB_KEY_DATA) + sizeof(NT_OWF_PASSWORD);
|
|
|
|
NewCredentialCount++;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the space for the base structure
|
|
//
|
|
|
|
NewCredentialSize += sizeof(KERB_STORED_CREDENTIAL) - (ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA));
|
|
|
|
//
|
|
// Allocate space for the credentials and start filling them in
|
|
//
|
|
|
|
Keys = (PKERB_STORED_CREDENTIAL) MIDL_user_allocate(NewCredentialSize);
|
|
if (Keys == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
RtlZeroMemory(
|
|
Keys,
|
|
NewCredentialSize
|
|
);
|
|
|
|
Offset = sizeof(KERB_STORED_CREDENTIAL) -
|
|
(ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA)) +
|
|
NewCredentialCount * sizeof(KERB_KEY_DATA);
|
|
|
|
Base = (PUCHAR) Keys;
|
|
|
|
Keys->CredentialCount = (USHORT) NewCredentialCount;
|
|
Keys->OldCredentialCount = 0;
|
|
Keys->Revision = KERB_PRIMARY_CRED_REVISION;
|
|
Keys->Flags = Flags;
|
|
|
|
//
|
|
// Add the credentials built from the OWF passwords first. We don't
|
|
// include a blank password or the null crypt system because they
|
|
// were present in the normal password
|
|
//
|
|
|
|
if (UseBuiltins)
|
|
{
|
|
//
|
|
// Create the key for RC4_HMAC_NT
|
|
//
|
|
|
|
if (OldNtPassword != NULL)
|
|
{
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
OldNtPassword,
|
|
NT_OWF_PASSWORD_LENGTH
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex++].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
KERB_ETYPE_RC4_HMAC_NT
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We don't know any other way to get the old password length. If
|
|
// it was present, it must be sizeof(NT_OWF_PASSWORD)
|
|
|
|
Offset += sizeof(NT_OWF_PASSWORD);
|
|
|
|
|
|
if (OldNtPasswordSecond != NULL)
|
|
{
|
|
|
|
//
|
|
// Set an empty salt for this key for the second previous password
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
OldNtPasswordSecond,
|
|
NT_OWF_PASSWORD_LENGTH
|
|
);
|
|
|
|
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex++].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
KERB_ETYPE_RC4_HMAC_NT
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We don't know any other way to get the old password length. If
|
|
// it was present, it must be sizeof(NT_OWF_PASSWORD)
|
|
|
|
Offset += sizeof(NT_OWF_PASSWORD);
|
|
}
|
|
|
|
#ifndef DONT_SUPPORT_OLD_TYPES_KDC
|
|
//
|
|
// Create the key for RC4_HMAC_OLD
|
|
//
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
OldNtPassword,
|
|
NT_OWF_PASSWORD_LENGTH
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex++].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
(ULONG) KERB_ETYPE_RC4_HMAC_OLD
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We don't know any other way to get the old password length. If
|
|
// it was present, it must be sizeof(NT_OWF_PASSWORD)
|
|
|
|
Offset += sizeof(NT_OWF_PASSWORD);
|
|
|
|
//
|
|
// Create the key for RC4_HMAC_OLD
|
|
//
|
|
|
|
//
|
|
// Set an empty salt for this key
|
|
//
|
|
|
|
Keys->Credentials[CredentialIndex].Salt.Length = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.MaximumLength = 0;
|
|
Keys->Credentials[CredentialIndex].Salt.Buffer = (LPWSTR) (Base + Offset);
|
|
|
|
RtlCopyMemory(
|
|
Base+Offset,
|
|
OldNtPassword,
|
|
NT_OWF_PASSWORD_LENGTH
|
|
);
|
|
|
|
KerbErr = KerbCreateKeyFromBuffer(
|
|
&Keys->Credentials[CredentialIndex++].Key,
|
|
Base+Offset,
|
|
UserAll->NtPassword.Length,
|
|
(ULONG) KERB_ETYPE_RC4_MD4
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We don't know any other way to get the old password length. If
|
|
// it was present, it must be sizeof(NT_OWF_PASSWORD)
|
|
|
|
Offset += sizeof(NT_OWF_PASSWORD);
|
|
|
|
#endif
|
|
|
|
}
|
|
}
|
|
//
|
|
// Now add the stored passwords
|
|
//
|
|
|
|
if (UseStoredCreds)
|
|
{
|
|
for (Index = 0; Index < StoredCreds->OldCredentialCount ; Index++ )
|
|
{
|
|
Keys->Credentials[CredentialIndex] = StoredCreds->Credentials[StoredCreds->CredentialCount + Index];
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value = Base+Offset;
|
|
RtlCopyMemory(
|
|
Keys->Credentials[CredentialIndex].Key.keyvalue.value,
|
|
StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.value + (ULONG_PTR) StoredCreds,
|
|
StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.length
|
|
);
|
|
Offset += StoredCreds->Credentials[StoredCreds->CredentialCount + Index].Key.keyvalue.length;
|
|
|
|
//
|
|
// Note - don't use salt here.
|
|
//
|
|
|
|
CredentialIndex++;
|
|
}
|
|
}
|
|
|
|
DsysAssert(CredentialIndex == NewCredentialCount);
|
|
DsysAssert(Offset == NewCredentialSize);
|
|
*OldPasswords = Keys;
|
|
Keys = NULL;
|
|
|
|
KerbErr = KDC_ERR_NONE;
|
|
|
|
Cleanup:
|
|
if (StoredCreds != NULL)
|
|
{
|
|
if (!UnMarshalledCreds)
|
|
{
|
|
LocalFree(StoredCreds);
|
|
}
|
|
else
|
|
{
|
|
MIDL_user_free(Cred64);
|
|
}
|
|
}
|
|
|
|
if (Keys != NULL)
|
|
{
|
|
MIDL_user_free(Keys);
|
|
}
|
|
|
|
return (KerbErr);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbDuplicateCredentials
|
|
//
|
|
// Synopsis: Copies a set of credentials (passwords)
|
|
//
|
|
// Effects: allocates output with MIDL_user_allocate
|
|
//
|
|
// Arguments: NewCredentials - recevies new set of credentials
|
|
// OldCredentials - contains credentials to copy
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
KERBERR
|
|
KdcDuplicateCredentials(
|
|
OUT PKERB_STORED_CREDENTIAL * NewCredentials,
|
|
OUT PULONG ReturnCredentialSize,
|
|
IN PKERB_STORED_CREDENTIAL OldCredentials,
|
|
IN BOOLEAN MarshallKeys
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
PKERB_STORED_CREDENTIAL Credential = NULL;
|
|
ULONG CredentialSize;
|
|
USHORT Index;
|
|
PBYTE Where;
|
|
LONG_PTR Offset;
|
|
|
|
TRACE(KDC, KdcDuplicateCredentials, DEB_FUNCTION);
|
|
|
|
//
|
|
// If there were no credentials, so be it. We can live with that.
|
|
//
|
|
|
|
if (OldCredentials == NULL)
|
|
{
|
|
*NewCredentials = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Calculate the size of the new credentials by summing the size of
|
|
// the base structure plus the keys
|
|
//
|
|
|
|
CredentialSize = sizeof(KERB_STORED_CREDENTIAL)
|
|
- ANYSIZE_ARRAY * sizeof(KERB_KEY_DATA)
|
|
+ OldCredentials->CredentialCount * sizeof(KERB_KEY_DATA)
|
|
+ OldCredentials->DefaultSalt.Length;
|
|
for ( Index = 0;
|
|
Index < OldCredentials->CredentialCount + OldCredentials->OldCredentialCount ;
|
|
Index++ )
|
|
{
|
|
CredentialSize += OldCredentials->Credentials[Index].Key.keyvalue.length +
|
|
OldCredentials->Credentials[Index].Salt.Length;
|
|
}
|
|
|
|
//
|
|
// Allocate the new credential and copy over the old credentials
|
|
//
|
|
|
|
|
|
Credential = (PKERB_STORED_CREDENTIAL) MIDL_user_allocate(CredentialSize);
|
|
if (Credential == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Credential->Revision = OldCredentials->Revision;
|
|
Credential->Flags = OldCredentials->Flags;
|
|
Credential->CredentialCount = OldCredentials->CredentialCount;
|
|
Credential->OldCredentialCount = OldCredentials->OldCredentialCount;
|
|
|
|
//
|
|
// Set the offset for data to be after the last array element
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
&Credential->Credentials[0],
|
|
&OldCredentials->Credentials[0],
|
|
OldCredentials->CredentialCount * sizeof(KERB_KEY_DATA)
|
|
);
|
|
|
|
Where = (PBYTE) &Credential->Credentials[Credential->CredentialCount];
|
|
|
|
if (MarshallKeys)
|
|
{
|
|
Offset = (LONG_PTR) Credential;
|
|
}
|
|
else
|
|
{
|
|
Offset = 0;
|
|
}
|
|
|
|
Credential->DefaultSalt = OldCredentials->DefaultSalt;
|
|
if (Credential->DefaultSalt.Buffer != NULL)
|
|
{
|
|
Credential->DefaultSalt.Buffer = (LPWSTR) (Where - Offset);
|
|
RtlCopyMemory(
|
|
Where,
|
|
OldCredentials->DefaultSalt.Buffer,
|
|
Credential->DefaultSalt.Length
|
|
);
|
|
Where += Credential->DefaultSalt.Length;
|
|
}
|
|
|
|
for ( Index = 0;
|
|
Index < OldCredentials->CredentialCount + OldCredentials->OldCredentialCount ;
|
|
Index++ )
|
|
{
|
|
Credential->Credentials[Index] = OldCredentials->Credentials[Index];
|
|
Credential->Credentials[Index].Key.keyvalue.value = (Where - Offset);
|
|
RtlCopyMemory(
|
|
Where,
|
|
OldCredentials->Credentials[Index].Key.keyvalue.value,
|
|
OldCredentials->Credentials[Index].Key.keyvalue.length
|
|
);
|
|
Where += OldCredentials->Credentials[Index].Key.keyvalue.length;
|
|
|
|
if (Credential->Credentials[Index].Salt.Buffer != NULL)
|
|
{
|
|
Credential->Credentials[Index].Salt.Buffer = (LPWSTR) (Where - Offset);
|
|
|
|
RtlCopyMemory(
|
|
Where,
|
|
OldCredentials->Credentials[Index].Salt.Buffer,
|
|
OldCredentials->Credentials[Index].Salt.Length
|
|
);
|
|
Where += OldCredentials->Credentials[Index].Salt.Length;
|
|
}
|
|
}
|
|
DsysAssert(Where - (PUCHAR) Credential == (LONG) CredentialSize);
|
|
|
|
*NewCredentials = Credential;
|
|
Credential = NULL;
|
|
*ReturnCredentialSize = CredentialSize;
|
|
|
|
Cleanup:
|
|
if (Credential != NULL)
|
|
{
|
|
MIDL_user_free(Credential);
|
|
}
|
|
return(KerbErr);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcGetTicketInfo
|
|
//
|
|
// Synopsis: Gets the info needed to build a ticket for a principal
|
|
// using name of the principal.
|
|
//
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: Name -- (in) Normalized of principal
|
|
// LookupFlags - Flags for SamIGetUserLogonInformation
|
|
// PrincipalName - (in) non-normalized principal name
|
|
// Realm - (in) client realm, to be used when mapping principals
|
|
// from another domain onto accounts in this one.
|
|
// TicketInfo -- (out) Ticket info.
|
|
// pExtendedError -- (out) Extended error
|
|
// UserHandle - receives handle to the user
|
|
// WhichFields - optionally specifies additional fields to fetch for RetUserInfo
|
|
// ExtendedFields - optionally specifies extended fields to fetch for RetUserInfo
|
|
// RetUserInfo - Optionally receives the user all info structure
|
|
// GroupMembership - Optionally receives the user's group membership
|
|
//
|
|
// Returns: KerbErr
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: 10-Nov-93 WadeR Created
|
|
// 22-Mar-95 SuChang Modified to use RIDs
|
|
//
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcGetTicketInfo(
|
|
IN PUNICODE_STRING GenericUserName,
|
|
IN ULONG LookupFlags,
|
|
IN BOOLEAN bRestrictUserAccounts,
|
|
IN OPTIONAL PKERB_INTERNAL_NAME PrincipalName,
|
|
IN OPTIONAL PKERB_REALM Realm,
|
|
OUT PKDC_TICKET_INFO TicketInfo,
|
|
OUT PKERB_EXT_ERROR pExtendedError,
|
|
OUT OPTIONAL SAMPR_HANDLE * UserHandle,
|
|
IN OPTIONAL ULONG WhichFields,
|
|
IN OPTIONAL ULONG ExtendedFields,
|
|
OUT OPTIONAL PUSER_INTERNAL6_INFORMATION * RetUserInfo,
|
|
OUT OPTIONAL PSID_AND_ATTRIBUTES_LIST GroupMembership
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_NOT_FOUND;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
SID_AND_ATTRIBUTES_LIST LocalMembership;
|
|
SAMPR_HANDLE LocalUserHandle = NULL;
|
|
PUSER_INTERNAL6_INFORMATION UserInfo = NULL;
|
|
PUSER_ALL_INFORMATION UserAll;
|
|
GUID testGuid = {0};
|
|
UNICODE_STRING TUserName = {0};
|
|
PUNICODE_STRING UserName = NULL;
|
|
UNICODE_STRING AlternateName = {0};
|
|
UNICODE_STRING TempString = {0};
|
|
BOOL IsValidGuid = FALSE;
|
|
|
|
TRACE(KDC, GetTicketInfo, DEB_FUNCTION);
|
|
|
|
D_DebugLog((DEB_T_PAPI, "KdcGetTicketInfo [entering] bRestrictUserAccounts %s, WhichFields %#x, ExtendedFields %#x, GenericUserName %wZ, LookupFlags %#x, PrincipalName ",
|
|
bRestrictUserAccounts ? "true" : "false", WhichFields, ExtendedFields, GenericUserName, LookupFlags));
|
|
D_KerbPrintKdcName((DEB_T_PAPI, PrincipalName));
|
|
|
|
//
|
|
// Add the fields we are going to require locally to the WhichFields parameter
|
|
//
|
|
|
|
WhichFields |=
|
|
USER_ALL_KDC_GET_USER_KEYS |
|
|
USER_ALL_PASSWORDMUSTCHANGE |
|
|
USER_ALL_USERACCOUNTCONTROL |
|
|
USER_ALL_USERID |
|
|
USER_ALL_USERNAME;
|
|
|
|
ExtendedFields |=
|
|
USER_EXTENDED_FIELD_KVNO | // passwd version raid #306501
|
|
USER_EXTENDED_FIELD_LOCKOUT_THRESHOLD; // for individual account lockout rather than domain wide
|
|
|
|
TUserName = *GenericUserName;
|
|
UserName = &TUserName;
|
|
|
|
RtlZeroMemory(TicketInfo, sizeof(KDC_TICKET_INFO));
|
|
RtlZeroMemory(&LocalMembership, sizeof(SID_AND_ATTRIBUTES_LIST));
|
|
|
|
if (ARGUMENT_PRESENT( RetUserInfo ))
|
|
{
|
|
*RetUserInfo = NULL;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(GroupMembership))
|
|
{
|
|
LookupFlags |= SAM_NO_MEMBERSHIPS;
|
|
}
|
|
|
|
//
|
|
// If this is the krbtgt account, use the cached version
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(UserHandle) &&
|
|
!ARGUMENT_PRESENT(RetUserInfo) &&
|
|
!ARGUMENT_PRESENT(GroupMembership) &&
|
|
(KdcState == Running) &&
|
|
RtlEqualUnicodeString(
|
|
SecData.KdcServiceName(),
|
|
UserName,
|
|
TRUE // case insensitive
|
|
))
|
|
{
|
|
//
|
|
// Duplicate the cached copy of the KRBTGT information
|
|
//
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "KdcGetTicketInfo using cached ticket info for krbtgt account\n"));
|
|
|
|
KerbErr = SecData.GetKrbtgtTicketInfo(
|
|
TicketInfo
|
|
);
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we cracked this name at a dc, and it's the same domain, we will
|
|
// not be able to generate a referral ticket. So, we need to be able
|
|
// to open the sam locally. Further more, crack name and sam lookups
|
|
// are much faster with guids (even though we have to do the string
|
|
// to guid operation.
|
|
//
|
|
|
|
if (LookupFlags & SAM_OPEN_BY_GUID)
|
|
{
|
|
if (IsValidGuid = IsStringGuid(UserName->Buffer, &testGuid))
|
|
{
|
|
UserName->Buffer = (LPWSTR)&testGuid;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The caller may provide an empty user name so as to force the lookup
|
|
// using the AltSecId. This is used when mapping names from an MIT realm
|
|
//
|
|
|
|
if (UserName->Length > 0)
|
|
{
|
|
//
|
|
// Open the user account
|
|
//
|
|
|
|
#if DBG
|
|
|
|
if (IsValidGuid)
|
|
{
|
|
D_KerbPrintGuid(DEB_TRACE, "KdcGetTicketInfo account is Guid ", &testGuid);
|
|
}
|
|
|
|
#endif
|
|
|
|
Status = SamIGetUserLogonInformation2(
|
|
GlobalAccountDomainHandle,
|
|
LookupFlags,
|
|
UserName,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
&UserInfo,
|
|
&LocalMembership,
|
|
&LocalUserHandle
|
|
);
|
|
|
|
//
|
|
// WASBUG: For now, if we couldn't find the account try again
|
|
// with a '$' at the end (if there wasn't one already)
|
|
//
|
|
|
|
if (((Status == STATUS_NOT_FOUND) ||
|
|
(Status == STATUS_NO_SUCH_USER)) &&
|
|
(!IsValidGuid) &&
|
|
((LookupFlags & ~SAM_NO_MEMBERSHIPS) == 0) &&
|
|
(UserName->Length >= sizeof(WCHAR)) &&
|
|
(UserName->Buffer[UserName->Length/sizeof(WCHAR)-1] != L'$'))
|
|
{
|
|
Status = KerbDuplicateString(
|
|
&TempString,
|
|
UserName
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
DsysAssert(TempString.MaximumLength >= TempString.Length + sizeof(WCHAR));
|
|
TempString.Buffer[TempString.Length/sizeof(WCHAR)] = L'$';
|
|
TempString.Length += sizeof(WCHAR);
|
|
|
|
D_DebugLog((DEB_TRACE, "Account not found ,trying machine account %wZ\n",
|
|
&TempString ));
|
|
|
|
Status = SamIGetUserLogonInformation2(
|
|
GlobalAccountDomainHandle,
|
|
LookupFlags,
|
|
&TempString,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
&UserInfo,
|
|
&LocalMembership,
|
|
&LocalUserHandle
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we still can't find the account, try the altsecid using
|
|
// the supplied principal name.
|
|
//
|
|
|
|
if (((Status == STATUS_NOT_FOUND) || (Status == STATUS_NO_SUCH_USER)) &&
|
|
ARGUMENT_PRESENT(PrincipalName) )
|
|
{
|
|
KerbErr = KerbBuildAltSecId(
|
|
&AlternateName,
|
|
PrincipalName,
|
|
Realm,
|
|
NULL // no unicode realm name
|
|
);
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_TRACE,"User account not found, trying alternate id: %wZ\n",&AlternateName ));
|
|
LookupFlags |= SAM_OPEN_BY_ALTERNATE_ID,
|
|
|
|
Status = SamIGetUserLogonInformation2(
|
|
GlobalAccountDomainHandle,
|
|
LookupFlags,
|
|
&AlternateName,
|
|
WhichFields,
|
|
ExtendedFields,
|
|
&UserInfo,
|
|
&LocalMembership,
|
|
&LocalUserHandle
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_T_TICKETS,"Could not open User %wZ: 0x%x\n",
|
|
UserName,
|
|
Status
|
|
));
|
|
|
|
if ((Status == STATUS_NO_SUCH_USER) || (Status == STATUS_NOT_FOUND) ||
|
|
(Status == STATUS_OBJECT_NAME_NOT_FOUND))
|
|
{
|
|
KerbErr = KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
}
|
|
else if (Status == STATUS_NO_LOGON_SERVERS)
|
|
{
|
|
//If there's no GC, this sam call returns STATUS_NO_LOGON_SERVERS
|
|
//and we should return this to the client. see bug 226073
|
|
FILL_EXT_ERROR(pExtendedError, Status,FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_SVC_UNAVAILABLE;
|
|
}
|
|
else if (Status != STATUS_INVALID_SERVER_STATE)
|
|
{
|
|
WCHAR LookupType[10];
|
|
WCHAR * LookupTypeStr;
|
|
WCHAR AccountName[MAX_PATH+1];
|
|
PUNICODE_STRING LookupName = NULL;
|
|
|
|
|
|
if (AlternateName.Buffer != NULL)
|
|
{
|
|
LookupName = &AlternateName;
|
|
}
|
|
else if (TempString.Buffer != NULL)
|
|
{
|
|
LookupName = &TempString;
|
|
}
|
|
else
|
|
{
|
|
LookupName = UserName;
|
|
}
|
|
if (LookupName->Length < MAX_PATH * sizeof(WCHAR))
|
|
{
|
|
RtlCopyMemory(
|
|
AccountName,
|
|
LookupName->Buffer,
|
|
LookupName->Length
|
|
);
|
|
AccountName[LookupName->Length/sizeof(WCHAR)] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
RtlCopyMemory(
|
|
AccountName,
|
|
LookupName->Buffer,
|
|
MAX_PATH * sizeof(WCHAR)
|
|
);
|
|
AccountName[MAX_PATH] = L'\0';
|
|
}
|
|
|
|
//
|
|
// Log name collisions separately to provide
|
|
// more information
|
|
//
|
|
|
|
if (Status == STATUS_OBJECT_NAME_COLLISION)
|
|
{
|
|
DS_NAME_FORMAT NameFormat = DS_UNKNOWN_NAME;
|
|
if ((LookupFlags & SAM_OPEN_BY_UPN) != 0)
|
|
{
|
|
NameFormat = DS_USER_PRINCIPAL_NAME;
|
|
}
|
|
else if ((LookupFlags & SAM_OPEN_BY_SPN) != 0)
|
|
{
|
|
NameFormat = DS_SERVICE_PRINCIPAL_NAME;
|
|
}
|
|
|
|
//
|
|
// Potentially deadly error, pass back to caller.
|
|
//
|
|
FILL_EXT_ERROR_EX(pExtendedError, Status,FILENO, __LINE__);
|
|
|
|
if (( NameFormat < sizeof( KdcGlobalNameTypes ) / sizeof( KdcGlobalNameTypes[0] )) &&
|
|
( KdcGlobalNameTypes[NameFormat] != NULL ))
|
|
{
|
|
LookupTypeStr = KdcGlobalNameTypes[NameFormat];
|
|
}
|
|
else
|
|
{
|
|
swprintf(LookupType,L"%d",(ULONG) NameFormat);
|
|
LookupTypeStr = LookupType;
|
|
}
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_NAME_NOT_UNIQUE,
|
|
0,
|
|
NULL,
|
|
2,
|
|
AccountName,
|
|
LookupTypeStr
|
|
);
|
|
|
|
KerbErr = KDC_ERR_PRINCIPAL_NOT_UNIQUE;
|
|
}
|
|
else
|
|
{
|
|
swprintf(LookupType,L"0x%x",LookupFlags);
|
|
|
|
ReportServiceEvent(
|
|
EVENTLOG_ERROR_TYPE,
|
|
KDCEVENT_SAM_CALL_FAILED,
|
|
sizeof(NTSTATUS),
|
|
&Status,
|
|
2,
|
|
AccountName,
|
|
LookupType
|
|
);
|
|
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Potentially deadly error, pass back to caller.
|
|
FILL_EXT_ERROR_EX(pExtendedError, Status,FILENO, __LINE__);
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_T_TICKETS, "KdcGetTicketInfo getting user keys\n"));
|
|
|
|
//
|
|
// if an account does not have at one SPNs registered, it is an user
|
|
// account and enforce user2user
|
|
//
|
|
|
|
if (bRestrictUserAccounts && ((UserInfo->I1.UserAccountControl & USER_MACHINE_ACCOUNT_MASK) == 0))
|
|
{
|
|
if ((ExtendedFields & USER_EXTENDED_FIELD_SPN) == 0)
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcGetTicketInfo can't restrict user accounts if USER_EXTENDED_FIELD_SPN is not requested\n"));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((UserInfo->RegisteredSPNs == NULL) || (UserInfo->RegisteredSPNs->NumSPNs == 0))
|
|
{
|
|
DebugLog((DEB_ERROR, "KdcVerifyKdcRequest must use user2user UserAccountControl %#x, GenericUserName %wZ, PrincipalName: ",
|
|
UserInfo->I1.UserAccountControl, GenericUserName));
|
|
KerbPrintKdcName(DEB_ERROR, PrincipalName);
|
|
KerbErr = KDC_ERR_MUST_USE_USER2USER;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
UserAll = &UserInfo->I1;
|
|
|
|
KerbErr = KdcGetUserKeys(
|
|
LocalUserHandle,
|
|
UserInfo,
|
|
&TicketInfo->Passwords,
|
|
&TicketInfo->OldPasswords,
|
|
pExtendedError
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to get user keys: 0x%x\n",KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!NT_SUCCESS(KerbDuplicateString(
|
|
&TicketInfo->AccountName,
|
|
&UserAll->UserName )))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((UserAll->UserAccountControl & USER_TRUSTED_FOR_DELEGATION) != 0)
|
|
{
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_OK_AS_DELEGATE;
|
|
}
|
|
|
|
if ((UserAll->UserAccountControl & USER_NOT_DELEGATED) == 0)
|
|
{
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_ALLOW_FORWARDABLE | AUTH_REQ_ALLOW_PROXIABLE;
|
|
}
|
|
|
|
if ((UserAll->UserAccountControl & USER_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) != 0)
|
|
{
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_ALLOW_S4U_DELEGATE;
|
|
}
|
|
|
|
TicketInfo->fTicketOpts |= AUTH_REQ_ALLOW_RENEWABLE |
|
|
AUTH_REQ_ALLOW_NOADDRESS |
|
|
AUTH_REQ_ALLOW_ENC_TKT_IN_SKEY |
|
|
AUTH_REQ_ALLOW_VALIDATE;
|
|
//
|
|
// These flags aren't turned on by default:
|
|
// AUTH_REQ_VALIDATE_CLIENT
|
|
//
|
|
|
|
//
|
|
// Mask with the domain policy flags
|
|
//
|
|
|
|
TicketInfo->fTicketOpts &= SecData.KdcFlags();
|
|
|
|
TicketInfo->PasswordExpires = UserAll->PasswordMustChange;
|
|
TicketInfo->PasswordVersion = UserInfo->KeyVersionNumber; // Raid #306501
|
|
TicketInfo->LockoutThreshold = UserInfo->LockoutThreshold; // account lockout
|
|
|
|
TicketInfo->UserAccountControl = UserAll->UserAccountControl;
|
|
TicketInfo->UserId = UserAll->UserId;
|
|
|
|
if (ARGUMENT_PRESENT(RetUserInfo))
|
|
{
|
|
*RetUserInfo = UserInfo;
|
|
UserInfo = NULL;
|
|
}
|
|
if (ARGUMENT_PRESENT(GroupMembership))
|
|
{
|
|
*GroupMembership = LocalMembership;
|
|
RtlZeroMemory(
|
|
&LocalMembership,
|
|
sizeof(SID_AND_ATTRIBUTES_LIST)
|
|
);
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(UserHandle))
|
|
{
|
|
*UserHandle = LocalUserHandle;
|
|
LocalUserHandle = NULL;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
KerbFreeString( &TempString );
|
|
KerbFreeString( &AlternateName );
|
|
|
|
SamIFree_UserInternal6Information( UserInfo );
|
|
|
|
if (LocalUserHandle != NULL)
|
|
{
|
|
SamrCloseHandle(&LocalUserHandle);
|
|
}
|
|
|
|
SamIFreeSidAndAttributesList( &LocalMembership );
|
|
|
|
if ( !KERB_SUCCESS( KerbErr ))
|
|
{
|
|
FreeTicketInfo( TicketInfo );
|
|
RtlZeroMemory(TicketInfo, sizeof(KDC_TICKET_INFO));
|
|
}
|
|
|
|
D_DebugLog((DEB_T_PAPI, "KdcGetTicketInfo returning %#x\n", KerbErr));
|
|
|
|
return KerbErr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FreeTicketInfo
|
|
//
|
|
// Synopsis: Frees a KDC_TICKET_INFO structure
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: 22-Mar-95 SuChang Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
FreeTicketInfo(
|
|
IN PKDC_TICKET_INFO TicketInfo
|
|
)
|
|
{
|
|
TRACE(KDC, FreeTicketInfo, DEB_FUNCTION);
|
|
|
|
if (ARGUMENT_PRESENT(TicketInfo))
|
|
{
|
|
KerbFreeString(
|
|
&TicketInfo->AccountName
|
|
);
|
|
|
|
KerbFreeString(
|
|
&TicketInfo->TrustedForest
|
|
);
|
|
|
|
if (TicketInfo->Passwords != NULL)
|
|
{
|
|
MIDL_user_free(TicketInfo->Passwords);
|
|
TicketInfo->Passwords = NULL;
|
|
}
|
|
if (TicketInfo->OldPasswords != NULL)
|
|
{
|
|
MIDL_user_free(TicketInfo->OldPasswords);
|
|
TicketInfo->OldPasswords = NULL;
|
|
}
|
|
if (TicketInfo->TrustSid != NULL)
|
|
{
|
|
MIDL_user_free(TicketInfo->TrustSid);
|
|
TicketInfo->TrustSid = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// Name: BuildReply
|
|
//
|
|
// Synopsis: Extracts reply information from an internal ticket
|
|
//
|
|
// Arguments: pkitTicket - (in) ticket data comes from
|
|
// dwNonce - (in) goes into the reply
|
|
// pkrReply - (out) reply that is built
|
|
//
|
|
// Notes: BUG 456265: Need to set tsKeyExpiry properly.
|
|
// See 3.1.3, 3.3.3 of the Kerberos V5 R5.2 spec
|
|
// tsKeyExpiry is zero for GetTGSTicket, and the
|
|
// expiry time of the client's key for GetASTicket.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
BuildReply(
|
|
IN OPTIONAL PKDC_TICKET_INFO ClientInfo,
|
|
IN ULONG Nonce,
|
|
IN PKERB_PRINCIPAL_NAME ServerName,
|
|
IN KERB_REALM ServerRealm,
|
|
IN OPTIONAL PKERB_HOST_ADDRESSES HostAddresses,
|
|
IN PKERB_TICKET Ticket,
|
|
OUT PKERB_ENCRYPTED_KDC_REPLY ReplyMessage
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
PKERB_ENCRYPTED_TICKET EncryptedTicket = (PKERB_ENCRYPTED_TICKET) Ticket->encrypted_part.cipher_text.value;
|
|
KERB_ENCRYPTED_KDC_REPLY ReplyBody;
|
|
LARGE_INTEGER CurrentTime;
|
|
|
|
TRACE(KDC, BuildReply, DEB_FUNCTION);
|
|
|
|
RtlZeroMemory(
|
|
&ReplyBody,
|
|
sizeof(KERB_ENCRYPTED_KDC_REPLY)
|
|
);
|
|
|
|
//
|
|
// Use the same flags field
|
|
//
|
|
|
|
ReplyBody.flags = ReplyMessage->flags;
|
|
|
|
|
|
|
|
ReplyBody.session_key = EncryptedTicket->key;
|
|
|
|
|
|
ReplyBody.last_request = (PKERB_LAST_REQUEST) MIDL_user_allocate(sizeof(KERB_LAST_REQUEST));
|
|
if (ReplyBody.last_request == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
RtlZeroMemory(
|
|
ReplyBody.last_request,
|
|
sizeof(KERB_LAST_REQUEST)
|
|
);
|
|
|
|
ReplyBody.last_request->next = NULL;
|
|
ReplyBody.last_request->value.last_request_type = 0;
|
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
|
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&ReplyBody.last_request->value.last_request_value,
|
|
NULL, // no usec
|
|
&CurrentTime
|
|
);
|
|
|
|
ReplyBody.nonce = Nonce;
|
|
|
|
DsysAssert((ReplyBody.flags.length == EncryptedTicket->flags.length) &&
|
|
(ReplyBody.flags.length== 8 * sizeof(ULONG)));
|
|
|
|
//
|
|
// Assign the flags over
|
|
//
|
|
|
|
*((PULONG)ReplyBody.flags.value) = *((PULONG)EncryptedTicket->flags.value);
|
|
|
|
if (ARGUMENT_PRESENT(ClientInfo))
|
|
{
|
|
|
|
KerbConvertLargeIntToGeneralizedTime(
|
|
&ReplyBody.key_expiration,
|
|
NULL,
|
|
&ClientInfo->PasswordExpires
|
|
);
|
|
ReplyBody.bit_mask |= key_expiration_present;
|
|
}
|
|
|
|
//
|
|
// Fill in the times
|
|
//
|
|
|
|
ReplyBody.authtime = EncryptedTicket->authtime;
|
|
ReplyBody.endtime = EncryptedTicket->endtime;
|
|
|
|
if (EncryptedTicket->bit_mask & KERB_ENCRYPTED_TICKET_starttime_present)
|
|
{
|
|
ReplyBody.KERB_ENCRYPTED_KDC_REPLY_starttime =
|
|
EncryptedTicket->KERB_ENCRYPTED_TICKET_starttime;
|
|
ReplyBody.bit_mask |= KERB_ENCRYPTED_KDC_REPLY_starttime_present;
|
|
|
|
}
|
|
|
|
if (EncryptedTicket->bit_mask & KERB_ENCRYPTED_TICKET_renew_until_present)
|
|
{
|
|
ReplyBody.KERB_ENCRYPTED_KDC_REPLY_renew_until =
|
|
EncryptedTicket->KERB_ENCRYPTED_TICKET_renew_until;
|
|
ReplyBody.bit_mask |= KERB_ENCRYPTED_KDC_REPLY_renew_until_present;
|
|
}
|
|
|
|
ReplyBody.server_realm = ServerRealm;
|
|
|
|
ReplyBody.server_name = *ServerName;
|
|
|
|
//
|
|
// Fill in the host addresses
|
|
//
|
|
|
|
|
|
if (HostAddresses != NULL)
|
|
{
|
|
ReplyBody.KERB_ENCRYPTED_KDC_REPLY_client_addresses = HostAddresses;
|
|
ReplyBody.bit_mask |= KERB_ENCRYPTED_KDC_REPLY_client_addresses_present;
|
|
}
|
|
|
|
*ReplyMessage = ReplyBody;
|
|
Cleanup:
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
if (ReplyBody.last_request != NULL)
|
|
{
|
|
MIDL_user_free(ReplyBody.last_request);
|
|
ReplyBody.last_request = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
return(KerbErr);
|
|
}
|
|
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcBuildSupplementalCredentials
|
|
//
|
|
// Synopsis: Builds a list of the supplemental credentials and then
|
|
// encrypts it with the supplied key
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
KERBERR
|
|
KdcBuildSupplementalCredentials(
|
|
IN PUSER_INTERNAL6_INFORMATION UserInfo,
|
|
IN PKERB_ENCRYPTION_KEY CredentialKey,
|
|
OUT PPAC_INFO_BUFFER * EncryptedCreds
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
PBYTE Credentials = NULL;
|
|
ULONG CredentialSize = 0;
|
|
KERB_ENCRYPTED_DATA EncryptedData = {0};
|
|
PPAC_CREDENTIAL_INFO CredInfo = NULL;
|
|
PPAC_INFO_BUFFER ReturnCreds = NULL;
|
|
ULONG ReturnCredSize;
|
|
|
|
Status = PAC_BuildCredentials(
|
|
(PSAMPR_USER_ALL_INFORMATION)&UserInfo->I1,
|
|
&Credentials,
|
|
&CredentialSize
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbAllocateEncryptionBufferWrapper(
|
|
CredentialKey->keytype,
|
|
CredentialSize,
|
|
&EncryptedData.cipher_text.length,
|
|
&EncryptedData.cipher_text.value
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbEncryptDataEx(
|
|
&EncryptedData,
|
|
CredentialSize,
|
|
Credentials,
|
|
KERB_NO_KEY_VERSION,
|
|
KERB_NON_KERB_SALT,
|
|
CredentialKey
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Compute the size of the blob returned
|
|
//
|
|
|
|
ReturnCredSize = sizeof(PAC_INFO_BUFFER) +
|
|
FIELD_OFFSET(PAC_CREDENTIAL_INFO, Data) +
|
|
EncryptedData.cipher_text.length ;
|
|
|
|
ReturnCreds = (PPAC_INFO_BUFFER) MIDL_user_allocate(ReturnCredSize);
|
|
if (ReturnCreds == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
//
|
|
// Fill in the outer structure
|
|
//
|
|
|
|
ReturnCreds->ulType = PAC_CREDENTIAL_TYPE;
|
|
ReturnCreds->cbBufferSize = ReturnCredSize - sizeof(PAC_INFO_BUFFER);
|
|
ReturnCreds->Data = (PBYTE) (ReturnCreds + 1);
|
|
CredInfo = (PPAC_CREDENTIAL_INFO) ReturnCreds->Data;
|
|
|
|
//
|
|
// Fill in the inner structure
|
|
//
|
|
|
|
CredInfo->EncryptionType = EncryptedData.encryption_type;
|
|
if (EncryptedData.bit_mask & version_present)
|
|
{
|
|
CredInfo->Version = EncryptedData.version;
|
|
}
|
|
else
|
|
{
|
|
CredInfo->Version = 0;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
CredInfo->Data,
|
|
EncryptedData.cipher_text.value,
|
|
EncryptedData.cipher_text.length
|
|
);
|
|
|
|
*EncryptedCreds = ReturnCreds;
|
|
ReturnCreds = NULL;
|
|
|
|
Cleanup:
|
|
if (Credentials != NULL)
|
|
{
|
|
MIDL_user_free(Credentials);
|
|
}
|
|
if (ReturnCreds != NULL)
|
|
{
|
|
MIDL_user_free(ReturnCreds);
|
|
}
|
|
if (EncryptedData.cipher_text.value != NULL)
|
|
{
|
|
MIDL_user_free(EncryptedData.cipher_text.value);
|
|
}
|
|
|
|
return (KerbErr);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcBuildPacVerifier
|
|
//
|
|
// Synopsis: Builds a verifier for the PAC. This structure ties the
|
|
// PAC to the ticket by including the client name and
|
|
// ticket authime in the PAC.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
KERBERR
|
|
KdcBuildPacVerifier(
|
|
IN PTimeStamp ClientId,
|
|
IN PUNICODE_STRING ClientName,
|
|
OUT PPAC_INFO_BUFFER * Verifier
|
|
)
|
|
{
|
|
ULONG ReturnVerifierSize = 0;
|
|
PPAC_CLIENT_INFO ClientInfo = NULL;
|
|
PPAC_INFO_BUFFER ReturnVerifier = NULL;
|
|
|
|
//
|
|
// Compute the size of the blob returned
|
|
//
|
|
ReturnVerifierSize = sizeof(PAC_INFO_BUFFER) + sizeof(PAC_CLIENT_INFO) +
|
|
ClientName->Length - sizeof(WCHAR) * ANYSIZE_ARRAY;
|
|
|
|
ReturnVerifier = (PPAC_INFO_BUFFER) MIDL_user_allocate(ReturnVerifierSize);
|
|
if (ReturnVerifier == NULL)
|
|
{
|
|
return(KRB_ERR_GENERIC);
|
|
}
|
|
//
|
|
// Fill in the outer structure
|
|
//
|
|
|
|
ReturnVerifier->ulType = PAC_CLIENT_INFO_TYPE;
|
|
ReturnVerifier->cbBufferSize = ReturnVerifierSize - sizeof(PAC_INFO_BUFFER);
|
|
ReturnVerifier->Data = (PBYTE) (ReturnVerifier + 1);
|
|
ClientInfo = (PPAC_CLIENT_INFO) ReturnVerifier->Data;
|
|
ClientInfo->ClientId = *ClientId;
|
|
ClientInfo->NameLength = ClientName->Length;
|
|
RtlCopyMemory(
|
|
ClientInfo->Name,
|
|
ClientName->Buffer,
|
|
ClientName->Length
|
|
);
|
|
*Verifier = ReturnVerifier;
|
|
|
|
return(KDC_ERR_NONE);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Name: KdcIsOurDomain
|
|
//
|
|
// Synopsis: Tells us if this sid is from our domain.
|
|
//
|
|
// Arguments:
|
|
//
|
|
//
|
|
//+---------------------------------------------------------------------------
|
|
|
|
|
|
BOOLEAN
|
|
KdcIsOurDomain(
|
|
IN PSID InputSid,
|
|
OUT PULONG Rid
|
|
)
|
|
{
|
|
BOOLEAN fRet = FALSE;
|
|
SID *Sid = (SID*) InputSid;
|
|
|
|
Sid->SubAuthorityCount--;
|
|
|
|
if (RtlEqualSid( Sid, GlobalDomainSid ))
|
|
{
|
|
D_DebugLog((DEB_T_PAC, "%p\n", Sid));
|
|
D_DebugLog((DEB_T_PAC, "%p add subauthority %i\n", &Sid->SubAuthority[Sid->SubAuthorityCount], Sid->SubAuthorityCount));
|
|
*Rid = Sid->SubAuthority[Sid->SubAuthorityCount];
|
|
|
|
D_DebugLog((DEB_T_PAC, "Rid %x\n", (*Rid)));
|
|
fRet = TRUE;
|
|
}
|
|
|
|
Sid->SubAuthorityCount++;
|
|
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Name: GetPacAndSuppCred
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Notes: ppPac is allocated using CoTaskMemAlloc and
|
|
// ppadlSuppCreds is allocated using operator 'new'.
|
|
//
|
|
//+---------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
GetPacAndSuppCred(
|
|
IN PUSER_INTERNAL6_INFORMATION UserInfo,
|
|
IN PSID_AND_ATTRIBUTES_LIST GroupMembership,
|
|
IN ULONG SignatureSize,
|
|
IN OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey,
|
|
IN OPTIONAL PTimeStamp ClientId,
|
|
IN OPTIONAL PUNICODE_STRING ClientName,
|
|
OUT PPACTYPE * Pac,
|
|
OUT PKERB_EXT_ERROR pExtendedError
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
NTSTATUS Status;
|
|
PPACTYPE NewPac = NULL;
|
|
PPAC_INFO_BUFFER Credentials[2];
|
|
ULONG AdditionalDataCount = 0;
|
|
|
|
|
|
ULONG ExtraSidCount = 0, GroupCount = 0, Rid;
|
|
|
|
SID_AND_ATTRIBUTES_LIST ExtraSidList = {0};
|
|
PSID_AND_ATTRIBUTES ExtraSids = NULL;
|
|
SAMPR_GET_GROUPS_BUFFER GroupsBuffer = {0};
|
|
PGROUP_MEMBERSHIP Groups = NULL;
|
|
|
|
|
|
TRACE(KDC, GetPACandSuppCred, DEB_FUNCTION);
|
|
|
|
RtlZeroMemory(
|
|
Credentials,
|
|
sizeof(Credentials)
|
|
);
|
|
|
|
*Pac = NULL;
|
|
|
|
D_DebugLog((DEB_T_PAC,
|
|
"GetPacAndSuppCred User %wZ, FullName %wZ, Upn %wZ, UserId %#x, UserAccountControl %#x, ExtendedFields %#x, WhichFields %#x\n",
|
|
&UserInfo->I1.UserName,
|
|
&UserInfo->I1.FullName,
|
|
&UserInfo->UPN,
|
|
&UserInfo->I1.UserId,
|
|
UserInfo->I1.UserAccountControl,
|
|
UserInfo->ExtendedFields,
|
|
UserInfo->I1.WhichFields));
|
|
|
|
if (ARGUMENT_PRESENT(CredentialKey) &&
|
|
(CredentialKey->keyvalue.value != NULL) )
|
|
{
|
|
KerbErr = KdcBuildSupplementalCredentials(
|
|
UserInfo,
|
|
CredentialKey,
|
|
&Credentials[AdditionalDataCount]
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
AdditionalDataCount++;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(ClientId))
|
|
{
|
|
KerbErr = KdcBuildPacVerifier(
|
|
ClientId,
|
|
ClientName,
|
|
&Credentials[AdditionalDataCount]
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
AdditionalDataCount++;
|
|
}
|
|
|
|
//
|
|
// Now we have to build up our PSAMPR_GET_GROUPS buffer. This is
|
|
// meant to reduce the overall PAC size. Be pessimistic about the buffer size
|
|
// we'll need.
|
|
//
|
|
SafeAllocaAllocate( Groups, ( GroupMembership->Count * sizeof(GROUP_MEMBERSHIP) ));
|
|
if ( Groups == NULL )
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
SafeAllocaAllocate( ExtraSids, ( GroupMembership->Count * sizeof(SID_AND_ATTRIBUTES) ));
|
|
if ( ExtraSids == NULL )
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
for ( ULONG i = 0 ; i < GroupMembership->Count ; i++ )
|
|
{
|
|
//
|
|
// If this is in our domain, add it to the Groups.
|
|
//
|
|
if ( KdcIsOurDomain( GroupMembership->SidAndAttributes[i].Sid, &Rid ))
|
|
{
|
|
D_DebugLog((DEB_T_PAC, "Adding %i from %p\n", Rid, GroupMembership->SidAndAttributes[i].Sid ));
|
|
Groups[GroupCount].RelativeId = Rid;
|
|
Groups[GroupCount].Attributes = GroupMembership->SidAndAttributes[i].Attributes;
|
|
GroupCount++;
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog(( DEB_T_PAC, "Adding %p to extrasids\n", GroupMembership->SidAndAttributes[i].Sid ));
|
|
ExtraSids[ExtraSidCount] = GroupMembership->SidAndAttributes[i];
|
|
ExtraSidCount++;
|
|
}
|
|
|
|
}
|
|
|
|
GroupsBuffer.Groups = Groups;
|
|
GroupsBuffer.MembershipCount = GroupCount;
|
|
|
|
ExtraSidList.SidAndAttributes = ExtraSids;
|
|
ExtraSidList.Count = ExtraSidCount;
|
|
|
|
//
|
|
// NOTE: this is o.k. because we don't lock the secdata while acecssing
|
|
// the machine name
|
|
//
|
|
Status = PAC_Init(
|
|
(PSAMPR_USER_ALL_INFORMATION)&UserInfo->I1,
|
|
&GroupsBuffer,
|
|
&ExtraSidList,
|
|
GlobalDomainSid,
|
|
&GlobalDomainName,
|
|
SecData.MachineName(),
|
|
SignatureSize,
|
|
AdditionalDataCount,
|
|
Credentials,
|
|
&NewPac
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to init pac: 0x%x\n",Status));
|
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
*Pac = NewPac;
|
|
NewPac = NULL;
|
|
|
|
|
|
Cleanup:
|
|
|
|
|
|
while (AdditionalDataCount > 0)
|
|
{
|
|
if (Credentials[--AdditionalDataCount] != NULL)
|
|
{
|
|
MIDL_user_free(Credentials[AdditionalDataCount]);
|
|
}
|
|
}
|
|
|
|
SafeAllocaFree( Groups );
|
|
SafeAllocaFree( ExtraSids );
|
|
|
|
if (NewPac != NULL)
|
|
{
|
|
MIDL_user_free(NewPac);
|
|
}
|
|
|
|
|
|
return(KerbErr);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcFreeInternalTicket
|
|
//
|
|
// Synopsis: frees a constructed ticket
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
VOID
|
|
KdcFreeInternalTicket(
|
|
IN PKERB_TICKET Ticket
|
|
)
|
|
{
|
|
PKERB_ENCRYPTED_TICKET EncryptedTicket = (PKERB_ENCRYPTED_TICKET) Ticket->encrypted_part.cipher_text.value;
|
|
|
|
TRACE(KDC, KdcFreeInternalTicket, DEB_FUNCTION);
|
|
|
|
if (EncryptedTicket != NULL)
|
|
{
|
|
KerbFreeKey(
|
|
&EncryptedTicket->key
|
|
);
|
|
KerbFreePrincipalName(&EncryptedTicket->client_name);
|
|
KerbFreeRealm(&EncryptedTicket->client_realm);
|
|
if (EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data != NULL)
|
|
{
|
|
KerbFreeAuthData(EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data);
|
|
}
|
|
if (EncryptedTicket->transited.contents.value != NULL)
|
|
{
|
|
MIDL_user_free(EncryptedTicket->transited.contents.value);
|
|
EncryptedTicket->transited.contents.value = 0;
|
|
EncryptedTicket->transited.contents.length = 0;
|
|
}
|
|
Ticket->encrypted_part.cipher_text.value = NULL;
|
|
Ticket->encrypted_part.cipher_text.length = 0;
|
|
}
|
|
|
|
KerbFreePrincipalName(
|
|
&Ticket->server_name
|
|
);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcFreeKdcReply
|
|
//
|
|
// Synopsis: frees a kdc reply
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
VOID
|
|
KdcFreeKdcReply(
|
|
IN PKERB_KDC_REPLY Reply
|
|
)
|
|
{
|
|
TRACE(KDC, KdcFreeKdcReply, DEB_FUNCTION);
|
|
|
|
KerbFreePrincipalName(&Reply->ticket.server_name);
|
|
|
|
if (Reply->ticket.encrypted_part.cipher_text.value != NULL)
|
|
{
|
|
MIDL_user_free(Reply->ticket.encrypted_part.cipher_text.value);
|
|
}
|
|
|
|
if (Reply->encrypted_part.cipher_text.value != NULL)
|
|
{
|
|
MIDL_user_free(Reply->encrypted_part.cipher_text.value);
|
|
}
|
|
if (Reply->KERB_KDC_REPLY_preauth_data != NULL)
|
|
{
|
|
KerbFreePreAuthData((PKERB_PA_DATA_LIST) Reply->KERB_KDC_REPLY_preauth_data);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcFreeKdcReplyBody
|
|
//
|
|
// Synopsis: frees a constructed KDC reply body
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
VOID
|
|
KdcFreeKdcReplyBody(
|
|
IN PKERB_ENCRYPTED_KDC_REPLY ReplyBody
|
|
)
|
|
{
|
|
|
|
TRACE(KDC, KdcFreeKdcReplyBody, DEB_FUNCTION);
|
|
|
|
//
|
|
// The names & the session key are just pointers into the ticket,
|
|
// so they don't need to be freed.
|
|
//
|
|
|
|
if (ReplyBody->last_request != NULL)
|
|
{
|
|
MIDL_user_free(ReplyBody->last_request);
|
|
ReplyBody->last_request = NULL;
|
|
}
|
|
ReplyBody->KERB_ENCRYPTED_KDC_REPLY_client_addresses = NULL;
|
|
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcVerifyTgsChecksum
|
|
//
|
|
// Synopsis: Verify the checksum on a TGS request
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcVerifyTgsChecksum(
|
|
IN PKERB_KDC_REQUEST_BODY RequestBody,
|
|
IN PKERB_ENCRYPTION_KEY Key,
|
|
IN PKERB_CHECKSUM OldChecksum
|
|
)
|
|
{
|
|
PCHECKSUM_FUNCTION ChecksumFunction;
|
|
PCHECKSUM_BUFFER CheckBuffer = NULL;
|
|
KERB_MESSAGE_BUFFER MarshalledRequestBody = {0, NULL};
|
|
NTSTATUS Status;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
KERB_CHECKSUM Checksum = {0};
|
|
|
|
|
|
Status = CDLocateCheckSum(
|
|
OldChecksum->checksum_type,
|
|
&ChecksumFunction
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate enough space for the checksum
|
|
//
|
|
|
|
Checksum.checksum.value = (PUCHAR) MIDL_user_allocate(ChecksumFunction->CheckSumSize);
|
|
if (Checksum.checksum.value == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
Checksum.checksum.length = ChecksumFunction->CheckSumSize;
|
|
|
|
//
|
|
// Initialize the checksum
|
|
//
|
|
|
|
if ((OldChecksum->checksum_type == KERB_CHECKSUM_REAL_CRC32) ||
|
|
(OldChecksum->checksum_type == KERB_CHECKSUM_CRC32))
|
|
{
|
|
if (ChecksumFunction->Initialize)
|
|
{
|
|
Status = ChecksumFunction->Initialize(
|
|
0,
|
|
&CheckBuffer
|
|
);
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (NULL != ChecksumFunction->InitializeEx2)
|
|
{
|
|
Status = ChecksumFunction->InitializeEx2(
|
|
Key->keyvalue.value,
|
|
(ULONG) Key->keyvalue.length,
|
|
OldChecksum->checksum.value,
|
|
KERB_TGS_REQ_AP_REQ_AUTH_CKSUM_SALT,
|
|
&CheckBuffer
|
|
);
|
|
}
|
|
else if (ChecksumFunction->InitializeEx)
|
|
{
|
|
Status = ChecksumFunction->InitializeEx(
|
|
Key->keyvalue.value,
|
|
(ULONG) Key->keyvalue.length,
|
|
KERB_TGS_REQ_AP_REQ_AUTH_CKSUM_SALT,
|
|
&CheckBuffer
|
|
);
|
|
}
|
|
else
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
}
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbPackData(
|
|
RequestBody,
|
|
KERB_MARSHALLED_REQUEST_BODY_PDU,
|
|
&MarshalledRequestBody.BufferSize,
|
|
&MarshalledRequestBody.Buffer
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now checksum the buffer
|
|
//
|
|
|
|
ChecksumFunction->Sum(
|
|
CheckBuffer,
|
|
MarshalledRequestBody.BufferSize,
|
|
MarshalledRequestBody.Buffer
|
|
);
|
|
|
|
ChecksumFunction->Finalize(
|
|
CheckBuffer,
|
|
Checksum.checksum.value
|
|
);
|
|
|
|
//
|
|
// Now compare
|
|
//
|
|
|
|
if ((OldChecksum->checksum.length != Checksum.checksum.length) ||
|
|
!RtlEqualMemory(
|
|
OldChecksum->checksum.value,
|
|
Checksum.checksum.value,
|
|
Checksum.checksum.length
|
|
))
|
|
{
|
|
DebugLog((DEB_ERROR,"Checksum on TGS request body did not match\n"));
|
|
KerbErr = KRB_AP_ERR_MODIFIED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if (CheckBuffer != NULL)
|
|
{
|
|
ChecksumFunction->Finish(&CheckBuffer);
|
|
}
|
|
if (MarshalledRequestBody.Buffer != NULL)
|
|
{
|
|
MIDL_user_free(MarshalledRequestBody.Buffer);
|
|
}
|
|
if (Checksum.checksum.value != NULL)
|
|
{
|
|
MIDL_user_free(Checksum.checksum.value);
|
|
}
|
|
return(KerbErr);
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcVerifyKdcRequest
|
|
//
|
|
// Synopsis: Verifies that the AP request accompanying a TGS or PAC request
|
|
// is valid.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: ApRequest - The AP request to verify
|
|
// UnmarshalledRequest - The unmarshalled request,
|
|
// returned to avoid needing to
|
|
// EncryptedTicket - Receives the ticket granting ticket
|
|
// SessionKey - receives the key to use in the reply
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: kerberos errors
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcVerifyKdcRequest(
|
|
IN PUCHAR RequestMessage,
|
|
IN ULONG RequestSize,
|
|
IN OPTIONAL SOCKADDR * ClientAddress,
|
|
IN BOOLEAN IsKdcRequest,
|
|
OUT OPTIONAL PKERB_AP_REQUEST * UnmarshalledRequest,
|
|
OUT OPTIONAL PKERB_AUTHENTICATOR * UnmarshalledAuthenticator,
|
|
OUT PKERB_ENCRYPTED_TICKET *EncryptedTicket,
|
|
OUT PKERB_ENCRYPTION_KEY SessionKey,
|
|
OUT OPTIONAL PKERB_ENCRYPTION_KEY ServerKey,
|
|
OUT PKDC_TICKET_INFO ServerTicketInfo,
|
|
OUT PBOOLEAN UseSubKey,
|
|
OUT PKERB_EXT_ERROR pExtendedError
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
PKERB_AP_REQUEST Request = NULL;
|
|
PKERB_ENCRYPTED_TICKET EncryptPart = NULL;
|
|
PKERB_AUTHENTICATOR Authenticator = NULL;
|
|
PKERB_INTERNAL_NAME ServerName = NULL;
|
|
UNICODE_STRING LocalServerRealm = {0};
|
|
UNICODE_STRING ServerRealm;
|
|
PKERB_ENCRYPTION_KEY KeyToUse;
|
|
BOOLEAN Referral = FALSE;
|
|
|
|
TRACE(KDC, KdcVerifyKdcRequest, DEB_FUNCTION);
|
|
|
|
ServerRealm.Buffer = NULL;
|
|
|
|
//
|
|
// First unpack the KDC request.
|
|
//
|
|
|
|
KerbErr = KerbUnpackApRequest(
|
|
RequestMessage,
|
|
RequestSize,
|
|
&Request
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_ERROR, "Failed to unpack KDC request: 0x%x\n", KerbErr));
|
|
KerbErr = KDC_ERR_NO_RESPONSE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbConvertPrincipalNameToKdcName(
|
|
&ServerName,
|
|
&Request->ticket.server_name
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbConvertRealmToUnicodeString(
|
|
&ServerRealm,
|
|
&Request->ticket.realm
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
//
|
|
// Get ticket info for the server
|
|
//
|
|
|
|
KerbErr = KdcNormalize(
|
|
ServerName,
|
|
&ServerRealm,
|
|
&ServerRealm,
|
|
NULL,
|
|
KDC_NAME_SERVER | KDC_NAME_INBOUND,
|
|
FALSE, // do not restrict user accounts (user2user)
|
|
&Referral,
|
|
&LocalServerRealm,
|
|
ServerTicketInfo,
|
|
pExtendedError,
|
|
NULL, // no user handle
|
|
0L, // no fields to fetch
|
|
0L, // no extended fileds to fetch
|
|
NULL, // no user all
|
|
NULL // no group membership
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_WARN, "KdcVerifyKdcRequest failed to get TGS ticket to service in another realm %#x: ", KerbErr));
|
|
KerbPrintKdcName(DEB_WARN, ServerName);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now Check the ticket
|
|
//
|
|
|
|
Retry:
|
|
|
|
KeyToUse = KerbGetKeyFromList(
|
|
ServerTicketInfo->Passwords,
|
|
Request->ticket.encrypted_part.encryption_type
|
|
);
|
|
|
|
if (KeyToUse == NULL)
|
|
{
|
|
DebugLog((DEB_ERROR,"Server does not have proper key to decrypt ticket: 0x%x\n",
|
|
Request->ticket.encrypted_part.encryption_type ));
|
|
|
|
//
|
|
// If this is our second attempt, do not overwrite the error code we had
|
|
//
|
|
|
|
if (KERB_SUCCESS(KerbErr))
|
|
{
|
|
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We don't need to supply a service name or service because we've looked up
|
|
// the account locally.
|
|
//
|
|
|
|
KerbErr = KerbCheckTicket(
|
|
&Request->ticket,
|
|
&Request->authenticator,
|
|
KeyToUse,
|
|
Authenticators,
|
|
&SkewTime,
|
|
0, // zero service names
|
|
NULL, // any service
|
|
NULL,
|
|
TRUE, // must check for clock_skew per RFC bug #322448 & 385404 (time skew only)
|
|
IsKdcRequest,
|
|
&EncryptPart,
|
|
&Authenticator,
|
|
NULL,
|
|
SessionKey,
|
|
UseSubKey
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to check ticket : 0x%x for",KerbErr));
|
|
KerbPrintKdcName(DEB_ERROR, ServerName);
|
|
|
|
//
|
|
// If an old password is available, give it a try. We remove the
|
|
// current password & put the old password in here.
|
|
//
|
|
|
|
if (ServerTicketInfo->OldPasswords && (KerbErr == KRB_AP_ERR_MODIFIED))
|
|
{
|
|
MIDL_user_free(ServerTicketInfo->Passwords);
|
|
ServerTicketInfo->Passwords = ServerTicketInfo->OldPasswords;
|
|
ServerTicketInfo->OldPasswords = NULL;
|
|
goto Retry;
|
|
}
|
|
|
|
|
|
//
|
|
// Here's the case where we're trying to use an expired TGT. Have
|
|
// the client retry using a new TGT
|
|
//
|
|
if (KerbErr == KRB_AP_ERR_TKT_EXPIRED)
|
|
{
|
|
FILL_EXT_ERROR_EX(pExtendedError, STATUS_TIME_DIFFERENCE_AT_DC, FILENO, __LINE__);
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the address from the ticket
|
|
//
|
|
// NOTE: the check mandated by RFC1510 can be thwarted by a registry setting
|
|
// (KdcDontCheckAddresses) to allow seamless operation through NATs
|
|
//
|
|
|
|
if ((EncryptPart->bit_mask & KERB_ENCRYPTED_TICKET_client_addresses_present) &&
|
|
ARGUMENT_PRESENT(ClientAddress) &&
|
|
!KdcDontCheckAddresses)
|
|
{
|
|
ULONG TicketFlags = KerbConvertFlagsToUlong(&EncryptPart->flags);
|
|
|
|
//
|
|
// Only check initial tickets
|
|
//
|
|
|
|
if ((TicketFlags & (KERB_TICKET_FLAGS_forwarded | KERB_TICKET_FLAGS_proxy)) == 0)
|
|
{
|
|
if ( !KerbVerifyClientAddress(
|
|
ClientAddress,
|
|
EncryptPart->KERB_ENCRYPTED_TICKET_client_addresses ))
|
|
{
|
|
KerbErr = KRB_AP_ERR_BADADDR;
|
|
DebugLog((DEB_ERROR,"Client sent request with wrong address\n"));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Verify that if the server is a trusted domain account, that it is an
|
|
// acceptable ticket (transitively). Verify that for non transitive
|
|
// trust the client realm is the same as the requesting ticket realm
|
|
//
|
|
|
|
if (((ServerTicketInfo->fTicketOpts & AUTH_REQ_TRANSITIVE_TRUST) == 0) &&
|
|
(ServerTicketInfo->UserId != DOMAIN_USER_RID_KRBTGT))
|
|
{
|
|
if (!KerbCompareRealmNames(
|
|
&EncryptPart->client_realm,
|
|
&Request->ticket.realm
|
|
))
|
|
{
|
|
//
|
|
// Could be a S4USelf request, in which case, we're looping back to local domain
|
|
//
|
|
if (!SecData.IsOurRealm(&EncryptPart->client_realm))
|
|
{
|
|
DebugLog((DEB_WARN, "KdcVerifyKdcRequest client from realm %s attempted to access non transitve trust via %s : illegal\n",
|
|
&EncryptPart->client_realm,
|
|
&Request->ticket.realm
|
|
));
|
|
|
|
KerbErr = KDC_ERR_PATH_NOT_ACCEPTED;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the caller wanted the server key, return it here.
|
|
//
|
|
|
|
if (ServerKey != NULL)
|
|
{
|
|
KerbErr = KerbDuplicateKey(
|
|
ServerKey,
|
|
KeyToUse
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
*EncryptedTicket = EncryptPart;
|
|
EncryptPart = NULL;
|
|
if (ARGUMENT_PRESENT(UnmarshalledRequest))
|
|
{
|
|
*UnmarshalledRequest = Request;
|
|
Request = NULL;
|
|
}
|
|
if (ARGUMENT_PRESENT(UnmarshalledAuthenticator))
|
|
{
|
|
*UnmarshalledAuthenticator = Authenticator;
|
|
Authenticator = NULL;
|
|
}
|
|
|
|
Cleanup:
|
|
KerbFreeApRequest(Request);
|
|
KerbFreeString(&LocalServerRealm);
|
|
KerbFreeKdcName(&ServerName);
|
|
KerbFreeString(&ServerRealm);
|
|
KerbFreeAuthenticator(Authenticator);
|
|
KerbFreeTicket(EncryptPart);
|
|
|
|
return(KerbErr);
|
|
}
|
|
|
|
#if DBG
|
|
|
|
void
|
|
PrintRequest( ULONG ulDebLevel, PKERB_KDC_REQUEST_BODY Request )
|
|
{
|
|
TRACE(KDC, PrintRequest, DEB_FUNCTION);
|
|
}
|
|
|
|
void
|
|
PrintTicket( ULONG ulDebLevel,
|
|
char * pszMessage,
|
|
PKERB_TICKET pkitTicket)
|
|
{
|
|
TRACE(KDC, PrintTicket, DEB_FUNCTION);
|
|
}
|
|
|
|
|
|
#endif // DBG
|
|
|