/*++

Copyright (c) 1998 - 2000  Microsoft Corporation

Module Name:
    ldappx.cpp

Abstract:
    Defines methods utilized by abstract data types used in LDAP portion of the H.323/LDAP proxy.

    LDAP Proxy is designed as an addition to H.323 proxy. The main purpose of the
    LDAP proxy is to maintain LDAP Address Translation Table, which is used to map
    aliases of H.323 endpoints to their IP addresses. The proxy adds an entry when it
    intercepts an LDAP PDU from a client to directory server, and the PDU matches all
    predefined criteria.

Author(s):          ArlieD, IlyaK   14-Jul-1999

Revision History:
    07/14/1999      File creation                                  Arlie Davis  (ArlieD)
    08/20/1999      Improvement of processing of LDAP              Ilya Kleyman (IlyaK)
                    LDAP SearchRequests
    12/20/1999      Added prediction of receive sizes in           Ilya Kleyman (IlyaK)
                    non-interpretative data transfer mode
    02/20/2000      Added expiration policy of the entries         Ilya Kleyman (IlyaK)
                    in LDAP Address Translation Table
    03/12/2000      Added support for multiple private and         Ilya Kleyman (IlyaK)
                    multiple public interface for RRAS

--*/

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Include files                                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ber.h"

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Constants                                                                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

static    const    ANSI_STRING        LdapText_C                        = ANSI_STRING_INIT("c");
static    const    ANSI_STRING        LdapText_CN                       = ANSI_STRING_INIT("cn");
static    const    ANSI_STRING        LdapText_ObjectClass              = ANSI_STRING_INIT("objectClass");
static    const    ANSI_STRING        LdapText_O                        = ANSI_STRING_INIT("o");
static    const    ANSI_STRING        LdapText_OU                       = ANSI_STRING_INIT("ou");

static    const    ANSI_STRING        LdapText_RTPerson                 = ANSI_STRING_INIT("RTPerson");
static    const    ANSI_STRING        LdapText_Attribute_sipaddress     = ANSI_STRING_INIT("sipaddress");
static    const    ANSI_STRING        LdapText_Attribute_ipAddress      = ANSI_STRING_INIT("ipAddress");
static    const    ANSI_STRING        LdapText_Attribute_sttl           = ANSI_STRING_INIT("sttl");
static    const    ANSI_STRING        LdapText_Attribute_comment        = ANSI_STRING_INIT("comment");
static    const    ANSI_STRING        LdapText_Modify_EntryTTL          = ANSI_STRING_INIT("EntryTTL");

static    const    ANSI_STRING        LdapText_GeneratedByTAPI          = ANSI_STRING_INIT("Generated by TAPI3");
static    const    ANSI_STRING        LdapText_ModifiedByICS            = ANSI_STRING_INIT("Made possible by ICS");
static    const    ANSI_STRING        LdapText_TableSizeExceededMessage = ANSI_STRING_INIT("Resources on proxy used up.");

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Global Variables                                                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

SYNC_COUNTER           LdapSyncCounter;
LDAP_CONNECTION_ARRAY  LdapConnectionArray;
LDAP_TRANSLATION_TABLE LdapTranslationTable;
LDAP_CODER             LdapCoder;
LDAP_ACCEPT            LdapAccept;
SOCKADDR_IN            LdapListenSocketAddress;
DWORD                  EnableLocalH323Routing;

// utility functions ------------------------------------------------------------------

#if DBG
static BOOL BerDumpStopFn (VOID)
{
    return FALSE;
}

static void BerDumpOutputFn (char * Format, ...)
{
    if (DebugLevel > 0) {
        va_list    Va;
        CHAR    Text    [0x200];

        va_start (Va, Format);
        _vsnprintf (Text, 0x200, Format, Va);
        va_end (Va);

        OutputDebugStringA (Text);
    }
}

static void BerDump (IN LPBYTE Data, IN DWORD Length)
{
    ber_decode (BerDumpOutputFn, BerDumpStopFn, Data,
        0, // DECODE_NEST_OCTET_STRINGS,
        0, 0, Length, 0);
}

#endif // DBG

// LdapQueryTable queries the LDAP translation table for a given alias.
// The alias was one that was previously registered by a LDAP endpoint.
// We do not care about the type of the alias (h323_ID vs emailID, etc.) --
// the semantics of the alias type are left to the Q.931 code.
//
// returns S_OK on success
// returns S_FALSE if no entry was found
// returns an error code if an actual error occurred.

HRESULT LdapQueryTableByAlias (
    IN    ANSI_STRING *    Alias,
    OUT    DWORD *    ReturnClientAddress) // host order
{
    HRESULT        Result;
    IN_ADDR     Address;

    assert (Alias);
    assert (ReturnClientAddress);

    Result = LdapTranslationTable.QueryTableByAlias (Alias, &Address);
    if (Result == S_OK) {
        *ReturnClientAddress = ntohl (Address.s_addr);
        return Result;
    }

    Result = LdapTranslationTable.QueryTableByCN (Alias, &Address);
    if (Result == S_OK) {
        *ReturnClientAddress = ntohl (Address.s_addr);
        return Result;
    }

    return Result;
}

HRESULT LdapQueryTableByAliasServer (
    IN    ANSI_STRING *    Alias,
    IN  SOCKADDR_IN *   ServerAddress,
    OUT    DWORD *    ReturnClientAddress) // host order
{
    HRESULT        Result;
    IN_ADDR     Address;

    assert (Alias);
    assert (ReturnClientAddress);

    Result = LdapTranslationTable.QueryTableByAliasServer (Alias, ServerAddress, &Address);
    if (Result == S_OK) {
        *ReturnClientAddress = ntohl (Address.s_addr);
        return Result;
    }

    Result = LdapTranslationTable.QueryTableByCNServer (Alias, ServerAddress, &Address);
    if (Result == S_OK) {
        *ReturnClientAddress = ntohl (Address.s_addr);
        return Result;
    }

    return Result;
}
#if    DBG
void LdapPrintTable (void) {
    LdapTranslationTable.PrintTable ();
}
#endif // DBG

static DWORD LdapDeterminePacketBoundary (
    IN   LDAP_BUFFER * Buffer,
    IN   DWORD         PacketOffset,   
    OUT  DWORD *       NextPacketOffset,   // Points to the beginning of next packet only if function returns ERROR_SUCCESS 
    OUT     DWORD *       NextReceiveSize)    // Is only meaningful when function returns any value other than ERROR_SUCCESS
{
    DWORD    PayloadLength;
    DWORD    ASNHeaderLength = ASN_MIN_HEADER_LEN;
    DWORD    PacketSize;
    DWORD    ByteIndex;
    DWORD   Length;
    LPBYTE  Data;

    assert (Buffer);
    assert (Buffer -> Data.Data);

    Length = Buffer -> Data.Length - PacketOffset;
    Data   = Buffer -> Data.Data;
    
    // Pick reasonable default for the size of
    // next receive request. Will be changed if necessary

    *NextReceiveSize = LDAP_BUFFER_RECEIVE_SIZE;

    if (Length != 0) {

        if (Data [PacketOffset] == ASN_SEQUENCE_TAG) {

            if (Length >= ASN_MIN_HEADER_LEN) {
            
                if (Data [PacketOffset + 1] & ASN_LONG_HEADER_BIT) {
                    // Long (more than ASN_MIN_HEADER_LEN bytes) ASN header
                    // Size of the payload length field is indicated in the
                    // second nybble of second byte

                    ASNHeaderLength += Data [PacketOffset + 1] & ~ASN_LONG_HEADER_BIT;

                    // This is where the limit on payload length is established.
                    // The test below assures it won't be greater than 2 ^ sizeof (DWORD) (4 GBytes)
                    if (ASNHeaderLength <= ASN_MIN_HEADER_LEN + sizeof (DWORD)) {

                        if (Length >= ASNHeaderLength) {

                            PayloadLength  = 0;

                            for (ByteIndex = ASN_MIN_HEADER_LEN;
                                 ByteIndex < ASNHeaderLength; 
                                 ByteIndex++) {
                                
                                 PayloadLength *= 1 << CHAR_BIT;
                                 PayloadLength += (DWORD) Data [PacketOffset + ByteIndex];  
                            }

                        } else {

                            // Not enough data to even read the ASN header
                            return ERROR_MORE_DATA;

                        }

                    } else {

                        DebugF (_T("LDAP: Payload size field (%d bytes) is too big.\n"), ASNHeaderLength - ASN_MIN_HEADER_LEN);

                        return ERROR_INVALID_DATA;
                    }

                } else  {

                    // Short (Exactly ASN_MIN_HEADER_LEN bytes) ASN header
                    // Payload length is indicated in the second byte

                    PayloadLength = (DWORD) Data [PacketOffset + 1];
                }

                PacketSize = ASNHeaderLength + PayloadLength;

                if (Length >= PacketSize) {

                    *NextPacketOffset = PacketOffset + PacketSize;

                    return ERROR_SUCCESS;

                 } else {
                    
                    *NextReceiveSize = PacketSize - Length;
                }
            }

        } else {

            Debug (_T("LDAP: Failed to find ASN sequence tag.\n"));
            
            return ERROR_INVALID_DATA;
        }
    }

    return ERROR_MORE_DATA;
}

static BOOL FindChar (
    IN    ANSI_STRING *    String,
    IN    CHAR            Char,
    OUT    USHORT *        ReturnIndex)
{
    LPSTR    Pos;
    LPSTR    End;

    assert (String);
    assert (ReturnIndex);

    Pos = String -> Buffer;
    End = String -> Buffer + String -> Length / sizeof (CHAR);

    for (; Pos < End; Pos++) {
        if (*Pos == Char) {
            *ReturnIndex = (USHORT) (Pos - String -> Buffer);
            return TRUE;
        }
    }

    return FALSE;
}

static void ParseDirectoryPathElement (
    IN        ANSI_STRING *            Element,
    IN    OUT    LDAP_PATH_ELEMENTS *    PathElements)
{
    ANSI_STRING        Tag;
    ANSI_STRING        Value;
    USHORT            Index;

    if (FindChar (Element, LDAP_PATH_EQUAL_CHAR, &Index)) {
        assert (Index * sizeof (CHAR) < Element -> Length);

        Tag.Buffer = Element -> Buffer;
        Tag.Length = Index * sizeof (CHAR);

        Index++;        // step over separator

        Value.Buffer = Element -> Buffer + Index;
        Value.Length = Element -> Length - Index * sizeof (CHAR);

        if (RtlEqualStringConst (&Tag, &LdapText_C, TRUE))
            PathElements -> C = Value;
        else if (RtlEqualStringConst (&Tag, &LdapText_CN, TRUE))
            PathElements -> CN = Value;
        else if (RtlEqualStringConst (&Tag, &LdapText_ObjectClass, TRUE))
            PathElements -> ObjectClass = Value;
        else if (RtlEqualStringConst (&Tag, &LdapText_O, TRUE))
            PathElements -> O = Value;
    }
}

static void ParseDirectoryPath (
    IN    ANSI_STRING *            DirectoryPath,
    OUT    LDAP_PATH_ELEMENTS *    ReturnData)
{
    ANSI_STRING        SubString;
    USHORT            Index;
    ANSI_STRING        Element;

    assert (DirectoryPath);
    assert (ReturnData);
    assert (DirectoryPath -> Buffer);

    ZeroMemory (ReturnData, sizeof (LDAP_PATH_ELEMENTS));

    SubString = *DirectoryPath;

    while (FindChar (&SubString, LDAP_PATH_SEP_CHAR, &Index)) {
        assert (Index * sizeof (CHAR) < SubString.Length);

        Element.Buffer = SubString.Buffer;
        Element.Length = Index * sizeof (CHAR);

        Index++;        // step over separator

        SubString.Buffer += Index;
        SubString.Length -= Index * sizeof (CHAR);

        ParseDirectoryPathElement (&Element, ReturnData);
    }

    ParseDirectoryPathElement (&SubString, ReturnData);
}

static void ParseObjectNameElement (
    IN        ANSI_STRING *                Element,
    IN    OUT    LDAP_OBJECT_NAME_ELEMENTS *    ObjectNameElements)
{
    ANSI_STRING        Tag;
    ANSI_STRING        Value;
    USHORT            Index;

    if (FindChar (Element, LDAP_PATH_EQUAL_CHAR, &Index)) {
        assert (Index * sizeof (CHAR) < Element -> Length);

        Tag.Buffer = Element -> Buffer;
        Tag.Length = Index * sizeof (CHAR);

        Index++;        // step over separator

        Value.Buffer = Element -> Buffer + Index;
        Value.Length = Element -> Length - Index * sizeof (CHAR);

        if (RtlEqualStringConst (&Tag, &LdapText_CN, TRUE))
            ObjectNameElements -> CN = Value;
        else if (RtlEqualStringConst (&Tag, &LdapText_O, TRUE))
            ObjectNameElements -> O = Value;
        else if (RtlEqualStringConst (&Tag, &LdapText_OU, TRUE))
            ObjectNameElements -> OU = Value;
    }
}

static void ParseObjectName (
    IN    ANSI_STRING *                ObjectName,
    OUT    LDAP_OBJECT_NAME_ELEMENTS *    ReturnData)
{
    ANSI_STRING        SubString;
    USHORT            Index;
    ANSI_STRING        Element;

    assert (ObjectName);
    assert (ReturnData);
    assert (ObjectName -> Buffer);

    ZeroMemory (ReturnData, sizeof (LDAP_OBJECT_NAME_ELEMENTS));

    SubString = *ObjectName;

    while (FindChar (&SubString, LDAP_PATH_SEP_CHAR, &Index)) {
        assert (Index * sizeof (CHAR) < SubString.Length);

        Element.Buffer = SubString.Buffer;
        Element.Length = Index * sizeof (CHAR);

        Index++;        // step over separator

        SubString.Buffer += Index;
        SubString.Length -= Index * sizeof (CHAR);

        ParseObjectNameElement (&Element, ReturnData);
    }

    ParseObjectNameElement (&SubString, ReturnData);
}

// LDAP_TRANSLATION_ENTRY ------------------------------------------------


HRESULT 
LDAP_TRANSLATION_ENTRY::IsRegisteredViaInterface (
    IN DWORD InterfaceAddress,      // host order
    OUT BOOL *Result
    )
/*++

Routine Description:
    Determines whether the entry is registered via the
    interface specified

Arguments:
    InterfaceAddress - address of the interface for which
        the determination is to be made.

    Result (out)     - TRUE if entry was registered via the interface
                       FALSE if entry was not registered via the interface

Return Values:
    TRUE - if determination succeeded

    FALSE - if determination failed

Notes:

--*/

{
    DWORD BestInterfaceAddress;
    ULONG Error;

    Error = GetBestInterfaceAddress (ntohl (ClientAddress.s_addr), &BestInterfaceAddress);

    *Result = FALSE;

    if (ERROR_SUCCESS == Error) {

        *Result = (BestInterfaceAddress == InterfaceAddress);
    }

    return HRESULT_FROM_WIN32 (Error);
}

// LDAP_TRANSLATION_TABLE ------------------------------------------------

LDAP_TRANSLATION_TABLE::LDAP_TRANSLATION_TABLE (void)
{
    IsEnabled = FALSE;
    GarbageCollectorTimerHandle = NULL;
}

LDAP_TRANSLATION_TABLE::~LDAP_TRANSLATION_TABLE (void)
{
    assert (!IsEnabled);
    assert (Array.Length == 0);
}

void LDAP_TRANSLATION_TABLE::Stop (void)
{
    HRESULT Result;

    Lock();

    IsEnabled = FALSE;

    if (GarbageCollectorTimerHandle) {

        if (DeleteTimerQueueTimer(NATH323_TIMER_QUEUE, 
                                  GarbageCollectorTimerHandle,
                                  INVALID_HANDLE_VALUE))
        {

            DebugF (_T("LDAP: Garbage collection is deactivated.\n"));

        }
        else {

            Result = GetLastError ();

            DebugError (Result, _T("LDAP: Could not deactivate garbage collection.\n"));

        }

        GarbageCollectorTimerHandle = NULL;
    }

    Array.Free();

    Unlock ();
}

HRESULT LDAP_TRANSLATION_TABLE::Start (void)
{
    HRESULT Result;

    Lock ();

    assert (!GarbageCollectorTimerHandle);

    if (CreateTimerQueueTimer(&GarbageCollectorTimerHandle,
                               NATH323_TIMER_QUEUE,
                               GarbageCollectorCallback,
                               this,
                               LDAP_TRANSLATION_TABLE_GARBAGE_COLLECTION_PERIOD,
                               LDAP_TRANSLATION_TABLE_GARBAGE_COLLECTION_PERIOD,    // periodic timer
                               WT_EXECUTEINIOTHREAD)) {

        DebugF (_T("LDAP: Successfully activated garbage collection.\n"));

        IsEnabled = TRUE;

        Result = S_OK;
    }
    else {

        Result = GetLastError ();

        DebugLastError (_T("LDAP: Failed to activate garbage collection.\n"));
    }

    Unlock ();

    return Result;
}

// static
void LDAP_TRANSLATION_TABLE::GarbageCollectorCallback (
    PVOID Context,
    BOOLEAN TimerOrWaitFired) 
{
    LDAP_TRANSLATION_TABLE * Table;

    Table = (LDAP_TRANSLATION_TABLE *) Context;

    Table -> RemoveOldEntries ();
}

HRESULT LDAP_TRANSLATION_TABLE::RefreshEntry (
    IN ANSI_STRING * Alias,
    IN ANSI_STRING * DirectoryPath,
    IN IN_ADDR       ClientAddress,
    IN SOCKADDR_IN * ServerAddress,
    IN DWORD         TimeToLive) // in seconds
{
    DebugF (_T("LDAP: Refreshing local entry for (%.*S) @ %08X:%04X.\n"), 
                ANSI_STRING_PRINTF (Alias),
                SOCKADDR_IN_PRINTF (ServerAddress));

    return InsertEntry (Alias, DirectoryPath, ClientAddress, ServerAddress, TimeToLive);
}

void LDAP_TRANSLATION_TABLE::RemoveOldEntries (void)
{
    DWORD CurrentTime;

    DWORD  Index;

    Lock ();

    if (IsEnabled) {

        CurrentTime = GetTickCount () / 1000;

        DebugF (_T("LDAP: Garbage collection commenced at %d.\n"), CurrentTime);

        Index = 0;

        while (Index < Array.Length) {

            if (CurrentTime > Array [Index].TimeStamp) {

                DebugF (_T("LDAP: Expiring entry @%d, alias -- (%.*S) from translation table.\n"),
                     Index, ANSI_STRING_PRINTF (&Array [Index].Alias));

                Array[Index].FreeContents ();
                Array.DeleteAtPos (Index);

                InterfaceArray.StopQ931ReceiveRedirects ();

            } else {
                
                Index++;
            }
        }

        DebugF (_T("LDAP: Garbage collection completed.\n"));
    }

    Unlock ();
}
    
HRESULT LDAP_TRANSLATION_TABLE::QueryTableByAlias (
    IN    ANSI_STRING *    Alias,
    OUT    IN_ADDR *        ReturnClientAddress)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    DWORD        Index;
    HRESULT        Result;
    
    assert (Alias);
    assert (ReturnClientAddress);

    Lock();

    if (IsEnabled) {

        Result = S_FALSE;

        Array.GetExtents (&Pos, &End);
        for (; Pos < End; Pos++) {
            if (RtlEqualStringConst (&Pos -> Alias, Alias, TRUE)) {
                *ReturnClientAddress = Pos -> ClientAddress;
                Result = S_OK;
                break;
            }
        }

    }
    else {
        Result = S_FALSE;
    }

    Unlock();

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::QueryTableByCN (
    IN    ANSI_STRING *    CN,
    OUT    IN_ADDR *        ReturnClientAddress)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;

    assert (CN);
    assert (ReturnClientAddress);

    Lock();

    if (IsEnabled) {

        Result = S_FALSE;

        Array.GetExtents (&Pos, &End);
        for (; Pos < End; Pos++) {
            if (RtlEqualStringConst (&Pos -> CN, CN, TRUE)) {
                *ReturnClientAddress = Pos -> ClientAddress;
                Result = S_OK;
                break;
            }
        }
    }
    else {

        Result = S_FALSE;
    }

    Unlock();

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::QueryTableByAliasServer (
    IN    ANSI_STRING *    Alias,
    IN  SOCKADDR_IN *   ServerAddress,
    OUT    IN_ADDR *        ReturnClientAddress)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    DWORD        Index;
    HRESULT        Result;
    BOOL        ServerIsSame;
    BOOL        AliasIsSame;
    
    assert (Alias);
    assert (ServerAddress);
    assert (ReturnClientAddress);

    Lock();

    if (IsEnabled) {

        Result = S_FALSE;

        Array.GetExtents (&Pos, &End);
        for (; Pos < End; Pos++) {

            AliasIsSame = RtlEqualStringConst (&Pos -> Alias, Alias, TRUE); 
            ServerIsSame = (ServerAddress -> sin_addr.s_addr == Pos -> ServerAddress.sin_addr.s_addr) // addresses are literally equal
                           ||
                           (    ::NhIsLocalAddress (ServerAddress -> sin_addr.s_addr)
                             && ::NhIsLocalAddress (Pos -> ServerAddress.sin_addr.s_addr));         // two addresses of the local machine

            if (AliasIsSame && ServerIsSame) {
                *ReturnClientAddress = Pos -> ClientAddress;
                Result = S_OK;
                break;
            }
        }

    }
    else {
        Result = S_FALSE;
    }

    Unlock();

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::QueryTableByCNServer (
    IN    ANSI_STRING *    CN,
    IN  SOCKADDR_IN *   ServerAddress,
    OUT    IN_ADDR *        ReturnClientAddress)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;
    BOOL        ServerIsSame;
    BOOL        CN_IsSame;

    assert (CN);
    assert (ServerAddress);
    assert (ReturnClientAddress);

    Lock();

    if (IsEnabled) {

        Result = S_FALSE;

        Array.GetExtents (&Pos, &End);
        for (; Pos < End; Pos++) {
            CN_IsSame = RtlEqualStringConst (&Pos -> CN, CN, TRUE); 
            ServerIsSame = (ServerAddress -> sin_addr.s_addr == Pos -> ServerAddress.sin_addr.s_addr) // addresses are literally equal
                           ||
                           (    ::NhIsLocalAddress (ServerAddress -> sin_addr.s_addr)
                             && ::NhIsLocalAddress (Pos -> ServerAddress.sin_addr.s_addr));         // two addresses of the local machine

            if (CN_IsSame && ServerIsSame) {
                *ReturnClientAddress = Pos -> ClientAddress;
                Result = S_OK;
                break;
            }
        }
    }
    else {

        Result = S_FALSE;
    }

    Unlock();

    return Result;
}

#if DBG
void LDAP_TRANSLATION_TABLE::PrintTable (void)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;

    DebugF (_T("LDAP: Printing out Address Translation Table.\n"));
 
    Lock();

    if (IsEnabled) {

        Array.GetExtents (&Pos, &End);

        DebugF (_T("\n"));

        for (; Pos < End; Pos++) {
            DebugF (_T("\tEntry at %x:\n"), Pos);
            DebugF (_T ("\t\tAlias - %.*S\n"),
                 ANSI_STRING_PRINTF (&Pos -> Alias)); 
            DebugF (_T ("\t\tDirectoryPath - %.*S\n"),
                 ANSI_STRING_PRINTF (&Pos -> DirectoryPath)); 
            DebugF (_T ("\t\tCN - %.*S\n"),
                 ANSI_STRING_PRINTF (&Pos -> CN)); 
            DebugF (_T("\t\tClientAddress - %x\n"), ntohl(Pos->ClientAddress.s_addr));
            DebugF (_T("\t\tServerAddress - %x:%x\n"),
                SOCKADDR_IN_PRINTF (&Pos -> ServerAddress));
            DebugF (_T("\t\tTimeStamp - %u\n"), Pos -> TimeStamp);
        }

        DebugF (_T("\n"));
    }

    Unlock();
}
#endif

HRESULT LDAP_TRANSLATION_TABLE::InsertEntry (
    IN    ANSI_STRING *    Alias,
    IN    ANSI_STRING *    DirectoryPath,
    IN    IN_ADDR            ClientAddress,
    IN    SOCKADDR_IN *    ServerAddress,
    IN    DWORD            TimeToLive) // in seconds
{
    HRESULT  Result;

    assert (Alias);
    assert (Alias -> Buffer);
    assert (DirectoryPath);
     assert (DirectoryPath -> Buffer);
    assert (ServerAddress);

    Lock();

    Result = InsertEntryLocked (Alias, DirectoryPath, ClientAddress, ServerAddress, TimeToLive);

    Unlock();

#if DBG
    if (DebugLevel > 1) 
    {

        LdapPrintTable ();

    }
#endif // DBG

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::FindEntryByPathServer (
    IN    ANSI_STRING *    DirectoryPath,
    IN    SOCKADDR_IN *    ServerAddress,
    OUT    LDAP_TRANSLATION_ENTRY **    ReturnTranslationEntry)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;

    Result = S_FALSE;

    Array.GetExtents (&Pos, &End);
    for (; Pos < End; Pos++) {

        if (RtlEqualStringConst (&Pos -> DirectoryPath, DirectoryPath, TRUE)
            && IsEqualSocketAddress (&Pos -> ServerAddress, ServerAddress)) {

            *ReturnTranslationEntry = Pos;
            Result = S_OK;
            break;
        }
    }

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::FindEntryByAliasServer (
    IN    ANSI_STRING *    Alias,
    IN    SOCKADDR_IN *    ServerAddress,
    OUT    LDAP_TRANSLATION_ENTRY **    ReturnTranslationEntry)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;

    Result = S_FALSE;

    Array.GetExtents (&Pos, &End);
    for (; Pos < End; Pos++) {

        if (RtlEqualStringConst (&Pos -> Alias, Alias, TRUE)
            // && IsEqualSocketAddress (&Pos -> ServerAddress, ServerAddress)) {
            && Pos -> ServerAddress.sin_addr.s_addr == ServerAddress -> sin_addr.s_addr) {

            *ReturnTranslationEntry = Pos;
            Result = S_OK;
            break;
        }
    }

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::InsertEntryLocked (
    IN    ANSI_STRING *    Alias,
    IN    ANSI_STRING *    DirectoryPath,
    IN    IN_ADDR            ClientAddress,
    IN    SOCKADDR_IN *    ServerAddress,
    IN    DWORD            TimeToLive) // in seconds
{
    LDAP_TRANSLATION_ENTRY *    TranslationEntry;
    LDAP_PATH_ELEMENTS    PathElements;
    HRESULT            Result;
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;

    assert (Alias);
    assert (DirectoryPath);
    assert (ServerAddress);

    if (!IsEnabled)
        return S_FALSE;

    // locate any existing entry
    // the identity of the entry is determined by the tuple:
    //        < ServerAddress ClientAlias >

    if (FindEntryByAliasServer (Alias, ServerAddress, &TranslationEntry) == S_OK) {
        Debug (_T("LDAP: Replacing existing translation entry.\n"));

        TranslationEntry -> FreeContents();
    }
    else {
        Debug (_T("LDAP: Allocating new translation entry.\n"));

        TranslationEntry = Array.AllocAtEnd();
        if (!TranslationEntry) {
            Debug (_T("LDAP: Failed to allocate translation entry.\n"));
            return E_OUTOFMEMORY;
        }
    }

    TranslationEntry -> ClientAddress = ClientAddress;
    TranslationEntry -> ServerAddress = *ServerAddress;
    TranslationEntry -> TimeStamp  = GetTickCount () / 1000 + TimeToLive;

    // copy the strings
    CopyAnsiString (Alias, &TranslationEntry -> Alias);
    CopyAnsiString (DirectoryPath, &TranslationEntry -> DirectoryPath);

    if (TranslationEntry -> DirectoryPath.Buffer) {
        ParseDirectoryPath (&TranslationEntry -> DirectoryPath, &PathElements);
        if (PathElements.CN.Buffer) {
            TranslationEntry -> CN = PathElements.CN;
        }
        else {
            Debug (_T("LDAP: Cannot insert translation entry -- CN is not specified.\n"));
            TranslationEntry -> CN.Buffer = NULL;
        }
    }
    else {
        TranslationEntry -> CN.Buffer = NULL;
    }

    // test and make sure all allocation code paths succeeded
    if (TranslationEntry -> Alias.Buffer
        && TranslationEntry -> DirectoryPath.Buffer
        && TranslationEntry -> CN.Buffer) {

        Result = S_OK;

    } else {
        Debug (_T("LDAP: Failed to allocate memory (or failed to find CN).\n"));

        FreeAnsiString (&TranslationEntry -> Alias);
        FreeAnsiString (&TranslationEntry -> DirectoryPath);

        Array.DeleteEntry (TranslationEntry);

        Result = E_OUTOFMEMORY;
    }

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::RemoveEntry (
    IN    SOCKADDR_IN *    ServerAddress,
    IN    ANSI_STRING *    DirectoryPath)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;
    
    Lock();

    assert (ServerAddress);
    assert (DirectoryPath);

    Result = S_FALSE;

    Array.GetExtents (&Pos, &End);
    for (; Pos < End; Pos++) {

        if (RtlEqualString (DirectoryPath, &Pos -> DirectoryPath, TRUE)
            && Compare_SOCKADDR_IN (ServerAddress, &Pos -> ServerAddress) == 0) {

            Pos -> FreeContents();

            Array.DeleteEntry (Pos);

            InterfaceArray.StopQ931ReceiveRedirects ();

            Result = S_OK;

            break;
        }
    }

    Unlock();

    return Result;
}

HRESULT LDAP_TRANSLATION_TABLE::RemoveEntryByAliasServer (
    IN    ANSI_STRING *    Alias,
    IN  SOCKADDR_IN *   ServerAddress)
{
    LDAP_TRANSLATION_ENTRY *    Pos;
    LDAP_TRANSLATION_ENTRY *    End;
    HRESULT        Result;
    BOOL        AliasIsSame;
    BOOL        ServerIsSame;
    
    Lock ();

    assert (Alias);

    Result = S_FALSE;

    Array.GetExtents (&Pos, &End);
    for (; Pos < End; Pos++) {
        AliasIsSame = RtlEqualStringConst (&Pos -> Alias, Alias, TRUE); 
        ServerIsSame = (ServerAddress -> sin_addr.s_addr == Pos -> ServerAddress.sin_addr.s_addr) // addresses are literally equal
                       ||
                       (    ::NhIsLocalAddress (ServerAddress -> sin_addr.s_addr)
                         && ::NhIsLocalAddress (Pos -> ServerAddress.sin_addr.s_addr));         // two addresses of the local machine


        if (AliasIsSame && ServerIsSame) {

            Pos -> FreeContents();

            Array.DeleteEntry (Pos);

            InterfaceArray.StopQ931ReceiveRedirects ();

            Result = S_OK;

            break;
        }
    }

    Unlock ();

    return Result;
}
 

void 
LDAP_TRANSLATION_TABLE::OnInterfaceShutdown (
    IN DWORD          InterfaceAddress
    )
/*++

Routine Description:
    Removes all entries registered by the clients reachable
    thorough the interface specified, except for entries registered by
    a local client.

Arguments:
    InterfaceAddress - address of the interface for which
        the determination is to be made.

Return Values:
    None

Notes:

--*/

{
    DWORD ArrayIndex = 0;
    LDAP_TRANSLATION_ENTRY * Entry;
    BOOL  IsEntryToBeDeleted;
    HRESULT Result;

    Lock ();
    
    if (IsEnabled) {

        DebugF (_T("LDAP: Forcibly removing non-local translation entries registered via %08X.\n"), InterfaceAddress);

        while (ArrayIndex < Array.GetLength ()) {
            Entry = &Array [ArrayIndex];

            Result = Entry -> IsRegisteredViaInterface (InterfaceAddress, &IsEntryToBeDeleted);

            // Don't delete the entry if it was registered by a local client. This is because
            // the client will still be available for H.323 calls.
            IsEntryToBeDeleted = IsEntryToBeDeleted && !::NhIsLocalAddress (Entry -> ClientAddress.s_addr);

            if (S_OK == Result) {

                if (IsEntryToBeDeleted) {

                    DebugF (_T("LDAP: Forcibly removing entry (%.*S:%08X) @ %08X:%04X.\n"), 
                        ANSI_STRING_PRINTF (&Entry -> Alias), 
                        ntohl (Entry -> ClientAddress.s_addr),
                        SOCKADDR_IN_PRINTF (&Entry -> ServerAddress));

                    Entry -> FreeContents();

                    Array.DeleteEntry (Entry);

                    InterfaceArray.StopQ931ReceiveRedirects ();

                } else {

                    ArrayIndex++;

                }

            } else {
            
                // There probably was something wrong with just this entry. Skip it and continue 
                // searching for entries registered via the interface

                ArrayIndex++;

                DebugF (_T("LDAP: Failed to determine whether entry (%.*S:%08X) @ %08X:%04X was registered via interface %08X. Error=0x%x\n"),
                        ANSI_STRING_PRINTF (&Entry -> Alias), 
                        ntohl (Entry -> ClientAddress.s_addr),
                        SOCKADDR_IN_PRINTF (&Entry -> ServerAddress),
                        InterfaceAddress,
                        Result);
            }
        }
    }

    Unlock ();

} // LDAP_TRANSLATION_TABLE::RemoveEntriesForClientsOnInterface

BOOL LDAP_TRANSLATION_TABLE::ReachedMaximumSize (void) {
    DWORD NumberOfEntries;

    Lock ();

    NumberOfEntries = Array.Length;

    Unlock ();

    return NumberOfEntries >= LDAP_MAX_TRANSLATION_TABLE_SIZE;
}

// LDAP_SOCKET ----------------------------------------------

LDAP_SOCKET::LDAP_SOCKET (
    IN    LDAP_CONNECTION    *    ArgLdapConnection,
    IN    LDAP_PUMP *            ArgRecvPump,
    IN    LDAP_PUMP *            ArgSendPump)
{
    assert (ArgLdapConnection);
    assert (ArgRecvPump);
    assert (ArgSendPump);

    LdapConnection = ArgLdapConnection;
    RecvPump = ArgRecvPump;
    SendPump = ArgSendPump;

    State = STATE_NONE;
    BytesToReceive = LDAP_BUFFER_RECEIVE_SIZE;
    Socket = INVALID_SOCKET;

    ZeroMemory (&RecvOverlapped, sizeof RecvOverlapped);
    RecvOverlapped.Socket = this;
    RecvBuffer = NULL;
    InitializeListHead (&RecvBufferQueue);

    ZeroMemory (&SendOverlapped, sizeof SendOverlapped);
    SendOverlapped.Socket = this;
    SendBuffer = NULL;
    InitializeListHead (&SendBufferQueue);

    ConnectEvent = NULL;
    ConnectWaitHandle = NULL;
    AttemptAnotherConnect = TRUE;

    IsNatRedirectActive = FALSE;
}

LDAP_SOCKET::~LDAP_SOCKET (void)
{
    DeleteBufferList (&RecvBufferQueue);
    DeleteBufferList (&SendBufferQueue);

    if (RecvBuffer) {

        delete RecvBuffer;

        RecvBuffer = NULL;
    }

    assert (IsListEmpty (&RecvBufferQueue));
    assert (IsListEmpty (&SendBufferQueue));
    assert (!SendBuffer);
    assert (!ConnectEvent);
    assert (!ConnectWaitHandle);
}

void LDAP_SOCKET::DeleteBufferList (LIST_ENTRY * ListHead)
{
    LIST_ENTRY *    ListEntry;
    LDAP_BUFFER *    Buffer;

    while (!IsListEmpty (ListHead)) {
        ListEntry = RemoveHeadList (ListHead);
        Buffer = CONTAINING_RECORD (ListEntry, LDAP_BUFFER, ListEntry);
        delete Buffer;
    }
}

BOOL LDAP_SOCKET::RecvRemoveBuffer (
    OUT    LDAP_BUFFER **    ReturnBuffer)
{
    LIST_ENTRY *    ListEntry;

    assert (ReturnBuffer);

    if (IsListEmpty (&RecvBufferQueue))
        return FALSE;
    else {
        ListEntry = RemoveHeadList (&RecvBufferQueue);
        *ReturnBuffer = CONTAINING_RECORD (ListEntry, LDAP_BUFFER, ListEntry);
        return TRUE;
    }
}

void LDAP_SOCKET::RecvBuildBuffer (
    IN    LPBYTE    Data,
    IN    DWORD    Length)
{
    LDAP_BUFFER *    Buffer;

    assert (Data);
    AssertLocked();

    Buffer = new LDAP_BUFFER;

    if (!Buffer) {
        Debug (_T("LDAP: RecvBuildBuffer, allocation failure #1.\n"));
        return;
    }

    if (Buffer -> Data.Grow (Length)) {
        memcpy (Buffer -> Data.Data, Data, Length);
        Buffer -> Data.Length = Length;

        InsertTailList (&RecvBufferQueue, &Buffer -> ListEntry);
    }
    else {
        Debug (_T("LDAP: RecvBuildBuffer, allocation failure #2.\n"));

        delete Buffer;
    }
}

HRESULT LDAP_SOCKET::AcceptSocket (
    SOCKET LocalClientSocket)
{
    if (State != STATE_NONE) {

        Debug (_T("LDAP: Not in a valid state for AcceptSocket (State != STATE_NONE).\n"));
        return E_UNEXPECTED;
    }

    State  = STATE_CONNECTED;
    Socket = LocalClientSocket;

    // notify parent about state change
    LdapConnection -> OnStateChange (this, State);
    
    if (!BindIoCompletionCallback ((HANDLE) Socket, LDAP_SOCKET::IoCompletionCallback, 0)) {

        DebugLastError (_T("LDAP: Failed to bind I/O completion callback.\n"));

        return GetLastErrorAsResult ();
    }
        
    return S_OK;
}

HRESULT LDAP_SOCKET::IssueConnect (
    SOCKADDR_IN * DestinationAddress)
{

    HRESULT Status;
    HRESULT Result;
    ULONG   Error;
    INT     RealSourceAddrSize = sizeof (SOCKADDR_IN);
    DWORD   BestInterfaceAddress; // host order
    int     ConnectError;
    BOOL    KeepaliveOption;

    assert (DestinationAddress);

    if (State != STATE_NONE) {

        Debug (_T("LDAP: Not in a valid state for IssueConnect (State != STATE_NONE).\n"));
        
        return E_UNEXPECTED;
    }

    assert (Socket == INVALID_SOCKET);
    assert (!ConnectEvent);
    assert (!ConnectWaitHandle);

    ActualDestinationAddress = *DestinationAddress;

    // If ILS runs on a remote (public) machine, we need to determine on which public
    // interface we will connect to the server. This is so to override global interface-restricted
    // NAT redirect by creating a trivial NAT redirect to the server's address from
    // the address of the public interface determined.
    //
    // If server happens to run on the local machine, then we use loopback address
    // as this is the address from where we will be "connecting" to the server.
    if (!::NhIsLocalAddress (DestinationAddress -> sin_addr.s_addr)) {

        Error = GetBestInterfaceAddress (
                    ntohl (DestinationAddress -> sin_addr.s_addr),
                    &BestInterfaceAddress);
            
        if (ERROR_SUCCESS != Error) {
            
            Result = HRESULT_FROM_WIN32 (Error); 
        
            DebugErrorF (Error, _T("LDAP: Failed to get best interface address for %08X.\n"), 
                    ntohl (DestinationAddress -> sin_addr.s_addr));

            return Result;
        }

    } else {

        BestInterfaceAddress = INADDR_LOOPBACK;
    }

    RealSourceAddress.sin_family      = AF_INET;
    RealSourceAddress.sin_addr.s_addr = htonl (BestInterfaceAddress);
    RealSourceAddress.sin_port        = htons (0); 

    Socket = WSASocket (AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

    if (Socket == INVALID_SOCKET) {

        Result = GetLastErrorAsResult ();

        DebugLastError (_T("LDAP: Failed to create destination socket.\n"));

    } else {

        // At this point we actually start the connect procedures. Everything before that
        // was just a preparation, so the socket stayed in the STATE_NONE.
        State = STATE_ISSUING_CONNECT;
    
        if (SOCKET_ERROR == bind(Socket, (PSOCKADDR)&RealSourceAddress, RealSourceAddrSize)) {

            Result = GetLastErrorAsResult();

            DebugLastError (_T("LDAP: Failed to bind destination socket.\n"));

        } else {

            // Set keepalive on the socket
            KeepaliveOption = TRUE;
            if (SOCKET_ERROR == setsockopt (Socket, SOL_SOCKET, SO_KEEPALIVE,
                                           (PCHAR) &KeepaliveOption, sizeof (KeepaliveOption)))
            {
                Result = GetLastErrorAsResult ();
                DebugLastError (_T("LDAP: Failed to set keepalive on destination socket.\n"));

            }  else {

                if (getsockname (Socket, (struct sockaddr *)&RealSourceAddress, &RealSourceAddrSize)) {

                    Result = GetLastErrorAsResult ();

                    DebugLastError (_T("LDAP: Failed to get name of TCP socket.\n"));

                } else {

                    DebugF (_T("LDAP: 0x%x setting up trivial redirect (%08X:%04X -> %08X:%04X) => (%08X:%04X -> %08X:%04X).\n"), 
                        LdapConnection,
                        SOCKADDR_IN_PRINTF(&RealSourceAddress), SOCKADDR_IN_PRINTF(DestinationAddress),
                        SOCKADDR_IN_PRINTF(&RealSourceAddress), SOCKADDR_IN_PRINTF(DestinationAddress));

                    if( NO_ERROR != NatCreateRedirectEx ( 
                            NatHandle, 
                            NatRedirectFlagLoopback,
                            IPPROTO_TCP, 
                            DestinationAddress -> sin_addr.s_addr,
                            DestinationAddress -> sin_port,
                            RealSourceAddress.sin_addr.s_addr, 
                            RealSourceAddress.sin_port,
                            DestinationAddress -> sin_addr.s_addr,
                            DestinationAddress -> sin_port,
                            RealSourceAddress.sin_addr.s_addr, 
                            RealSourceAddress.sin_port, 
                            NULL,
                            NULL, 
                            NULL, 
                            NULL)) {
                        
                        Result = GetLastErrorAsResult();

                        DebugLastErrorF (_T("LDAP: 0x%x failed to create trivial redirect.\n"),  
                            LdapConnection);
                
                    } else {

                        // we have successfully created a redirect
                        IsNatRedirectActive = TRUE;

                        do
                        {
                            ConnectEvent = CreateEvent (NULL, FALSE, FALSE, NULL); 

                            if (!ConnectEvent) { 

                                Result = GetLastErrorAsResult();

                                DebugLastErrorF (_T("LDAP: 0x%x failed to create connect-event.\n"),
                                    LdapConnection);

                                break;
                            }

                            Status = WSAEventSelect (Socket, ConnectEvent, FD_CONNECT);

                            if (Status) {
                                Result = GetLastErrorAsResult();

                                DebugLastErrorF (_T("LDAP: 0x%x failed to select events on the socket.\n"),
                                    LdapConnection);
                                break;
                            }

                            LdapConnection -> AddRef ();

                            if (!RegisterWaitForSingleObject (
                                    &ConnectWaitHandle, 
                                    ConnectEvent, 
                                    LDAP_SOCKET::OnConnectCompletion,
                                    this,
                                    INFINITE,
                                    WT_EXECUTEDEFAULT)) {

                                Result = GetLastErrorAsResult();

                                DebugLastErrorF (_T("LDAP: 0x%x failed to RegisterWaitForSingleObject.\n"),
                                    LdapConnection);
                                
                                LdapConnection -> Release ();

                                break;
                            } 
                            
                            if (connect (Socket, (SOCKADDR *)DestinationAddress, sizeof (SOCKADDR_IN))) {

                                ConnectError = WSAGetLastError ();

                                if(ConnectError == WSAEWOULDBLOCK) {

                                    State = STATE_CONNECT_PENDING;

                                    LdapConnection->OnStateChange (this, State);

                                    Result = S_OK;

                                } else {

                                    // a real error
                        
                                    Result = GetLastErrorAsResult();
                                        
                                    DebugLastErrorF (_T("LDAP: 0x%x failed to issue async connect.\n"),
                                        LdapConnection);
                                    
                                    FreeConnectResources ();

                                    // If remote server refused to connect, make an attempt to 
                                    // connect on a different port. Don't try to do so 
                                    // for any other error.

                                    if ((WSAECONNREFUSED == ConnectError || WSAECONNRESET == ConnectError)
                                         && AttemptAnotherConnect) {

                                        AttemptAnotherConnect = FALSE;

                                        DebugF (_T ("LDAP: 0x%x cancels trivial redirect (%08X:%04X -> %08X:%04X) => (%08X:%04X -> %08X:%04X).\n"), 
                                                    LdapConnection,
                                                    SOCKADDR_IN_PRINTF (&RealSourceAddress),
                                                    SOCKADDR_IN_PRINTF (&ActualDestinationAddress),
                                                    SOCKADDR_IN_PRINTF (&RealSourceAddress),
                                                    SOCKADDR_IN_PRINTF (&ActualDestinationAddress));

                                        NatCancelRedirect ( 
                                            NatHandle, 
                                            IPPROTO_TCP, 
                                            ActualDestinationAddress.sin_addr.s_addr,
                                            ActualDestinationAddress.sin_port,
                                            RealSourceAddress.sin_addr.s_addr, 
                                            RealSourceAddress.sin_port,
                                            ActualDestinationAddress.sin_addr.s_addr,
                                            ActualDestinationAddress.sin_port,
                                            RealSourceAddress.sin_addr.s_addr, 
                                            RealSourceAddress.sin_port); 

                                        IsNatRedirectActive = FALSE;

                                        closesocket (Socket);
                                        Socket = INVALID_SOCKET;
                                        
                                        State = STATE_NONE;

                                        Result = AttemptAlternateConnect (); // calls IssueConnect internally
                                    }

                                    LdapConnection -> Release ();
                                }

                                break;

                            } else {
                                // connect completed synchronously
                                // this should never occur

                                DebugF (_T("LDAP: 0x%x completed synchronously -- this should never occur.\n"),
                                    LdapConnection);

                                FreeConnectResources ();
                               
                                LdapConnection -> Release ();

                                Result = E_UNEXPECTED;

                            }
                       } while(FALSE);
                   }
                }
            }
        }
    }
            
    return Result;
}

// static
void LDAP_SOCKET::IoCompletionCallback (
    DWORD            Status, 
    DWORD            BytesTransferred, 
    LPOVERLAPPED    Overlapped)
{
    LDAP_OVERLAPPED *    LdapOverlapped;
    LDAP_CONNECTION *    Connection;

    LdapOverlapped = CONTAINING_RECORD (Overlapped, LDAP_OVERLAPPED, Overlapped);

    assert (LdapOverlapped -> Socket);

    Connection = LdapOverlapped -> Socket -> LdapConnection;

    LdapOverlapped -> Socket -> OnIoComplete (Status, BytesTransferred, LdapOverlapped);

    Connection -> Release();
}

void LDAP_SOCKET::OnIoComplete (
    DWORD                Status, 
    DWORD                BytesTransferred, 
    LDAP_OVERLAPPED *    Overlapped)
{
    Lock();

    assert (Overlapped -> IsPending);

    Overlapped -> IsPending = FALSE;
    Overlapped -> BytesTransferred = BytesTransferred;

    if (Overlapped == &RecvOverlapped)
        OnRecvComplete (Status);
    else if (Overlapped == &SendOverlapped)
        OnSendComplete (Status);
    else {
        AssertNeverReached();
    }

    Unlock();
}

// static
void LDAP_SOCKET::OnConnectCompletion (
    PVOID        Context,
    BOOLEAN        TimerOrWaitFired)
{
    LDAP_SOCKET *    LdapSocket;

    assert (Context);

    LdapSocket = (LDAP_SOCKET *) Context;

    LdapSocket -> Lock ();
    LdapSocket -> OnConnectCompletionLocked ();
    LdapSocket -> Unlock ();

    LdapSocket -> LdapConnection -> Release ();
}

void LDAP_SOCKET::OnRecvComplete (DWORD Status)
{
    DWORD StartOffset;
    DWORD NextPacketOffset;
    DWORD NextReceiveSize = 0;
    DWORD Result;

    LIST_ENTRY *    ListEntry;
    LDAP_BUFFER *    Buffer;


    if (Status != ERROR_SUCCESS) {

        if (State != STATE_TERMINATED) {

            Terminate();
        }

        return;
    }

    if (RecvOverlapped.BytesTransferred == 0) {

#if    DBG
        if (this == &LdapConnection -> ClientSocket)
        {
            DebugF (_T("LDAP: 0x%x client has closed transport socket.\n"), LdapConnection);
        }
        else if (this == &LdapConnection -> ServerSocket)
        {
            DebugF (_T("LDAP: 0x%x server has closed transport socket.\n"), LdapConnection);
        }
        else
            AssertNeverReached();
#endif

        Terminate();

        return;
    }

    assert (RecvBuffer);

    assert (RecvBuffer -> Data.Length + RecvOverlapped.BytesTransferred
        <= RecvBuffer -> Data.MaxLength);

    RecvBuffer -> Data.Length += RecvOverlapped.BytesTransferred;

    if (State == STATE_TERMINATED) {

        DebugF (_T("LDAP: 0x%x is terminating, no further processing will occur.\n"), LdapConnection);

        return;
    }

    if (RecvPump -> IsActivelyPassingData ()) {

        StartOffset = 0;

        for (;;) {

            assert (StartOffset <= RecvBuffer -> Data.Length);

            Result = LdapDeterminePacketBoundary (
                            RecvBuffer,
                            StartOffset, 
                            &NextPacketOffset,
                            &NextReceiveSize);

            if (Result == ERROR_SUCCESS) {

                RecvBuildBuffer (&RecvBuffer -> Data.Data [StartOffset], NextPacketOffset - StartOffset);

                StartOffset = NextPacketOffset;

            } else {

                RecvBuffer -> Data.DeleteRangeAtPos (0, StartOffset);

                if (Result == ERROR_INVALID_DATA) {

                    RecvPump -> StartPassiveDataTransfer ();

                    DebugF (_T("LDAP: 0x%x starts non-interpreting data transfer.\n"), LdapConnection);

                    InsertTailList (&RecvBufferQueue, &RecvBuffer -> ListEntry);

                    RecvBuffer = NULL;

                } 

                BytesToReceive = NextReceiveSize;

                break;

            } 

        }

    } else {
        LONG    PreviousRecvSize;
        LONG    PredictedRecvSize;
        HRESULT QueryResult;
        DWORD   BytesPreviouslyRequested = BytesToReceive;
        
        QueryResult = RecvSizePredictor.RetrieveOldSample (0, &PreviousRecvSize);

        if (ERROR_SUCCESS != RecvSizePredictor.AddSample ((LONG) RecvOverlapped.BytesTransferred)) {

            delete RecvBuffer;
            RecvBuffer = NULL;

            DebugErrorF (Status, _T("LDAP: 0x%x could not add sample to SamplePredictor.\n"), LdapConnection);

            Terminate();

            return;
        }

        if (BytesPreviouslyRequested == RecvOverlapped.BytesTransferred) {
            // Exact receive

            if (ERROR_SUCCESS != QueryResult) {

                BytesToReceive = (DWORD) (RecvOverlapped.BytesTransferred * 1.5);

            } else {

                PredictedRecvSize = RecvSizePredictor.PredictNextSample ();

                if (PredictedRecvSize < (LONG) RecvOverlapped.BytesTransferred) {

                    if ((DWORD) PreviousRecvSize < RecvOverlapped.BytesTransferred) {

                        BytesToReceive =  RecvOverlapped.BytesTransferred * 1000 / (DWORD) PreviousRecvSize * 
                                          RecvOverlapped.BytesTransferred        / 1000;

                    } else {

                        BytesToReceive = (DWORD) PreviousRecvSize;
                    }

                } else {
                    
                    BytesToReceive = (DWORD) PredictedRecvSize;
                }

            }

        } else {
            // Inexact receive
        
            PredictedRecvSize = RecvSizePredictor.PredictNextSample ();

            BytesToReceive = (PredictedRecvSize < LDAP_BUFFER_RECEIVE_SIZE) ? 
                              LDAP_BUFFER_RECEIVE_SIZE :
                             (DWORD) PredictedRecvSize;

        }

        if (BytesToReceive > LDAP_BUFFER_MAX_RECV_SIZE)  {

            DebugF (_T("LDAP: 0x%x intended to receive %d bytes. Lowering the number to %d bytes.\n"),
                    LdapConnection, BytesToReceive, LDAP_BUFFER_MAX_RECV_SIZE);

            BytesToReceive = LDAP_BUFFER_MAX_RECV_SIZE;
        }

        InsertTailList (&RecvBufferQueue, &RecvBuffer -> ListEntry);
            
        RecvBuffer = NULL;
    }


    while (!IsListEmpty (&RecvBufferQueue)) {

        ListEntry = RemoveHeadList (&RecvBufferQueue);

        Buffer = CONTAINING_RECORD (ListEntry, LDAP_BUFFER, ListEntry);

        RecvPump -> OnRecvBuffer (Buffer);
    }

    RecvIssue ();
}

void LDAP_SOCKET::OnSendComplete (DWORD Status)
{
    assert (SendBuffer);

    delete SendBuffer;
    SendBuffer = NULL;

    // before notifying the owning context, transmit any buffers
    // that are queued for send.

    if (SendNextBuffer())
        return;

    SendPump -> OnSendDrain();
}

void LDAP_SOCKET::OnConnectCompletionLocked (void) {

    WSANETWORKEVENTS NetworkEvents;
    HRESULT Result;
    int     ConnectError;

    AssertLocked();

    if (State != STATE_CONNECT_PENDING) {
        DebugF (_T("LDAP: 0x%x connect request completed, but socket is no longer interested.\n"), LdapConnection);
        return;
    }

    if (WSAEnumNetworkEvents (Socket, ConnectEvent, &NetworkEvents)) {

        DebugLastErrorF (_T("LDAP: 0x%x failed to retrieve network events.\n"), LdapConnection);

        Terminate();

        return;
    }

    if (!(NetworkEvents.lNetworkEvents & FD_CONNECT)) {

        DebugF (_T("LDAP: 0x%x connect event fired, but event mask does not indicate that connect completed -- internal error.\n"),
                LdapConnection);
        
        Terminate();

        return;
    }

    ConnectError = S_OK;

    if (NetworkEvents.iErrorCode [FD_CONNECT_BIT]) {
     
        ConnectError = NetworkEvents.iErrorCode [FD_CONNECT_BIT];
        DebugErrorF (ConnectError, _T("LDAP: 0x%x failed async connect request. "), LdapConnection);

        // If remote host refused to connect, we may attempt
        // a connection to an alternate port later, so we don't terminate 
        // the socket. All other error codes result in termination.
        if (WSAECONNRESET != ConnectError && WSAECONNREFUSED != ConnectError) {

            Terminate ();
            
            return;

        }
    }

    FreeConnectResources ();

    // If first attempt to connect fail, try to connect using an alternate port
    if ((WSAECONNREFUSED == ConnectError || WSAECONNRESET == ConnectError)
         && AttemptAnotherConnect) {

        AttemptAnotherConnect = FALSE;

        DebugF (_T ("LDAP: 0x%x cancels trivial redirect (%08X:%04X -> %08X:%04X) => (%08X:%04X -> %08X:%04X).\n"), 
                    LdapConnection,
                    SOCKADDR_IN_PRINTF (&RealSourceAddress),
                    SOCKADDR_IN_PRINTF (&ActualDestinationAddress),
                    SOCKADDR_IN_PRINTF (&RealSourceAddress),
                    SOCKADDR_IN_PRINTF (&ActualDestinationAddress));

        NatCancelRedirect ( 
            NatHandle, 
            IPPROTO_TCP, 
            ActualDestinationAddress.sin_addr.s_addr,
            ActualDestinationAddress.sin_port,
            RealSourceAddress.sin_addr.s_addr, 
            RealSourceAddress.sin_port,
            ActualDestinationAddress.sin_addr.s_addr,
            ActualDestinationAddress.sin_port,
            RealSourceAddress.sin_addr.s_addr, 
            RealSourceAddress.sin_port); 

        IsNatRedirectActive = FALSE;

        closesocket (Socket);
        Socket = INVALID_SOCKET;
        
        State = STATE_NONE;

        Result = AttemptAlternateConnect ();

        if (S_OK != Result) {

            Terminate ();
        }

        return;
    }

    DebugF (_T("LDAP: 0x%x established connection to server %08X:%04X.\n"), LdapConnection, SOCKADDR_IN_PRINTF (&ActualDestinationAddress));

    if (!BindIoCompletionCallback ((HANDLE)Socket, LDAP_SOCKET::IoCompletionCallback, 0)) {
        DebugLastErrorF (_T("LDAP: 0x%x failed to bind I/O completion callback.\n"), LdapConnection);

        Terminate();

        return;
    }
    
    // Asynchronous connect succeeded
    State = STATE_CONNECTED;

    LdapConnection -> OnStateChange (this, State);
}

void LDAP_SOCKET::FreeConnectResources (void) {

    // refrain from receiving notifications of further transport events
    WSAEventSelect (Socket, ConnectEvent, 0);

    assert (ConnectWaitHandle);
    UnregisterWaitEx (ConnectWaitHandle, NULL);
    ConnectWaitHandle = NULL;

    assert (ConnectEvent);
    CloseHandle(ConnectEvent);
    ConnectEvent = NULL;
}


// assumes that connect resources for previous
// connect attempt were freed
HRESULT LDAP_SOCKET::AttemptAlternateConnect (void) {

    HRESULT Result;

    // switch connection port to the other alternative

    ActualDestinationAddress.sin_port = 
        (ActualDestinationAddress.sin_port == htons (LDAP_STANDARD_PORT)) ?
        htons (LDAP_ALTERNATE_PORT) :
        htons (LDAP_STANDARD_PORT);

    DebugF (_T("LDAP: 0x%x will try to connect on an alternate address %08X:%04X.\n"),
                LdapConnection,
                SOCKADDR_IN_PRINTF (&ActualDestinationAddress));

    // attempting to connect on an alternate port
    Result = IssueConnect (&ActualDestinationAddress);
    
    if (S_OK != Result) {

        DebugF (_T("LDAP: 0x%x failed to issue connect on an alternate address %08X:%04X.\n"),
                    LdapConnection,
                    SOCKADDR_IN_PRINTF (&ActualDestinationAddress));
    }

    return Result;
}

void LDAP_SOCKET::Terminate (void)
{
    switch (State) {

    case    STATE_TERMINATED:
        // nothing to do
        return;

    case    STATE_NONE:
        // a different kind of nothing to do
        break;

    default:
        // in all other states, the socket handle must be set
        assert (Socket != INVALID_SOCKET);

        State = STATE_TERMINATED;

        if (INVALID_SOCKET != Socket) {

            closesocket (Socket);
            Socket = INVALID_SOCKET;

        }

        if (IsNatRedirectActive) {
                DebugF (_T ("LDAP: 0x%x cancels trivial redirect (%08X:%04X -> %08X:%04X) => (%08X:%04X -> %08X:%04X).\n"), 
                            LdapConnection,
                            SOCKADDR_IN_PRINTF (&RealSourceAddress),
                            SOCKADDR_IN_PRINTF (&ActualDestinationAddress),
                            SOCKADDR_IN_PRINTF (&RealSourceAddress),
                            SOCKADDR_IN_PRINTF (&ActualDestinationAddress));
             NatCancelRedirect ( 
                NatHandle, 
                IPPROTO_TCP, 
                ActualDestinationAddress.sin_addr.s_addr,
                ActualDestinationAddress.sin_port,
                RealSourceAddress.sin_addr.s_addr, 
                RealSourceAddress.sin_port,
                ActualDestinationAddress.sin_addr.s_addr,
                ActualDestinationAddress.sin_port,
                RealSourceAddress.sin_addr.s_addr, 
                RealSourceAddress.sin_port); 

            IsNatRedirectActive = FALSE;
        }

        if (ConnectWaitHandle) {

            if (UnregisterWaitEx (ConnectWaitHandle, NULL)) {

                // Take care of the case when the connection was terminated AFTER
                // async connect has been issued, but BEFORE the connect was completed. 
                // 
                // This should not normally happen.
                LdapConnection -> Release ();
                
            }

            ConnectWaitHandle = NULL;
        }

        if (ConnectEvent) {
            CloseHandle (ConnectEvent);
            ConnectEvent = NULL;
        }

        SendPump -> Terminate ();
        RecvPump -> Terminate ();

        break;
    }
    

    LdapConnection -> OnStateChange (this, State);
}

HRESULT LDAP_SOCKET::RecvIssue (void)
{
    WSABUF    BufferArray    [1];
    DWORD    Status;
    DWORD    BytesRequested;

    if (RecvOverlapped.IsPending) {
        DebugF (_T("LDAP: 0x%x receive is already pending.\n"), LdapConnection);
        return S_OK;
    }

    if (!RecvPump -> CanIssueRecv()) {
        // we gate the rate at which we receive data from the network on the
        // rate at which the other network connection consumes it.
        // this is how we preserve flow control.

        return S_OK;
    }

    if (!RecvBuffer) {

        RecvBuffer = new LDAP_BUFFER;

        if (!RecvBuffer) {

            DebugF (_T("LDAP: 0x%x RecvIssue allocation failure.\n"), LdapConnection);

            Terminate();

            return E_OUTOFMEMORY;
        }
    }

    BytesRequested = RecvBuffer -> Data.Length + BytesToReceive;

    if (!RecvBuffer -> Data.Grow (BytesRequested)) {

        DebugF (_T("LDAP: 0x%x failed to expand receive buffer to %d bytes.\n"), 
                LdapConnection, BytesRequested);

        Terminate();

        return E_OUTOFMEMORY;
    }

    BufferArray [0].len = BytesToReceive;
    BufferArray [0].buf = reinterpret_cast <char *>(RecvBuffer -> Data.Data) + RecvBuffer -> Data.Length;


    ZeroMemory (&RecvOverlapped.Overlapped, sizeof (OVERLAPPED));

    RecvFlags = 0;

    LdapConnection -> AddRef ();

    if (WSARecv (Socket, BufferArray, 1,
        &RecvOverlapped.BytesTransferred, &RecvFlags,
        &RecvOverlapped.Overlapped, NULL)) {

        Status = WSAGetLastError();

        if (Status != WSA_IO_PENDING) {
            // a true error, probably a transport failure
            
            LdapConnection -> Release ();

            DebugErrorF (Status, _T("LDAP: 0x%x failed to issue receive.\n"), LdapConnection);
            return HRESULT_FROM_WIN32 (Status);
        }
    }

    RecvOverlapped.IsPending = TRUE;

    return S_OK;
}

void LDAP_SOCKET::SendQueueBuffer (
    IN    LDAP_BUFFER *    Buffer)
{
    AssertLocked();

    assert (!IsInList (&SendBufferQueue, &Buffer -> ListEntry));
    InsertTailList (&SendBufferQueue, &Buffer -> ListEntry);

    SendNextBuffer();
}

BOOL LDAP_SOCKET::SendNextBuffer (void)
{
    WSABUF            BufferArray [1];
    LIST_ENTRY *    ListEntry;
    DWORD            Status;

    if (SendOverlapped.IsPending) {
        assert (SendBuffer);

//        Debug (_T("LDAP_SOCKET::SendNextMessage: already sending a message, must wait.\n"));
        return FALSE;
    }

    assert (!SendBuffer);

    // remove the next buffer to be sent from the queue

    if (IsListEmpty (&SendBufferQueue))
        return FALSE;

    ListEntry = RemoveHeadList (&SendBufferQueue);
    SendBuffer = CONTAINING_RECORD (ListEntry, LDAP_BUFFER, ListEntry);

    BufferArray [0].buf = reinterpret_cast<char *> (SendBuffer -> Data.Data);
    BufferArray [0].len = SendBuffer -> Data.Length;

    ZeroMemory (&SendOverlapped.Overlapped, sizeof (OVERLAPPED));

    LdapConnection -> AddRef ();

    if (WSASend (Socket, BufferArray, 1,
        &SendOverlapped.BytesTransferred, 0,
        &SendOverlapped.Overlapped, NULL)) {

        Status = WSAGetLastError();

        if (Status != WSA_IO_PENDING) {

            LdapConnection -> Release ();

            DebugError (Status, _T("LDAP: Failed to issue send.\n"));

            delete SendBuffer;
            SendBuffer = NULL;

            Terminate();

            // we return TRUE, because we did dequeue a buffer,
            // even if that buffer could not be transmitted.
            return TRUE;
        }
    }

    SendOverlapped.IsPending = TRUE;

    return TRUE;
}

BOOL LDAP_SOCKET::GetLocalAddress (
    OUT    SOCKADDR_IN *    ReturnAddress)
{
    INT            AddressLength;

    AssertLocked();

    if (State == STATE_CONNECTED) {
        AddressLength = sizeof (SOCKADDR_IN);

        if (getsockname (Socket, (SOCKADDR *) ReturnAddress, &AddressLength)) {
            DebugLastErrorF (_T("LDAP: 0x%x failed to retrieve socket address.\n"), LdapConnection);
            ZeroMemory (&ReturnAddress, sizeof (SOCKADDR_IN));

            return FALSE;
        }

        return TRUE;
    }
    else {
        return FALSE;
    }
}

BOOL LDAP_SOCKET::GetRemoteAddress (
    OUT    SOCKADDR_IN *    ReturnAddress)
{
    INT            AddressLength;

    AssertLocked();

    if (State == STATE_CONNECTED) {
        AddressLength = sizeof (SOCKADDR_IN);

        if (getpeername (Socket, (SOCKADDR *) ReturnAddress, &AddressLength)) {
            DebugLastErrorF (_T("LDAP: 0x%x failed to retrieve peer address.\n"), LdapConnection);
            ZeroMemory (&ReturnAddress, sizeof (SOCKADDR_IN));

            return FALSE;
        }

        return TRUE;
    }
    else {
        return FALSE;
    }
}

// LDAP_CONNECTION ---------------------------------------------------

LDAP_CONNECTION::LDAP_CONNECTION (NAT_KEY_SESSION_MAPPING_EX_INFORMATION * RedirectInformation)
: LIFETIME_CONTROLLER (&LdapSyncCounter) ,
    ClientSocket (this, &PumpClientToServer, &PumpServerToClient),
    ServerSocket   (this, &PumpServerToClient, &PumpClientToServer),
    PumpClientToServer    (this, &ClientSocket, &ServerSocket),
    PumpServerToClient    (this, &ServerSocket, &ClientSocket)
{
    SourceInterfaceAddress      = 0;
    DestinationInterfaceAddress = 0;

    State = STATE_NONE;

    DestinationAddress.sin_family      = AF_INET;
    DestinationAddress.sin_addr.s_addr = RedirectInformation -> DestinationAddress;
    DestinationAddress.sin_port        = RedirectInformation -> DestinationPort;

    SourceAddress.sin_family           = AF_INET;
    SourceAddress.sin_addr.s_addr      = RedirectInformation -> SourceAddress;
    SourceAddress.sin_port             = RedirectInformation -> SourcePort;

    DebugF (_T("LDAP: 0x%x created.\n"), this);
}

HRESULT LDAP_CONNECTION::Initialize (
    IN NAT_KEY_SESSION_MAPPING_EX_INFORMATION * RedirectInformation
    )
{
    
    HRESULT Result;

    Lock ();

    Result = InitializeLocked (RedirectInformation);

    Unlock ();

    return Result;
}

HRESULT LDAP_CONNECTION::InitializeLocked (
    IN NAT_KEY_SESSION_MAPPING_EX_INFORMATION * RedirectInformation
    )
{
    ULONG Error;

    DebugF (_T ("LDAP: 0x%x connection accepted on adapter %d.\n"), this, RedirectInformation -> AdapterIndex);

    SourceInterfaceAddress = H323MapAdapterToAddress (RedirectInformation -> AdapterIndex);

    if (INADDR_NONE == SourceInterfaceAddress) {

        DebugF (_T ("LDAP: 0x%x failed to get source interface address (via H323MapAdapterToAddress).\n"), this);

        return E_FAIL;

    }

    Error = GetBestInterfaceAddress (ntohl (DestinationAddress.sin_addr.s_addr), &DestinationInterfaceAddress);

    if (ERROR_SUCCESS != Error) {

        DebugErrorF (Error, _T ("LDAP: 0x%x failed to get destination interface address.\n"), this);

        return HRESULT_FROM_WIN32 (Error);

    }

    DebugF (_T("LDAP: 0x%x arrived on interface %08X.\n"), this, SourceInterfaceAddress);

    return S_OK;
}

LDAP_CONNECTION::~LDAP_CONNECTION (void)
{    
    DebugF (_T("LDAP: 0x%x destroyed.\n"), this);
}

void LDAP_CONNECTION::StartIo (void)
{
    PumpClientToServer.Start ();
    PumpServerToClient.Start ();
}

HRESULT LDAP_CONNECTION::AcceptSocket (
    IN    SOCKET           Socket,
    IN    SOCKADDR_IN *    LocalAddress,
    IN    SOCKADDR_IN *    RemoteAddress,
    IN    SOCKADDR_IN *    ArgActualDestinationAddress)
{
    HRESULT        Result;

    Lock();

    if (State == STATE_NONE) {

        Result = ClientSocket.AcceptSocket (Socket);

        if (Result == S_OK) {

            Result = ServerSocket.IssueConnect (ArgActualDestinationAddress);

            if (Result != S_OK) {

                DebugErrorF (Result, _T("LDAP: 0x%x failed to issue async connect to %08X:%04X.\n"),
                    this,
                    SOCKADDR_IN_PRINTF (ArgActualDestinationAddress));

                Terminate ();
            }
        }
        else {

            DebugErrorF (Result, _T("LDAP: 0x%x could not successfully complete accept.\n"), this);

            Terminate ();
        }            
    }
    else {

        DebugF (_T("LDAP: 0x%x is not in a valid state for accept (state != STATE_NONE).\n"), this);

        Result = E_UNEXPECTED;
    }

    Unlock();
        
    return Result;
}
    
HRESULT LDAP_CONNECTION::CreateOperation (
    IN    LDAP_OPERATION_TYPE        Type,
    IN    LDAP_MESSAGE_ID            MessageID,
    IN    ANSI_STRING *            DirectoryPath,
    IN    ANSI_STRING *            Alias,
    IN    IN_ADDR                    ClientAddress,
    IN    SOCKADDR_IN *            ServerAddress,
    IN    DWORD                    EntryTimeToLive  // in seconds
    )
{
    LDAP_OPERATION *    Operation;
    DWORD                Index;
    HRESULT                Result;

    if (FindOperationIndexByMessageID (MessageID, &Index)) {
        DebugF (_T("LDAP: 0x%x - an operation with message ID (%u) is already pending.\n"),
            this, 
            MessageID);
        return E_FAIL;
    }

    Operation = OperationArray.AllocAtPos (Index);
    if (!Operation) {
        DebugF (_T("LDAP: 0x%x - CreateOperation allocation failure #1.\n"), this);
        return E_OUTOFMEMORY;
    }

    Operation -> Type = Type;
    Operation -> MessageID = MessageID;
    Operation -> ClientAddress = ClientAddress;
    Operation -> ServerAddress = *ServerAddress;
    Operation -> EntryTimeToLive = EntryTimeToLive;

    CopyAnsiString (DirectoryPath, &Operation -> DirectoryPath);
    CopyAnsiString (Alias, &Operation -> Alias);

    if ((Operation -> DirectoryPath.Buffer
        && Operation -> Alias.Buffer)) {
        // all is well

        Result = S_OK;
    }
    else {
        DebugF (_T("LDAP: 0x%x - CreateOperation allocation failure #2.\n"), this);

        FreeAnsiString (&Operation -> DirectoryPath);
        FreeAnsiString (&Operation -> Alias);

        Result = E_OUTOFMEMORY;
    }

    return Result;
}


// Processing of LDAP messages ---------------------------------------

BOOL LDAP_CONNECTION::ProcessAddRequest (
    IN    LDAPMessage *    Message)
{
    AddRequest *        Request;
    ANSI_STRING            DirectoryPath;
    LDAP_PATH_ELEMENTS    PathElements;
    ANSI_STRING            AttributeTag;
    IN_ADDR                OldClientAddress;        // the address the client submitted in AddRequest
    IN_ADDR                NewClientAddress;        // the address we are replacing it with
    LDAP_OPERATION *    Operation;
    DWORD                OperationInsertionIndex;
    ASN1octetstring_t    IPAddressOldValue;
    SOCKADDR_IN            LocalToServerAddress;
    SOCKADDR_IN            LocalToClientAddress;
    SOCKADDR_IN            ServerAddress;
    INT                    AddressLength;
    BOOL                NeedObjectClass;
    ANSI_STRING            ClientAlias;

    AddRequest_attrs *                Iter;
    AddRequest_attrs_Seq *            Attribute;
    AddRequest_attrs_Seq_values *    ValueSequence;
    AttributeValue *                Attribute_Alias;
    AttributeValue *                Attribute_IPAddress;
    AttributeValue *                Attribute_ObjectClass;
    AttributeValue *                Attribute_Comment;
    AttributeValue                    Attribute_Comment_Old;
    ANSI_STRING            String;
                
    CHAR    IPAddressText    [0x20];
    USHORT    IPAddressTextLength;

    Request = &Message -> protocolOp.u.addRequest;

    // check to see if an existing operation with the same message id is pending.
    // if so, the client is in violation of the LDAP spec.
    // we'll just ignore the packet in this case.
    // at the same time, compute the insertion position for the new operation (for use later).

    if (FindOperationIndexByMessageID (Message -> messageID, &OperationInsertionIndex)) {
        DebugF (_T("LDAP: 0x%x - client has issued two requests with the same message ID (%u), LDAP protocol violation, packet will not be processed.\n"),
            this,
            Message -> messageID);

        return FALSE;
    }

    // NetMeeting supplies the objectClass in the directory path.
    // TAPI supplies the objectClass in the attribute set.
    // Don't you just love standards?

    InitializeAnsiString (&DirectoryPath, &Request -> entry);
    ParseDirectoryPath (&DirectoryPath, &PathElements);

    // make sure that the alias is present
    if (!PathElements.CN.Buffer) { 
        DebugF (_T("LDAP: 0x%x client %08X issued unqualified AddRequest (no alias present).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 
        return FALSE;
    }

    ClientAlias = PathElements.CN;

    if (PathElements.ObjectClass.Buffer) {
        if (RtlEqualStringConst (&PathElements.ObjectClass, &LdapText_RTPerson, TRUE)) {
            NeedObjectClass = FALSE;
        }
        else {
            DebugF (_T("LDAP: 0x%x client %08X issued unqualified AddRequest (no object class (1)).\n"),
                        this,
                        ntohl (SourceAddress.sin_addr.s_addr)); 

            return FALSE;
        }
    }
    else {

        NeedObjectClass = TRUE;
    }

    // first, determine if the attributes of this object
    // match the set of objects we wish to modify.

    // scan through the set of attributes
    // find interesting data

    Attribute_IPAddress   = NULL;
    Attribute_ObjectClass = NULL;
    Attribute_Comment     = NULL;

    for (Iter = Request -> attrs; Iter; Iter = Iter -> next) {
        Attribute = &Iter -> value;

        InitializeAnsiString (&AttributeTag, &Attribute -> type);

        if (Attribute -> values) {
            // we are only concerned with single-value attributes
            // if it's one of the attributes that we want,
            // then store in local variable

            if (RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_sipaddress, TRUE)
                || RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_ipAddress, TRUE))
                Attribute_IPAddress = &Attribute -> values -> value;
            else if (RtlEqualStringConst (&AttributeTag, &LdapText_ObjectClass, TRUE))
                Attribute_ObjectClass = &Attribute -> values -> value;
            else if (RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_comment, TRUE))
                Attribute_Comment = &Attribute -> values -> value;
            // else, we aren't interested in the attribute
        }
        else {
            // else, the attribute has no values
        }
    }

    // make sure that we found an objectClass value.
    // make sure that the objectClass = RTPerson

    if (NeedObjectClass) {

        if (!Attribute_ObjectClass) {
            DebugF (_T("LDAP: 0x%x client %08X issued unqualified AddRequest (no object class (2)).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

            return FALSE;
        }

        InitializeAnsiString (&String, Attribute_ObjectClass);
        if (!RtlEqualStringConst (&String, &LdapText_RTPerson, TRUE)) {
            DebugF (_T("LDAP: 0x%x client %08X issued unqualified AddRequest (not for RTPerson).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

            return FALSE;
        }
    }

    // if a comment field is present, and the comment is "Generated by TAPI3"
    // modify it so that it says "Generated by TAPI3, modified by ICS"

    if (Attribute_Comment) {
        Attribute_Comment_Old = *Attribute_Comment;

        InitializeAnsiString (&String, Attribute_Comment);
        if (RtlEqualStringConst (&String, &LdapText_GeneratedByTAPI, TRUE)) {
            Attribute_Comment -> value = (PUCHAR) LdapText_ModifiedByICS.Buffer;
            Attribute_Comment -> length = LdapText_ModifiedByICS.Length * sizeof (CHAR);
        }
    }

    // make sure ip address attribute is present
    // parse the address, build replacement address

    if (!Attribute_IPAddress) {
        DebugF (_T("LDAP: 0x%x client %08X issued unqualified AddRequest (IP address not present).\n"),
                this,
                ntohl (SourceAddress.sin_addr.s_addr)); 
        return FALSE;
    }

    if (LdapTranslationTable.ReachedMaximumSize ()) {
        LDAPMessage AddRequestFailed;

        DebugF(_T("LDAP: Size of LDAP Address Translation Table exceeded limit. Sending back AddResponse with an error code.\n"));

        AddRequestFailed.messageID = Message -> messageID;
        AddRequestFailed.protocolOp.choice = addResponse_choice;

        AddRequestFailed.protocolOp.u.addResponse.resultCode = sizeLimitExceeded;
        AddRequestFailed.protocolOp.u.addResponse.matchedDN.length = 0;
        AddRequestFailed.protocolOp.u.addResponse.matchedDN.value = NULL;
        AddRequestFailed.protocolOp.u.addResponse.errorMessage.length = LdapText_TableSizeExceededMessage.Length * sizeof (CHAR);
        AddRequestFailed.protocolOp.u.addResponse.errorMessage.value = (PUCHAR) LdapText_TableSizeExceededMessage.Buffer;

        PumpServerToClient.EncodeSendMessage (&AddRequestFailed);

    } else {

        if (!ServerSocket.GetRemoteAddress (&ServerAddress)) {
            return FALSE;
        }

        IPAddressTextLength = min (0x1F, (USHORT) Attribute_IPAddress -> length);
        IPAddressText [IPAddressTextLength] = 0;
        memcpy (IPAddressText, Attribute_IPAddress -> value, IPAddressTextLength * sizeof (CHAR));

        if (RtlCharToInteger (IPAddressText, 10, &OldClientAddress.s_addr) != STATUS_SUCCESS) {
            DebugF (_T("LDAP: 0x%x - AddRequest: bogus IP address value (%.*S).\n"),
                this,
                Attribute_IPAddress -> length,
                Attribute_IPAddress -> value);

            return FALSE;
        }

        // If ILS is running locally, we will not modify AddRequest sent by any private client.
        // Instead, we will later modify SearchResponse PDU if it turned out that
        // the name of the client being searched for is stored in LDAP Address Translation Table, and
        // the matching SearchRequest PDU came from a machine external to the client's local subnet.
        // The address we will put into the modified SearchResponse PDU will be that of the
        // interface on which SearchRequest was received (public interface, or another local interface)
        // If SearchRequest came from a private client located on the same subnet as the client we
        // are registering here, we won't modify it as those two clients can communicate directly 
        // and don't require any proxying by the NAT machine.
        
        if (!::NhIsLocalAddress (ServerAddress.sin_addr.s_addr)) {

            // get the address that we want to substitute (our external interface address)

            if (!ServerSocket.GetLocalAddress (&LocalToServerAddress)) {

                DebugF (_T("LDAP: 0x%x failed to get local address to server -- internal error.\n"), this);
                return FALSE;

            }

            // Convoluted code alert!
            // NetMeeting stores its IP address as an attribute on an LDAP object on an ILS server.
            // Makes sense. The attribute is encoded as a textual string, so they had to convert
            // the IP address to text.  Any sane person would have chosen the standard dotted-quad
            // format, but they chose to interpret the IP address as a 32-bit unsigned integer,
            // which is fair enough, and then to convert that integer to a single decimal text string.

            // That's all great, that's just fine.  But NetMeeting stores the attribute byte-swapped
            // -- they used ntohl one too many times.  Grrrrrrrr....  The value should have been stored
            // without swapping the bytes, since the interpretation was "unsigned integer" and not "octet sequence".

            OldClientAddress.s_addr = htonl (ByteSwap (OldClientAddress.s_addr));

            NewClientAddress = LocalToServerAddress.sin_addr;
                                            
            // believe me, this IS CORRECT.
            // see the long note above for more info. -- arlied

            if (RtlIntegerToChar (ByteSwap (ntohl (NewClientAddress.s_addr)),
                10, 0x1F, IPAddressText) != STATUS_SUCCESS) {
                DebugF (_T("LDAP: 0x%x failed to convert IP address to text -- internal error.\n"), this);

                return FALSE;
            }

            DebugF (_T("LDAP: 0x%x will register %08X on the ILS.\n"), this, ntohl (NewClientAddress.s_addr));

        } else {

            DebugF (_T("LDAP: 0x%x will register %08X on the ILS.\n"), this, ntohl (OldClientAddress.s_addr));

        }

        // allocate and build an LDAP_OPERATION structure.

        DebugF (_T("LDAP: 0x%x inserts valid AddRequest into operation table.\n"), this);

        CreateOperation (
            LDAP_OPERATION_ADD,
            Message -> messageID,
            &DirectoryPath,
            &ClientAlias,
            OldClientAddress,
            &ServerAddress,
            LDAP_TRANSLATION_TABLE_ENTRY_INITIAL_TIME_TO_LIVE);

        // the entry is now in the operation array
        // later, when the server sends the AddResponse,
        // we'll match the response with the request,
        // and modify the LDAP_TRANSLATION_TABLE

        // now, in-place, we modify the PDU structure,
        // reencode it, send it, undo the modification
        // (so ASN1Free_AddRequest doesn't act up)

        assert (Attribute_IPAddress);
        IPAddressOldValue = *Attribute_IPAddress;

        Attribute_IPAddress -> value = (PUCHAR) IPAddressText;
        Attribute_IPAddress -> length = strlen (IPAddressText);

        PumpClientToServer.EncodeSendMessage (Message);

        // switch back so we don't a/v when decoder frees pdu
        *Attribute_IPAddress = IPAddressOldValue;
        if (Attribute_Comment)
            *Attribute_Comment = Attribute_Comment_Old;
    }

    return TRUE;
}

BOOL LDAP_CONNECTION::ProcessModifyRequest (
    IN    LDAP_MESSAGE_ID    MessageID,
    IN    ModifyRequest *    Request)
{
    ModifyRequest_modifications *        ModificationIterator;
    ModifyRequest_modifications_Seq *    Modification;
    PModifyRequest_modifications_Seq_modification_values ModificationValue;

    LPCTSTR                  Op;
    ANSI_STRING              ModificationType;
    ANSI_STRING              TimeToLive;
    ANSI_STRING              DirectoryPath;
    ANSI_STRING              ClientAlias;

    DWORD                    OperationInsertionIndex;
    LDAP_PATH_ELEMENTS       PathElements;
    BOOL                     IsValidRefreshRequest = FALSE;
    SOCKADDR_IN              ServerAddress;

    DWORD                    EntryTimeToLive = 0;
    CHAR                     EntryTimeToLiveText [11];
    USHORT                   EntryTimeToLiveLength;

    // check to see if an existing operation with the same message id is pending.
    // if so, the client is in violation of the LDAP spec.
    // we'll just ignore the packet in this case.
    // at the same time, compute the insertion position for the new operation (for use later).

    if (FindOperationIndexByMessageID (MessageID, &OperationInsertionIndex)) {
        DebugF (_T("LDAP: 0x%x - ModifyRequest: client has issued two requests with the same message ID (%u), LDAP protocol violation, packet will not be processed.\n"),
            this,
            MessageID);

        return FALSE;
    }

    // Cover the case when ModifyRequest does not supply baseObject 
    if (Request -> object.value == NULL || Request -> object.length == 0) {

        DebugF (_T("LDAP: 0x%x client %08X issued unqualified ModifyRequest (no base object).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

        return FALSE;
    }

    InitializeAnsiString (&DirectoryPath, &Request -> object);
    ParseDirectoryPath (&DirectoryPath, &PathElements);

    ClientAlias = PathElements.CN;

    for (ModificationIterator = Request -> modifications; ModificationIterator; ModificationIterator = ModificationIterator -> next) {

        Modification = &ModificationIterator -> value;
        
        InitializeAnsiString (&ModificationType, &Modification -> modification.type);

        if (RtlEqualStringConst (&ModificationType, &LdapText_Modify_EntryTTL, TRUE) && Modification -> operation == replace) {
            IsValidRefreshRequest = TRUE;

            assert (Modification -> modification.values);

            ModificationValue = Modification -> modification.values;

            InitializeAnsiString (&TimeToLive, &ModificationValue -> value);

            EntryTimeToLiveLength = min (10, (USHORT) ModificationValue -> value.length);
            EntryTimeToLiveText [EntryTimeToLiveLength] = 0;
            memcpy (EntryTimeToLiveText, ModificationValue -> value.value, EntryTimeToLiveLength * sizeof (CHAR));

            if (RtlCharToInteger (&EntryTimeToLiveText [0], 10, &EntryTimeToLive) != STATUS_SUCCESS) {

                DebugF (_T("LDAP: 0x%x - ModifyRequest: bogus Time-To-Live value (%.*S).\n"),
                    this,
                    EntryTimeToLiveLength,
                    EntryTimeToLiveText);

                return FALSE;

            }

            DebugF (_T("LDAP: 0x%x %08X requested lifetime increase of %d seconds for (%.*S).\n"),
                 this, 
                 ntohl (SourceAddress.sin_addr.s_addr), 
                 EntryTimeToLive, // in seconds 
                 ANSI_STRING_PRINTF (&ClientAlias));

        }
    }

    // If type of the modification was 'replace', and the attribute to be modified was 'EntryTTL'
    // then we have just received a valid refresh request from PhoneDialer
    //
    if (!IsValidRefreshRequest) {

        DebugF (_T("LDAP: 0x%x client %08X issued unqualified ModifyRequest (not a refresh request).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

        return FALSE;
    }

    if (!ClientAlias.Buffer || ClientAlias.Length == 0) {

        DebugF (_T("LDAP: 0x%x client %08X issued unqualified ModifyRequest (no client alias in refresh request).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

        return FALSE;
    }

    if (!ServerSocket.GetRemoteAddress (&ServerAddress)) {
        DebugF (_T("LDAP: 0x%x ModifyRequest: failed to get server address -- internal error.\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

        return FALSE;
    }

    DebugF (_T("LDAP: 0x%x inserts valid ModifyRequest into operation table.\n"), this);

    // Allocate and build an LDAP_OPERATION structure.
    CreateOperation (
        LDAP_OPERATION_MODIFY,
        MessageID,
        &DirectoryPath,
        &ClientAlias,
        SourceAddress.sin_addr,
        &ServerAddress,
        EntryTimeToLive
        );

    // The entry is now in the operation array
    // later, when the server sends the ModifyResponse,
    // we'll check whether it was successful.
    // If so, we will find and refresh matching
    // entry in the LDAP Address Translation Table
    
    return FALSE;
}

BOOL LDAP_CONNECTION::ProcessDeleteRequest (
    IN    LDAP_MESSAGE_ID    MessageID,
    IN    DelRequest *    Request)
{
    ANSI_STRING  DirectoryPath;
    HRESULT Result = S_FALSE;
    LDAP_PATH_ELEMENTS    PathElements;

    assert (Request);

    DirectoryPath.Buffer = (PCHAR) Request -> value;
    DirectoryPath.Length = (USHORT) Request -> length;
    DirectoryPath.MaximumLength = (USHORT) Request -> length;

    ParseDirectoryPath (&DirectoryPath, &PathElements);

    if (RtlEqualStringConst (&PathElements.ObjectClass, &LdapText_RTPerson, TRUE)) {

        Result = LdapTranslationTable.RemoveEntryByAliasServer (&PathElements.CN, &DestinationAddress);

        if (Result == S_OK) {

            DebugF (_T("LDAP: 0x%x removed entry (%.*S) from LDAP table.\n"),
                this,
                ANSI_STRING_PRINTF (&DirectoryPath));

        } else {
            
            DebugF (_T("LDAP: 0x%x attempted to remove entry (%.*S) from LDAP table, but it was not there.\n"),
                this,
                ANSI_STRING_PRINTF (&DirectoryPath));
        
        }

    } else {

        DebugF (_T("LDAP: 0x%x client %08X issued unqualified DeleteRequest.\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 
    }

    return FALSE;
}

BOOL LDAP_CONNECTION::ProcessSearchRequest (
    IN    LDAPMessage *    Message)
{
    SearchRequest            * Request;

    ANSI_STRING                DirectoryPath;
    ANSI_STRING                AttributeTag;
    ANSI_STRING                AttributeType;
    ANSI_STRING                AttributeValue;
    ANSI_STRING                RequestedForAlias;

    LDAP_PATH_ELEMENTS         PathElements;
    LDAP_OPERATION           * Operation;

    Filter                   * SearchFilter;
    Filter_and               * FilterIterator;
    SearchRequest_attributes * Iterator;
    AttributeValueAssertion  * EqualityAssertion;
                
    BOOL IsRequestForIPAddress     = FALSE;
    BOOL IsRequestForRTPerson      = FALSE;
    BOOL IsRequestForSttl          = FALSE;
    BOOL IsQualifiedRequest        = FALSE;
    BOOL IsRequestForSpecificAlias = FALSE;

    DWORD                      EntryTimeToLive = 0;
    CHAR                       EntryTimeToLiveText [11];
    USHORT                     EntryTimeToLiveLength;
                
    DWORD OperationInsertionIndex;

    Request = &Message -> protocolOp.u.searchRequest;

    // check to see if an existing operation with the same message id is pending.
    // if so, the client is in violation of the LDAP spec.
    // we'll just ignore the packet in this case.
    // at the same time, compute the insertion position for the new operation (for use later).

    if (FindOperationIndexByMessageID (Message -> messageID, &OperationInsertionIndex)) {
        DebugF (_T("LDAP: 0x%x - SearchRequest - client has issued two requests with the same message ID (%u), LDAP protocol violation, packet will not be processed.\n"),
            this,
            Message -> messageID);

        return FALSE;
    }

    // Cover the case when SearchRequest does not supply baseObject 
    if (Request -> baseObject.value == NULL || Request -> baseObject.length == 0) {

        DebugF (_T("LDAP: 0x%x client %08X issued unqualified SearchRequest (no base object).\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

            return FALSE;

    }

    InitializeAnsiString (&DirectoryPath, &Request -> baseObject);
    ParseDirectoryPath (&DirectoryPath, &PathElements);

    // Determine whether we are interested in this request. 
    // 
    // This is what we are interested in from SearchRequests originated by NetMeeting:
    //    1. It searches for IP address (query to the server), or
    //    2. It searches for Sttl attribute, AND for RTPerson attribute (refresh request)
    //

    for (Iterator = Request -> attributes; Iterator; Iterator = Iterator -> next) {
       
        InitializeAnsiString (&AttributeValue, &Iterator -> value);

        if (RtlEqualStringConst (&AttributeValue, &LdapText_Attribute_sipaddress, TRUE)
            || RtlEqualStringConst (&AttributeValue, &LdapText_Attribute_ipAddress, TRUE)) {

            IsRequestForIPAddress = TRUE;

        }

        // else not interesting attribute
    }

    // Look closer at the composition of the filter. 
    //
    // NetMeeting specifies the following filter in SearchRequest:
    //
    // FilterType = AND
    //     FilterType = EqualityMatch
    //         AttributeType  = objectClass
    //         AttributeValue = RTPerson
    //     FilterType = EqualityMatch
    //         AttributeType  = cn
    //         AttributeValue = <...alias, for which IP address is searched, or refresh is requested...>
    //
    // NetMeeting may also add the following 'EqualityMatch' clause to the filter
    // if a Time-To-Live increase is requested
    //
    //     FilterType = EqualityMatch
    //         AttributeType  = sttl
    //         AttributeValue = <...increase in Time-To-Live, in minutes...>
    //
    // Phone Dialer DOES NOT query directory server to determine IP address of the called party.
    // It does the determination by some other means (DNS lookup, most certainly).

    SearchFilter = &Request -> filter;

    switch (SearchFilter -> choice) {
    case and_choice:
        for (FilterIterator = SearchFilter -> u.and; FilterIterator; FilterIterator = FilterIterator -> next) {
            switch (FilterIterator -> value.choice) {
            case equalityMatch_choice:

                EqualityAssertion = &FilterIterator -> value.u.equalityMatch;

                InitializeAnsiString (&AttributeType,  &EqualityAssertion -> attributeType);
                InitializeAnsiString (&AttributeValue, &EqualityAssertion -> attributeValue);

                if (RtlEqualStringConst (&AttributeType, &LdapText_Attribute_sttl, TRUE)) {

                    IsRequestForSttl = TRUE;

                    EntryTimeToLiveLength = min (10, (USHORT) AttributeValue.Length);
                    EntryTimeToLiveText [EntryTimeToLiveLength] = 0;
                    memcpy (EntryTimeToLiveText, AttributeValue.Buffer, EntryTimeToLiveLength * sizeof (CHAR));

                    if (RtlCharToInteger (&EntryTimeToLiveText [0], 10, &EntryTimeToLive) != STATUS_SUCCESS) {

                        DebugF (_T("LDAP: 0x%x - SearchRequest: bogus Time-To-Live value (%.*S).\n"),
                            this,
                            EntryTimeToLiveLength,
                            EntryTimeToLiveText);

                        return FALSE;

                    }

                }

                if (RtlEqualStringConst (&AttributeType, &LdapText_ObjectClass, TRUE)
                    || RtlEqualStringConst (&AttributeValue, &LdapText_RTPerson, TRUE)) {

                    IsRequestForRTPerson = TRUE;
                }

                if (RtlEqualStringConst (&AttributeType, &LdapText_CN, TRUE)) {

                    RequestedForAlias = AttributeValue;

                    IsRequestForSpecificAlias = TRUE;

                }
                
                break;

            default:

                break;
            }
        }
                
        break;

    default:

        break;
    }

    IsQualifiedRequest = IsRequestForIPAddress || (IsRequestForRTPerson && IsRequestForSttl);
        
    if (!IsQualifiedRequest) {
        DebugF (_T("LDAP: 0x%x client %08X issued unqualified SearchRequest.\n"),
                    this,
                    ntohl (SourceAddress.sin_addr.s_addr)); 

        return FALSE;
    }

    if (IsRequestForSttl) {

        DebugF (_T("LDAP: 0x%x %08X requested lifetime increase of %d seconds for (%.*S).\n"),
             this, 
             ntohl (SourceAddress.sin_addr.s_addr), 
             EntryTimeToLive * 60, // in seconds
             ANSI_STRING_PRINTF (&RequestedForAlias));
    }

    if (IsRequestForIPAddress) {

        if (IsRequestForSpecificAlias) {

            DebugF (_T("LDAP: 0x%x %08X requested IP address for (%.*S).\n"),
                 this, 
                 ntohl (SourceAddress.sin_addr.s_addr), 
                 ANSI_STRING_PRINTF (&RequestedForAlias));

        } else {

            DebugF (_T("LDAP: 0x%x %08X issued unspecified request for IP address.\n"),
                 this, 
                 ntohl (SourceAddress.sin_addr.s_addr) );
        }
    }

    DebugF (_T("LDAP: 0x%x inserts valid SearchRequest into operation table.\n"), this);

    // allocate and build an LDAP_OPERATION structure.
    CreateOperation (
        LDAP_OPERATION_SEARCH,
        Message -> messageID,
        &DirectoryPath,
        &PathElements.CN,
        SourceAddress.sin_addr,
        &DestinationAddress,
        EntryTimeToLive * 60); // in seconds

    // the entry is now in the operation array
    // later, when the server sends the SearchResponse,
    // we'll match the response with the request,
    // and modify the IP address if an entry with
    // the matching alias happens to be in the 
    // LDAP_TRANSLATION_TABLE. This would mean that 
    // the client running on the proxy machine itself
    // wishes to connect to a private subnet client.

    PumpClientToServer.EncodeSendMessage (Message);

    return TRUE;
}

// server to client messages

void LDAP_CONNECTION::ProcessAddResponse (
    IN    LDAPMessage *    Message)
{
    AddResponse *        Response;
    LDAP_OPERATION *    Operation;

    Response = &Message -> protocolOp.u.addResponse;

    AssertLocked();

    if (!FindOperationByMessageID (Message -> messageID, &Operation)) {

        return;
    }

    if (Operation -> Type == LDAP_OPERATION_ADD) {

        if (Response -> resultCode == success) {
            DebugF (_T("LDAP: 0x%x server has approved AddRequest for (%.*S).\n"), 
                this, ANSI_STRING_PRINTF (&Operation -> Alias));

            assert (Operation -> Alias.Buffer);
            assert (Operation -> DirectoryPath.Buffer);

            InterfaceArray.StartQ931ReceiveRedirects ();

            LdapTranslationTable.InsertEntry (
                &Operation -> Alias,
                &Operation -> DirectoryPath,
                Operation -> ClientAddress,
                &Operation -> ServerAddress,
                 Operation -> EntryTimeToLive);

        }
        else {
            DebugF (_T("LDAP: 0x%x Server has rejected AddRequest, result code (%u).\n"),
                this,
                Response -> resultCode);
        }
    }
    else {
        DebugF (_T("LDAP: 0x%x received AddResponse with message ID (%u), and found matching pending request, but the type of the request does not match (%d).\n"),
            this,
            Message -> messageID,
            Operation -> Type);
    }

    Operation -> FreeContents ();
                
    OperationArray.DeleteEntry (Operation);
}

void LDAP_CONNECTION::ProcessModifyResponse (
    IN    LDAP_MESSAGE_ID        MessageID,
    IN    ModifyResponse *    Response)
{
    LDAP_OPERATION *    Operation;

    AssertLocked();

    if (!FindOperationByMessageID (MessageID, &Operation)) {

        return;
    }

    if (Operation -> Type == LDAP_OPERATION_MODIFY) {

        if (Response -> resultCode == success) {
            assert (Operation -> Alias.Buffer);
            assert (Operation -> DirectoryPath.Buffer);

            DebugF (_T("LDAP: 0x%x server %08X has approved increase in lifetime of entry (%.*S) by %d seconds.\n"),
                    this,
                    ntohl (Operation -> ServerAddress.sin_addr.s_addr),
                    ANSI_STRING_PRINTF (&Operation -> Alias),
                    Operation -> EntryTimeToLive);

            LdapTranslationTable.RefreshEntry (&Operation -> Alias,
                                               &Operation -> DirectoryPath,
                                                Operation -> ClientAddress,
                                               &Operation -> ServerAddress,
                                                Operation -> EntryTimeToLive);

        }
        else {
            DebugF (_T("LDAP: 0x%x server %08X has rejected ModifyRequest, result code (%u).\n"),
                this,
                ntohl (Operation -> ServerAddress.sin_addr.s_addr), Response -> resultCode);
        }
    }
    else {
        DebugF (_T("LDAP: 0x%x received with message ID (%u), and found matching pending request, but the type of the request does not match (%d).\n"),
            this,
            MessageID,
            Operation -> Type);
    }

    Operation -> FreeContents ();
                
    OperationArray.DeleteEntry (Operation);
}

void LDAP_CONNECTION::ProcessDeleteResponse (
    IN    LDAP_MESSAGE_ID        MessageID,
    IN    DelResponse *        Response)
{
}

BOOL LDAP_CONNECTION::ProcessSearchResponse (
    IN    LDAPMessage *    Message)
{
    SearchResponse *    Response;
    ANSI_STRING         ObjectName;
    LDAP_OBJECT_NAME_ELEMENTS    ObjectNameElements = { 0 };
    ANSI_STRING         AttributeTag;
    ASN1octetstring_t   IPAddressOldValue;
    ANSI_STRING         ClientAlias;
    ANSI_STRING         String;
    ANSI_STRING         ErrorMessage;
    HRESULT             TranslationTableLookupResult;
    DWORD               LookupAddress;           // host order
    SOCKADDR_IN         ServerAddress;
    DWORD               LocalToRequestedAddress; // host order
    DWORD               AddressToReport;

    SearchResponse_entry_attributes             * Iter;
    SearchResponse_entry_attributes_Seq_values  * ValueSequence;
    SearchResponse_entry_attributes_Seq         * Attribute;
    AttributeValue *    Attribute_IPAddress;
    AttributeValue *    Attribute_Sttl;

    CHAR                IPAddressText    [0x20];
    BOOL                Result = FALSE;
    LDAP_OPERATION *    Operation;

    SOCKET UDP_Socket      = INVALID_SOCKET;

    assert (Message);

    Response = &Message -> protocolOp.u.searchResponse;

    AssertLocked();

    if (!FindOperationByMessageID (Message -> messageID, &Operation)) {

        return FALSE;
    }

    switch (Response -> choice) {
    case entry_choice:

        // Determine address of the server
        if (!ServerSocket.GetRemoteAddress (&ServerAddress)) {
            return FALSE;
        }
        
        if(Response -> choice == entry_choice) {

            if (Operation -> Type == LDAP_OPERATION_SEARCH) {
                // Parse this object's name to get alias and IP address
                InitializeAnsiString (&ObjectName, &Response -> u.entry.objectName);
                ParseObjectName (&ObjectName, &ObjectNameElements);

                // scan through the set of attributes
                // find the ones of interest

                Attribute_IPAddress = NULL;
                Attribute_Sttl      = NULL;

                for (Iter = Response -> u.entry.attributes; Iter; Iter = Iter -> next) {
                    Attribute = &Iter -> value;

                    InitializeAnsiString (&AttributeTag, &Attribute -> type);

                    if (Attribute -> values) {
                        if (RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_sipaddress, TRUE)
                            || RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_ipAddress, TRUE)) {

                            Attribute_IPAddress = &Attribute -> values -> value;

                        } else if (RtlEqualStringConst (&AttributeTag, &LdapText_Attribute_sttl, TRUE)) {

                            Attribute_Sttl = &Attribute -> values -> value;
                        }

                        // else, we aren't interested in the attribute
                    }
                    else {
                        // else, the attribute has no values
                    }
                }

                // make sure that the alias is present
                ClientAlias = ObjectNameElements.CN;

                if (ClientAlias.Length == 0) {
                    Result = FALSE;
                } else {
                    
                    if (Attribute_Sttl) {

                        // NetMeeting refreshes its registrations on an ILS by periodically sending a SearchRequest with 
                        // attribute "sttl"

                        DebugF (_T ("LDAP: 0x%x server %08X has approved increase in lifetime of entry (%.*S) by %d seconds.\n"),
                                    this, 
                                    ntohl (ServerAddress.sin_addr.s_addr), 
                                    ANSI_STRING_PRINTF (&ClientAlias), 
                                    Operation -> EntryTimeToLive);

                        LdapTranslationTable.RefreshEntry (&ClientAlias,
                                                           &ObjectName,
                                                            Operation -> ClientAddress,
                                                           &Operation -> ServerAddress, 
                                                            Operation -> EntryTimeToLive);

                    } else {
                        // make sure ip address attribute is present
                        if (!Attribute_IPAddress) {
                            Result = FALSE;
                        } else {
                            // see whether there is a registration entry in the translation table
                            // with the same alias
                            TranslationTableLookupResult = LdapQueryTableByAliasServer (&ClientAlias,
                                                                                   &Operation -> ServerAddress,
                                                                                   &LookupAddress);
                            // If an entry with the same alias is not in the table, 
                            // then we send the SearchResponse PDU unmodified to the requestor
                            if (S_OK != TranslationTableLookupResult) {

                                Result = FALSE;
                            
                            } else {
                                // Otherwise, we decide what would be the correct address
                                // to report to the requestor. We will either report the address
                                // read from the Address Translation Table, or the address
                                // of the interface requestor used to reach us. The decision will be
                                // made based on the interface address requestor used to reach us, and
                                // address of the interface we would use to reach the requestee.

                                if (INADDR_NONE == SourceInterfaceAddress) {
                                    DebugF (_T("LDAP: 0x%x failed to get best interface address for the requestor.\n"), this);
                                    return FALSE;
                                }

                                // Determine on which interface we would connect to the entity whose address was requested
                                if (GetBestInterfaceAddress (LookupAddress, &LocalToRequestedAddress)) {
                                    DebugF (_T("LDAP: 0x%x failed to get best interface address for the requestee.\n"), this);
                                    return FALSE;
                                }

                                // The default reporting behaviour is to report the address of the best interface to reach the requestor,
                                // thus shielding the requestor from the knowledge of the network internals.
                                //
                                // However, there are three exceptions to this rule, when we report the actual address stored
                                // in the Address Translation Table. These exceptions are as follows:
                                //
                                // 1. If the requestor is local.
                                //    In this case we assume that the requestor can directly reach the requestee, and thus
                                //    an H.323 call between them doesn't have to be routed via us.
                                //
                                // 2. If local H323 routing is enabled, AND the requestor and the requestee have the same address
                                //    This is necessary to allow the requestor to do something special with the calls to itself
                                //    (NetMeeting, for example, prohibits such calls).
                                // 
                                // 3. If local H323 routing is disabled, and requestor and requestee are reachable through the
                                //    same interface.
                                //    In these case we assume that the requestor and the requestee can directly reach one another,
                                //    and thus an H.323 call between them doesn't have to be routed via us.
                                //   
                        
                                if (::NhIsLocalAddress (SourceAddress.sin_addr.s_addr)) {

                                    AddressToReport = LookupAddress;

                                } else {

                                    if (EnableLocalH323Routing) {
                                    
                                        AddressToReport = (ntohl (SourceAddress.sin_addr.s_addr) == LookupAddress)  ?
                                                    LookupAddress            :
                                                    SourceInterfaceAddress;
                                    } else {

                                        AddressToReport = (SourceInterfaceAddress == LocalToRequestedAddress)       ?
                                                    LookupAddress            :
                                                    SourceInterfaceAddress;

                                    }
                                }

                                assert (Attribute_IPAddress);

                                if (RtlIntegerToChar (htonl (AddressToReport),
                                    10, 0x1F, IPAddressText) != STATUS_SUCCESS) {
                                    DebugF (_T("LDAP: 0x%x failed to convert IP address to text -- internal error.\n"), this);
                                    Result = FALSE;
                                } else {

                                    DebugF (_T("LDAP: 0x%x read %08X in table. Reporting  %08X to requestor %08X.\n"),
                                         this,
                                         LookupAddress, AddressToReport, ntohl (SourceAddress.sin_addr.s_addr));

                                    // Save the old value of the IP address, - we will need to restore
                                    // the PDU structure later.
                                    IPAddressOldValue = *Attribute_IPAddress;

                                    // now, in-place, we modify the PDU structure,
                                    // reencode it, send it, undo the modification
                                    // (so ASN1Free_SearchResponse doesn't act up)
                                    Attribute_IPAddress -> value = (PUCHAR) IPAddressText;
                                    Attribute_IPAddress -> length = strlen (IPAddressText);

                                    PumpServerToClient.EncodeSendMessage (Message);

                                    // switch back so we don't a/v when decoder frees pdu
                                    *Attribute_IPAddress = IPAddressOldValue;

                                    Result = TRUE;
                                }
                            }
                        }
                    }
                }
            } else {
                DebugF (_T("LDAP: 0x%x received response with message ID (%u), and found matching pending request, but the type of the request does not match (%d).\n"),
                    this,
                    Message -> messageID,
                    Operation -> Type);

                Result = FALSE;
            }
        } 
    break;

    case resultCode_choice:

        // We free the operation and associated memory on SearchResponses containing result
        // code, no matter whether the code indicated success or failure
        InitializeAnsiString (&ErrorMessage, &Response -> u.resultCode.errorMessage);

        DebugF (_T("LDAP: 0x%x result in SearchResponse (%d) (code=%d message=(%*.S)).\n"),
             this,
             Message -> messageID,
             Response -> u.resultCode.resultCode,
             ANSI_STRING_PRINTF (&ErrorMessage));

        Operation -> FreeContents ();
                
        OperationArray.DeleteEntry (Operation);

    break;

    default:

        AssertNeverReached ();
    }

    return Result;
}

// this method does not assume ownership of the LdapMessage structure,
// which has scope only of this call stack.

BOOL LDAP_CONNECTION::ProcessLdapMessage (
    IN    LDAP_PUMP *        Pump,
    IN    LDAPMessage *    LdapMessage)
{
    assert (Pump);
    assert (LdapMessage);

    if (Pump == &PumpClientToServer) {

        switch (LdapMessage -> protocolOp.choice) {
        case addRequest_choice:
            DebugF (_T("LDAP: 0x%x received AddRequest (%d).\n"), this, LdapMessage -> messageID);
            return ProcessAddRequest (LdapMessage);

        case modifyRequest_choice:
            DebugF (_T("LDAP: 0x%x received ModifyRequest (%d).\n"), this, LdapMessage -> messageID);
            return ProcessModifyRequest (LdapMessage -> messageID, &LdapMessage -> protocolOp.u.modifyRequest);

        case delRequest_choice:
            DebugF (_T("LDAP: 0x%x received DeleteRequest (%d).\n"), this, LdapMessage -> messageID);
            return ProcessDeleteRequest (LdapMessage -> messageID, &LdapMessage -> protocolOp.u.delRequest);

        case searchRequest_choice:
            DebugF (_T("LDAP: 0x%x received SearchRequest (%d).\n"), this, LdapMessage -> messageID);
            return ProcessSearchRequest (LdapMessage);

        case bindRequest_choice:
            DebugF (_T("LDAP: 0x%x received BindRequest (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case abandonRequest_choice:
            DebugF (_T("LDAP: 0x%x received AbandonRequest (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case unbindRequest_choice:
            DebugF (_T("LDAP: 0x%x received UnbindRequest (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case modifyRDNRequest_choice:
            DebugF (_T("LDAP: 0x%x received ModifyRDNRequest (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case compareDNRequest_choice:
            DebugF (_T("LDAP: 0x%x received CompareDNRequest (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        default:
            return FALSE;
        }

        return FALSE;
    }
    else if (Pump == &PumpServerToClient) {

        switch (LdapMessage -> protocolOp.choice) {
        case    addResponse_choice:
            DebugF (_T("LDAP: 0x%x received AddResponse (%d).\n"), this, LdapMessage -> messageID);
            ProcessAddResponse (LdapMessage);
            break;

        case    modifyResponse_choice:
            DebugF (_T("LDAP: 0x%x received ModifyResponse (%d).\n"), this, LdapMessage -> messageID);
            ProcessModifyResponse (LdapMessage -> messageID, &LdapMessage -> protocolOp.u.modifyResponse);
            break;

        case    delResponse_choice:
            DebugF (_T("LDAP: 0x%x received DeleteResponse (%d).\n"), this, LdapMessage -> messageID);
            ProcessDeleteResponse (LdapMessage -> messageID, &LdapMessage -> protocolOp.u.delResponse);
            break;

        case    searchResponse_choice:
            DebugF (_T("LDAP: 0x%x received SearchResponse (%d).\n"), this, LdapMessage -> messageID);
            return ProcessSearchResponse (LdapMessage);
            break;

        case bindResponse_choice:
            DebugF (_T("LDAP: 0x%x received BindResponse (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case modifyRDNResponse_choice:
            DebugF (_T("LDAP: 0x%x received ModifyRDNResponse (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        case compareDNResponse_choice:
            DebugF (_T("LDAP: 0x%x received CompareDNResponse (%d).\n"), this, LdapMessage -> messageID);
            return FALSE;

        default:
            break;
        }

        return FALSE;

    }
    else {
        AssertNeverReached();
        return FALSE;
    }
}

void LDAP_CONNECTION::ProcessBuffer (
    IN    LDAP_PUMP *        Pump,
    IN    LDAP_BUFFER *    Buffer)
{
    ASN1error_e        Error;
    LDAPMessage *    PduStructure;
    ASN1decoding_t    Decoder;

    assert (Pump);
    assert (Buffer);
        
    // decode the PDU

    Error = ASN1_CreateDecoder (LDAP_Module, &Decoder, Buffer -> Data.Data, Buffer -> Data.Length, NULL);

    if (Error == ASN1_SUCCESS) {

        PduStructure = NULL;
        Error = ASN1_Decode (Decoder, (PVOID *) &PduStructure, LDAPMessage_ID, 0, NULL, 0);

        if (ASN1_SUCCEEDED (Error)) {
            if (ProcessLdapMessage (Pump, PduStructure)) {
                // a TRUE return value indicates that ProcessLdapMessage interpreted
                // and acted on the contents of PduStructure.  therefore, the
                // original PDU is no longer needed, and is destroyed.

                delete Buffer;
            }
            else {
                // a FALSE return value indicates that ProcessLdapMessage did NOT
                // interpret the contents of PduStructure, and that no data has been
                // sent to the other socket.  In this case, we forward the original PDU.

                Pump -> SendQueueBuffer (Buffer);
            }

            ASN1_FreeDecoded (Decoder, PduStructure, LDAPMessage_ID);
        }
        else {
            DebugF (_T("LDAP: 0x%x failed to decode pdu, ASN.1 error %d, forwarding pdu without interpreting contents.\n"),
                this,
                Error);

#if    DBG
        if (DebugLevel > 1) {
            DumpMemory (Buffer -> Data.Data, Buffer -> Data.Length);
            BerDump (Buffer -> Data.Data, Buffer -> Data.Length);
            ASN1_Decode (Decoder, (PVOID *) &PduStructure, LDAPMessage_ID, 0, Buffer -> Data.Data, Buffer -> Data.Length);
        }
#endif
            Pump -> SendQueueBuffer (Buffer);
        }

        ASN1_CloseDecoder (Decoder);
    }
    else {
        DebugF (_T("LDAP: 0x%x failed to create ASN.1 decoder, ASN.1 error %08X, forwarding pdu without interpreting contents.\n"),
            this,
            Error);

        Pump -> SendQueueBuffer (Buffer);
    }
}

// static
INT LDAP_CONNECTION::BinarySearchOperationByMessageID (
    IN    const    LDAP_MESSAGE_ID *    SearchKey,
    IN    const    LDAP_OPERATION *    Comparand)
{
    if (*SearchKey < Comparand -> MessageID) return -1;
    if (*SearchKey > Comparand -> MessageID) return 1;

    return 0;
}

BOOL LDAP_CONNECTION::FindOperationIndexByMessageID (
    IN    LDAP_MESSAGE_ID    MessageID,
    OUT    DWORD *            ReturnIndex)
{
    return OperationArray.BinarySearch ((SEARCH_FUNC_LDAP_OPERATION)BinarySearchOperationByMessageID, &MessageID, ReturnIndex);
}

BOOL LDAP_CONNECTION::FindOperationByMessageID (
    IN    LDAP_MESSAGE_ID    MessageID,
    OUT    LDAP_OPERATION **    ReturnOperation)
{
    DWORD    Index;

    if (OperationArray.BinarySearch ((SEARCH_FUNC_LDAP_OPERATION)BinarySearchOperationByMessageID, &MessageID, &Index)) {
        *ReturnOperation = &OperationArray[Index];
        return TRUE;
    }
    else {
        *ReturnOperation = NULL;
        return FALSE;
    }
}

void LDAP_CONNECTION::OnStateChange (
    LDAP_SOCKET *        ContainedSocket,
    LDAP_SOCKET::STATE    NewSocketState)
{
    assert (ContainedSocket == &ClientSocket || ContainedSocket == &ServerSocket);

    AssertLocked();

    switch (NewSocketState) {

    case    LDAP_SOCKET::STATE_CONNECT_PENDING:
        // Client socket transitions directly from
        // STATE_NONE to STATE_CONNECTED
        assert (ContainedSocket != &ClientSocket);

        State = STATE_CONNECT_PENDING;

        break;

    case    LDAP_SOCKET::STATE_CONNECTED:

        if (ClientSocket.GetState() == LDAP_SOCKET::STATE_CONNECTED
            && ServerSocket.GetState() == LDAP_SOCKET::STATE_CONNECTED) {
            
            State = STATE_CONNECTED;

            StartIo();
        }

        break;

    case    LDAP_SOCKET::STATE_TERMINATED:

        Terminate ();

        break;
    }
}


void LDAP_CONNECTION::Terminate (void)
{
    switch (State) {
        
    case    STATE_TERMINATED:
        // nothing to do
        break;

    default:
        State = STATE_TERMINATED;

        ClientSocket.Terminate();
        ServerSocket.Terminate();
        PumpClientToServer.Terminate();
        PumpServerToClient.Terminate();

        LdapConnectionArray.RemoveConnection (this);
        
        break;
    }
}

void LDAP_CONNECTION::TerminateExternal (void) 
{
    Lock ();
    
    Terminate ();

    Unlock ();
}


BOOL
LDAP_CONNECTION::IsConnectionThrough (
    IN DWORD InterfaceAddress // host order
    )
/*++

Routine Description:
    Determines whether the connection goes through the
    interface specified

Arguments:
    InterfaceAddress - address of the interface for which
        the determination is to be made.

Return Values:
    TRUE - if the connection being proxied goes through the
        interface specified

    FALSE - if the connection being proxied does not go through the
        interface specified

Notes:

--*/

{
    BOOL IsThrough;
    
    IsThrough = (InterfaceAddress == SourceInterfaceAddress) || 
                (InterfaceAddress == DestinationInterfaceAddress);

    return IsThrough;

} // LDAP_CONNECTION::IsConnectionThrough

// LDAP_CONNECTION_ARRAY ----------------------------------------------
LDAP_CONNECTION_ARRAY::LDAP_CONNECTION_ARRAY (void) {        
    IsEnabled = FALSE;
}

void LDAP_CONNECTION_ARRAY::RemoveConnection (
    LDAP_CONNECTION *    LdapConnection)
{
    LDAP_CONNECTION **    Pos;
    LDAP_CONNECTION **    End;
    BOOL                DoRelease;

    Lock();

    // linear scan, yick

    DoRelease = FALSE;

    ConnectionArray.GetExtents (&Pos, &End);

    for (; Pos < End; Pos++) {
        if (*Pos == LdapConnection) {

            // swap with last entry
            // quick way to delete entry from table
            *Pos = *(End - 1);
            ConnectionArray.Length--;

            DoRelease = TRUE;
            break;
        }
    }

    Unlock();

    if (DoRelease) {

        LdapConnection -> Release();
    }
    else {
        // when could this happen?
        // perhaps a harmless race condition?

        DebugF (_T("LDAP: 0x%x could not be removed from table -- was not in table to begin with.\n"), LdapConnection);
    }
}

void LDAP_CONNECTION_ARRAY::OnInterfaceShutdown (
    IN DWORD InterfaceAddress) { // host order
    
    DWORD ArrayIndex = 0;
    LDAP_CONNECTION * Connection;
    LDAP_CONNECTION ** ConnectionHolder = NULL;
    DYNAMIC_ARRAY <LDAP_CONNECTION *> TempArray;

    Lock ();

    while (ArrayIndex < ConnectionArray.GetLength ()) {
        Connection = ConnectionArray [ArrayIndex];

        if (Connection -> IsConnectionThrough (InterfaceAddress)) {

            DebugF (_T("LDAP: 0x%x terminating (killing all connections through %08X).\n"), 
                Connection, InterfaceAddress);

            ConnectionHolder = TempArray.AllocAtEnd ();

            if (NULL == ConnectionHolder) {

                Debug (_T("LDAP_CONNECTION_ARRAY::OnInterfaceShutdown - unable to grow array.\n"));

            } else {

                Connection -> AddRef ();

                *ConnectionHolder = Connection;
            }
        }

        ArrayIndex++;
    }

    Unlock ();

    ArrayIndex = 0;

    while (ArrayIndex < TempArray.GetLength ()) {
        Connection = TempArray[ArrayIndex];

        Connection -> TerminateExternal ();

        Connection -> Release ();

        ArrayIndex++;
    }
}

void LDAP_CONNECTION_ARRAY::Start (void)
{        
    Lock ();

    IsEnabled = TRUE;

    Unlock ();
}

void LDAP_CONNECTION_ARRAY::Stop (void)
{
    LDAP_CONNECTION * LdapConnection;

    Lock ();

    IsEnabled = FALSE;

    while (ConnectionArray.GetLength()) {

        LdapConnection = ConnectionArray[0];

        LdapConnection -> AddRef ();

        Unlock ();

        LdapConnection -> TerminateExternal ();

        Lock ();

        LdapConnection -> Release ();
    }

    ConnectionArray.Free ();

    Unlock ();
}
    
HRESULT LDAP_CONNECTION_ARRAY::InsertConnection (
                                LDAP_CONNECTION * LdapConnection)
{
    LDAP_CONNECTION ** ConnectionHolder = NULL;
    HRESULT Result;

    Lock ();

    if (IsEnabled) {

        if (ConnectionArray.Length <= LDAP_MAX_CONNECTIONS) {

            ConnectionHolder = ConnectionArray.AllocAtEnd ();

            if (NULL == ConnectionHolder) {

                Result = E_OUTOFMEMORY;

            } else {

                LdapConnection -> AddRef ();

                *ConnectionHolder = LdapConnection;

                Result = S_OK;
            }

        } else {

            return E_ABORT;

        }

    } else {
    
        Result = E_FAIL;
    }

    Unlock ();

    return Result;
}

// LDAP_ACCEPT ----------------------------------------------

LDAP_ACCEPT::LDAP_ACCEPT (void)
{
}

// static
void 
LDAP_ACCEPT::AsyncAcceptFunction (
    IN    PVOID         Context,
    IN    SOCKET        Socket,
    IN    SOCKADDR_IN * LocalAddress,
    IN    SOCKADDR_IN * RemoteAddress
    )
{
    HRESULT Result;

    Result = AsyncAcceptFunctionInternal (
                Context,
                Socket,
                LocalAddress,
                RemoteAddress
                );

    if (S_OK != Result) {
    
        if (INVALID_SOCKET != Socket) {

            closesocket (Socket);

            Socket = INVALID_SOCKET;
        }
    }
}
   
// static
HRESULT 
LDAP_ACCEPT::AsyncAcceptFunctionInternal (
    IN    PVOID         Context,
    IN    SOCKET        Socket,
    IN    SOCKADDR_IN * LocalAddress,
    IN    SOCKADDR_IN * RemoteAddress
    )
{
    LDAP_CONNECTION *   LdapConnection;
    HRESULT             Result;
    NAT_KEY_SESSION_MAPPING_EX_INFORMATION RedirectInformation;
    ULONG               RedirectInformationLength;
    ULONG               Error;
    DWORD               BestInterfaceAddress;
    SOCKADDR_IN         DestinationAddress;

    DebugF (_T("LDAP: ----------------------------------------------------------------------\n"));

#if DBG
    ExposeTimingWindow ();
#endif

    // a new LDAP connection has been accepted from the network.
    // first, we determine the original addresses of the transport connection.
    // if the connection was redirected to our socket (due to NAT),
    // then the query of the NAT redirect table will yield the original transport addresses.
    // if an errant client has connected to our service, well, we really didn't
    // intend for that to happen, so we just immediately close the socket.

    RedirectInformationLength = sizeof RedirectInformation;

    Result = NatLookupAndQueryInformationSessionMapping (
        NatHandle,
        IPPROTO_TCP,
        LocalAddress -> sin_addr.s_addr,
        LocalAddress -> sin_port,
        RemoteAddress -> sin_addr.s_addr,
        RemoteAddress -> sin_port,
        &RedirectInformation,
        &RedirectInformationLength,
        NatKeySessionMappingExInformation);

    if (STATUS_SUCCESS != Result) {

        DebugErrorF (Result, _T("LDAP: new connection was accepted from (%08X:%04X), but it is not in the NAT redirect table -- rejecting connection.\n"),
            ntohl (RemoteAddress -> sin_addr.s_addr),
            ntohs (RemoteAddress -> sin_port));

        return Result;
    }

    Error = GetBestInterfaceAddress (ntohl (RedirectInformation.DestinationAddress), &BestInterfaceAddress);

    if (ERROR_SUCCESS != Error) {

        if (WSAEHOSTUNREACH == Error) {
    
            Error = RasAutoDialSharedConnection ();
        
            if (ERROR_SUCCESS != Error) {
                
                DebugF (_T("LDAP: RasAutoDialSharedConnection failed. Error=%d\n"), Error);
    
            }
    
        } else {

            DebugError (Error, _T("LDAP: Failed to get interface address for the destination.\n"));
            
            return HRESULT_FROM_WIN32 (Error);
        }
    }
    
#if DBG
    BOOL                IsPrivateOrLocalSource;
    BOOL                IsPublicDestination;

    Result = ::IsPrivateAddress (ntohl (RedirectInformation.SourceAddress), &IsPrivateOrLocalSource);

    if (S_OK != Result) {

        return Result;

    }

    IsPrivateOrLocalSource = IsPrivateOrLocalSource || ::NhIsLocalAddress (RedirectInformation.SourceAddress);

    Result = ::IsPublicAddress (ntohl (RedirectInformation.DestinationAddress), &IsPublicDestination);

    if (S_OK != Result) {

        return Result;

    }

    if (::NhIsLocalAddress (RedirectInformation.SourceAddress) &&
        ::NhIsLocalAddress (RedirectInformation.DestinationAddress))  {

        Debug (_T("LDAP: New LOCAL connection.\n"));

    } else {

        if (IsPrivateOrLocalSource && IsPublicDestination) {

            Debug (_T("LDAP: New OUTBOUND connection.\n"));

        } else {

            Debug (_T("LDAP: New INBOUND connection.\n"));
        }
    }
#endif // DBG

    DebugF (_T("LDAP: Connection redirected: (%08X:%04X -> %08X:%04X) => (%08X:%04X -> %08X:%04X).\n"),
        ntohl (RedirectInformation.SourceAddress),
        ntohs (RedirectInformation.SourcePort),
        ntohl (RedirectInformation.DestinationAddress),
        ntohs (RedirectInformation.DestinationPort),
        ntohl (RedirectInformation.NewSourceAddress),
        ntohs (RedirectInformation.NewSourcePort),
        ntohl (RedirectInformation.NewDestinationAddress),
        ntohs (RedirectInformation.NewDestinationPort));

    // Create new LDAP_CONNECTION object
    LdapConnection = new LDAP_CONNECTION (&RedirectInformation);

    if (!LdapConnection) {

        DebugF(_T("LDAP: Failed to allocate LDAP_CONNECTION.\n"));

        return E_OUTOFMEMORY;
    }

    LdapConnection -> AddRef ();

    Result = LdapConnection -> Initialize (&RedirectInformation);

    if (S_OK == Result) {

        DestinationAddress.sin_family      = AF_INET;
        DestinationAddress.sin_addr.s_addr = RedirectInformation.DestinationAddress;
        DestinationAddress.sin_port        = RedirectInformation.DestinationPort; 

        if (S_OK == LdapConnectionArray.InsertConnection (LdapConnection)) {

            Result = LdapConnection -> AcceptSocket (Socket,
              LocalAddress,
              RemoteAddress,
              &DestinationAddress);

            if (S_OK != Result) {

                DebugF (_T("LDAP: 0x%x accepted new LDAP client, but failed to proceed.\n"), LdapConnection);

                // Probably there was something wrong with just this
                // Accept failure. Continue to accept more LDAP connections.
            }
        }

    } else {

        DebugF (_T("LDAP: 0x%x failed to initialize.\n"), LdapConnection);

    }

    LdapConnection -> Release (); 
    
    return Result;
}

HRESULT LDAP_ACCEPT::StartLoopbackNatRedirects (void) {

    NTSTATUS    Status;

    Status = NatCreateDynamicAdapterRestrictedPortRedirect (
        NatRedirectFlagLoopback | NatRedirectFlagSendOnly,
        IPPROTO_TCP,
        htons (LDAP_STANDARD_PORT),
        LdapListenSocketAddress.sin_addr.s_addr,
        LdapListenSocketAddress.sin_port,
        ::NhMapAddressToAdapter (htonl (INADDR_LOOPBACK)),
        MAX_LISTEN_BACKLOG,
        &LoopbackRedirectHandle1);

    if (Status != STATUS_SUCCESS) {
        LoopbackRedirectHandle1 = NULL;

        DebugError (Status, _T("LDAP: Failed to create local dynamic redirect #1.\n"));

        return (HRESULT) Status;
    }

    DebugF (_T ("LDAP: Connections traversing loopback interface to port %04X will be redirected to %08X:%04X.\n"),
                LDAP_STANDARD_PORT,
                ntohl (LdapListenSocketAddress.sin_addr.s_addr),
                ntohs (LdapListenSocketAddress.sin_port));

    Status = NatCreateDynamicAdapterRestrictedPortRedirect (
        NatRedirectFlagLoopback | NatRedirectFlagSendOnly,
        IPPROTO_TCP,
        htons (LDAP_ALTERNATE_PORT),
        LdapListenSocketAddress.sin_addr.s_addr,
        LdapListenSocketAddress.sin_port,
        ::NhMapAddressToAdapter (htonl (INADDR_LOOPBACK)),
        MAX_LISTEN_BACKLOG,
        &LoopbackRedirectHandle2); 

    if (Status != STATUS_SUCCESS) {

        NatCancelDynamicRedirect (LoopbackRedirectHandle1);

        LoopbackRedirectHandle1 = NULL;
        LoopbackRedirectHandle2 = NULL;

        DebugError (Status, _T("LDAP: Failed to create local dynamic redirect #2.\n"));

        return (HRESULT) Status;
    }

    DebugF (_T ("LDAP: Connections traversing loopback interface to port %04X will be redirected to %08X:%04X.\n"),
                LDAP_ALTERNATE_PORT,
                ntohl (LdapListenSocketAddress.sin_addr.s_addr),
                ntohs (LdapListenSocketAddress.sin_port));

    return (HRESULT) Status; 
} 

HRESULT LDAP_ACCEPT::CreateBindSocket (
    void) {

    HRESULT            Result;
    SOCKADDR_IN        SocketAddress;

    SocketAddress.sin_family      = AF_INET;
    SocketAddress.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    SocketAddress.sin_port        = htons (0);             // request dynamic port

    Result = AsyncAcceptContext.StartIo (
        &SocketAddress,
        AsyncAcceptFunction,
        NULL);

    if (Result != S_OK) {

        DebugError (Result, _T("LDAP: Failed to create and bind socket.\n"));

        return Result;
    }

    DebugF (_T("LDAP: Asynchronous Accept started.\n"));

    Result = AsyncAcceptContext.GetListenSocketAddress (&LdapListenSocketAddress);

    if (Result != S_OK) {

        DebugError (Result, _T("LDAP: Failed to get listen socket address.\n"));

        return Result;
    }

    return S_OK;
}

HRESULT LDAP_ACCEPT::Start (void)
{
    HRESULT        Result;

    Result = CreateBindSocket ();

    if (S_OK == Result) {

        Result = StartLoopbackNatRedirects ();

        if (S_OK == Result) {

            return  S_OK;

        }

        CloseSocket ();
    }

    return Result;
}

void LDAP_ACCEPT::Stop (void) {

    StopLoopbackNatRedirects ();

    CloseSocket ();
}

void LDAP_ACCEPT::StopLoopbackNatRedirects (void) {

    if (LoopbackRedirectHandle1) {

        NatCancelDynamicRedirect (LoopbackRedirectHandle1);

        LoopbackRedirectHandle1 = NULL;

    }

    if (LoopbackRedirectHandle2) {

        NatCancelDynamicRedirect (LoopbackRedirectHandle2);

        LoopbackRedirectHandle2 = NULL;

    }
}

void LDAP_ACCEPT::CloseSocket (void) {

    ZeroMemory ((PVOID)&LdapListenSocketAddress, sizeof (SOCKADDR_IN));

    AsyncAcceptContext.StopWait ();
}

LDAP_BUFFER::LDAP_BUFFER (void) 
{
}

LDAP_BUFFER::~LDAP_BUFFER (void) 
{
}


// LDAP_CODER ---------------------------------------------------------------------

LDAP_CODER::LDAP_CODER (void)
{
    Encoder = NULL;
    Decoder = NULL;

    LDAP_Module_Startup();
}

LDAP_CODER::~LDAP_CODER (void)
{
    Encoder = NULL;
    Decoder = NULL;

    LDAP_Module_Cleanup();
}

DWORD LDAP_CODER::Start (void)
{
    DWORD    Status;
    ASN1error_e    Error;

    Lock();

    Status = ERROR_SUCCESS;

    Error = ASN1_CreateEncoder (LDAP_Module, &Encoder, NULL, 0, NULL);

    if (ASN1_FAILED (Error)) {
        DebugF (_T("LDAP: Failed to initialize LDAP ASN.1 BER encoder, 0x%08X.\n"), Error);
        Encoder = NULL;
        Status = ERROR_GEN_FAILURE;
    }

    Error = ASN1_CreateDecoder (LDAP_Module, &Decoder, NULL, 0, NULL);

    if (ASN1_FAILED (Error)) {
        DebugF (_T("LDAP: Failed to initialize LDAP ASN.1 BER decoder, 0x%08X.\n"), Error);
        Decoder = NULL;
        Status = ERROR_GEN_FAILURE;
    }

    Unlock();

    return Status;
}

void LDAP_CODER::Stop (void)
{
    Lock();

    if (Encoder) {
        ASN1_CloseEncoder (Encoder);
        Encoder = NULL;
    }

    if (Decoder) {
        ASN1_CloseDecoder (Decoder);
        Decoder = NULL;
    }

    Unlock();
}

ASN1error_e LDAP_CODER::Decode (
    IN    LPBYTE    Data,
    IN    DWORD    Length,
    OUT    LDAPMessage **    ReturnPduStructure,
    OUT    DWORD *    ReturnIndex)
{
    ASN1error_e        Error;

    assert (Data);
    assert (ReturnPduStructure);
    assert (ReturnIndex);

    Lock();

    if (Decoder) {

#if DBG
        BerDump (Data, Length);
#endif

        Error = ASN1_Decode (
            Decoder,
            (PVOID *) ReturnPduStructure,
            LDAPMessage_ID,
            ASN1DECODE_SETBUFFER,
            Data,
            Length);

        switch (Error) {
        case    ASN1_SUCCESS:
            // successfully decoded pdu

            *ReturnIndex = Decoder -> len;
            assert (*ReturnPduStructure);

            DebugF (_T("LDAP: Successfully decoded PDU, submitted buffer length %d, used %d bytes.\n"),
                Length,
                *ReturnIndex);

            break;

        case    ASN1_ERR_EOD:
            // not enough data has been accumulated yet

            *ReturnIndex = 0;
            *ReturnPduStructure = NULL;

            DebugF (_T("LDAP: Cannot yet decode PDU, not enough data submitted (%d bytes in buffer).\n"),
                Length);
            break;

        default:
            if (ASN1_FAILED (Error)) {
                DebugF (_T("LDAP: Failed to decode PDU, for unknown reasons, 0x%08X.\n"),
                    Error);
            }
            else {
                DebugF (_T("LDAP: PDU decoded, but with warning code, 0x%08X.\n"),
                    Error);
            
                *ReturnIndex = Decoder -> len;
            }
            break;
        }
    }
    else {
        Debug (_T("LDAP: cannot decode pdu, because decoder was not initialized.\n"));

        Error = ASN1_ERR_INTERNAL;
    }

    Unlock();

    return Error;
} 

// LDAP_PUMP --------------------------------------------------------------

LDAP_PUMP::LDAP_PUMP (
    IN    LDAP_CONNECTION *    ArgConnection,
    IN    LDAP_SOCKET *        ArgSource,
    IN    LDAP_SOCKET *        ArgDest)
{
    assert (ArgConnection);
    assert (ArgSource);
    assert (ArgDest);

    Connection = ArgConnection;
    Source = ArgSource;
    Dest = ArgDest;
    IsPassiveDataTransfer = FALSE;
}

LDAP_PUMP::~LDAP_PUMP (void)
{
}

void LDAP_PUMP::Terminate (void)
{
}

void LDAP_PUMP::Start (void)
{
    Source -> RecvIssue();
}

void LDAP_PUMP::Stop (void)
{
}

// called only by source socket OnRecvComplete
void LDAP_PUMP::OnRecvBuffer (
    IN    LDAP_BUFFER * Buffer)
{
    if (IsActivelyPassingData ()) {

        Connection -> ProcessBuffer (this, Buffer);
    
    } else {
        
        SendQueueBuffer (Buffer);
    }
}

void LDAP_PUMP::OnSendDrain (void)
{
    Source -> RecvIssue();
}

BOOL LDAP_PUMP::CanIssueRecv (void)
{
    return !Dest -> SendOverlapped.IsPending;
}

void LDAP_PUMP::SendQueueBuffer (
    IN    LDAP_BUFFER *    Buffer)
{
    Dest -> SendQueueBuffer (Buffer);
}

void LDAP_PUMP::EncodeSendMessage (
   IN    LDAPMessage *    Message)
{
    LDAP_BUFFER *    Buffer;
    ASN1encoding_t    Encoder;
    ASN1error_e        Error;

    Buffer = new LDAP_BUFFER;
    if (!Buffer) {
        Debug (_T("LDAP: EncodeSendMessage: allocation failure.\n"));
        return;
    }

    Error = ASN1_CreateEncoder (LDAP_Module, &Encoder, NULL, 0, NULL);
    if (ASN1_FAILED (Error)) {
        DebugF (_T("LDAP: EncodeSendMessage: failed to create ASN.1 encoder, error 0x%08X.\n"),
            Error);

        delete Buffer;
        return;
    }

    Error = ASN1_Encode (Encoder, Message, LDAPMessage_ID, ASN1ENCODE_ALLOCATEBUFFER, NULL, 0);
    if (ASN1_FAILED (Error)) {
        DebugF (_T("LDAP: Failed to encode LDAP message, error 0x%08X.\n"), Error);

        ASN1_CloseEncoder (Encoder);
        delete Buffer;
        return;
    }

    if (Buffer -> Data.Grow (Encoder -> len)) {

        memcpy (Buffer -> Data.Data, Encoder -> buf, Encoder -> len);
        Buffer -> Data.Length = Encoder -> len;

        ASN1_FreeEncoded (Encoder, Encoder -> buf);

        SendQueueBuffer (Buffer);
        Buffer = NULL;
    }
    else {

        delete Buffer;
    }

    ASN1_CloseEncoder (Encoder);
}

BOOL    LDAP_PUMP::IsActivelyPassingData (void) const {

    return !IsPassiveDataTransfer;

}

void LDAP_PUMP::StartPassiveDataTransfer (void) {

    IsPassiveDataTransfer = TRUE;

}
