windows-nt/Source/XPSP1/NT/ds/security/protocols/kerberos/server/transit.cxx
2020-09-26 16:20:57 +08:00

664 lines
18 KiB
C++

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1997
//
// File: transit.cxx
//
// Contents: Code for compressing transitive realm list
//
//
// History:
//
//------------------------------------------------------------------------
#include "kdcsvr.hxx"
//+-----------------------------------------------------------------------
//
// Function: KerbAppendString
//
// Synopsis: Appends to unicode strings together and allocates the output
//
// Effects:
//
// Parameters: Output - Output appended String
// InputTail - Trailing portion of input
// InputHead - Head potion of input
//
//
// Return:
//
// Notes:
//
//------------------------------------------------------------------------
NTSTATUS
KerbAppendString(
OUT PUNICODE_STRING Output,
IN PUNICODE_STRING InputTail,
IN PUNICODE_STRING InputHead
)
{
Output->Buffer = NULL;
Output->Length = InputHead->Length + InputTail->Length;
if ((InputHead->Buffer == NULL) && (InputTail->Buffer == NULL))
{
Output->MaximumLength = 0;
return(STATUS_SUCCESS);
}
else
{
Output->MaximumLength = Output->Length + sizeof(WCHAR);
}
Output->Buffer = (LPWSTR) MIDL_user_allocate(Output->MaximumLength);
if (Output->Buffer == NULL)
{
return(STATUS_INSUFFICIENT_RESOURCES);
}
RtlCopyMemory(
Output->Buffer,
InputHead->Buffer,
InputHead->Length
);
RtlCopyMemory(
Output->Buffer + InputHead->Length / sizeof(WCHAR),
InputTail->Buffer,
InputTail->Length
);
Output->Buffer[Output->Length/sizeof(WCHAR)] = L'\0';
return(STATUS_SUCCESS);
}
typedef enum _KERB_DOMAIN_COMPARISON {
Above,
Below,
Equal,
NotEqual
} KERB_DOMAIN_COMPARISON, *PKERB_DOMAIN_COMPARISON;
//+-----------------------------------------------------------------------
//
// Function: KerbCompareDomains
//
// Synopsis: Compares two domain names and returns whether one is a
// prefix of the other, and the offset of the prefix.
//
// Effects:
//
// Parameters:
//
// Return: Above - domain1 is a postfix of domain2
// Below - domain2 is a postfix of domain1
// Equal - the domains are equal
// NotEqual - the domains are not equal and not above or below
//
// Notes: This does not work for x-500 realm names (/foo/bar)
//
//------------------------------------------------------------------------
KERB_DOMAIN_COMPARISON
KerbCompareDomains(
IN PUNICODE_STRING Domain1,
IN PUNICODE_STRING Domain2,
OUT PULONG PostfixOffset
)
{
ULONG Index;
UNICODE_STRING TempString;
if (Domain1->Length > Domain2->Length)
{
TempString.Length = TempString.MaximumLength = Domain2->Length;
TempString.Buffer = Domain1->Buffer + (Domain1->Length - Domain2->Length) / sizeof(WCHAR);
if (RtlEqualUnicodeString(
&TempString,
Domain2,
TRUE) && TempString.Buffer[-1] == '.')
{
*PostfixOffset = (Domain1->Length - Domain2->Length) / sizeof(WCHAR);
return(Below);
}
else
{
return(NotEqual);
}
}
else if (Domain1->Length < Domain2->Length)
{
TempString.Length = TempString.MaximumLength = Domain1->Length;
TempString.Buffer = Domain2->Buffer + (Domain2->Length - Domain1->Length) / sizeof(WCHAR);
if (RtlEqualUnicodeString(
&TempString,
Domain1,
TRUE) && TempString.Buffer[-1] == '.')
{
*PostfixOffset = (Domain2->Length - Domain1->Length) / sizeof(WCHAR);
return(Above);
}
else
{
return(NotEqual);
}
}
else if (RtlEqualUnicodeString(Domain1,Domain2, TRUE))
{
*PostfixOffset = 0;
return(Equal);
}
else
{
return(NotEqual);
}
}
//+-----------------------------------------------------------------------
//
// Function: KdcExpandTranistedRealms
//
// Synopsis: Expands the transited realm field into an array of realms
//
// Effects: Allocates an array of realm names
//
// Parameters: FullRealmList - receives the full list of realms
// CountOfRealms - receveies the number of entries in the list
// TranistedList - The transited field to expand
//
// Return:
//
// Notes:
//
//------------------------------------------------------------------------
KERBERR
KdcExpandTransitedRealms(
OUT PUNICODE_STRING * FullRealmList,
OUT PULONG CountOfRealms,
IN PUNICODE_STRING TransitedList
)
{
KERBERR KerbErr = KDC_ERR_NONE;
ULONG RealmCount;
ULONG Index;
ULONG RealmIndex;
PUNICODE_STRING RealmList = NULL;
UNICODE_STRING CurrentRealm;
*FullRealmList = NULL;
*CountOfRealms = 0;
//
// First count the number of realms in the tranisted list. We can do
// this by counting the number of ',' in the list. Note: if the encoding
// is compressed by using a null entry to include all domains in a path
// up or down a hierarchy, this code will fail.
//
if (TransitedList->Length == 0)
{
return(KDC_ERR_NONE);
}
RealmCount = 1;
for (Index = 0; Index < TransitedList->Length / sizeof(WCHAR) ; Index++ )
{
if (TransitedList->Buffer[Index] == ',')
{
RealmCount++;
//
// Check for a ',,' compression indicating that all the
// realms between two names have been traversed.
// BUG 453997: we don't handle this yet.
//
if ( (Index == 0) ||
(Index == (TransitedList->Length / sizeof(WCHAR)) -1) ||
(TransitedList->Buffer[Index-1] == L','))
{
DebugLog((DEB_ERROR,"BUG 453997:: hit overcompressed transited encoding: %wZ\n",
TransitedList ));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
}
}
//
// We now have a the count of realms. Allocate an array of UNICODE_STRING
// structures to hold the realms.
//
RealmList = (PUNICODE_STRING) MIDL_user_allocate(RealmCount * sizeof(UNICODE_STRING));
if (RealmList == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
RealmList,
RealmCount * sizeof(UNICODE_STRING)
);
//
// Now loop through and insert the full names of all the domains into
// the list
//
RealmIndex = 0;
CurrentRealm = *TransitedList;
for (Index = 0; Index <= TransitedList->Length / sizeof(WCHAR) ; Index++ )
{
//
// If we hit the end of the string or found a ',', split off a
// new realm.
//
if ((Index == TransitedList->Length / sizeof(WCHAR)) ||
(TransitedList->Buffer[Index] == ',' ))
{
//
// Check for a ',,' compression indicating that all the
// realms between two names have been traversed.
// BUG 453997:: we don't handle this yet.
//
if ( (Index == 0) ||
(Index == (TransitedList->Length / sizeof(WCHAR)) -1) ||
(TransitedList->Buffer[Index-1] == L','))
{
DebugLog((DEB_ERROR,"BUG 453997:: hit overcompressed transited encoding: %wZ\n",
TransitedList ));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
CurrentRealm.Length = CurrentRealm.MaximumLength =
(USHORT)(&TransitedList->Buffer[Index] - CurrentRealm.Buffer) * sizeof(WCHAR);
//
// Check for a trailing '.' - if so, append it
// to the parent
//
if (TransitedList->Buffer[Index-1] == '.')
{
//
// This is a compressed name, so append it to the previous
// name
//
if (RealmIndex == 0)
{
DebugLog((DEB_ERROR,"First element in transited encoding has a trailing '.': %wZ\n",
TransitedList ));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
if (!NT_SUCCESS(KerbAppendString(
&RealmList[RealmIndex],
&RealmList[RealmIndex-1],
&CurrentRealm)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
}
else if ((RealmIndex != 0) && (CurrentRealm.Buffer[0] == '/'))
{
if (!NT_SUCCESS(KerbAppendString(
&RealmList[RealmIndex],
&CurrentRealm,
&RealmList[RealmIndex-1]
)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
}
else if (!NT_SUCCESS(KerbDuplicateString(
&RealmList[RealmIndex],
&CurrentRealm)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
CurrentRealm.Buffer =CurrentRealm.Buffer + 1 + CurrentRealm.Length/sizeof(WCHAR);
RealmIndex++;
}
}
DsysAssert(RealmIndex == RealmCount);
*FullRealmList = RealmList;
*CountOfRealms = RealmCount;
Cleanup:
if (!KERB_SUCCESS(KerbErr))
{
if (RealmList != NULL)
{
for (RealmIndex = 0; RealmIndex < RealmCount ; RealmIndex++ )
{
KerbFreeString(&RealmList[RealmIndex]);
}
MIDL_user_free(RealmList);
}
}
return(KerbErr);
}
//+-----------------------------------------------------------------------
//
// Function: KdcCompressTransitedRealms
//
// Synopsis: Compresses an ordered list of realms by removing
// redundant information.
//
// Effects: Allocates an output string
//
// Parameters: CompressedRealms - receives the compressed list of realms
// RealmList - List of domains to compress
// RealmCount - number of entries in realm list
// NewRealm - new realm to add to the lsit
// NewRealmIndex - Location before which to insert the new
// realm
//
// Return:
//
// Notes:
//
//------------------------------------------------------------------------
KERBERR
KdcCompressTransitedRealms(
OUT PUNICODE_STRING CompressedRealms,
IN PUNICODE_STRING RealmList,
IN ULONG RealmCount,
IN PUNICODE_STRING NewRealm,
IN ULONG NewRealmIndex
)
{
UNICODE_STRING OutputRealms;
WCHAR OutputRealmBuffer[1000];
KERBERR KerbErr = KDC_ERR_NONE;
ULONG Index;
ULONG InsertionIndex = NewRealmIndex;
PWCHAR Where;
PUNICODE_STRING PreviousName = NULL;
PUNICODE_STRING CurrentName = NULL;
ULONG PostfixOffset;
KERB_DOMAIN_COMPARISON Comparison;
UNICODE_STRING NameToAdd;
RtlInitUnicodeString(
CompressedRealms,
NULL
);
OutputRealms.Buffer = OutputRealmBuffer;
OutputRealms.MaximumLength = sizeof(OutputRealmBuffer);
OutputRealms.Length = 0;
Where = OutputRealms.Buffer;
Index = 0;
while (Index <= RealmCount)
{
PreviousName = CurrentName;
//
// If this is the index to insert, add the new realm
//
if (InsertionIndex == Index)
{
CurrentName = NewRealm;
}
else if (Index == RealmCount)
{
//
// If we already added all the original realms, get out now
//
break;
}
else
{
CurrentName = &RealmList[Index];
}
NameToAdd = *CurrentName;
//
// If the previous name is above this one, lop off the postfix from
// this name
//
if ((PreviousName != NULL) &&
KerbCompareDomains(
PreviousName,
CurrentName,
&PostfixOffset
) == Above)
{
NameToAdd.Length = (USHORT) PostfixOffset * sizeof(WCHAR);
}
DsysAssert(OutputRealms.Length + NameToAdd.Length + sizeof(WCHAR) < OutputRealms.MaximumLength);
if (OutputRealms.Length + NameToAdd.Length + sizeof(WCHAR) > OutputRealms.MaximumLength)
{
//
// BUG 453652: wrong error
//
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
if (OutputRealms.Length != 0)
{
*Where++ = L',';
OutputRealms.Length += sizeof(WCHAR);
}
RtlCopyMemory(
Where,
NameToAdd.Buffer,
NameToAdd.Length
);
Where += NameToAdd.Length/sizeof(WCHAR);
OutputRealms.Length += NameToAdd.Length;
//
// If we inserted the transited realm here, run through the loop
// again with the same index.
//
if (InsertionIndex == Index)
{
InsertionIndex = 0xffffffff;
}
else
{
Index++;
}
}
*Where++ = L'\0';
if (!NT_SUCCESS(KerbDuplicateString(
CompressedRealms,
&OutputRealms)))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Cleanup:
return(KerbErr);
}
//+-----------------------------------------------------------------------
//
// Function: KdcInsertTransitedRealm
//
// Synopsis: Inserts the referree's realm into the tranisted encoding
// in a ticket. This uses domain-x500-compress which
// eliminates redundant information when one domain is the
// prefix or suffix of another.
//
// Effects: Allocates output buffer
//
// Parameters: NewTransitedField - receives the new tranisted field, to
// be freed with KerbFreeString
// OldTransitedField - the existing transited frield.
// ClientRealm - Realm of client (from ticket)
// TransitedRealm - Realm of referring domain
// OurRealm - Our realm name
//
// Return:
//
// Notes:
//
//------------------------------------------------------------------------
KERBERR
KdcInsertTransitedRealm(
OUT PUNICODE_STRING NewTransitedField,
IN PUNICODE_STRING OldTransitedField,
IN PUNICODE_STRING ClientRealm,
IN PUNICODE_STRING TransitedRealm,
IN PUNICODE_STRING OurRealm
)
{
KERBERR KerbErr = KDC_ERR_NONE;
PUNICODE_STRING FullDomainList = NULL;
ULONG CountOfDomains;
ULONG NewEntryIndex = 0xffffffff;
UNICODE_STRING NewDomain;
ULONG PostfixOffset;
ULONG Index;
KERB_DOMAIN_COMPARISON Comparison = NotEqual;
KERB_DOMAIN_COMPARISON LastComparison;
//
// The first thing to do is to expand the existing transited field. This
// is because the compression scheme does not allow new domains to simply
// append or insert information - the encoding of existing realms
// can change. For example, going from a domain to its parent means
// that the original domain can be encoded as a prefix of the parent
// whereas originally it was a name unto itself.
//
D_DebugLog((DEB_T_TRANSIT, "Inserted realm %wZ into list %wZ for client fomr %wZ\n",
TransitedRealm, OldTransitedField, ClientRealm ));
KerbErr = KdcExpandTransitedRealms(
&FullDomainList,
&CountOfDomains,
OldTransitedField
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Now loop through the domains. Based on the compression, we know that
// higher up domains come first.
//
for (Index = 0; Index < CountOfDomains ; Index++ )
{
LastComparison = Comparison;
Comparison = KerbCompareDomains(
TransitedRealm,
&FullDomainList[Index],
&PostfixOffset
);
if (Comparison == Above)
{
//
// If the new domain is above an existing domain, it gets inserted
// before the existing domain because all the existing domains
// are ordered from top to bottom
//
NewEntryIndex = Index;
break;
}
else if (Comparison == Below)
{
//
// There may be other domains below which are closer, so
// store the result and continue.
//
LastComparison = Comparison;
}
else if (Comparison == NotEqual)
{
//
// The domains aren't above or below each other. If the last
// comparison was below, stick the domain underneath it.
//
if (LastComparison == Below)
{
NewEntryIndex = Index;
break;
}
}
}
//
// If we didn't find a place for it, stick it in at the end.
//
if (NewEntryIndex == 0xffffffff)
{
NewEntryIndex = Index;
}
//
// Now build the new encoding
//
KerbErr = KdcCompressTransitedRealms(
NewTransitedField,
FullDomainList,
CountOfDomains,
TransitedRealm,
NewEntryIndex
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Cleanup:
if (FullDomainList != NULL)
{
for (Index = 0; Index < CountOfDomains ; Index++ )
{
KerbFreeString(&FullDomainList[Index]);
}
MIDL_user_free(FullDomainList);
}
return(KerbErr);
}