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.
2173 lines
58 KiB
2173 lines
58 KiB
/*++
|
|
|
|
Copyright (c) 1987-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ftnfoctx.c
|
|
|
|
Abstract:
|
|
|
|
Utility routines to manipulate the forest trust context
|
|
|
|
Author:
|
|
|
|
27-Jul-00 (cliffv)
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <netdebug.h>
|
|
#include <ntlsa.h>
|
|
#include <ftnfoctx.h>
|
|
#include <align.h> // ROUND_UP_POINTER
|
|
#include <rpcutil.h> // MIDL_user_free
|
|
#include <stdlib.h> // qsort
|
|
|
|
|
|
VOID
|
|
NetpInitFtinfoContext(
|
|
OUT PNL_FTINFO_CONTEXT FtinfoContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to initialize the Ftinfo context structure.
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to initialize
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
RtlZeroMemory( FtinfoContext, sizeof(*FtinfoContext) );
|
|
InitializeListHead( &FtinfoContext->FtinfoList );
|
|
}
|
|
|
|
|
|
VOID
|
|
NetpMarshalFtinfoEntry (
|
|
IN PLSA_FOREST_TRUST_RECORD InFtinfoRecord,
|
|
OUT PLSA_FOREST_TRUST_RECORD OutFtinfoRecord,
|
|
IN OUT LPBYTE *WherePtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to marshalls a single Ftinfo entry
|
|
|
|
Arguments:
|
|
|
|
InFtinfoRecord - Template to copy into InFtinfoRecord
|
|
|
|
OutFtinfoRecord - Entry to fill in
|
|
On input, points to a zeroed buffer.
|
|
|
|
WherePtr - On input, specifies where to marshal to.
|
|
On output, points to the first byte past the marshalled data.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
|
|
FALSE - if no memory can be allocated
|
|
|
|
--*/
|
|
{
|
|
LPBYTE Where = *WherePtr;
|
|
ULONG Size;
|
|
ULONG SidSize;
|
|
ULONG NameSize;
|
|
|
|
NetpAssert( Where == ROUND_UP_POINTER( Where, ALIGN_WORST ));
|
|
|
|
//
|
|
// Copy the fixed size data
|
|
//
|
|
|
|
OutFtinfoRecord->ForestTrustType = InFtinfoRecord->ForestTrustType;
|
|
OutFtinfoRecord->Flags = InFtinfoRecord->Flags;
|
|
OutFtinfoRecord->Time = InFtinfoRecord->Time;
|
|
|
|
|
|
//
|
|
// Fill in a domain entry
|
|
//
|
|
|
|
switch( InFtinfoRecord->ForestTrustType ) {
|
|
|
|
case ForestTrustDomainInfo:
|
|
|
|
//
|
|
// Copy the DWORD aligned data
|
|
//
|
|
|
|
if ( InFtinfoRecord->ForestTrustData.DomainInfo.Sid != NULL ) {
|
|
SidSize = RtlLengthSid( InFtinfoRecord->ForestTrustData.DomainInfo.Sid );
|
|
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.Sid = (PISID) Where;
|
|
RtlCopyMemory( Where, InFtinfoRecord->ForestTrustData.DomainInfo.Sid, SidSize );
|
|
Where += SidSize;
|
|
|
|
}
|
|
|
|
//
|
|
// Copy the WCHAR aligned data
|
|
//
|
|
|
|
NameSize = InFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Length;
|
|
if ( NameSize != 0 ) {
|
|
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Buffer = (LPWSTR) Where;
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.DnsName.MaximumLength = (USHORT) (NameSize+sizeof(WCHAR));
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Length = (USHORT)NameSize;
|
|
|
|
RtlCopyMemory( Where, InFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Buffer, NameSize );
|
|
Where += NameSize;
|
|
|
|
*((LPWSTR)Where) = L'\0';
|
|
Where += sizeof(WCHAR);
|
|
}
|
|
|
|
NameSize = InFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Length;
|
|
if ( NameSize != 0 ) {
|
|
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Buffer = (LPWSTR) Where;
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.MaximumLength = (USHORT) (NameSize+sizeof(WCHAR));
|
|
OutFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Length = (USHORT)NameSize;
|
|
|
|
RtlCopyMemory( Where, InFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Buffer, NameSize );
|
|
Where += NameSize;
|
|
|
|
*((LPWSTR)Where) = L'\0';
|
|
Where += sizeof(WCHAR);
|
|
}
|
|
|
|
break;
|
|
|
|
//
|
|
// Fill in a TLN entry
|
|
//
|
|
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
|
|
//
|
|
// Copy the WCHAR aligned data
|
|
//
|
|
|
|
NameSize = InFtinfoRecord->ForestTrustData.TopLevelName.Length;
|
|
if ( NameSize != 0 ) {
|
|
|
|
OutFtinfoRecord->ForestTrustData.TopLevelName.Buffer = (LPWSTR) Where;
|
|
OutFtinfoRecord->ForestTrustData.TopLevelName.MaximumLength = (USHORT) (NameSize+sizeof(WCHAR));
|
|
OutFtinfoRecord->ForestTrustData.TopLevelName.Length = (USHORT)NameSize;
|
|
|
|
RtlCopyMemory( Where, InFtinfoRecord->ForestTrustData.TopLevelName.Buffer, NameSize );
|
|
Where += NameSize;
|
|
|
|
*((LPWSTR)Where) = L'\0';
|
|
Where += sizeof(WCHAR);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
NetpAssert( FALSE );
|
|
}
|
|
|
|
Where = ROUND_UP_POINTER( Where, ALIGN_WORST );
|
|
*WherePtr = Where;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
NetpCompareHelper (
|
|
IN PUNICODE_STRING Name,
|
|
IN OUT PULONG Index,
|
|
OUT PUNICODE_STRING CurrentLabel
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is a helper routine for finding the next rightmost label in a string.
|
|
|
|
|
|
Arguments:
|
|
|
|
Name - The input dns name. The dns name should not have a trailing .
|
|
|
|
Index - On input, should contain the value returned by the previous call to this routine.
|
|
On input for the first call, should be set to Name->Length/sizeof(WCHAR).
|
|
On output, zero is returned to indicate that this is the last of the name. The
|
|
caller should not call again. Any other value output is a context for the next
|
|
call to this routine.
|
|
|
|
CurrentLabel - Returns a descriptor describing the substring which is the next label.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG PreviousIndex = *Index;
|
|
ULONG CurrentIndex = *Index;
|
|
ULONG LabelIndex;
|
|
|
|
NetpAssert( CurrentIndex != 0 );
|
|
|
|
//
|
|
// Find the beginning of the next label
|
|
//
|
|
|
|
while ( CurrentIndex > 0 ) {
|
|
CurrentIndex--;
|
|
if ( Name->Buffer[CurrentIndex] == L'.' ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( CurrentIndex == 0 ) {
|
|
LabelIndex = CurrentIndex;
|
|
} else {
|
|
LabelIndex = CurrentIndex + 1;
|
|
}
|
|
|
|
//
|
|
// Return it to the caller
|
|
//
|
|
|
|
CurrentLabel->Buffer = &Name->Buffer[LabelIndex];
|
|
CurrentLabel->Length = (USHORT)((PreviousIndex - LabelIndex) * sizeof(WCHAR));
|
|
CurrentLabel->MaximumLength = CurrentLabel->Length;
|
|
|
|
*Index = CurrentIndex;
|
|
|
|
}
|
|
|
|
|
|
int
|
|
NetpCompareDnsNameWithSortOrder(
|
|
IN PUNICODE_STRING Name1,
|
|
IN PUNICODE_STRING Name2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to compare two DNS names. The DNS names must not have a trailing "."
|
|
|
|
Labels are compare right to left to present a pleasent viewing order.
|
|
|
|
Arguments:
|
|
|
|
Name1 - First name to compare.
|
|
|
|
Name2 - Second name to compare.
|
|
|
|
Return Value:
|
|
|
|
Signed value that gives the results of the comparison:
|
|
|
|
Zero - String1 equals String2
|
|
|
|
< Zero - String1 less than String2
|
|
|
|
> Zero - String1 greater than String2
|
|
|
|
--*/
|
|
{
|
|
ULONG Index1 = Name1->Length/sizeof(WCHAR);
|
|
ULONG Index2 = Name2->Length/sizeof(WCHAR);
|
|
|
|
UNICODE_STRING Label1;
|
|
UNICODE_STRING Label2;
|
|
|
|
LONG Result;
|
|
|
|
|
|
//
|
|
// Loop comparing labels
|
|
//
|
|
|
|
while ( Index1 != 0 && Index2 != 0 ) {
|
|
|
|
//
|
|
// Get the next label from each string
|
|
//
|
|
|
|
NetpCompareHelper ( Name1, &Index1, &Label1 );
|
|
NetpCompareHelper ( Name2, &Index2, &Label2 );
|
|
|
|
//
|
|
// If the labels are different,
|
|
// return that result to the caller.
|
|
//
|
|
|
|
Result = RtlCompareUnicodeString( &Label1, &Label2, TRUE );
|
|
|
|
if ( Result != 0 ) {
|
|
return (int)Result;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// ASSERT: one label is a (proper) substring of the other
|
|
//
|
|
// If the first name is longer, indicate it is greater than the second
|
|
//
|
|
|
|
return Index1-Index2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int __cdecl NetpCompareFtinfoEntryDns(
|
|
const void *String1,
|
|
const void *String2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
qsort comparison routine for Dns string in Ftinfo entries
|
|
|
|
Arguments:
|
|
|
|
String1: First string to compare
|
|
|
|
String2: Second string to compare
|
|
|
|
Return Value:
|
|
|
|
Signed value that gives the results of the comparison:
|
|
|
|
Zero - String1 equals String2
|
|
|
|
< Zero - String1 less than String2
|
|
|
|
> Zero - String1 greater than String2
|
|
|
|
--*/
|
|
{
|
|
PLSA_FOREST_TRUST_RECORD Entry1 = *((PLSA_FOREST_TRUST_RECORD *)String1);
|
|
PLSA_FOREST_TRUST_RECORD Entry2 = *((PLSA_FOREST_TRUST_RECORD *)String2);
|
|
|
|
PUNICODE_STRING Name1;
|
|
PUNICODE_STRING Name2;
|
|
|
|
int Result;
|
|
|
|
//
|
|
// Get the name from the entry
|
|
//
|
|
|
|
switch ( Entry1->ForestTrustType ) {
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
Name1 = &Entry1->ForestTrustData.TopLevelName;
|
|
break;
|
|
case ForestTrustDomainInfo:
|
|
Name1 = &Entry1->ForestTrustData.DomainInfo.DnsName;
|
|
break;
|
|
default:
|
|
//
|
|
// If Entry2 can be recognized,
|
|
// then entry 2 is less than this one.
|
|
//
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
case ForestTrustDomainInfo:
|
|
return 1; // This name is greater than the other
|
|
}
|
|
|
|
//
|
|
// Otherwise simply leave them in the same order
|
|
//
|
|
if ((Entry1 - Entry2) < 0 ) {
|
|
return -1;
|
|
} else if ((Entry1 - Entry2) > 0 ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
Name2 = &Entry2->ForestTrustData.TopLevelName;
|
|
break;
|
|
case ForestTrustDomainInfo:
|
|
Name2 = &Entry2->ForestTrustData.DomainInfo.DnsName;
|
|
break;
|
|
default:
|
|
//
|
|
// Since Entry1 is a recognized type,
|
|
// this Entry2 is greater.
|
|
//
|
|
return -1; // This name is greater than the other
|
|
}
|
|
|
|
|
|
//
|
|
// If the labels are different,
|
|
// return the difference to the caller.
|
|
//
|
|
|
|
Result = NetpCompareDnsNameWithSortOrder( Name1, Name2 );
|
|
|
|
if ( Result != 0 ) {
|
|
return Result;
|
|
}
|
|
|
|
//
|
|
// If the labels are the same,
|
|
// indicate TLNs are before domain info records.
|
|
//
|
|
|
|
return Entry1->ForestTrustType - Entry2->ForestTrustType;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
NetpCompareSid(
|
|
PSID Sid1,
|
|
PSID Sid2
|
|
)
|
|
/*++
|
|
|
|
Routine description:
|
|
|
|
SID comparison routine that actually indicates if one sid is greater than another
|
|
|
|
Arguments:
|
|
|
|
Sid1 - First Sid
|
|
|
|
Sid2 - Second Sid
|
|
|
|
Returns:
|
|
|
|
Signed value that gives the results of the comparison:
|
|
|
|
Zero - String1 equals String2
|
|
|
|
< Zero - String1 less than String2
|
|
|
|
> Zero - String1 greater than String2
|
|
|
|
--*/
|
|
{
|
|
DWORD Size1;
|
|
DWORD Size2;
|
|
LPBYTE Byte1;
|
|
LPBYTE Byte2;
|
|
ULONG i;
|
|
|
|
NetpAssert( Sid1 && RtlValidSid( Sid1 ));
|
|
NetpAssert( Sid2 && RtlValidSid( Sid2 ));
|
|
|
|
//
|
|
// The NULL SID is smaller
|
|
//
|
|
|
|
if ( Sid1 == NULL ) {
|
|
if ( Sid2 != NULL ) {
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( Sid2 == NULL ) {
|
|
if ( Sid1 != NULL ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The longer sid is greater
|
|
//
|
|
|
|
Size1 = RtlLengthSid( Sid1 );
|
|
Size2 = RtlLengthSid( Sid2 );
|
|
|
|
if ( Size1 != Size2 ) {
|
|
return Size1 - Size2;
|
|
}
|
|
|
|
//
|
|
// Otherwise compare the bytes
|
|
//
|
|
|
|
Byte1 = (LPBYTE)Sid1;
|
|
Byte2 = (LPBYTE)Sid2;
|
|
|
|
for ( i=0; i<Size1; i++ ) {
|
|
|
|
if ( Byte1[i] != Byte2[i] ) {
|
|
return Byte1[i] - Byte2[i];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int __cdecl NetpCompareFtinfoEntrySid(
|
|
const void *String1,
|
|
const void *String2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
qsort comparison routine for Sid string in Ftinfo entries
|
|
|
|
Arguments:
|
|
|
|
String1: First string to compare
|
|
|
|
String2: Second string to compare
|
|
|
|
Return Value:
|
|
|
|
Signed value that gives the results of the comparison:
|
|
|
|
Zero - String1 equals String2
|
|
|
|
< Zero - String1 less than String2
|
|
|
|
> Zero - String1 greater than String2
|
|
|
|
--*/
|
|
{
|
|
PLSA_FOREST_TRUST_RECORD Entry1 = *((PLSA_FOREST_TRUST_RECORD *)String1);
|
|
PLSA_FOREST_TRUST_RECORD Entry2 = *((PLSA_FOREST_TRUST_RECORD *)String2);
|
|
|
|
PSID Sid1;
|
|
PSID Sid2;
|
|
|
|
int Result;
|
|
|
|
//
|
|
// Get the Sid from the entry
|
|
//
|
|
|
|
switch ( Entry1->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
Sid1 = Entry1->ForestTrustData.DomainInfo.Sid;
|
|
break;
|
|
default:
|
|
//
|
|
// If Entry2 can be recognized,
|
|
// then entry 2 is less than this one.
|
|
//
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
return 1; // This name is greater than the other
|
|
}
|
|
|
|
//
|
|
// Otherwise simply leave them in the same order
|
|
//
|
|
if ((Entry1 - Entry2) < 0 ) {
|
|
return -1;
|
|
} else if ((Entry1 - Entry2) > 0 ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
Sid2 = Entry2->ForestTrustData.DomainInfo.Sid;
|
|
break;
|
|
default:
|
|
//
|
|
// Since Entry1 is a recognized type,
|
|
// this Entry2 is greater.
|
|
//
|
|
return -1; // This name is greater than the other
|
|
}
|
|
|
|
|
|
//
|
|
// Simply return the different of the sids.
|
|
//
|
|
|
|
return NetpCompareSid( Sid1, Sid2 );
|
|
|
|
}
|
|
|
|
|
|
|
|
int __cdecl NetpCompareFtinfoEntryNetbios(
|
|
const void *String1,
|
|
const void *String2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
qsort comparison routine for Netbios name in Ftinfo entries
|
|
|
|
Arguments:
|
|
|
|
String1: First string to compare
|
|
|
|
String2: Second string to compare
|
|
|
|
Return Value:
|
|
|
|
Signed value that gives the results of the comparison:
|
|
|
|
Zero - String1 equals String2
|
|
|
|
< Zero - String1 less than String2
|
|
|
|
> Zero - String1 greater than String2
|
|
|
|
--*/
|
|
{
|
|
PLSA_FOREST_TRUST_RECORD Entry1 = *((PLSA_FOREST_TRUST_RECORD *)String1);
|
|
PLSA_FOREST_TRUST_RECORD Entry2 = *((PLSA_FOREST_TRUST_RECORD *)String2);
|
|
|
|
PUNICODE_STRING Name1;
|
|
PUNICODE_STRING Name2;
|
|
|
|
int Result;
|
|
|
|
//
|
|
// Get the Sid from the entry
|
|
//
|
|
|
|
switch ( Entry1->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
Name1 = &Entry1->ForestTrustData.DomainInfo.NetbiosName;
|
|
if ( Name1->Length != 0 && Name1->Buffer != NULL ) {
|
|
break;
|
|
}
|
|
default:
|
|
//
|
|
// If Entry2 can be recognized,
|
|
// then entry 2 is less than this one.
|
|
//
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
return 1; // This name is greater than the other
|
|
}
|
|
|
|
//
|
|
// Otherwise simply leave them in the same order
|
|
//
|
|
if ((Entry1 - Entry2) < 0 ) {
|
|
return -1;
|
|
} else if ((Entry1 - Entry2) > 0 ) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch ( Entry2->ForestTrustType ) {
|
|
case ForestTrustDomainInfo:
|
|
Name2 = &Entry2->ForestTrustData.DomainInfo.NetbiosName;
|
|
if ( Name2->Length != 0 && Name2->Buffer != NULL ) {
|
|
break;
|
|
}
|
|
default:
|
|
//
|
|
// Since Entry1 is a recognized type,
|
|
// this Entry2 is greater.
|
|
//
|
|
return -1; // This name is greater than the other
|
|
}
|
|
|
|
|
|
//
|
|
// Simply return the difference of the names
|
|
//
|
|
|
|
return RtlCompareUnicodeString( Name1, Name2, TRUE );
|
|
|
|
}
|
|
|
|
|
|
|
|
PLSA_FOREST_TRUST_INFORMATION
|
|
NetpCopyFtinfoContext(
|
|
IN PNL_FTINFO_CONTEXT FtinfoContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to allocate an FTinfo array from an FTinfo context.
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to use
|
|
The caller must have previously called NetpInitFtinfoContext
|
|
|
|
Return Value:
|
|
|
|
FTinfo array. The caller should free this array using MIDL_user_free.
|
|
|
|
If NULL, not enough memory was available.
|
|
|
|
--*/
|
|
{
|
|
PNL_FTINFO_ENTRY FtinfoEntry;
|
|
PLIST_ENTRY ListEntry;
|
|
|
|
PLSA_FOREST_TRUST_INFORMATION LocalForestTrustInfo;
|
|
LPBYTE Where;
|
|
ULONG Size;
|
|
ULONG i;
|
|
PLSA_FOREST_TRUST_RECORD Entries;
|
|
|
|
//
|
|
// Allocate a structure to return to the caller.
|
|
//
|
|
|
|
Size = ROUND_UP_COUNT( sizeof( *LocalForestTrustInfo ), ALIGN_WORST) +
|
|
ROUND_UP_COUNT( FtinfoContext->FtinfoCount * sizeof(LSA_FOREST_TRUST_RECORD), ALIGN_WORST) +
|
|
ROUND_UP_COUNT( FtinfoContext->FtinfoCount * sizeof(PLSA_FOREST_TRUST_RECORD), ALIGN_WORST) +
|
|
FtinfoContext->FtinfoSize;
|
|
|
|
LocalForestTrustInfo = MIDL_user_allocate( Size );
|
|
|
|
if ( LocalForestTrustInfo == NULL ) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
RtlZeroMemory( LocalForestTrustInfo, Size );
|
|
Where = (LPBYTE)(LocalForestTrustInfo+1);
|
|
Where = ROUND_UP_POINTER( Where, ALIGN_WORST );
|
|
|
|
//
|
|
// Fill it in
|
|
//
|
|
|
|
LocalForestTrustInfo->RecordCount = FtinfoContext->FtinfoCount;
|
|
|
|
//
|
|
// Grab a huge chunk of ALIGN_WORST
|
|
// (We fill it in during the loop below.)
|
|
//
|
|
|
|
Entries = (PLSA_FOREST_TRUST_RECORD) Where;
|
|
Where = (LPBYTE)(&Entries[FtinfoContext->FtinfoCount]);
|
|
Where = ROUND_UP_POINTER( Where, ALIGN_WORST );
|
|
|
|
//
|
|
// Grab a huge chunk of dword aligned
|
|
// (We fill it in during the loop below.)
|
|
//
|
|
|
|
LocalForestTrustInfo->Entries = (PLSA_FOREST_TRUST_RECORD *) Where;
|
|
Where = (LPBYTE)(&LocalForestTrustInfo->Entries[FtinfoContext->FtinfoCount]);
|
|
Where = ROUND_UP_POINTER( Where, ALIGN_WORST );
|
|
|
|
//
|
|
// Fill in the individual entries
|
|
//
|
|
|
|
i = 0;
|
|
|
|
for ( ListEntry = FtinfoContext->FtinfoList.Flink ;
|
|
ListEntry != &FtinfoContext->FtinfoList ;
|
|
ListEntry = ListEntry->Flink) {
|
|
|
|
FtinfoEntry = CONTAINING_RECORD( ListEntry, NL_FTINFO_ENTRY, Next );
|
|
|
|
LocalForestTrustInfo->Entries[i] = &Entries[i];
|
|
|
|
NetpMarshalFtinfoEntry (
|
|
&FtinfoEntry->Record,
|
|
&Entries[i],
|
|
&Where );
|
|
|
|
i++;
|
|
}
|
|
|
|
NetpAssert( i == FtinfoContext->FtinfoCount );
|
|
NetpAssert( Where == ((LPBYTE)LocalForestTrustInfo) + Size );
|
|
|
|
//
|
|
// Sort them into alphabetical order
|
|
//
|
|
|
|
qsort( LocalForestTrustInfo->Entries,
|
|
LocalForestTrustInfo->RecordCount,
|
|
sizeof(PLSA_FOREST_TRUST_RECORD),
|
|
NetpCompareFtinfoEntryDns );
|
|
|
|
|
|
//
|
|
// Return the allocated buffer to the caller.
|
|
//
|
|
|
|
return LocalForestTrustInfo;
|
|
}
|
|
|
|
|
|
VOID
|
|
NetpCleanFtinfoContext(
|
|
IN PNL_FTINFO_CONTEXT FtinfoContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to cleanup the Ftinfo context structure.
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to clean
|
|
The caller must have previously called NetpInitFtinfoContext
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PNL_FTINFO_ENTRY FtinfoEntry;
|
|
|
|
//
|
|
// Loop freeing the entries
|
|
//
|
|
|
|
while ( !IsListEmpty( &FtinfoContext->FtinfoList ) ) {
|
|
|
|
//
|
|
// Delink an entry
|
|
//
|
|
|
|
ListEntry = RemoveHeadList( &FtinfoContext->FtinfoList );
|
|
|
|
FtinfoEntry = CONTAINING_RECORD( ListEntry, NL_FTINFO_ENTRY, Next );
|
|
|
|
FtinfoContext->FtinfoCount -= 1;
|
|
FtinfoContext->FtinfoSize -= FtinfoEntry->Size;
|
|
RtlFreeHeap( RtlProcessHeap(), 0, FtinfoEntry );
|
|
}
|
|
NetpAssert( FtinfoContext->FtinfoCount == 0 );
|
|
NetpAssert( FtinfoContext->FtinfoSize == 0 );
|
|
}
|
|
|
|
|
|
PLSA_FOREST_TRUST_RECORD
|
|
NetpAllocFtinfoEntry2 (
|
|
IN PNL_FTINFO_CONTEXT FtinfoContext,
|
|
IN PLSA_FOREST_TRUST_RECORD InFtinfoRecord
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Same as NetpAllocFtinfoEntry except takes a template of an FTinfo entry on input.
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to link the entry onto.
|
|
|
|
InFtinfoRecord - Template to copy into InFtinfoRecord
|
|
|
|
Return Value:
|
|
|
|
Returns the address of the allocated forest trust record.
|
|
|
|
The caller should not and cannot deallocate this buffer. It has a header and is
|
|
linked into the FtinfoContext.
|
|
|
|
|
|
Returns NULL if no memory can be allocated.
|
|
|
|
--*/
|
|
{
|
|
PNL_FTINFO_ENTRY FtinfoEntry;
|
|
ULONG Size = ROUND_UP_COUNT(sizeof(NL_FTINFO_ENTRY), ALIGN_WORST);
|
|
ULONG DataSize = 0;
|
|
LPBYTE Where;
|
|
|
|
//
|
|
// Compute the size of the entry.
|
|
//
|
|
|
|
switch( InFtinfoRecord->ForestTrustType ) {
|
|
|
|
case ForestTrustDomainInfo:
|
|
|
|
if ( InFtinfoRecord->ForestTrustData.DomainInfo.Sid != NULL ) {
|
|
DataSize += RtlLengthSid( InFtinfoRecord->ForestTrustData.DomainInfo.Sid );
|
|
}
|
|
if ( InFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Length != 0 ) {
|
|
DataSize += InFtinfoRecord->ForestTrustData.DomainInfo.DnsName.Length + sizeof(WCHAR);
|
|
}
|
|
if ( InFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Length != 0 ) {
|
|
DataSize += InFtinfoRecord->ForestTrustData.DomainInfo.NetbiosName.Length + sizeof(WCHAR);
|
|
}
|
|
|
|
break;
|
|
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
|
|
if ( InFtinfoRecord->ForestTrustData.TopLevelName.Length != 0 ) {
|
|
DataSize += InFtinfoRecord->ForestTrustData.TopLevelName.Length + sizeof(WCHAR);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NetpAssert( FALSE );
|
|
return NULL;
|
|
}
|
|
|
|
DataSize = ROUND_UP_COUNT(DataSize, ALIGN_WORST);
|
|
|
|
//
|
|
// Allocate an entry
|
|
//
|
|
|
|
Size += DataSize;
|
|
FtinfoEntry = RtlAllocateHeap( RtlProcessHeap(), 0, Size );
|
|
|
|
if ( FtinfoEntry == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
RtlZeroMemory( FtinfoEntry, Size );
|
|
Where = (LPBYTE)(FtinfoEntry+1);
|
|
|
|
//
|
|
// Fill it in.
|
|
//
|
|
|
|
FtinfoEntry->Size = DataSize;
|
|
|
|
NetpMarshalFtinfoEntry ( InFtinfoRecord,
|
|
&FtinfoEntry->Record,
|
|
&Where );
|
|
|
|
NetpAssert( Where == ((LPBYTE)FtinfoEntry) + Size )
|
|
|
|
//
|
|
// Link it onto the list
|
|
//
|
|
|
|
InsertHeadList( &FtinfoContext->FtinfoList, &FtinfoEntry->Next );
|
|
FtinfoContext->FtinfoSize += FtinfoEntry->Size;
|
|
FtinfoContext->FtinfoCount += 1;
|
|
|
|
return &FtinfoEntry->Record;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NetpAllocFtinfoEntry (
|
|
IN PNL_FTINFO_CONTEXT FtinfoContext,
|
|
IN LSA_FOREST_TRUST_RECORD_TYPE ForestTrustType,
|
|
IN PUNICODE_STRING Name,
|
|
IN PSID Sid,
|
|
IN PUNICODE_STRING NetbiosName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to allocate a single Ftinfo entry and link it onto the context.
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to link the entry onto.
|
|
|
|
ForestTypeType - Specifies the type of record to allocate. This must be
|
|
ForestTrustTopLevelName or ForestTrustDomainInfo.
|
|
|
|
Name - Specifies the name for the record.
|
|
|
|
Sid - Specifies the SID for the record. (Ignored for ForestTrustTopLevelName.)
|
|
|
|
NetbiosName - Specifies the netbios name for the record. (Ignored for ForestTrustTopLevelName.)
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
|
|
FALSE - if no memory can be allocated
|
|
|
|
--*/
|
|
{
|
|
LSA_FOREST_TRUST_RECORD FtinfoRecord = {0};
|
|
|
|
//
|
|
// Initialize the template Ftinfo entry
|
|
//
|
|
|
|
FtinfoRecord.ForestTrustType = ForestTrustType;
|
|
|
|
switch( ForestTrustType ) {
|
|
|
|
case ForestTrustDomainInfo:
|
|
|
|
FtinfoRecord.ForestTrustData.DomainInfo.Sid = Sid;
|
|
FtinfoRecord.ForestTrustData.DomainInfo.DnsName = *Name;
|
|
FtinfoRecord.ForestTrustData.DomainInfo.NetbiosName = *NetbiosName;
|
|
|
|
break;
|
|
|
|
case ForestTrustTopLevelName:
|
|
case ForestTrustTopLevelNameEx:
|
|
|
|
FtinfoRecord.ForestTrustData.TopLevelName = *Name;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NetpAssert( FALSE );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Call the routine that takes a template and does the rest of the job
|
|
//
|
|
|
|
return (NetpAllocFtinfoEntry2( FtinfoContext, &FtinfoRecord ) != NULL);
|
|
}
|
|
|
|
BOOLEAN
|
|
NetpIsSubordinate(
|
|
IN const UNICODE_STRING * Subordinate,
|
|
IN const UNICODE_STRING * Superior,
|
|
IN BOOLEAN EqualReturnsTrue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines if Subordinate string is indeed subordinate to Superior
|
|
For example, "NY.acme.com" is subordinate to "acme.com", but
|
|
"NY.acme.com" is NOT subordinate to "me.com" or "NY.acme.com"
|
|
|
|
Arguments:
|
|
|
|
Subordinate name to test for subordinate status
|
|
|
|
Superior name to test for superior status
|
|
|
|
EqualReturnsTrue - TRUE if equal names should return TRUE also
|
|
|
|
Returns:
|
|
|
|
TRUE is Subordinate is subordinate to Superior
|
|
|
|
FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
USHORT SubIndex, SupIndex;
|
|
UNICODE_STRING Temp;
|
|
|
|
ASSERT( Subordinate && Subordinate->Buffer );
|
|
ASSERT( Superior && Superior->Buffer );
|
|
|
|
//
|
|
// If equal names are to be considered subordinate,
|
|
// compare the names for equality.
|
|
//
|
|
|
|
if ( EqualReturnsTrue &&
|
|
RtlEqualUnicodeString( Subordinate, Superior, TRUE )) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// A subordinate name must be longer than the superior name
|
|
//
|
|
|
|
if ( Subordinate->Length <= Superior->Length ) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Subordinate name must be separated from the superior part by a period
|
|
//
|
|
|
|
if ( Subordinate->Buffer[( Subordinate->Length - Superior->Length ) / sizeof( WCHAR ) - 1] != L'.' ) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Ensure the trailing part of the two names are the same.
|
|
//
|
|
|
|
Temp = *Subordinate;
|
|
Temp.Buffer += ( Subordinate->Length - Superior->Length ) / sizeof( WCHAR );
|
|
Temp.Length = Superior->Length;
|
|
Temp.MaximumLength = Temp.Length;
|
|
|
|
if ( !RtlEqualUnicodeString( &Temp, Superior, TRUE )) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NetpAddTlnFtinfoEntry (
|
|
IN PNL_FTINFO_CONTEXT FtinfoContext,
|
|
IN PUNICODE_STRING Name
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Routine to add a TLN Ftinfo entry to the list.
|
|
|
|
If there is already a TLN that is equal to or superior to this one, this TLN is
|
|
ignored. (e.g., a TLN of a.acme.com is ignored of acme.com already exists in the list.)
|
|
|
|
If there is already a TLN that is inferior to this one, the inferior TLN is
|
|
removed and this one is added. (e.g., a TLN of acme.com causes an existing TLN of
|
|
a.acme.com to be replaced by the new entry.)
|
|
|
|
Arguments:
|
|
|
|
FtinfoContext - Context to link the entry onto.
|
|
|
|
Name - Specifies the name for the record.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
|
|
FALSE - if no memory can be allocated
|
|
|
|
--*/
|
|
{
|
|
PNL_FTINFO_ENTRY FtinfoEntry;
|
|
PLIST_ENTRY ListEntry;
|
|
|
|
|
|
//
|
|
// Loop through the list of existing entries
|
|
//
|
|
|
|
for ( ListEntry = FtinfoContext->FtinfoList.Flink ;
|
|
ListEntry != &FtinfoContext->FtinfoList ;
|
|
) {
|
|
|
|
FtinfoEntry = CONTAINING_RECORD( ListEntry, NL_FTINFO_ENTRY, Next );
|
|
ListEntry = ListEntry->Flink;
|
|
|
|
//
|
|
// Ignore entries that aren't TLNs.
|
|
//
|
|
|
|
if ( FtinfoEntry->Record.ForestTrustType != ForestTrustTopLevelName ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If the new name is subordinate (or equal to) to one already in the list,
|
|
// ignore the new name.
|
|
//
|
|
|
|
if ( NetpIsSubordinate( Name,
|
|
&FtinfoEntry->Record.ForestTrustData.TopLevelName,
|
|
TRUE ) ) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If the existing name is subordinate to the new name,
|
|
// remove the existing name.
|
|
//
|
|
|
|
if ( NetpIsSubordinate( &FtinfoEntry->Record.ForestTrustData.TopLevelName,
|
|
Name,
|
|
FALSE ) ) {
|
|
|
|
RemoveEntryList( &FtinfoEntry->Next );
|
|
FtinfoContext->FtinfoCount -= 1;
|
|
FtinfoContext->FtinfoSize -= FtinfoEntry->Size;
|
|
RtlFreeHeap( RtlProcessHeap(), 0, FtinfoEntry );
|
|
|
|
// continue looping since there may be more names to remove
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Add the new entry to the list
|
|
//
|
|
|
|
return NetpAllocFtinfoEntry( FtinfoContext,
|
|
ForestTrustTopLevelName,
|
|
Name,
|
|
NULL, // No sid
|
|
NULL ); // No Netbios name
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
NetpMergeFtinfoHelper(
|
|
IN PLSA_FOREST_TRUST_INFORMATION NewForestTrustInfo,
|
|
IN PLSA_FOREST_TRUST_INFORMATION OldForestTrustInfo,
|
|
IN OUT PULONG NewIndex,
|
|
IN OUT PULONG OldIndex,
|
|
OUT PLSA_FOREST_TRUST_RECORD *NewEntry,
|
|
OUT PLSA_FOREST_TRUST_RECORD *OldEntry,
|
|
OUT PULONG OldFlags,
|
|
IN int (__cdecl *Routine) (const void *, const void *)
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks a pair of FTinfo arrays in sorted order and returns the next
|
|
entry. If both entries are the same in the sort order, this routine returns an entry
|
|
from both arrays
|
|
|
|
Arguments:
|
|
|
|
NewForestTrustInfo - Pointer to the first array
|
|
OldForestTrustInfo - Pointer to the second array
|
|
|
|
NewIndex - Current index into the first sorted array
|
|
OldIndex - Current index into the second sorted array
|
|
Before calling this routine the first time, the caller should set these parameters to zero.
|
|
Both indices zero triggers this routine to qsort the arrays.
|
|
The caller should *not* call this routine if both NewIndex and OldIndex are greater
|
|
than the corresponding record count.
|
|
|
|
NewEntry - Returns a pointer to an entry to be processed from the first sorted array.
|
|
OldEntry - Returns a pointer to an entry to be processed from the second sorted array.
|
|
Returns NULL if no entry is to be processed from the corresponding array.
|
|
|
|
OldFlags - Returns the Flags field that corresponds to OldEntry.
|
|
If there are duplicates of OldEntry, those duplicates are silently ignored by
|
|
this routine. This field returns the logical OR of the Flags field of those entries.
|
|
|
|
Routine - Comparison routine to passed to qsort to sort the FTinfo arrays.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
int RetVal;
|
|
|
|
//
|
|
// Sort the arrays
|
|
//
|
|
|
|
if ( *NewIndex == 0 && *OldIndex == 0 ) {
|
|
|
|
qsort( NewForestTrustInfo->Entries,
|
|
NewForestTrustInfo->RecordCount,
|
|
sizeof(PLSA_FOREST_TRUST_RECORD),
|
|
Routine );
|
|
|
|
qsort( OldForestTrustInfo->Entries,
|
|
OldForestTrustInfo->RecordCount,
|
|
sizeof(PLSA_FOREST_TRUST_RECORD),
|
|
Routine );
|
|
}
|
|
|
|
//
|
|
// Compare the first entry at the front of each list to determine which list
|
|
// to consume an entry from.
|
|
//
|
|
|
|
*NewEntry = NULL;
|
|
*OldEntry = NULL;
|
|
*OldFlags = 0;
|
|
|
|
if ( *NewIndex < NewForestTrustInfo->RecordCount ) {
|
|
|
|
//
|
|
// If neither list is empty,
|
|
// compare the entries to determine which is next.
|
|
//
|
|
|
|
if ( *OldIndex < OldForestTrustInfo->RecordCount ) {
|
|
|
|
RetVal = (*Routine)(
|
|
&NewForestTrustInfo->Entries[*NewIndex],
|
|
&OldForestTrustInfo->Entries[*OldIndex] );
|
|
|
|
//
|
|
// If the new entry is less than or equal to the old entry,
|
|
// consume the new entry.
|
|
//
|
|
|
|
if ( RetVal <= 0 ) {
|
|
*NewEntry = NewForestTrustInfo->Entries[*NewIndex];
|
|
(*NewIndex) ++;
|
|
}
|
|
|
|
//
|
|
// If the old entry is less than or equal to the new entry,
|
|
// consume the old entry.
|
|
//
|
|
|
|
if ( RetVal >= 0 ) {
|
|
*OldEntry = OldForestTrustInfo->Entries[*OldIndex];
|
|
(*OldIndex) ++;
|
|
}
|
|
|
|
//
|
|
// If the old list is empty and the new list isn't,
|
|
// consume an entry from the new list.
|
|
//
|
|
|
|
} else {
|
|
*NewEntry = NewForestTrustInfo->Entries[*NewIndex];
|
|
(*NewIndex) ++;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the new list is empty and the old list isn't,
|
|
// consume an entry from the old list.
|
|
//
|
|
if ( *OldIndex < OldForestTrustInfo->RecordCount ) {
|
|
|
|
*OldEntry = OldForestTrustInfo->Entries[*OldIndex];
|
|
(*OldIndex) ++;
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we're returning an "OldEntry",
|
|
// weed out all duplicates of that OldEntry.
|
|
//
|
|
|
|
|
|
if ( *OldEntry != NULL ) {
|
|
|
|
*OldFlags |= (*OldEntry)->Flags;
|
|
while ( *OldIndex < OldForestTrustInfo->RecordCount ) {
|
|
|
|
//
|
|
// Stop as soon as we hit an entry that isn't a duplicate.
|
|
//
|
|
|
|
RetVal = (*Routine)(
|
|
OldEntry,
|
|
&OldForestTrustInfo->Entries[*OldIndex] );
|
|
|
|
if ( RetVal != 0 ) {
|
|
break;
|
|
}
|
|
|
|
*OldFlags |= (*OldEntry)->Flags;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NetpMergeFtinfo(
|
|
IN PUNICODE_STRING TrustedDomainName,
|
|
IN PLSA_FOREST_TRUST_INFORMATION InNewForestTrustInfo,
|
|
IN PLSA_FOREST_TRUST_INFORMATION InOldForestTrustInfo OPTIONAL,
|
|
OUT PLSA_FOREST_TRUST_INFORMATION *MergedForestTrustInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function merges the changes from a new FTinfo into an old FTinfo and
|
|
produces the resultant FTinfo.
|
|
|
|
The merged FTinfo records are a combinition of the new and old records.
|
|
Here's where the merged records come from:
|
|
|
|
* The TLN exclusion records are copied from the TDO intact.
|
|
* The TLN record from the trusted domain that maps to the dns domain name of the
|
|
TDO is copied enabled. This reflects the LSA requirement that such a TLN not
|
|
be disabled. For instance, if the TDO is for a.acme.com and there is a TLN for
|
|
a.acme.com that TLN will be enabled. Also, if the TDO is for a.acme.com and
|
|
there is a TLN for acme.com, that TLN will be enabled.
|
|
* All other TLN records from the trusted domain are copied disabled with the
|
|
following exceptions. If there is an enabled TLN on the TDO, all TLNs from the
|
|
trusted domain that equal (or are subordinate to) the TDO TLN are marked as
|
|
disabled. This follows the philosophy that new TLNs are imported as enabled.
|
|
For instance, if the TDO had an enabled TLN for a.acme.com that TLN will still
|
|
be enabled after the automatic update. If the TDO had an enabled TLN for
|
|
acme.com and the trusted forest now has a TLN for a.acme.com, the resultant
|
|
FTinfo will have an enabled TLN for a.acme.com.
|
|
* The domain records from the trusted domain are copied enabled with the
|
|
following exceptions. If there is a disabled domain record on the TDO whose
|
|
dns domain name, or domain sid exactly matches the domain record, then the domain
|
|
remains disabled. If there is a domain record on the TDO whose netbios name is
|
|
disabled and whose netbios name exactly matches the netbios name on a domain
|
|
record, then the netbios name is disabled.
|
|
* Finally, orphaned exclusion records (those that are not subordinate to any TLN)
|
|
are removed (Bug #707630).
|
|
|
|
Arguments:
|
|
|
|
TrustedDomainName - Trusted domain that is to be updated.
|
|
|
|
NewForestTrustInfo - Specified the new array of FTinfo records as returned from the
|
|
TrustedDomainName.
|
|
The Flags field and Time field of the TLN entries are ignored.
|
|
|
|
OldForestTrustInfo - Specified the array of FTinfo records as returned from the
|
|
TDO. This field may be NULL if there is no existing records.
|
|
|
|
MergedForestTrustInfo - Returns the resulant FTinfo records.
|
|
The caller should free this buffer using MIDL_user_free.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: Success.
|
|
|
|
STATUS_INVALID_PARAMETER: One of the following happened:
|
|
* There was no New TLN that TrustedDomainName is subordinate to.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
LSA_FOREST_TRUST_INFORMATION OldForestTrustInfo;
|
|
LSA_FOREST_TRUST_INFORMATION NewForestTrustInfo;
|
|
LSA_FOREST_TRUST_INFORMATION NetbiosForestTrustInfo;
|
|
NL_FTINFO_CONTEXT FtinfoContext;
|
|
ULONG NewIndex;
|
|
ULONG OldIndex;
|
|
ULONG OldFlags;
|
|
PLSA_FOREST_TRUST_RECORD NewEntry;
|
|
PLSA_FOREST_TRUST_RECORD PreviousNewEntry;
|
|
PLSA_FOREST_TRUST_RECORD OldEntry;
|
|
BOOLEAN DomainTlnFound = FALSE;
|
|
PLSA_FOREST_TRUST_RECORD OldTlnPrefix;
|
|
ULONG Index;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*MergedForestTrustInfo = NULL;
|
|
NetpInitFtinfoContext( &FtinfoContext );
|
|
RtlZeroMemory( &OldForestTrustInfo, sizeof(OldForestTrustInfo) );
|
|
RtlZeroMemory( &NewForestTrustInfo, sizeof(NewForestTrustInfo) );
|
|
RtlZeroMemory( &NetbiosForestTrustInfo, sizeof(NetbiosForestTrustInfo) );
|
|
|
|
//
|
|
// Make a copy of the data that'll be qsorted so that we don't modify the caller's buffer.
|
|
//
|
|
|
|
if ( InOldForestTrustInfo != NULL ) {
|
|
OldForestTrustInfo.RecordCount = InOldForestTrustInfo->RecordCount;
|
|
OldForestTrustInfo.Entries = RtlAllocateHeap( RtlProcessHeap(), 0, OldForestTrustInfo.RecordCount * sizeof(PLSA_FOREST_TRUST_RECORD) );
|
|
if ( OldForestTrustInfo.Entries == NULL ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory( OldForestTrustInfo.Entries,
|
|
InOldForestTrustInfo->Entries,
|
|
OldForestTrustInfo.RecordCount * sizeof(PLSA_FOREST_TRUST_RECORD) );
|
|
}
|
|
|
|
NewForestTrustInfo.RecordCount = InNewForestTrustInfo->RecordCount;
|
|
NewForestTrustInfo.Entries = RtlAllocateHeap( RtlProcessHeap(), 0, NewForestTrustInfo.RecordCount * sizeof(PLSA_FOREST_TRUST_RECORD) );
|
|
if ( NewForestTrustInfo.Entries == NULL ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory( NewForestTrustInfo.Entries,
|
|
InNewForestTrustInfo->Entries,
|
|
NewForestTrustInfo.RecordCount * sizeof(PLSA_FOREST_TRUST_RECORD) );
|
|
|
|
//
|
|
// Allocate a temporary Ftinfo array containing all of the domain entries.
|
|
// Allocate it a worst case size.
|
|
//
|
|
|
|
NetbiosForestTrustInfo.Entries = RtlAllocateHeap(
|
|
RtlProcessHeap(),
|
|
0,
|
|
(OldForestTrustInfo.RecordCount+NewForestTrustInfo.RecordCount) * sizeof(PLSA_FOREST_TRUST_RECORD) );
|
|
|
|
if ( NetbiosForestTrustInfo.Entries == NULL ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Loop through each list in DNS canonical order processing the least entry.
|
|
//
|
|
// This loop handles TLN and TLNEX entries only
|
|
//
|
|
|
|
NewIndex = 0;
|
|
OldIndex = 0;
|
|
OldTlnPrefix = NULL;
|
|
PreviousNewEntry = NULL;
|
|
|
|
while ( NewIndex < NewForestTrustInfo.RecordCount ||
|
|
OldIndex < OldForestTrustInfo.RecordCount ) {
|
|
|
|
//
|
|
// Grab the next entry from each of the sorted arrays
|
|
//
|
|
|
|
NetpMergeFtinfoHelper( &NewForestTrustInfo,
|
|
&OldForestTrustInfo,
|
|
&NewIndex,
|
|
&OldIndex,
|
|
&NewEntry,
|
|
&OldEntry,
|
|
&OldFlags,
|
|
NetpCompareFtinfoEntryDns );
|
|
|
|
//
|
|
// Process the old entry
|
|
//
|
|
|
|
if ( OldEntry != NULL ) {
|
|
|
|
//
|
|
// Remember to most recent TLN record from the old array.
|
|
//
|
|
|
|
if ( OldEntry->ForestTrustType == ForestTrustTopLevelName ) {
|
|
|
|
OldTlnPrefix = OldEntry;
|
|
|
|
//
|
|
// TLN exclusion records are taken from the old entries
|
|
//
|
|
|
|
} else if ( OldEntry->ForestTrustType == ForestTrustTopLevelNameEx ) {
|
|
|
|
if ( NetpAllocFtinfoEntry2( &FtinfoContext, OldEntry ) == NULL ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process the new entry
|
|
//
|
|
|
|
if ( NewEntry != NULL ) {
|
|
|
|
//
|
|
// Handle TLN entries
|
|
//
|
|
|
|
if ( NewEntry->ForestTrustType == ForestTrustTopLevelName ) {
|
|
BOOLEAN SetTlnNewFlag;
|
|
LSA_FOREST_TRUST_RECORD NewEntryCopy;
|
|
|
|
//
|
|
// Make a copy of the new entry.
|
|
//
|
|
// We modify the entry to get the time and flags right. We don't want
|
|
// to modify the callers buffer.
|
|
//
|
|
|
|
NewEntryCopy = *NewEntry;
|
|
|
|
//
|
|
// Ignore duplicate new entries
|
|
//
|
|
// If the name of this new entry is subordinate to the previous new entry,
|
|
// then this TLN can be quietly dropped.
|
|
//
|
|
// This is the case where the trusted domain sent us a TLN for both
|
|
// acme.com and a.acme.com. The second entry is a duplicate.
|
|
//
|
|
|
|
if ( PreviousNewEntry != NULL &&
|
|
PreviousNewEntry->ForestTrustType == ForestTrustTopLevelName ) {
|
|
|
|
if ( NetpIsSubordinate( &NewEntry->ForestTrustData.TopLevelName,
|
|
&PreviousNewEntry->ForestTrustData.TopLevelName,
|
|
TRUE ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// By default any TLN from the new list should be marked as new.
|
|
//
|
|
|
|
SetTlnNewFlag = TRUE;
|
|
|
|
//
|
|
// Set the flags and timestamp on the new entry.
|
|
//
|
|
// If we're processing an entry from both lists,
|
|
// grab the flags and timestamp from the old entry.
|
|
//
|
|
|
|
if ( OldEntry != NULL ) {
|
|
|
|
NewEntryCopy.Flags = OldFlags;
|
|
NewEntryCopy.Time = OldEntry->Time;
|
|
|
|
// This entry isn't 'new'.
|
|
SetTlnNewFlag = FALSE;
|
|
|
|
//
|
|
// Otherwise indicate that we have no information
|
|
//
|
|
|
|
} else {
|
|
|
|
NewEntryCopy.Flags = 0;
|
|
NewEntryCopy.Time.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// If this new entry is subordinate to the most recent old TLN record,
|
|
// use the flag bits from that most recent old TLN record.
|
|
//
|
|
|
|
if ( OldTlnPrefix != NULL &&
|
|
NetpIsSubordinate( &NewEntryCopy.ForestTrustData.TopLevelName,
|
|
&OldTlnPrefix->ForestTrustData.TopLevelName,
|
|
FALSE ) ) {
|
|
|
|
//
|
|
// If the old TLN was disabled by the admin,
|
|
// so should the new entry.
|
|
//
|
|
|
|
if ( OldTlnPrefix->Flags & LSA_TLN_DISABLED_ADMIN ) {
|
|
|
|
NewEntryCopy.Flags |= LSA_TLN_DISABLED_ADMIN;
|
|
SetTlnNewFlag = FALSE;
|
|
|
|
//
|
|
// If the old TLN was enabled,
|
|
// so should the new entry.
|
|
//
|
|
|
|
} else if ( (OldTlnPrefix->Flags & LSA_FTRECORD_DISABLED_REASONS) == 0 ) {
|
|
|
|
SetTlnNewFlag = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the name of the forest is subordinate of or equal to the TLN name,
|
|
// enable the entry.
|
|
//
|
|
|
|
if ( NetpIsSubordinate( TrustedDomainName,
|
|
&NewEntryCopy.ForestTrustData.TopLevelName,
|
|
TRUE )) {
|
|
|
|
SetTlnNewFlag = FALSE;
|
|
DomainTlnFound = TRUE;
|
|
}
|
|
|
|
//
|
|
// If this is a new TLN,
|
|
// mark it as such.
|
|
//
|
|
|
|
if ( SetTlnNewFlag ) {
|
|
|
|
NewEntryCopy.Flags |= LSA_TLN_DISABLED_NEW;
|
|
}
|
|
|
|
//
|
|
// Merge the new entry into the list
|
|
//
|
|
|
|
if ( NetpAllocFtinfoEntry2( &FtinfoContext, &NewEntryCopy ) == NULL ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Remember this previous entry for the next iteration.
|
|
//
|
|
|
|
PreviousNewEntry = NewEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through each list in SID canonical order processing the least entry.
|
|
//
|
|
// This loop handles DOMAIN entries only
|
|
//
|
|
// This is in a separate loop since we want to process domain entries in SID order
|
|
// to ensure the correct disabled bits are merged from the old list even though the
|
|
// DNS domain name changes.
|
|
//
|
|
|
|
NewIndex = 0;
|
|
OldIndex = 0;
|
|
PreviousNewEntry = NULL;
|
|
|
|
while ( NewIndex < NewForestTrustInfo.RecordCount ||
|
|
OldIndex < OldForestTrustInfo.RecordCount ) {
|
|
|
|
//
|
|
// Grab the next entry from each of the sorted arrays
|
|
//
|
|
|
|
NetpMergeFtinfoHelper( &NewForestTrustInfo,
|
|
&OldForestTrustInfo,
|
|
&NewIndex,
|
|
&OldIndex,
|
|
&NewEntry,
|
|
&OldEntry,
|
|
&OldFlags,
|
|
NetpCompareFtinfoEntrySid );
|
|
|
|
//
|
|
// Ignore the netbios bits for now (We'll get them on the next pass through the data.)
|
|
//
|
|
|
|
OldFlags &= ~(LSA_NB_DISABLED_ADMIN|LSA_NB_DISABLED_CONFLICT);
|
|
|
|
//
|
|
// Process the old entry
|
|
//
|
|
|
|
if ( OldEntry != NULL ) {
|
|
|
|
//
|
|
// Don't let the lack of a new entry allow an admin disabled entry to be deleted.
|
|
//
|
|
|
|
if ( OldEntry->ForestTrustType == ForestTrustDomainInfo &&
|
|
(OldFlags & LSA_SID_DISABLED_ADMIN) != 0 &&
|
|
NewEntry == NULL ) {
|
|
|
|
//
|
|
// Make a copy of the entry to ensure we don't modify the caller's buffer
|
|
//
|
|
|
|
LSA_FOREST_TRUST_RECORD OldEntryCopy;
|
|
|
|
OldEntryCopy = *OldEntry;
|
|
OldEntryCopy.Flags = OldFlags;
|
|
|
|
//
|
|
// Allocate entry.
|
|
//
|
|
// Remember the address of the entry for the netbios pass.
|
|
//
|
|
|
|
NetbiosForestTrustInfo.Entries[NetbiosForestTrustInfo.RecordCount] =
|
|
NetpAllocFtinfoEntry2( &FtinfoContext, &OldEntryCopy );
|
|
|
|
if ( NetbiosForestTrustInfo.Entries[NetbiosForestTrustInfo.RecordCount] == NULL ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetbiosForestTrustInfo.RecordCount++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process the new entry
|
|
//
|
|
|
|
if ( NewEntry != NULL ) {
|
|
|
|
//
|
|
// Handle domain entries
|
|
//
|
|
|
|
if ( NewEntry->ForestTrustType == ForestTrustDomainInfo ) {
|
|
LSA_FOREST_TRUST_RECORD NewEntryCopy;
|
|
|
|
//
|
|
// Make a copy of the new entry.
|
|
//
|
|
// We modify the entry to get the time and flags right. We don't want
|
|
// to modify the callers buffer.
|
|
//
|
|
|
|
NewEntryCopy = *NewEntry;
|
|
|
|
//
|
|
// Ignore duplicate new entries
|
|
//
|
|
// If the name of this new entry is subordinate to the previous new entry,
|
|
// then this entry can be quietly dropped.
|
|
//
|
|
// We arbitrarily drop the second entry even though the other fields of the
|
|
// triple might be different.
|
|
//
|
|
|
|
if ( PreviousNewEntry != NULL &&
|
|
PreviousNewEntry->ForestTrustType == ForestTrustDomainInfo ) {
|
|
|
|
if ( RtlEqualSid( NewEntryCopy.ForestTrustData.DomainInfo.Sid,
|
|
PreviousNewEntry->ForestTrustData.DomainInfo.Sid ) ) {
|
|
continue;
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the flags and timestamp on the new entry.
|
|
//
|
|
// If we're processing an entry from both lists,
|
|
// grab the flags and timestamp from the old entry.
|
|
//
|
|
|
|
if ( OldEntry != NULL ) {
|
|
|
|
NewEntryCopy.Flags = OldFlags;
|
|
NewEntryCopy.Time = OldEntry->Time;
|
|
|
|
//
|
|
// Otherwise indicate that we have no information
|
|
//
|
|
|
|
} else {
|
|
NewEntryCopy.Flags = 0;
|
|
NewEntryCopy.Time.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// Merge the new entry into the list
|
|
//
|
|
// Remember the address of the entry for the netbios pass.
|
|
//
|
|
|
|
NetbiosForestTrustInfo.Entries[NetbiosForestTrustInfo.RecordCount] =
|
|
NetpAllocFtinfoEntry2( &FtinfoContext, &NewEntryCopy );
|
|
|
|
if ( NetbiosForestTrustInfo.Entries[NetbiosForestTrustInfo.RecordCount] == NULL ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetbiosForestTrustInfo.RecordCount++;
|
|
|
|
//
|
|
// Ensure there is a TLN for this domain entry
|
|
//
|
|
|
|
if ( !NetpAddTlnFtinfoEntry ( &FtinfoContext,
|
|
&NewEntryCopy.ForestTrustData.DomainInfo.DnsName ) ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Remember this previous entry for the next iteration.
|
|
//
|
|
PreviousNewEntry = NewEntry;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through each list in Netbios canonical order processing the least entry.
|
|
//
|
|
// This loop handle the Netbios name in the domain entries.
|
|
//
|
|
// This is in a separate loop since we want to process domain entries in Netbios order
|
|
// to ensure the correct disabled bits are merged from the old list even though the
|
|
// DNS domain name or domain sid changes.
|
|
//
|
|
// This iteration is fundamentally different than the previous two. This iteration
|
|
// uses NetbiosForestTrustInfo as the 'new' array. It is a psuedo ftinfo array that
|
|
// is built as the list of all the domain entries that have been copied into FtinfoContext.
|
|
// So, this iteration simply has to find that pre-existing entry and set the flags
|
|
// appropriately.
|
|
//
|
|
|
|
NewIndex = 0;
|
|
OldIndex = 0;
|
|
PreviousNewEntry = NULL;
|
|
|
|
while ( NewIndex < NetbiosForestTrustInfo.RecordCount ||
|
|
OldIndex < OldForestTrustInfo.RecordCount ) {
|
|
|
|
//
|
|
// Grab the next entry from each of the sorted arrays
|
|
//
|
|
|
|
NetpMergeFtinfoHelper( &NetbiosForestTrustInfo,
|
|
&OldForestTrustInfo,
|
|
&NewIndex,
|
|
&OldIndex,
|
|
&NewEntry,
|
|
&OldEntry,
|
|
&OldFlags,
|
|
NetpCompareFtinfoEntryNetbios );
|
|
|
|
//
|
|
// Ignore everything except the netbios bits.
|
|
//
|
|
// Everything else was processed on the previous iteration.
|
|
//
|
|
|
|
OldFlags &= (LSA_NB_DISABLED_ADMIN|LSA_NB_DISABLED_CONFLICT);
|
|
|
|
|
|
//
|
|
// This loop preserves the netbios disabled bits.
|
|
// If there is no old entry, there's nothing to preserve.
|
|
//
|
|
|
|
if ( OldEntry == NULL ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If there is no new entry,
|
|
// ensure the *admin* disabled bit it preserved anyway.
|
|
//
|
|
|
|
if ( NewEntry == NULL ) {
|
|
|
|
//
|
|
// Don't let the lack of a new entry allow an admin disabled entry to be deleted.
|
|
//
|
|
// Note that the newly added entry might have a duplicate DNS name or SID.
|
|
//
|
|
|
|
if ( OldEntry->ForestTrustType == ForestTrustDomainInfo &&
|
|
(OldFlags & LSA_NB_DISABLED_ADMIN) != 0 ) {
|
|
|
|
//
|
|
// Make a copy of the entry to ensure we don't modify the caller's buffer
|
|
//
|
|
|
|
LSA_FOREST_TRUST_RECORD OldEntryCopy;
|
|
|
|
OldEntryCopy = *OldEntry;
|
|
OldEntryCopy.Flags = OldFlags;
|
|
|
|
if ( !NetpAllocFtinfoEntry2( &FtinfoContext, &OldEntryCopy ) ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy any netbios disabled bits to the existing new entry.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// The NetbiosForestTrustInfo array only has domain entries.
|
|
// And the entries are equal so both must be domain entries.
|
|
//
|
|
|
|
NetpAssert( NewEntry->ForestTrustType == ForestTrustDomainInfo );
|
|
NetpAssert( OldEntry->ForestTrustType == ForestTrustDomainInfo );
|
|
|
|
NewEntry->Flags |= OldFlags;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Ensure there is a TLN that DomainName is subordinate to
|
|
//
|
|
|
|
if ( !DomainTlnFound ) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Return the collected entries to the caller.
|
|
//
|
|
|
|
*MergedForestTrustInfo = NetpCopyFtinfoContext( &FtinfoContext );
|
|
|
|
if ( *MergedForestTrustInfo == NULL ) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Remove orphaned TLN exclusion records from the merged information
|
|
//
|
|
|
|
for ( Index = 0; Index < (*MergedForestTrustInfo)->RecordCount; Index++ ) {
|
|
|
|
PLSA_FOREST_TRUST_RECORD This = (*MergedForestTrustInfo)->Entries[Index];
|
|
UNICODE_STRING * ExclusionName;
|
|
ULONG Index2;
|
|
BOOL Subordinate = FALSE;
|
|
|
|
if ( This->ForestTrustType != ForestTrustTopLevelNameEx ) {
|
|
|
|
//
|
|
// Only interested in orphaned exclusions
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
ExclusionName = &This->ForestTrustData.TopLevelName;
|
|
|
|
for ( Index2 = 0; Index2 < (*MergedForestTrustInfo)->RecordCount; Index2++ ) {
|
|
|
|
PLSA_FOREST_TRUST_RECORD Other = (*MergedForestTrustInfo)->Entries[Index2];
|
|
UNICODE_STRING * TopLevelName;
|
|
|
|
if ( Other->ForestTrustType != ForestTrustTopLevelName ) {
|
|
|
|
//
|
|
// Only interested in top level names (exclusion must be subordinate to it)
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
TopLevelName = &Other->ForestTrustData.TopLevelName;
|
|
|
|
//
|
|
// First perform a subordinate check where equality is not enough
|
|
//
|
|
|
|
if ( NetpIsSubordinate( ExclusionName, TopLevelName, FALSE )) {
|
|
|
|
Subordinate = TRUE;
|
|
|
|
//
|
|
// Now check for equality
|
|
//
|
|
|
|
} else if ( RtlEqualUnicodeString( ExclusionName, TopLevelName, TRUE )) {
|
|
|
|
//
|
|
// A top level name is the same as an exclusion name.
|
|
// Throw away the exclusion record, but ensure that
|
|
// the top level name record is marked "disabled".
|
|
//
|
|
|
|
if (( Other->Flags & LSA_FTRECORD_DISABLED_REASONS ) == 0 ) {
|
|
|
|
Other->Flags |= LSA_TLN_DISABLED_NEW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ( Subordinate ) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !Subordinate ) {
|
|
|
|
//
|
|
// This is an orphaned exclusion record. Remove it.
|
|
//
|
|
|
|
(*MergedForestTrustInfo)->RecordCount -= 1;
|
|
(*MergedForestTrustInfo)->Entries[Index] = (*MergedForestTrustInfo)->Entries[(*MergedForestTrustInfo)->RecordCount];
|
|
Index -= 1;
|
|
}
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Clean FtInfoContext
|
|
//
|
|
|
|
NetpCleanFtinfoContext( &FtinfoContext );
|
|
|
|
if ( OldForestTrustInfo.Entries != NULL ) {
|
|
|
|
RtlFreeHeap( RtlProcessHeap(), 0, OldForestTrustInfo.Entries );
|
|
}
|
|
|
|
if ( NewForestTrustInfo.Entries != NULL ) {
|
|
|
|
RtlFreeHeap( RtlProcessHeap(), 0, NewForestTrustInfo.Entries );
|
|
}
|
|
|
|
if ( NetbiosForestTrustInfo.Entries != NULL ) {
RtlFreeHeap( RtlProcessHeap(), 0, NetbiosForestTrustInfo.Entries );
}
return Status;
}
|