//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1997.
//
//  File:       mgroup.c
//
//  Contents:   LSA Mode Context API
//
//  Classes:
//
//  Functions:
//
//  History:    2-24-97   RichardW   Created
//
//----------------------------------------------------------------------------

#include "xtcbpkg.h"
#include <cryptdll.h>

LIST_ENTRY MachineGroupList ;
CRITICAL_SECTION MachineGroupLock ;
WCHAR MachineLocalName[ MAX_PATH ];

#define LOOPBACK_KEY    L"Loopback"
#define GROUPKEY_VALUE  L"$$GroupKey"

PXTCB_MACHINE_GROUP_ENTRY
MGpCreateGroupEntry(
    PWSTR MachineName,
    PUCHAR Key
    )
{
    PXTCB_MACHINE_GROUP_ENTRY Entry ;
    ULONG Length ;

    Length = wcslen( MachineName ) + 1;
    
    Entry = LocalAlloc( LMEM_FIXED, 
                        sizeof( XTCB_MACHINE_GROUP_ENTRY ) + (Length * sizeof(WCHAR) ) );

    if ( Entry )
    {
        Entry->MachineName = (PWSTR) (Entry + 1);
        CopyMemory( Entry->UniqueKey, 
                    Key, 
                    SEED_KEY_SIZE );

        CopyMemory( Entry->MachineName,
                    MachineName,
                    Length * sizeof(WCHAR) );

        if ( _wcsicmp( MachineName, MachineLocalName ) == 0 )
        {
            Entry->Flags = MGROUP_ENTRY_SELF ;
        }
        else 
        {
            Entry->Flags = 0;
        }

    }

    return Entry ;
    
}

VOID
MGpFreeGroup(
    PXTCB_MACHINE_GROUP Group
    )
{
    ULONG i ;

    for ( i = 0 ; i < Group->Count ; i++ )
    {
        LocalFree( Group->GroupList[ i ] );
    }

    LocalFree( Group );
}

PXTCB_MACHINE_GROUP
MGpCreateMachineGroup(
    HKEY Root,
    PWSTR KeyName
    )
{
    ULONG Count ;
    ULONG Size ;
    ULONG Type ;
    UCHAR Key[ SEED_KEY_SIZE ];
    PWSTR Name ;
    ULONG MaxName ;
    ULONG NameSize ;
    ULONG Index ;
    PXTCB_MACHINE_GROUP Group ;
    int err ;


    err = RegQueryInfoKey(
                Root,
                NULL,
                NULL,
                NULL,
                NULL,
                NULL,
                NULL,
                &Count,
                &MaxName,
                NULL,
                NULL,
                NULL );

    if ( err )
    {
        return NULL ;
    }

    MaxName++;

    Name = LocalAlloc( LMEM_FIXED, (MaxName) * sizeof( WCHAR ) );

    if ( !Name )
    {
        return NULL ;
    }

    Group = LocalAlloc( LMEM_FIXED,
                        sizeof( XTCB_MACHINE_GROUP ) +
                            ( Count ) * sizeof( PXTCB_MACHINE_GROUP_ENTRY ) +
                            ( wcslen( KeyName ) + 1 ) * sizeof( WCHAR ) );

    if ( !Group )
    {
        LocalFree( Name );
        return NULL ;
    }

    //
    // We've got all the base structures.  Let's load it in:
    //

    Group->List.Flink = NULL ;
    Group->List.Blink = NULL ;
    Group->Count = 0 ;
    Group->GroupList = (PXTCB_MACHINE_GROUP_ENTRY *) (Group + 1);
    Group->Group.Buffer = (PWSTR) ((PUCHAR) Group->GroupList + 
                               (Count * sizeof( PXTCB_MACHINE_GROUP_ENTRY ) ) );

    wcscpy( Group->Group.Buffer, KeyName );
    Group->Group.Length = wcslen( Group->Group.Buffer ) * sizeof( WCHAR );
    Group->Group.MaximumLength = Group->Group.Length + sizeof( WCHAR );

    for ( Index = 0 ; Index < Count ; Index++ )
    {
        NameSize = MaxName ;
        Size = SEED_KEY_SIZE ;

        err = RegEnumValue(
                    Root,
                    Index,
                    Name,
                    &NameSize,
                    NULL,
                    &Type,
                    Key,
                    &Size );

        if ( (err == 0) && (Type == REG_BINARY) )
        {
            if ( _wcsicmp( Name, GROUPKEY_VALUE ) == 0 )
            {
                CopyMemory(
                    Group->SeedKey,
                    Key,
                    Size );

                continue;
            }

            Group->GroupList[ Group->Count ] = MGpCreateGroupEntry( Name, Key );
            if ( Group->GroupList[ Group->Count ] )
            {
                Group->Count++ ;
            }
        }

    }

    LocalFree( Name );

    if ( Group->Count == 0 )
    {
        DebugLog(( DEB_ERROR, "No machines found in group %ws\n", KeyName ));
        LocalFree( Group );
        Group = NULL ;
    }

    if ( Group )
    {
        for ( Index = 0 ; Index < Group->Count ; Index++ )
        {
            if ( Group->GroupList[ Index ]->Flags & MGROUP_ENTRY_SELF )
            {
                break;
            }
        }

        if ( Index == Group->Count )
        {
            DebugLog(( DEB_ERROR, "No entry for self found in group %ws\n", KeyName ));
            MGpFreeGroup( Group );
            Group = NULL ;
        }
    }


    return Group ;
}

VOID
MGpCreateLoopback(
    HKEY RootKey
    )
{
    int err ;
    DWORD Disp ;
    HKEY LoopbackKey ;
    UCHAR Random1[ XTCB_SEED_LENGTH ];
    UCHAR Random2[ XTCB_SEED_LENGTH ];


    err =  RegCreateKeyEx(
            RootKey,
            LOOPBACK_KEY,
            0,
            NULL,
            REG_OPTION_VOLATILE,
            KEY_READ | KEY_WRITE,
            NULL,
            &LoopbackKey,
            &Disp );

    if ( err == 0 )
    {
        if ( Disp == REG_OPENED_EXISTING_KEY )
        {
            RegCloseKey( LoopbackKey );
            return;
        }

        CDGenerateRandomBits( Random1, XTCB_SEED_LENGTH );
        CDGenerateRandomBits( Random2, XTCB_SEED_LENGTH );

        (VOID) RegSetValueEx(
                    LoopbackKey,
                    GROUPKEY_VALUE,
                    0,
                    REG_BINARY,
                    Random2,
                    XTCB_SEED_LENGTH );

        (VOID) RegSetValueEx(
                    LoopbackKey,
                    L"LocalHost",
                    0,
                    REG_BINARY,
                    Random1,
                    XTCB_SEED_LENGTH );

        (VOID) RegSetValueEx(
                    LoopbackKey,
                    XtcbUnicodeDnsName.Buffer,
                    0,
                    REG_BINARY,
                    Random1,
                    XTCB_SEED_LENGTH );

        //
        // Enumerate and stick aliases for this machine in the key here.
        //

        RegCloseKey( LoopbackKey );
                
    }
}



BOOL
MGpLoadGroups(
    VOID
    )
{
    HKEY RootKey = NULL ;
    HKEY Enum ;
    int err ;
    DWORD Index ;
    DWORD Disp ;
    PXTCB_MACHINE_GROUP Group ;
    DWORD MaxLen ;
    PWSTR KeyName = NULL ;
    WCHAR Buffer[ 32 ];
    DWORD Len ;
    BOOL Success = FALSE ;
    ULONG KeyCount ;

    err = RegCreateKeyEx(
            HKEY_LOCAL_MACHINE,
            L"System\\CurrentControlSet\\Control\\LSA\\XTCB\\Groups",
            0,
            NULL,
            REG_OPTION_NON_VOLATILE,
            KEY_READ | KEY_WRITE,
            NULL,
            &RootKey,
            &Disp );

    if ( err )
    {
        return FALSE ;
    }

    MGpCreateLoopback( RootKey );

    err = RegQueryInfoKey(
            RootKey,
            NULL,
            NULL,
            NULL,
            &KeyCount,
            &MaxLen,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL );

    if ( err )
    {
        goto Cleanup ;

    }

    DebugLog(( DEB_TRACE, "Found %d groups\n", KeyCount ));

    if ( MaxLen < 32 )
    {
        KeyName = Buffer ;
        MaxLen = 32 ;
    }
    else 
    {
        KeyName = LocalAlloc( LMEM_FIXED, (MaxLen + 1) * sizeof( WCHAR ) );

        if ( KeyName == NULL )
        {
            goto Cleanup ;
        }
    }

    Index = 0 ;

    while ( Index < KeyCount )
    {

        Len = MaxLen ;
        
        err = RegEnumKeyEx(
                    RootKey,
                    Index,
                    KeyName,
                    &Len,
                    NULL,
                    NULL,
                    NULL,
                    NULL );

        if ( err )
        {
            Index++ ;
            continue;
        }

        err = RegOpenKeyEx(
                RootKey,
                KeyName,
                REG_OPTION_NON_VOLATILE,
                KEY_READ,
                &Enum );

        if ( err )
        {
            err = RegOpenKeyEx(
                        RootKey,
                        KeyName,
                        REG_OPTION_VOLATILE,
                        KEY_READ,
                        &Enum );
        }

        if ( err == 0 )
        {
            DebugLog(( DEB_TRACE, "Processing group %d:%ws\n", Index, KeyName ));

            Group = MGpCreateMachineGroup( Enum, KeyName );

            RegCloseKey( Enum );

            if ( Group )
            {
                InsertTailList( &MachineGroupList, &Group->List );
            }
        }
        else 
        {
            DebugLog(( DEB_TRACE, "Unable to open key %ws\n", KeyName ));
        }

        Index++ ;

    }
    
    Success = TRUE ;


Cleanup:
    if ( RootKey )
    {
        RegCloseKey( RootKey );
    }

    if ( KeyName )
    {
        if ( KeyName != Buffer )
        {
            LocalFree( KeyName );
        }
    }

    return Success ;

}



BOOL
MGroupReload(
    VOID
    )
{
    PLIST_ENTRY List ;
    PXTCB_MACHINE_GROUP Group ;
    BOOL Success ;
    DWORD Size = MAX_PATH ;

    EnterCriticalSection( &MachineGroupLock );

    while ( !IsListEmpty( &MachineGroupList ) )
    {
        List = RemoveHeadList( &MachineGroupList );

        Group = CONTAINING_RECORD( List, XTCB_MACHINE_GROUP, List );

        MGpFreeGroup( Group );
    }

    GetComputerNameEx( ComputerNamePhysicalDnsFullyQualified,
                       MachineLocalName,
                       &Size );

    Success = MGpLoadGroups();

    LeaveCriticalSection( &MachineGroupLock );

    return Success ;

}


BOOL
MGroupInitialize(
    VOID
    )
{
    BOOL Success = TRUE ;
    try {

        InitializeCriticalSection( &MachineGroupLock );
    }
    except ( EXCEPTION_EXECUTE_HANDLER )
    {
        Success = FALSE ;
    }

    InitializeListHead( &MachineGroupList );

    if ( Success )
    {
        Success = MGroupReload();
    }

    return Success ;
}

BOOL
MGroupLocateInboundKey(
    IN PSECURITY_STRING GroupName,
    IN PSECURITY_STRING Origin,
    OUT PUCHAR TargetKey,
    OUT PUCHAR GroupKey,
    OUT PUCHAR MyKey
    )
{
    PLIST_ENTRY Scan ;
    PXTCB_MACHINE_GROUP Group ;
    PXTCB_MACHINE_GROUP_ENTRY Entry ;
    PXTCB_MACHINE_GROUP_ENTRY Self = NULL ;
    ULONG i ;
    BOOL Success = FALSE ;


    EnterCriticalSection( &MachineGroupLock );

    Scan = MachineGroupList.Flink ;

    while ( Scan != &MachineGroupList )
    {
        Group = CONTAINING_RECORD( Scan, XTCB_MACHINE_GROUP, List );

        if ( RtlEqualUnicodeString( GroupName,
                                    &Group->Group,
                                    TRUE ) )
        {
            for ( i = 0 ; i < Group->Count ; i++ )
            {
                Entry = Group->GroupList[ i ];
                if ( Entry->Flags & MGROUP_ENTRY_SELF )
                {
                    Self = Entry ;
                }
                if ( _wcsicmp( Origin->Buffer, Entry->MachineName ) == 0 )
                {
                    //
                    // We have a hit:
                    //

                    Success = TRUE ;
                    CopyMemory( TargetKey, Entry->UniqueKey, SEED_KEY_SIZE );
                    CopyMemory( GroupKey, Group->SeedKey, SEED_KEY_SIZE );
                    break;
                }
            }

        }


        if ( Success )
        {
            break;
        }

        Scan = Scan->Flink ;
        Self = NULL ;

    }

    if ( Success && ( Self == NULL ) )
    {
        //
        // Continue through the group, looking for the 
        // self entry
        //
        for (  ; i < Group->Count ; i++ )
        {
            if ( Group->GroupList[ i ]->Flags & MGROUP_ENTRY_SELF )
            {
                Self = Group->GroupList[ i ];
                break;
            }
        }
    }

    if ( Success )
    {
        CopyMemory( MyKey, Self->UniqueKey, SEED_KEY_SIZE );
    }

    LeaveCriticalSection( &MachineGroupLock );

    return Success ;

}

BOOL
MGroupLocateKeys(
    IN PWSTR Target,
    OUT PSECURITY_STRING * GroupName,
    OUT PUCHAR TargetKey,
    OUT PUCHAR GroupKey,
    OUT PUCHAR MyKey
    )
{
    PLIST_ENTRY Scan ;
    PXTCB_MACHINE_GROUP Group ;
    PXTCB_MACHINE_GROUP_ENTRY Entry ;
    PXTCB_MACHINE_GROUP_ENTRY Self = NULL ;
    ULONG i ;
    BOOL Success = FALSE ;


    EnterCriticalSection( &MachineGroupLock );

    Scan = MachineGroupList.Flink ;

    while ( Scan != &MachineGroupList )
    {
        Group = CONTAINING_RECORD( Scan, XTCB_MACHINE_GROUP, List );

        for ( i = 0 ; i < Group->Count ; i++ )
        {
            Entry = Group->GroupList[ i ];
            if ( Entry->Flags & MGROUP_ENTRY_SELF )
            {
                Self = Entry ;
            }
            if ( _wcsicmp( Target, Entry->MachineName ) == 0 )
            {
                //
                // We have a hit:
                //

                Success = TRUE ;
                CopyMemory( TargetKey, Entry->UniqueKey, SEED_KEY_SIZE );
                CopyMemory( GroupKey, Group->SeedKey, SEED_KEY_SIZE );
                *GroupName = &Group->Group ;
                break;
            }
        }

        if ( Success )
        {
            break;
        }

        Scan = Scan->Flink ;
        Self = NULL ;

    }

    if ( Success && ( Self == NULL ) )
    {
        //
        // Continue through the group, looking for the 
        // self entry
        //
        for (  ; i < Group->Count ; i++ )
        {
            if ( Group->GroupList[ i ]->Flags & MGROUP_ENTRY_SELF )
            {
                Self = Group->GroupList[ i ];
                break;
            }
        }
    }

    if ( Success )
    {
        CopyMemory( MyKey, Self->UniqueKey, SEED_KEY_SIZE );
    }

    LeaveCriticalSection( &MachineGroupLock );

    return Success ;
}

BOOL
MGroupParseTarget(
    PWSTR TargetSpn,
    PWSTR * MachineName
    )
{
    PWSTR Scan ;
    PWSTR Tail ;
    PWSTR Copy ;
    ULONG Length ;

    *MachineName = NULL ;

    Scan = wcschr( TargetSpn, L'/' );

    if ( !Scan )
    {
        return FALSE ;
    }

    Scan++ ;
    Tail = wcschr( Scan, L'/' );

    if ( Tail != NULL )
    {
        //
        // three-part SPN (e.g. HOST/hostname.domain.com).
        // null out this slash for now
        //

        *Tail = L'\0';

    }

    Length = wcslen( Scan );

    Copy = LocalAlloc( LMEM_FIXED, (Length + 1) * sizeof( WCHAR ) );

    if ( Copy )
    {
        CopyMemory( Copy, Scan, (Length + 1) * sizeof( WCHAR ) );
    }

    if ( Tail )
    {
        *Tail = L'/' ;
    }

    *MachineName = Copy ;

    return ( Copy != NULL );
    
}