/*++

Copyright (c) 1992 Microsoft Corporation

Module Name:

    gethost.c

Abstract:

    This file contains the a version of GetHostByName for IPX/SPX for
    dos and windows.

Author:

    31 May 94   AlexMit

    15 Oct 94   JRoberts - changed to avoid using Novell headers and libs
--*/


#include "sysinc.h"
#include "rpc.h"
#include "rpctran.h"
#include "novell.h"
#include "gethost.h"
#include "netcons.h"
#include "ncb.h"

#ifdef WIN
#    include "windows.h"
#    define I_RpcAllocate                   (*(RpcRuntimeInfo->Allocate))
#    define I_RpcFree                       (*(RpcRuntimeInfo->Free))
#else
#    include "regalloc.h"
#endif

#define NAME_LEN           48
#define SAP_ECB_COUNT      5

/********************************************************************/

#ifdef WIN
#define CACHE_LENGTH 16
#else
#define CACHE_LENGTH 4
#endif

#define CACHE_EXPIRATION_TIME  (10 * 60 * 18)

struct
{
    char        Name[16];
    IPX_ADDRESS Address;
    unsigned long Time;
}
ServerCache[CACHE_LENGTH];

/********************************************************************/

extern int atoi(const char *);

/********************************************************************/
unsigned char chtob( unsigned char c1, unsigned char c2 )
/* Convert two hex digits (stored as ascii) into one byte. */

{
   unsigned char out;

   if (c1 >= '0' && c1 <= '9')
      out = (c1 - '0') << 4;
   else
   {
      if (c1 >= 'a' && c1 <= 'f')
     out = (c1 - 'a' + 10) << 4;
      else if (c1 >= 'A' && c1 <= 'F')
     out = (c1 - 'A' + 10) << 4;
      else
     out = 0;
   }

   if (c2 >= '0' && c2 <= '9')
      out |= c2 -'0';
   else
   {
      if (c2 >= 'a' && c2 <= 'f')
     out |= c2 - 'a' + 10;
      else if (c2 >= 'A' && c2 <= 'F')
     out |= c2 - 'A' + 10;
      else
         out = 0;
   }

   return out;
}

unsigned char chtob( unsigned char c1, unsigned char c2 );
RPC_STATUS    FromBindery( IPX_ADDRESS __RPC_FAR *host,
                           RPC_CHAR __RPC_FAR *name );

unsigned long
DosGetTickCount(
    )
/*++

Routine Description:

    Returns the number of ticks since the last time it was midnight.
    There are 65536 ticks per hour.

--*/

{
    _asm
    {
        push    bp
        push    si
        push    di

        xor     ax, ax
        int     0x1a
        mov     ax, dx
        mov     dx, cx

        pop     di
        pop     si
        pop     bp
    }
}

void
AddServerToCache(
    char PAPI * Name,
    IPX_ADDRESS PAPI * Address
    )
{
    unsigned i;

    //
    // If the server is already in the table, overwrite the existing entry.
    //
    for (i=0; i < CACHE_LENGTH; ++i)
        {
        if (0 == _fstrnicmp(ServerCache[i].Name, Name, 16))
            {
            break;
            }
        }

    //
    // If it is not in the table, try to find an empty entry to fill.
    //
    if (i == CACHE_LENGTH)
        {
        for (i=0; i < CACHE_LENGTH; ++i)
            {
            if (ServerCache[i].Name[0] == '\0')
                {
                break;
                }
            }
        }

    //
    // If all entries are full, overwrite the oldest one.
    //
    if (i == CACHE_LENGTH)
        {
        unsigned long BestTime = ~0;
        unsigned BestIndex;

        for (i=0; i < CACHE_LENGTH; ++i)
            {
            if (ServerCache[i].Time <= BestTime)
                {
                BestTime = ServerCache[i].Time;
                BestIndex = i;
                }
            }

        i = BestIndex;
        }

    //
    // Update the entry's information.
    //
    _fstrcpy(ServerCache[i].Name, Name);
    _fmemcpy(&ServerCache[i].Address, Address, sizeof(IPX_ADDRESS));

    ServerCache[i].Time = DosGetTickCount();
}

IPX_ADDRESS *
FindServerInCache(
    char PAPI * Name
    )
{
    unsigned i;

    for (i=0; i < CACHE_LENGTH; ++i)
        {
        if (0 == _fstrcmp(ServerCache[i].Name, Name))
            {
            return &ServerCache[i].Address;
            }
        }

    return 0;
}

void
CachedServerContacted(
    char PAPI * Name
    )
{
    unsigned i;

    for (i=0; i < CACHE_LENGTH; ++i)
        {
        if (0 == _fstrcmp(ServerCache[i].Name, Name))
            {
            ServerCache[i].Time = DosGetTickCount();
            break;
            }
        }
}

BOOL
CachedServerNotContacted(
    char PAPI * Name
    )
{
    BOOL Flushed = FALSE;
    unsigned i;

    for (i=0; i < CACHE_LENGTH; ++i)
        {
        if (0 == _fstrcmp(ServerCache[i].Name, Name))
            {
            if (DosGetTickCount() - ServerCache[i].Time > CACHE_EXPIRATION_TIME)
                {
                ServerCache[i].Name[0] = '\0';
                Flushed = TRUE;
                break;
                }
            }
        }

    return Flushed;
}

BOOL               PreferredServerFound = FALSE;
NETWARE_CONNECTION PreferredServer;


RPC_STATUS
SearchBindery(
    RPC_CHAR __RPC_FAR *name,
    IPX_ADDRESS __RPC_FAR *Address,
    unsigned BindingTimeout
    )
{
    char buffer[128];
    unsigned SapTickCount;
    unsigned RetryCount = 0;

    //
    //
    //
    if (FALSE == PreferredServerFound)
        {
reconnect:

        PreferredServer.TickLimit = 19;
        SapTickCount              = 19;

        if (BindingTimeout > RPC_C_BINDING_DEFAULT_TIMEOUT)
            {
            PreferredServer.TickLimit <<= BindingTimeout - RPC_C_BINDING_DEFAULT_TIMEOUT;
            SapTickCount              <<= BindingTimeout - RPC_C_BINDING_DEFAULT_TIMEOUT;
            }

        ConnectToAnyFileServer(&PreferredServer, SapTickCount);

        if (PreferredServer.ReturnCode || PreferredServer.ConnectionStatus)
            {
            DisconnectFromServer(&PreferredServer);
            PreferredServerFound = FALSE;
            return RPC_S_SERVER_UNAVAILABLE;
            }

        PreferredServerFound = TRUE;
        }

    //
    // Read from the bindery.
    //
    ReadPropertyValue(&PreferredServer,
                      name,
                      RPC_SAP_TYPE,
                      "NET_ADDRESS",
                      1,
                      buffer,
                      0,
                      0
                      );

    if (PreferredServer.ReturnCode)
        {
        DisconnectFromServer(&PreferredServer);
        PreferredServerFound = FALSE;

        if (RetryCount++)
            {
            return RPC_S_SERVER_UNAVAILABLE;
            }
        else
            {
            goto reconnect;
            }
        }

    //
    // Save the host address.
    //
    _fmemcpy( Address, buffer, 10 );

    DisconnectFromServer(&PreferredServer);
    return RPC_S_OK;
}


/********************************************************************/
RPC_STATUS
SearchWithSap(
    RPC_CHAR __RPC_FAR *name,
    IPX_ADDRESS __RPC_FAR *host,
    unsigned Timeout

#ifdef WIN
                  , RPC_CLIENT_RUNTIME_INFO PAPI * RpcRuntimeInfo
#endif
                  )
{
    WORD            start;
    unsigned        SapSocket;
    int             result;
    int             i;
    int             retry;

    struct
    {
        ECB             ecb;
        IPX_HEADER      ipx;
        SAP_REQUEST     req;
    } send;

    struct SAP_RESPONSE_ECB
    {
        ECB             ecb;
        IPX_HEADER      ipx;
        SAP_RESPONSE    resp;
    }
    __RPC_FAR * mem;

    RPC_STATUS     status = RPC_S_SERVER_UNAVAILABLE;

    //
    // The base timeout is two seconds; max is a minute.
    //
    if (Timeout <= RPC_C_BINDING_DEFAULT_TIMEOUT)
        {
        Timeout = 18 * 2;
        }
    else
        {
        Timeout = (18 * 2) << (Timeout - RPC_C_BINDING_DEFAULT_TIMEOUT);
        }

    result = IPXOpenSocket(TASKID_C &SapSocket, 0);
    if (result != 0)
        return RPC_S_OUT_OF_RESOURCES;

    // Allocate memory for ECBs.
#ifdef WIN
    mem = I_RpcAllocate( SAP_ECB_COUNT * sizeof(*mem) );
#else
    mem = I_RpcRegisteredBufferAllocate( SAP_ECB_COUNT * sizeof(*mem) );
#endif

    if (mem == NULL)
        {
        status = RPC_S_OUT_OF_MEMORY;
        goto cleanup;
        }

    _fmemset( mem, 0, SAP_ECB_COUNT * sizeof(mem) );

    //
    // Post some ECBs.
    //
    for (i = 0; i < SAP_ECB_COUNT; i++)
        {
        SetupEcb(&mem[i].ecb, SapSocket, sizeof(SAP_RESPONSE));
        IPXListenForPacket(TASKID_C  &mem[i].ecb );
        }

    //
    // We want to send a SAP request and scan responses for the server
    // that we want.  We transmit the request up to two times because
    // servers sometimes miss a single broadcast.
    //
    for (retry = 0; retry < 2 ; ++retry)
        {
        send.ipx.PacketType = IPX_PACKET_TYPE;
        send.ipx.Destination.Network = 0;
        send.ipx.Destination.Socket  = SAP_SOCKET;
        _fmemset( send.ipx.Destination.Node, 0xff, sizeof(send.ipx.Destination.Node) );

        SetupEcb(&send.ecb, SapSocket, sizeof(send));
        _fmemset( send.ecb.immediateAddress, 0xff, sizeof(send.ecb.immediateAddress) );

        // Send the data.

        send.req.QueryType  = SAP_GENERAL_QUERY;
        send.req.ServerType = RPC_SAP_TYPE_SWAPPED;

        IPXSendPacket(TASKID_C &send.ecb );

        while (send.ecb.inUseFlag)
            IPXRelinquishControl();

        // Verify that the send was successful.

        if (send.ecb.completionCode)
            {
            goto cleanup;
            }

        //
        // Get packets till timeout is returned or a good reply is returned.
        //
        start = IPXGetIntervalMarker(TASKID);
        do
            {
            struct SAP_RESPONSE_ECB __RPC_FAR * curr;

            for (curr = mem; curr < mem + SAP_ECB_COUNT; curr++)
                {
                if (curr->ecb.inUseFlag == 0)
                    {
                    if (curr->ecb.completionCode == 0x00)
                        {
                        // Verify the packet.
                        //
                        curr->ipx.Length = ByteSwapShort( curr->ipx.Length );
                        curr->ipx.Length -= sizeof(IPX_HEADER);

                        if (curr->ipx.Source.Socket == SAP_SOCKET     &&
                            curr->ipx.Length >= sizeof (SAP_RESPONSE) &&
                            curr->resp.PacketType == SAP_GENERAL_RESPONSE )
                            {
                            unsigned num_entry = curr->ipx.Length / sizeof(SAP_ENTRY);
                            for (i = 0; i < num_entry; i++)
                                {
                                if (0 == _fstrnicmp( name, curr->resp.Entries[i].Name, NAME_LEN))
                                    {
                                    // Only copy the network and node numbers, not the socket.
                                    //
                                    _fmemcpy( host, &curr->resp.Entries[i].Address, 10 );
                                    status = RPC_S_OK;
                                    goto cleanup;
                                    }
                                }
                            }
                        }
                    //
                    // Repost the receive.
                    //
                    IPXListenForPacket( TASKID_C &curr->ecb );
                    }
                }
            IPXRelinquishControl();
            }
        while (IPXGetIntervalMarker(TASKID) - start < Timeout);
        }

cleanup:

    //
    // Cancel the ECBs.
    //
    if (mem)
        {
        for (i = 0; i < SAP_ECB_COUNT; i++)
            {
            if (mem[i].ecb.inUseFlag)
                {
                IPXCancelEvent( TASKID_C &mem[i].ecb );
                while (mem[i].ecb.inUseFlag)
                    {
                    IPXRelinquishControl();
                    }
                }
            }

        //
        // Free the memory.
        //
#ifdef WIN
        I_RpcFree( mem );
#else
        I_RpcRegisteredBufferFree( mem );
#endif
        }

    IPXCloseSocket(TASKID_C SapSocket);

    return status;
}

/********************************************************************/
RPC_STATUS
IpxGetHostByName(
    RPC_CHAR    __RPC_FAR * name,
    IPX_ADDRESS __RPC_FAR * Address,
    RPC_CHAR    __RPC_FAR * endpoint,
    unsigned                Timeout
#ifdef WIN
    , RPC_CLIENT_RUNTIME_INFO * RpcClientRuntimeInfo
#endif
    )
{
    RPC_STATUS status;
    int        i;
    int        length;
    IPX_ADDRESS * CachedAddress;

    // Set the endpoint.
    Address->Socket = ByteSwapShort(atoi(endpoint));

    // Fail if no address was specified.
    if (name == NULL || name[0] == '\0')
        return RPC_S_SERVER_UNAVAILABLE;

    // If the name starts with ~, convert it directly to a network address.
    length = _fstrlen(name);
    if (name[0] == '~')
        {
        unsigned char __RPC_FAR * Temp;

        if (length != 21)
            return RPC_S_INVALID_NET_ADDR;

        Temp = (unsigned char __RPC_FAR *) &Address->Network;
        for (i = 0; i < 4; i++)
            {
            Temp[i] = chtob( name[2*i + 1], name[2*i + 2] );
            }

        Temp = (unsigned char __RPC_FAR *) Address->Node;
        for (i = 0; i < 6; i++)
            {
            Temp[i] = chtob( name[2*i + 9], name[2*i + 10] );
            }

        return RPC_S_OK;
        }

    if (length > 15)
        {
        return RPC_S_INVALID_NET_ADDR;
        }

    CachedAddress = FindServerInCache(name);
    if (CachedAddress)
        {
        _fmemcpy(Address, CachedAddress, 10);
        return RPC_S_OK;
        }

    // Try the bindery.
    status = SearchBindery(name, Address, Timeout);
    if (status && !PreferredServerFound)
        {
        status = SearchWithSap(name,
                      Address,
                      Timeout
#ifdef WIN
                      , RpcClientRuntimeInfo
#endif
                      );
        }

    if (!status)
        {
        AddServerToCache(name, Address);
        }

    return status;
}