/*++

Copyright(c) 1995 Microsoft Corporation

MODULE NAME
    netmap.c

ABSTRACT
    Network map routines

AUTHOR
    Anthony Discolo (adiscolo) 21-May-1996

REVISION HISTORY

--*/


#define UNICODE
#define _UNICODE

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

#include <stdlib.h>
#include <windows.h>
#include <tdi.h>
#include <nb30.h>
#include <nbtioctl.h>
#include <stdio.h>
#include <npapi.h>
#include <ctype.h>
#include <winsock.h>
#include <acd.h>
#include <ras.h>
#include <raserror.h>
#include <rasman.h>
#include <debug.h>
#include <ipexport.h>
#include <icmpapi.h>

#include "reg.h"
#include "misc.h"
#include "table.h"
#include "access.h"
#include "rasprocs.h"

//
// We keep a map of network name to
// address that groups related addresses
// by network name.  We use the network
// name as a remote network identifier to
// allow us to quickly determine whether
// any address belongs to a network that
// is connected or not.
//
typedef struct _NETWORK_MAP_ENTRY {
    BOOLEAN bUp;            // network is connected
    DWORD dwConnectionTag;  // unique index for connections
    PHASH_TABLE pTable;     // table of addresses
    LIST_ENTRY listEntry;   // addresses sorted by tag
} NETWORK_MAP_ENTRY, *PNETWORK_MAP_ENTRY;

//
// The network map.
//
//
typedef struct _NETWORK_MAP {
    CRITICAL_SECTION csLock;
    LPTSTR pszDnsAddresses; // DNS server list
    DWORD dwcConnections; // number of RAS connections
    DWORD dwcUpNetworks;  // number of up networks
    DWORD dwConnectionTag; // unique index for connections for NULL network
    PHASH_TABLE pTable;   // network table
} NETWORK_MAP, PNETWORK_MAP;

//
// This structure is passed to an address
// enumerator procedure to keep track of
// any hosts that are accessible.
//
typedef struct _NETWORK_MAP_ACCESS {
    LPTSTR pszNbDevice; // Netbios device for find name requests
    BOOLEAN bUp;        // network is up
    DWORD dwFailures;   // number of host access failures
} NETWORK_MAP_ACCESS, *PNETWORK_MAP_ACCESS;

//
// This structure is used to store the
// network addresses sorted by tag.
//
typedef struct _TAGGED_ADDRESS {
    DWORD dwTag;            // the tag
    LPTSTR pszAddress;      // the address
    LIST_ENTRY listEntry;   // sorted address list
} TAGGED_ADDRESS, *PTAGGED_ADDRESS;

//
// Netbios device information passed
// to AcsCheckNetworkThread
//
typedef struct _CHECK_NETWORK_INFO {
    LPTSTR *pszNbDevices;   // array of Netbios device strings
    DWORD dwcNbDevices;     // array size
    BOOLEAN fDns;           // DNS server is up
} CHECK_NETWORK_INFO, *PCHECK_NETWORK_INFO;

//
// Global variables
//
NETWORK_MAP NetworkMapG;



LPTSTR
GetPrimaryNetbiosDevice(VOID)
{
    typedef struct _LANA_MAP {
        BOOLEAN fEnum;
        UCHAR bLana;
    } LANA_MAP, *PLANA_MAP;
    BOOLEAN fNetworkPresent = FALSE;
    HKEY hKey;
    PLANA_MAP pLanaMap = NULL, pLana;
    DWORD dwError, dwcbLanaMap;
    PWCHAR pwszLanas = NULL, pwszBuf;
    DWORD dwcBindings, dwcMaxLanas, i, dwcbLanas;
    LONG iLana;
    DWORD dwZero = 0;
    PWCHAR *paszLanas = NULL;
    SOCKET s;
    NTSTATUS status;
    UNICODE_STRING deviceName;
    OBJECT_ATTRIBUTES attributes;
    IO_STATUS_BLOCK iosb;
    HANDLE handle;
    PWCHAR pwszDevice = NULL;

    dwError = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                L"System\\CurrentControlSet\\Services\\Netbios\\Linkage",
                0,
                KEY_READ,
                &hKey);
    if (dwError != ERROR_SUCCESS) {
        RASAUTO_TRACE1(
          "GetPrimaryNetbiosDevice: RegKeyOpenEx failed (dwError=%d)",
          GetLastError());
        return FALSE;
    }
    //
    // Read in the LanaMap.
    //
    if (!RegGetValue(hKey, L"LanaMap", &pLanaMap, &dwcbLanaMap, NULL)) {
        RASAUTO_TRACE("GetPrimaryNetbiosDevice: RegGetValue(LanaMap) failed");
        goto done;
    }
    dwcBindings = dwcbLanaMap / sizeof (LANA_MAP);
    //
    // Read in the bindings.
    //
    if (!RegGetValue(hKey, L"bind", &pwszLanas, &dwcbLanas, NULL)) {
        RASAUTO_TRACE("GetPrimaryNetbiosDevice: RegGetValue(bind) failed");
        goto done;
    }
    //
    // Allocate a buffer for the binding array.
    //
    paszLanas = LocalAlloc(LPTR, dwcBindings * sizeof (PWCHAR));
    if (paszLanas == NULL) {
        RASAUTO_TRACE("GetPrimaryNetbiosDevice: LocalAlloc failed");
        goto done;
    }
    //
    // Parse the bindings into an array of strings.
    //
    for (dwcMaxLanas = 0, pwszBuf = pwszLanas; 
        (*pwszBuf) && (dwcMaxLanas < dwcBindings); 
        pwszBuf++) 
    {
        paszLanas[dwcMaxLanas++] = pwszBuf;
        while(*++pwszBuf);
    }

    for (iLana = 0, pLana = pLanaMap; dwcBindings--; iLana++, pLana++) {
        int iLanaMap = (int)pLana->bLana;

        if (pLana->fEnum && (DWORD)iLana < dwcMaxLanas) {
            int iError;
            WCHAR *pwsz, szDevice[MAX_DEVICE_NAME + 1];

            if (wcsstr(paszLanas[iLana], L"NwlnkNb") != NULL ||
                wcsstr(paszLanas[iLana], L"_NdisWan") != NULL)
            {
                RASAUTO_TRACE1(
                  "GetPrimaryNetbiosDevice: ignoring %S",
                  RASAUTO_TRACESTRW(paszLanas[iLana]));
                continue;
            }

            RtlInitUnicodeString(&deviceName, paszLanas[iLana]);
            InitializeObjectAttributes(
              &attributes,
              &deviceName,
              OBJ_CASE_INSENSITIVE,
              NULL,
              NULL);
            //
            // Open the lana device.
            //
            status = NtOpenFile(&handle, READ_CONTROL, &attributes, &iosb, 0, 0);
            NtClose(handle);
            if (!NT_SUCCESS(status)) {
                RASAUTO_TRACE2(
                  "GetPrimaryNetbiosDevice: NtOpenFile(%S) failed (status=0x%x)",
                  RASAUTO_TRACESTRW(paszLanas[iLana]),
                  status);
                continue;
            }
            RASAUTO_TRACE1("GetPrimaryNetbiosDevice: opened %S", paszLanas[iLana]);
            //
            // If we succeed in opening the lana
            // device, we need to make sure the
            // underlying netcard device is loaded
            // as well, since transports create
            // device object for non-existent devices.
            //
            pwsz = wcsrchr(paszLanas[iLana], '_');
            if (pwsz == NULL) {
                RASAUTO_TRACE1(
                  "GetPrimaryNetbiosDevice: couldn't parse %S",
                  paszLanas[iLana]);
                continue;
            }
            wsprintf(szDevice, L"\\Device\\%s", pwsz + 1);
            //
            // Open the underlying netcard device.
            //
            RtlInitUnicodeString(&deviceName, szDevice);
            InitializeObjectAttributes(
              &attributes,
              &deviceName,
              OBJ_CASE_INSENSITIVE,
              NULL,
              NULL);
            status = NtOpenFile(&handle, READ_CONTROL, &attributes, &iosb, 0, 0);
            NtClose(handle);
            if (!NT_SUCCESS(status)) {
                RASAUTO_TRACE2(
                  "GetPrimaryNetbiosDevice: NtOpenFile(%S) failed (status=0x%x)",
                  RASAUTO_TRACESTRW(szDevice),
                  status);
                continue;
            }
            //
            // We've succeeded.  The netcard device must
            // be really loaded.
            //
            RASAUTO_TRACE3(
              "GetPrimaryNetbiosDevice: network (%S, %S, %d) is up",
              RASAUTO_TRACESTRW(paszLanas[iLana]),
              szDevice,
              iLana);
            pwszDevice = CopyString(paszLanas[iLana]);
            break;
        }
    }
    //
    // Free resources.
    //
done:
    if (paszLanas != NULL)
        LocalFree(paszLanas);
    if (pwszLanas != NULL)
        LocalFree(pwszLanas);
    if (pLanaMap != NULL)
        LocalFree(pLanaMap);
    RegCloseKey(hKey);

    return pwszDevice;
} // GetPrimaryNetbiosDevice



LPTSTR
DnsAddresses()

/*++

DESCRIPTION
    Return the list of DNS servers for this host.

ARGUMENTS
    None.

RETURN VALUE
    NULL if no DNS servers are configured; a list
    of IP addresses separated by a space otherwise.

--*/

{
    HKEY hkey;
    BOOLEAN fFound = FALSE;
    LPTSTR pszIpAddresses = NULL;
    LPTSTR pszIpAddress, pszIpAddressEnd;
    DWORD dwcbIpAddresses = 0;

    //
    // Look in various places in the registry
    // for one or more DNS addresses.
    //
    if (RegOpenKeyEx(
          HKEY_LOCAL_MACHINE,
          L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Transient",
          0,
          KEY_QUERY_VALUE,
          &hkey) == ERROR_SUCCESS)
    {
        fFound = RegGetValue(
                   hkey,
                   L"NameServer",
                   &pszIpAddresses,
                   &dwcbIpAddresses,
                   NULL);
        RegCloseKey(hkey);
    }
    if (fFound && dwcbIpAddresses > sizeof (TCHAR))
        goto found;
    if (pszIpAddresses != NULL) {
        LocalFree(pszIpAddresses);
        pszIpAddresses = NULL;
    }
    if (RegOpenKeyEx(
          HKEY_LOCAL_MACHINE,
          L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters",
          0,
          KEY_QUERY_VALUE,
          &hkey) == ERROR_SUCCESS)
    {
        fFound = RegGetValue(
                   hkey,
                   L"NameServer",
                   &pszIpAddresses,
                   &dwcbIpAddresses,
                   NULL);
        if (fFound && dwcbIpAddresses > sizeof (TCHAR)) {
            RegCloseKey(hkey);
            goto found;
        }
        if (pszIpAddresses != NULL) {
            LocalFree(pszIpAddresses);
            pszIpAddresses = NULL;
        }
        fFound = RegGetValue(
                   hkey,
                   L"DhcpNameServer",
                   &pszIpAddresses,
                   &dwcbIpAddresses,
                   NULL);
        RegCloseKey(hkey);
        if (fFound && dwcbIpAddresses > sizeof (TCHAR))
            goto found;
        if (pszIpAddresses != NULL) {
            LocalFree(pszIpAddresses);
            pszIpAddresses = NULL;
        }
    }

found:
    RASAUTO_TRACE1("DnsAddresses: pszIpAddresses=%S", RASAUTO_TRACESTRW(pszIpAddresses));
    return pszIpAddresses;
} // DnsAddresses



BOOLEAN
PingAddressList(
    IN LPTSTR pszAddresses
    )
{
    TCHAR szAddress[17];
    TCHAR *pSrc, *pDst;

    //
    // If the address list is NULL, we're done.
    //
    if (pszAddresses == NULL)
        return FALSE;
    //
    // Loop through the addresses and try to
    // ping each until one succeeds.
    //
    for (;;) {
        //
        // Copy the next address into szAddress.
        //
        for (pSrc = pszAddresses, pDst = szAddress;
             *pSrc != TEXT(' ') && *pSrc != TEXT(',') && *pSrc != TEXT('\0');
             *pSrc++, *pDst++)
        {
             *pDst = *pSrc;
        }
        *pDst = TEXT('\0');
        //
        // Ping it.  If it succeeds, then
        // we're done.
        //
        if (PingIpAddress(szAddress))
            return TRUE;
        //
        // Skip to the next address.
        //
        if (*pSrc == TEXT('\0'))
            break;
        pSrc++;
        if (*pSrc == TEXT('\0'))
            break;
        pszAddresses = pSrc;
    }

    return FALSE;
} // PingAddressList



BOOLEAN
InitializeNetworkMap(VOID)
{
    InitializeCriticalSection(&NetworkMapG.csLock);
    NetworkMapG.pszDnsAddresses = NULL;
    NetworkMapG.dwcConnections = 0;
    NetworkMapG.dwcUpNetworks = 0;
    NetworkMapG.dwConnectionTag = 0;
    NetworkMapG.pTable = NewTable();
    if (NetworkMapG.pTable == NULL) {
        RASAUTO_TRACE("InitializeNetworkMap: NewTable failed");
        return FALSE;
    }
    return TRUE;
} // InitializeNetworkMap



VOID
LockNetworkMap(VOID)
{
    EnterCriticalSection(&NetworkMapG.csLock);
} // LockNetworkMap



VOID
UnlockNetworkMap(VOID)
{
    LeaveCriticalSection(&NetworkMapG.csLock);
} // UnlockNetworkMap


PNETWORK_MAP_ENTRY
NewNetworkMapEntry(
    IN LPTSTR pszNetwork
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry;
    DWORD i;

    pNetworkMapEntry = LocalAlloc(LPTR, sizeof (NETWORK_MAP_ENTRY));
    if (pNetworkMapEntry == NULL) {
        RASAUTO_TRACE("NewNetworkMapEntry: LocalAlloc failed");
        return NULL;
    }
    pNetworkMapEntry->bUp = FALSE;
    pNetworkMapEntry->dwConnectionTag = 0;
    pNetworkMapEntry->pTable = NewTable();
    if (pNetworkMapEntry->pTable == NULL) {
        RASAUTO_TRACE("NewNetworkMapEntry: NewTable failed");
        LocalFree(pNetworkMapEntry);
        return NULL;
    }
    InitializeListHead(&pNetworkMapEntry->listEntry);
    if (!PutTableEntry(NetworkMapG.pTable, pszNetwork, pNetworkMapEntry)) {
        RASAUTO_TRACE("NewNetworkMapEntry: PutTableEntry failed");
        LocalFree(pNetworkMapEntry);
        return NULL;
    }

    return pNetworkMapEntry;
} // NewNetworkMapEntry


VOID
FreeNetworkMapEntry(
    IN PNETWORK_MAP_ENTRY pNetworkMapEntry
    )
{
    PLIST_ENTRY pEntry;
    PTAGGED_ADDRESS pTaggedAddress;

    /*
    
    //
    // Since the PTAGGED_ADDRESS structures are
    // in a hash table and a list, we need to
    // free the structures in a special way.  The
    // table package automatically frees the
    // structures when a PutTableEntry(pTable, address, NULL)
    // is called.
    //
    for (pEntry = pNetworkMapEntry->listEntry.Flink;
         pEntry != &pNetworkMapEntry->listEntry;
         pEntry = pEntry->Flink)
    {
        pTaggedAddress = CONTAINING_RECORD(pEntry, TAGGED_ADDRESS, listEntry);

        LocalFree(pTaggedAddress->pszAddress);
    }

    */

    
    while (!IsListEmpty(&pNetworkMapEntry->listEntry)) {

        LPTSTR pszAddress;
    
        pEntry = RemoveHeadList(&pNetworkMapEntry->listEntry);
        pTaggedAddress = CONTAINING_RECORD(pEntry, TAGGED_ADDRESS, listEntry);

        pszAddress = pTaggedAddress->pszAddress;

        //
        // The following call frees the
        // pTaggedAddress structure, as
        // well as frees the table entry.
        //
        PutTableEntry(pNetworkMapEntry->pTable, pszAddress, NULL);

        LocalFree(pszAddress);
    }
    ClearTable(pNetworkMapEntry->pTable);
} // FreeNetworkMapEntry



ACD_ADDR_TYPE
AddressToType(
    IN LPTSTR pszAddress
    )
{
    LONG inaddr;
    CHAR szAddress[17];

    UnicodeStringToAnsiString(pszAddress, szAddress, sizeof (szAddress));
    inaddr = inet_addr(szAddress);
    if (inaddr != INADDR_NONE)
        return ACD_ADDR_IP;
    if (wcschr(pszAddress, ':') != NULL)
        return ACD_ADDR_IPX;
    if (wcschr(pszAddress, '.') != NULL)
        return ACD_ADDR_INET;
    return ACD_ADDR_NB;
} // AddressToType



PNETWORK_MAP_ENTRY
GetNetworkMapEntry(
    IN LPTSTR pszNetwork
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry;

    if (GetTableEntry(
          NetworkMapG.pTable,
          pszNetwork,
          &pNetworkMapEntry))
    {
        return pNetworkMapEntry;
    }

    return NULL;
} // GetNetworkMapEntry



BOOLEAN
AddNetworkAddress(
    IN LPTSTR pszNetwork,
    IN LPTSTR pszAddress,
    IN DWORD dwTag
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry;
    PTAGGED_ADDRESS pNewTaggedAddress, pTaggedAddress;
    PLIST_ENTRY pPrevEntry, pEntry;
    BOOLEAN bInserted = FALSE;
    BOOLEAN bCreateNew = TRUE;

    RASAUTO_TRACE3(
      "AddNetworkAddress(%S,%S,%d)",
      RASAUTO_TRACESTRW(pszNetwork),
      pszAddress,
      dwTag);
    //
    // Create the network map entry if necessary.
    //
    LockNetworkMap();
    pNetworkMapEntry = GetNetworkMapEntry(pszNetwork);
    if (pNetworkMapEntry == NULL) {
        pNetworkMapEntry = NewNetworkMapEntry(pszNetwork);
        if (pNetworkMapEntry == NULL) {
            UnlockNetworkMap();
            return FALSE;
        }
    }
    else {
        //
        // Check to see if the address already exists.
        //
        if (GetTableEntry(
              pNetworkMapEntry->pTable,
              pszAddress,
              &pNewTaggedAddress))
        {
            RASAUTO_TRACE2(
              "AddNetworkAddress: %S exists with dwTag=%d",
              pszAddress,
              pNewTaggedAddress->dwTag);
            //
            // If the address exists with a lower tag, then
            // we don't need to do anything.
            //
            if (pNewTaggedAddress->dwTag <= dwTag) {
                UnlockNetworkMap();
                return TRUE;
            }
            //
            // If the address exists with a higher tag, then
            // we need to remove the existing entry from
            // the list.
            //
            RemoveEntryList(&pNewTaggedAddress->listEntry);
            bCreateNew = FALSE;
        }
    }
    if (bCreateNew) {
        //
        // Create the new tagged address structure.
        //
        pNewTaggedAddress = LocalAlloc(LPTR, sizeof (TAGGED_ADDRESS));
        if (pNewTaggedAddress == NULL) {
            RASAUTO_TRACE("AddNetworkMap: LocalAlloc failed");
            UnlockNetworkMap();
            return FALSE;
        }
        pNewTaggedAddress->pszAddress = CopyString(pszAddress);
        if (pNewTaggedAddress->pszAddress == NULL) {
            RASAUTO_TRACE("AddNetworkMap: LocalAlloc failed");
            UnlockNetworkMap();
            LocalFree(pNewTaggedAddress);
            LocalFree(pNetworkMapEntry);
            return FALSE;
        }
        if (!PutTableEntry(
              pNetworkMapEntry->pTable,
              pszAddress,
              pNewTaggedAddress))
        {
            RASAUTO_TRACE("AddNetworkMap: PutTableEntry failed");
            UnlockNetworkMap();
            LocalFree(pNewTaggedAddress->pszAddress);
            LocalFree(pNewTaggedAddress);
            return FALSE;
        }
    }
    pNewTaggedAddress->dwTag = dwTag;
    //
    // Insert the new address into the list sorted by tag.
    //
    pPrevEntry = &pNetworkMapEntry->listEntry;
    for (pEntry = pNetworkMapEntry->listEntry.Flink;
         pEntry != &pNetworkMapEntry->listEntry;
         pEntry = pEntry->Flink)
    {
        pTaggedAddress = CONTAINING_RECORD(pEntry, TAGGED_ADDRESS, listEntry);

        if (pTaggedAddress->dwTag >= pNewTaggedAddress->dwTag) {
            InsertHeadList(pPrevEntry, &pNewTaggedAddress->listEntry);
            bInserted = TRUE;
            break;
        }
        pPrevEntry = pEntry;
    }
    if (!bInserted) {
        InsertTailList(
          &pNetworkMapEntry->listEntry,
          &pNewTaggedAddress->listEntry);
    }
    UnlockNetworkMap();

    return TRUE;
} // AddNetworkAddress



BOOLEAN
ClearNetworkMapEntry(
    IN PVOID pArg,
    IN LPTSTR pszNetwork,
    IN PVOID pData
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry = (PNETWORK_MAP_ENTRY)pData;

    FreeNetworkMapEntry(pNetworkMapEntry);

    return TRUE;
} // ClearNetworkMapEntry



VOID
ClearNetworkMap(VOID)
{
    LockNetworkMap();
    NetworkMapG.dwcConnections = 0;
    NetworkMapG.dwcUpNetworks = 0;
    NetworkMapG.dwConnectionTag = 0;
    EnumTable(NetworkMapG.pTable, ClearNetworkMapEntry, NULL);
    ClearTable(NetworkMapG.pTable);
    UnlockNetworkMap();
} // ClearNetworkMap



BOOLEAN
IsAddressAccessible(
    IN LPTSTR *pszNbDevices,
    IN DWORD dwcNbDevices,
    IN BOOLEAN fDnsAvailable,
    IN LPTSTR pszAddress
    )
{
    ACD_ADDR_TYPE fType;
    BOOLEAN bSuccess = FALSE;

    //
    // Get the type of the address.
    //
    fType = AddressToType(pszAddress);
    RASAUTO_TRACE2(
      "IsAddressAccessible: fType=%d, pszAddress=%S",
      fType,
      pszAddress);
    //
    // Call the address-specific accessibility routine.
    //
    switch (fType) {
    case ACD_ADDR_IP:
        bSuccess = PingIpAddress(pszAddress);
        break;
    case ACD_ADDR_IPX:
        RASAUTO_TRACE("IsAddressAccessible: IPX address!");
        break;
    case ACD_ADDR_NB:
        bSuccess = NetbiosFindName(pszNbDevices, dwcNbDevices, pszAddress);
        break;
    case ACD_ADDR_INET:
        if (fDnsAvailable) {
            struct hostent *hp;
            struct in_addr in;
            PCHAR pch;
            TCHAR szIpAddress[17];
            LPTSTR psz;

            psz = LocalAlloc(LPTR, (lstrlen(pszAddress) + 1) * sizeof(TCHAR));
            if(NULL == psz)
            {
                break;
            }
            lstrcpy(psz, pszAddress);
            UnlockNetworkMap();
            hp = InetAddressToHostent(psz);
            LocalFree(psz);
            LockNetworkMap();

            if (hp != NULL) {
                in.s_addr = *(PULONG)hp->h_addr;
                pch = inet_ntoa(in);
                if (pch != NULL) {
                    AnsiStringToUnicodeString(
                      pch,
                      szIpAddress,
                      sizeof (szIpAddress));
                    bSuccess = PingIpAddress(szIpAddress);
                }
            }
        }
        break;
    default:
        RASAUTO_TRACE1("IsAddressAccessible: invalid type: %d", fType);
        break;
    }

    return bSuccess;
} // IsAddressAccessible



BOOLEAN
CheckNetwork(
    IN PVOID pArg,
    IN LPTSTR pszNetwork,
    IN PVOID pData
    )
{
    PCHECK_NETWORK_INFO pCheckNetworkInfo = (PCHECK_NETWORK_INFO)pArg;
    PNETWORK_MAP_ENTRY pNetworkMapEntry = (PNETWORK_MAP_ENTRY)pData;
    PLIST_ENTRY pEntry;
    DWORD dwFailures = 0;
    PTAGGED_ADDRESS pTaggedAddress;

    LockNetworkMap();
    //
    // Check the accessiblilty of up
    // to three addresses to
    // determine if the network is up.
    //
    if (!pNetworkMapEntry->bUp) {
        for (pEntry = pNetworkMapEntry->listEntry.Flink;
             pEntry != &pNetworkMapEntry->listEntry;
             pEntry = pEntry->Flink)
        {
            pTaggedAddress = CONTAINING_RECORD(pEntry, TAGGED_ADDRESS, listEntry);

            if (IsAddressAccessible(
                  pCheckNetworkInfo->pszNbDevices,
                  pCheckNetworkInfo->dwcNbDevices,
                  pCheckNetworkInfo->fDns,
                  pTaggedAddress->pszAddress))
            {
                pNetworkMapEntry->bUp = TRUE;
                NetworkMapG.dwcUpNetworks++;
                break;
            }

            //
            // Sanity check to see if the pEntry is
            // still valid - since IsAddressAccessible
            // releases the network map lock.
            //
            {
                PLIST_ENTRY pEntryT;
                
                for (pEntryT = pNetworkMapEntry->listEntry.Flink;
                     pEntryT != &pNetworkMapEntry->listEntry;
                     pEntryT = pEntryT->Flink)
                {
                    if(pEntryT == pEntry)
                    {
                        RASAUTO_TRACE("CheckNetworkMap: Entry valid");
                        break;
                    }
                }

                if(pEntryT != pEntry)
                {
                    RASAUTO_TRACE1("CheckNetworkMap: Entry %p is invalid!",
                            pEntry);
                    break;
                }
            }
            
            if (dwFailures++ > 2)
                break;
        }
    }
    RASAUTO_TRACE3(
      "CheckNetwork: %S is %s (NetworkMapG.dwcUpNetworks=%d",
      pszNetwork,
      pNetworkMapEntry->bUp ? "up" : "down",
      NetworkMapG.dwcUpNetworks);

    UnlockNetworkMap();      
    return TRUE;
} // CheckNetwork



BOOLEAN
MarkNetworkDown(
    IN PVOID pArg,
    IN LPTSTR pszNetwork,
    IN PVOID pData
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry = (PNETWORK_MAP_ENTRY)pData;

    pNetworkMapEntry->bUp = FALSE;
    pNetworkMapEntry->dwConnectionTag = 0;

    return TRUE;
} // MarkNetworkDown



DWORD
AcsCheckNetworkThread(
    LPVOID lpArg
    )
{
    PCHECK_NETWORK_INFO pCheckNetworkInfo = (PCHECK_NETWORK_INFO)lpArg;

    RASAUTO_TRACE("AcsCheckNetworkThread");
    EnumTable(NetworkMapG.pTable, CheckNetwork, pCheckNetworkInfo);

    return 0;
} // AcsCheckNetworkThread



BOOLEAN
UpdateNetworkMap(
    IN BOOLEAN bForce
    )
{
    LPTSTR *pszNbDevices = NULL;
    DWORD i, dwcConnections, dwcNbDevices = 0;
    LPTSTR pszNetwork, *lpActiveEntries = NULL;
    LPTSTR pszDnsAddresses;
    HRASCONN *lphRasconns = NULL;
    PNETWORK_MAP_ENTRY pNetworkMapEntry;
    CHECK_NETWORK_INFO checkNetworkInfo;
    HANDLE hThread;
    DWORD dwThreadId;
    BOOL fLockAcquired = FALSE;

    LockNetworkMap();

    fLockAcquired = TRUE;
    
    //
    // If the previous number of RAS connections
    // equals the current number of RAS connections,
    // then don't waste our time.
    //
    dwcConnections = ActiveConnections(TRUE, &lpActiveEntries, &lphRasconns);
    if (!bForce && dwcConnections == NetworkMapG.dwcConnections) {
        RASAUTO_TRACE1("UpdateNetworkMap: no change (%d connections)", dwcConnections);
        goto done;
    }
    //
    // Allocate the Netbios device array up front.
    //
    pszNbDevices = (LPTSTR *)LocalAlloc(
                               LPTR,
                               (dwcConnections + 1) *
                                 sizeof (LPTSTR));
    if (pszNbDevices == NULL) {
        RASAUTO_TRACE("UpdateNetworkMap: LocalAlloc failed");
        goto done;
    }
    pszNbDevices[0] = GetPrimaryNetbiosDevice();
    if (pszNbDevices[0] != NULL)
        dwcNbDevices++;
    //
    // Wait up to 3 seconds for the new
    // DNS servers to get set.  Otherwise,
    // we may get inaccurate results from
    // subsequent Winsock getxbyy calls.
    //
    if (dwcConnections != NetworkMapG.dwcConnections) {
        for (i = 0; i < 3; i++) {
            BOOLEAN bChanged;

            pszDnsAddresses = DnsAddresses();
            RASAUTO_TRACE2(
              "UpdateNetworkMap: old DNS=%S, new DNS=%S",
              RASAUTO_TRACESTRW(NetworkMapG.pszDnsAddresses),
              RASAUTO_TRACESTRW(pszDnsAddresses));
            bChanged = (pszDnsAddresses != NULL && NetworkMapG.pszDnsAddresses != NULL) ?
                         wcscmp(pszDnsAddresses, NetworkMapG.pszDnsAddresses) :
                         (pszDnsAddresses != NULL || NetworkMapG.pszDnsAddresses != NULL);
            if (bChanged) {
                if (NetworkMapG.pszDnsAddresses != NULL)
                    LocalFree(NetworkMapG.pszDnsAddresses);
                NetworkMapG.pszDnsAddresses = pszDnsAddresses;
                break;
            }
            LocalFree(pszDnsAddresses);
            Sleep(1000);
        }
    }
    else if (bForce && NetworkMapG.pszDnsAddresses == NULL)
        NetworkMapG.pszDnsAddresses = DnsAddresses();
    //
    //
    NetworkMapG.dwcConnections = dwcConnections;
    NetworkMapG.dwConnectionTag = 0;
    //
    // Mark all networks as down initially.
    //
    NetworkMapG.dwcUpNetworks = dwcNbDevices;
    EnumTable(NetworkMapG.pTable, MarkNetworkDown, NULL);
    //
    // Enumerate the connected phonebook entries
    // and automatically mark those networks as
    // connected.
    //
    for (i = 0; i < dwcConnections; i++) {
        pszNetwork = EntryToNetwork(lpActiveEntries[i]);
        RASAUTO_TRACE2(
          "UpdateNetworkMap: entry %S, network %S is connected",
          lpActiveEntries[i],
          RASAUTO_TRACESTRW(pszNetwork));
        //
        // Increment the number of up networks.
        //
        NetworkMapG.dwcUpNetworks++;
        if (pszNetwork != NULL) {
            pNetworkMapEntry = GetNetworkMapEntry(pszNetwork);
            if (pNetworkMapEntry != NULL) {
                pNetworkMapEntry->bUp = TRUE;
                RASAUTO_TRACE2(
                  "UpdateNetworkMap: network %S is up (dwcUpNetworks=%d)",
                  pszNetwork,
                  NetworkMapG.dwcUpNetworks);
            }
            LocalFree(pszNetwork);
        }
        else {
            //
            // Add a Netbios device associated with
            // this phonebook entry to the list
            // of Netbios devices representing unknown
            // networks so we can do FIND NAME
            // requests on them below.
            //
            pszNbDevices[dwcNbDevices] = GetNetbiosDevice(lphRasconns[i]);
            if (pszNbDevices[dwcNbDevices] != NULL)
                dwcNbDevices++;
        }
    }


    UnlockNetworkMap();
    fLockAcquired = FALSE;
    
    //
    // Now go through all the networks that are
    // not associated with a connected phonebook
    // entry and see if they are connected (via
    // a netcard).  We need to do this in a new
    // thread because only new Winsock threads
    // will get the new DNS server addresses.
    //
    checkNetworkInfo.pszNbDevices = pszNbDevices;
    checkNetworkInfo.dwcNbDevices = dwcNbDevices;
    checkNetworkInfo.fDns = PingAddressList(NetworkMapG.pszDnsAddresses);
    RASAUTO_TRACE1(
      "UpdateNetworkMap: DNS is %s",
      checkNetworkInfo.fDns ? "up" : "down");
    hThread = CreateThread(
                NULL,
                10000L,
                (LPTHREAD_START_ROUTINE)AcsCheckNetworkThread,
                &checkNetworkInfo,
                0,
                &dwThreadId);
    if (hThread == NULL) {
        RASAUTO_TRACE1(
          "UpdateNetworkMap: CreateThread failed (error=0x%x)",
          GetLastError());
        goto done;
    }
    //
    // Wait for the thread to terminate.
    //
    RASAUTO_TRACE("UpdateNetworkMap: waiting for AcsCheckNetworkThread to terminate...");
    WaitForSingleObject(hThread, INFINITE);
    RASAUTO_TRACE1(
      "UpdateNetworkMap: AcsCheckNetworkThread done (NetworkMapG.dwcUpNetworks=%d",
      NetworkMapG.dwcUpNetworks);
    CloseHandle(hThread);

done:

    if(fLockAcquired)
        UnlockNetworkMap();

    if (lpActiveEntries != NULL)
        FreeStringArray(lpActiveEntries, dwcConnections);
    if (lphRasconns != NULL)
        LocalFree(lphRasconns);
    if (pszNbDevices != NULL)
        FreeStringArray(pszNbDevices, dwcNbDevices);
    return TRUE;
} // UpdateNetworkMap



BOOLEAN
GetNetworkConnected(
    IN LPTSTR pszNetwork,
    OUT PBOOLEAN pbConnected
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry;

    pNetworkMapEntry = GetNetworkMapEntry(pszNetwork);
    if (pNetworkMapEntry == NULL)
        return FALSE;
    *pbConnected = pNetworkMapEntry->bUp;
    RASAUTO_TRACE2("GetNetworkConnected: %S is %d", pszNetwork, *pbConnected);

    return TRUE;
} // GetNetworkConnected



BOOLEAN
SetNetworkConnected(
    IN LPTSTR pszNetwork,
    IN BOOLEAN bConnected
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry;

    pNetworkMapEntry = GetNetworkMapEntry(pszNetwork);
    if (pNetworkMapEntry != NULL)
        pNetworkMapEntry->bUp = bConnected;
    if (bConnected)
        NetworkMapG.dwcUpNetworks++;
    else
        NetworkMapG.dwcUpNetworks--;
    RASAUTO_TRACE3(
      "SetNetworkConnected: %S is %d (dwcUpNetworks=%d)",
      RASAUTO_TRACESTRW(pszNetwork),
      bConnected,
      NetworkMapG.dwcUpNetworks);

    return TRUE;
} // SetNetworkConnected



DWORD
GetNetworkConnectionTag(
    IN LPTSTR pszNetwork,
    IN BOOLEAN bIncrement
    )
{
    PNETWORK_MAP_ENTRY pNetworkMapEntry = NULL;
    DWORD dwTag;

    if (pszNetwork != NULL)
        pNetworkMapEntry = GetNetworkMapEntry(pszNetwork);
    if (bIncrement) {
        dwTag = (pNetworkMapEntry == NULL) ?
                  NetworkMapG.dwConnectionTag++ :
                    pNetworkMapEntry->dwConnectionTag++;
    }
    else {
        dwTag = (pNetworkMapEntry == NULL) ?
                  NetworkMapG.dwConnectionTag :
                    pNetworkMapEntry->dwConnectionTag;
    }
    RASAUTO_TRACE2(
      "GetNetworkConnectionTag: network=%S, tag=%d",
      RASAUTO_TRACESTRW(pszNetwork),
      dwTag);
    return dwTag;
} // GetNetworkConnectionTag



BOOLEAN
IsNetworkConnected(VOID)
{
    BOOLEAN bConnected;

    LockNetworkMap();
    bConnected = (NetworkMapG.dwcUpNetworks > 0);
    RASAUTO_TRACE1("IsNetworkConnected: dwcUpNetworks=%d", NetworkMapG.dwcUpNetworks);
    UnlockNetworkMap();

    return bConnected;
} // IsNetworkConnected

VOID
UninitializeNetworkMap(VOID)
{
    DeleteCriticalSection(&NetworkMapG.csLock);
}