/* Copyright (c) 1995, Microsoft Corporation, all rights reserved
**
** pbk.c
** Remote Access phonebook library
** General routines
** Listed alphabetically
**
** 06/20/95 Steve Cobb
*/

#include "pbkp.h"
#include <search.h>  // Qsort


/*----------------------------------------------------------------------------
** Local prototypes
**----------------------------------------------------------------------------
*/

DWORD
AppendPbportToList(
    IN DTLLIST*     pdtllist,
    IN RASMAN_PORT* pPort );

DWORD
AppendStringToList(
    IN DTLLIST* pdtllist,
    IN TCHAR*   psz );

int _CRTAPI1
CompareDevices(
    const void* pDevice1,
    const void* pDevice2 );

int _CRTAPI1
ComparePorts(
    const void* pPort1,
    const void* pPort2 );

CHAR*
PbMedia(
    IN PBDEVICETYPE pbdt,
    IN CHAR*        pszMedia );

/*----------------------------------------------------------------------------
** Routines
**----------------------------------------------------------------------------
*/

DWORD
AppendPbportToList(
    IN DTLLIST*     pdtllist,
    IN RASMAN_PORT* pPort )

    /* Append a PBPORT onto the list 'pdtllist' which has the characteristics
    ** of RAS Manager port 'pPort'.
    **
    ** Returns 0 if successful, otherwise a non-zero error code.
    */
{
    DWORD    dwErr;
    DTLNODE* pdtlnode;
    PBPORT*  ppbport;

    dwErr = 0;

    pdtlnode = CreatePortNode();
    if (!pdtlnode)
        return ERROR_NOT_ENOUGH_MEMORY;

    ppbport = (PBPORT* )DtlGetData( pdtlnode );

    ppbport->pszDevice = StrDupTFromA( pPort->P_DeviceName );
    ppbport->pszPort = StrDupTFromA( pPort->P_PortName );
    ppbport->pbdevicetype = PbdevicetypeFromPszTypeA( pPort->P_DeviceType );
    ppbport->pszMedia = StrDupTFromA(
        PbMedia( ppbport->pbdevicetype, pPort->P_MediaName ) );

    if (!ppbport->pszPort || !ppbport->pszDevice || !ppbport->pszMedia)
        dwErr = ERROR_NOT_ENOUGH_MEMORY;
    else if (ppbport->pbdevicetype == PBDT_Modem)
    {
        if (pPort->P_LineDeviceId == 0xFFFFFFFF)
        {
            /* MXS modem port.
            */
            ppbport->fMxsModemPort = TRUE;

            GetRasPortMaxBps( pPort->P_Handle,
                &ppbport->dwMaxConnectBps, &ppbport->dwMaxCarrierBps );

            GetRasPortModemSettings( pPort->P_Handle, &ppbport->fHwFlowDefault,
                &ppbport->fEcDefault, &ppbport->fEccDefault );
        }
        else
        {
            /* Unimodem port.
            */
            UNIMODEMINFO info;

            GetRasUnimodemInfo( pPort->P_Handle, pPort->P_DeviceType, &info );

            TRACE6("Port=%s,fHw=%d,fEc=%d,fEcc=%d,bps=%d,fSp=%d",
                pPort->P_PortName,info.fHwFlow,info.fEc,info.fEcc,
                info.dwBps,info.fSpeaker);

            ppbport->fHwFlowDefault = info.fHwFlow;
            ppbport->fEcDefault = info.fEc;
            ppbport->fEccDefault = info.fEcc;
            ppbport->dwBpsDefault = info.dwBps;
            ppbport->fSpeakerDefault = info.fSpeaker;
        }
    }

    if (dwErr == 0)
        DtlAddNodeLast( pdtllist, pdtlnode );
    else
    {
        Free0( ppbport->pszDevice );
        Free0( ppbport->pszMedia );
        Free0( ppbport->pszPort );
        DtlDestroyNode( pdtlnode );
    }

    return dwErr;
}


DWORD
AppendStringToList(
    IN DTLLIST* pdtllist,
    IN TCHAR*   psz )

    /* Appends a copy of 'psz' to the end of list 'pdtllist'.
    **
    ** Returns 0 if successful, otherwise a non-zero error code.
    ** ERROR_NOT_ENOUGH_MEMORY is returned if 'psz' is NULL.
    */
{
    DTLNODE* pdtlnode;
    TCHAR*   pszDup;

    if (!psz)
        return ERROR_NOT_ENOUGH_MEMORY;

    pszDup = StrDup( psz );
    if (!pszDup)
        return ERROR_NOT_ENOUGH_MEMORY;

    pdtlnode = DtlCreateNode( pszDup, 0L );
    if (!pdtlnode )
    {
        Free( pszDup );
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    DtlAddNodeLast( pdtllist, pdtlnode );
    return 0;
}


DTLNODE*
CloneEntryNode(
    DTLNODE* pdtlnodeSrc )

    /* Duplicates entry node 'pdtlnodeSrc' with fields that cannot be cloned
    ** set to "like new" settings.
    */
{
    DTLNODE* pdtlnode;

    pdtlnode = DuplicateEntryNode( pdtlnodeSrc );
    if (pdtlnode)
    {
        PBENTRY* ppbentry = (PBENTRY* )DtlGetData( pdtlnode );
        ASSERT(ppbentry);

        ppbentry->fSkipDownLevelDialog = FALSE;
        ppbentry->fSkipNwcWarning = FALSE;
        ppbentry->dwDialParamsUID = GetTickCount();
        ppbentry->fUsePwForNetwork = FALSE;
        ppbentry->fDirty = FALSE;
    }

    return pdtlnode;
}


int _CRTAPI1
CompareDevices(
    const void* pDevice1,
    const void* pDevice2 )

    /* Qsort compare function for RASMAN_DEVICEs.
    */
{
    return
        lstrcmpiA( ((RASMAN_DEVICE* )pDevice1)->D_Name,
                   ((RASMAN_DEVICE* )pDevice2)->D_Name );
}


int _CRTAPI1
ComparePorts(
    const void* pPort1,
    const void* pPort2 )

    /* Qsort compare function for RASMAN_PORTs.
    */
{
    return
        lstrcmpiA( ((RASMAN_PORT* )pPort1)->P_PortName,
                   ((RASMAN_PORT* )pPort2)->P_PortName );
}


DWORD
CopyToPbport(
    IN PBPORT* ppbportDst,
    IN PBPORT* ppbportSrc )

    /* Make a duplicate of 'ppbportSrc' in 'ppbportDst'.  If 'ppbportSrc' is
    ** NULL it sets 'ppbportDst' to defaults.
    **
    ** Returns 0 if successful or an error code.
    */
{
    Free0( ppbportDst->pszDevice );
    Free0( ppbportDst->pszMedia );
    Free0( ppbportDst->pszPort );

    if (!ppbportSrc)
    {
        ppbportDst->pszPort = NULL;
        ppbportDst->fConfigured = TRUE;
        ppbportDst->pszDevice = NULL;
        ppbportDst->pszMedia = NULL;
        ppbportDst->pbdevicetype = PBDT_None;
        ppbportDst->fMxsModemPort = FALSE;
        ppbportDst->dwMaxConnectBps = 0;
        ppbportDst->dwMaxCarrierBps = 0;
        ppbportDst->fHwFlowDefault = FALSE;
        ppbportDst->fEcDefault = FALSE;
        ppbportDst->fEccDefault = FALSE;
        ppbportDst->dwBpsDefault = 0;
        ppbportDst->fSpeakerDefault = TRUE;
        return 0;
    }

    CopyMemory( ppbportDst, ppbportSrc, sizeof(*ppbportDst) );
    ppbportDst->pszDevice = StrDup( ppbportSrc->pszDevice );
    ppbportDst->pszMedia = StrDup( ppbportSrc->pszMedia );
    ppbportDst->pszPort = StrDup( ppbportSrc->pszPort );

    if ((ppbportSrc->pszDevice && !ppbportDst->pszDevice)
        || (ppbportSrc->pszMedia && !ppbportDst->pszMedia)
        || (ppbportSrc->pszPort && !ppbportDst->pszPort))
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    return 0;
}


DTLNODE*
CreateEntryNode(
    BOOL fCreateLink )

    /* Allocates a sized phonebook entry node and fills it with default
    ** values.  If 'fCreateLink' is true a default node is added the list of
    ** links.  Otherwise, the list of links is empty.
    **
    ** Returns the address of the allocated node if successful, NULL
    ** otherwise.
    */
{
    DTLNODE* pdtlnode;
    PBENTRY* ppbentry;

    TRACE("CreateEntryNode");

    pdtlnode = DtlCreateSizedNode( sizeof(PBENTRY), 0L );
    if (!pdtlnode)
        return NULL;

    ppbentry = (PBENTRY* )DtlGetData( pdtlnode );
    ASSERT(ppbentry);

    /* Basic page fields.
    */
    ppbentry->pszEntryName = NULL;
    ppbentry->pszDescription = NULL;
    ppbentry->pszAreaCode = NULL;
    ppbentry->dwCountryCode = 1;
    ppbentry->dwCountryID = 1;
    ppbentry->fUseCountryAndAreaCode = FALSE;

    /* The list of links is created but left empty.
    */
    ppbentry->pdtllistLinks = DtlCreateList( 0 );
    if (!ppbentry->pdtllistLinks)
    {
        DestroyEntryNode( pdtlnode );
        return NULL;
    }

    if (fCreateLink)
    {
        DTLNODE* pLinkNode;

        pLinkNode = CreateLinkNode();
        if (!pLinkNode)
        {
            DestroyEntryNode( pdtlnode );
            return NULL;
        }

        DtlAddNodeLast( ppbentry->pdtllistLinks, pLinkNode );
    }

    ppbentry->dwDialMode = RASEDM_DialAll;
    ppbentry->dwDialPercent = 90;
    ppbentry->dwDialSeconds = 120;
    ppbentry->dwHangUpPercent = 50;
    ppbentry->dwHangUpSeconds = 120;

    /* Server page fields.
    */
    ppbentry->dwBaseProtocol = BP_Ppp;
    ppbentry->dwfExcludedProtocols = 0;
    ppbentry->fLcpExtensions = TRUE;
    ppbentry->dwAuthentication = (DWORD )AS_Default;
    ppbentry->fSkipNwcWarning = FALSE;
    ppbentry->fSkipDownLevelDialog = FALSE;
    ppbentry->fSwCompression = TRUE;

    /* TCPIP Settings dialog.
    */
    ppbentry->fIpPrioritizeRemote = TRUE;
    ppbentry->fIpHeaderCompression = TRUE;
    ppbentry->pszIpAddress = NULL;
    ppbentry->pszIpDnsAddress = NULL;
    ppbentry->pszIpDns2Address = NULL;
    ppbentry->pszIpWinsAddress = NULL;
    ppbentry->pszIpWins2Address = NULL;
    ppbentry->dwIpAddressSource = ASRC_ServerAssigned;
    ppbentry->dwIpNameSource = ASRC_ServerAssigned;
    ppbentry->dwFrameSize = 1006;

    /* Script settings.
    */
    ppbentry->dwScriptModeBefore = SM_None;
    ppbentry->pszScriptBefore = NULL;
    ppbentry->dwScriptModeAfter = SM_None;
    ppbentry->pszScriptAfter = NULL;

    /* Security page fields.
    */
    ppbentry->dwAuthRestrictions = AR_AuthAny;
    ppbentry->fDataEncryption = FALSE;
    ppbentry->fAutoLogon = FALSE;
    ppbentry->fSecureLocalFiles = FALSE;

    /* X.25 page fields.
    */
    ppbentry->pszX25Network = NULL;
    ppbentry->pszX25Address = NULL;
    ppbentry->pszX25UserData = NULL;
    ppbentry->pszX25Facilities = NULL;

    /* Other fields not shown in UI.
    */
    ppbentry->pszCustomDialDll = NULL;
    ppbentry->pszCustomDialFunc = NULL;
    ppbentry->dwIdleDisconnectSeconds = 0;

    /* Authentication dialog fields.
    */
    ppbentry->dwDialParamsUID = GetTickCount();
    ppbentry->fUsePwForNetwork = FALSE;
    ppbentry->pszOldUser = NULL;
    ppbentry->pszOldDomain = NULL;

    /* Status flags.  'fDirty' is set when the entry has changed so as to
    ** differ from the phonebook file on disk.  'fCustom' is set when the
    ** entry contains at least one MEDIA and DEVICE (so RASAPI is able to read
    ** it) but was not created by us.  When 'fCustom' is set only 'pszEntry'
    ** is guaranteed valid and the entry cannot be edited.
    */
    ppbentry->fDirty = FALSE;
    ppbentry->fCustom = FALSE;

    return pdtlnode;
}


DTLNODE*
CreateLinkNode(
    void )

    /* Allocates a sized phonebook entry link node and fills it with default
    ** values.
    **
    ** Returns the address of the allocated node if successful, NULL
    ** otherwise.  It's the caller's responsibility to free the block.
    */
{
    DTLNODE* pdtlnode;
    PBLINK*  ppblink;

    TRACE("CreateLinkNode");

    pdtlnode = DtlCreateSizedNode( sizeof(PBLINK), 0L );
    if (!pdtlnode)
        return NULL;

    ppblink = (PBLINK* )DtlGetData( pdtlnode );
    ASSERT(ppblink);

    CopyToPbport( &ppblink->pbport, NULL );
    ppblink->fOtherPortOk = TRUE;

    ppblink->dwBps = 0;
    ppblink->fHwFlow = TRUE;
    ppblink->fEc = TRUE;
    ppblink->fEcc = TRUE;
    ppblink->fManualDial = FALSE;
    ppblink->fSpeaker = TRUE;

    ppblink->fProprietaryIsdn = FALSE;
    ppblink->lLineType = 0;
    ppblink->fFallback = TRUE;
    ppblink->fCompression = TRUE;
    ppblink->lChannels = 1;

    ppblink->pTapiBlob = NULL;
    ppblink->cbTapiBlob = 0;
    ppblink->fPromoteHuntNumbers = TRUE;

    ppblink->fEnabled = TRUE;

    /* The list of phone numbers is created but left empty.
    */
    ppblink->pdtllistPhoneNumbers = DtlCreateList( 0 );
    if (!ppblink->pdtllistPhoneNumbers)
    {
        Free( ppblink );
        return NULL;
    }

    return pdtlnode;
}


DTLNODE*
CreatePortNode(
    void )

    /* Allocates a sized port node and fills it with default values.
    **
    ** Returns the address of the allocated node if successful, NULL
    ** otherwise.  It's the caller's responsibility to free the block.
    */
{
    DTLNODE* pdtlnode;
    PBPORT*  ppbport;

    TRACE("CreatePortNode");

    pdtlnode = DtlCreateSizedNode( sizeof(PBPORT), 0L );
    if (!pdtlnode)
        return NULL;

    ppbport = (PBPORT* )DtlGetData( pdtlnode );
    ASSERT(ppbport);

    CopyToPbport( ppbport, NULL );

    return pdtlnode;
}


VOID
DestroyEntryNode(
    IN DTLNODE* pdtlnode )

    /* Release all memory associated with phonebook entry node 'pdtlnode'.
    ** See DtlDestroyList.
    */
{
    PBENTRY* ppbentry;

    TRACE("DestroyEntryNode");

    ASSERT(pdtlnode);
    ppbentry = (PBENTRY* )DtlGetData( pdtlnode );
    ASSERT(ppbentry);

    Free0( ppbentry->pszEntryName );
    Free0( ppbentry->pszDescription );
    Free0( ppbentry->pszAreaCode );
    Free0( ppbentry->pszIpAddress );
    Free0( ppbentry->pszIpDnsAddress );
    Free0( ppbentry->pszIpDns2Address );
    Free0( ppbentry->pszIpWinsAddress );
    Free0( ppbentry->pszIpWins2Address );
    Free0( ppbentry->pszScriptBefore );
    Free0( ppbentry->pszScriptAfter );
    Free0( ppbentry->pszX25Network );
    Free0( ppbentry->pszX25Address );
    Free0( ppbentry->pszX25UserData );
    Free0( ppbentry->pszX25Facilities );
    Free0( ppbentry->pszCustomDialDll );
    Free0( ppbentry->pszCustomDialFunc );
    Free0( ppbentry->pszOldUser );
    Free0( ppbentry->pszOldDomain );
    DtlDestroyList( ppbentry->pdtllistLinks, DestroyLinkNode );

    DtlDestroyNode( pdtlnode );
}


VOID
DestroyLinkNode(
    IN DTLNODE* pdtlnode )

    /* Release all memory associated with phonebook entry link node
    ** 'pdtlnode'.  See DtlDestroyList.
    */
{
    PBLINK* ppblink;

    TRACE("DestroyLinkNode");

    ASSERT(pdtlnode);
    ppblink = (PBLINK* )DtlGetData( pdtlnode );
    ASSERT(ppblink);

    Free( ppblink->pbport.pszDevice );
    Free( ppblink->pbport.pszMedia );
    Free0( ppblink->pbport.pszPort );
    Free( ppblink->pTapiBlob );
    DtlDestroyList( ppblink->pdtllistPhoneNumbers, DestroyPszNode );

    DtlDestroyNode( pdtlnode );
}


VOID
DestroyPortNode(
    IN DTLNODE* pdtlnode )

    /* Release memory associated with PBPORT node 'pdtlnode'.  See
    ** DtlDestroyList.
    */
{
    PBPORT* pPort;

    TRACE("DestroyPortNode");

    ASSERT(pdtlnode);
    pPort = (PBPORT* )DtlGetData( pdtlnode );
    ASSERT(pPort);

    Free0( pPort->pszDevice );
    Free0( pPort->pszMedia );
    Free0( pPort->pszPort );

    DtlDestroyNode( pdtlnode );
}


DTLNODE*
DuplicateEntryNode(
    DTLNODE* pdtlnodeSrc )

    /* Duplicates phonebook entry node 'pdtlnodeSrc'.  See CloneEntryNode and
    ** DtlDuplicateList.
    **
    ** Returns the address of the allocated node if successful, NULL
    ** otherwise.  It's the caller's responsibility to free the block.
    */
{
    DTLNODE* pdtlnodeDst;
    PBENTRY* ppbentrySrc;
    PBENTRY* ppbentryDst;
    BOOL     fDone;

    TRACE("DuplicateEntryNode");

    pdtlnodeDst = DtlCreateSizedNode( sizeof(PBENTRY), 0L );
    if (!pdtlnodeDst)
        return NULL;

    ppbentrySrc = (PBENTRY* )DtlGetData( pdtlnodeSrc );
    ppbentryDst = (PBENTRY* )DtlGetData( pdtlnodeDst );
    fDone = FALSE;

    CopyMemory( ppbentryDst, ppbentrySrc, sizeof(PBENTRY) );

    ppbentryDst->pszEntryName = NULL;
    ppbentryDst->pszDescription = NULL;
    ppbentryDst->pszAreaCode = NULL;
    ppbentryDst->pszIpAddress = NULL;
    ppbentryDst->pszIpDnsAddress = NULL;
    ppbentryDst->pszIpDns2Address = NULL;
    ppbentryDst->pszIpWinsAddress = NULL;
    ppbentryDst->pszIpWins2Address = NULL;
    ppbentryDst->pszScriptBefore = NULL;
    ppbentryDst->pszScriptAfter = NULL;
    ppbentryDst->pszX25Network = NULL;
    ppbentryDst->pszX25Address = NULL;
    ppbentryDst->pszX25UserData = NULL;
    ppbentryDst->pszX25Facilities = NULL;
    ppbentryDst->pszCustomDialDll = NULL;
    ppbentryDst->pszCustomDialFunc = NULL;
    ppbentryDst->pszOldUser = NULL;
    ppbentryDst->pszOldDomain = NULL;
    ppbentryDst->pdtllistLinks = NULL;

    do
    {
        /* Duplicate strings.
        */
        if (ppbentrySrc->pszEntryName
            && (!(ppbentryDst->pszEntryName =
                    StrDup( ppbentrySrc->pszEntryName ))))
        {
            break;
        }

        if (ppbentrySrc->pszDescription
            && (!(ppbentryDst->pszDescription =
                    StrDup( ppbentrySrc->pszDescription ))))
        {
            break;
        }

        if (ppbentrySrc->pszAreaCode
            && (!(ppbentryDst->pszAreaCode =
                    StrDup( ppbentrySrc->pszAreaCode ))))
        {
            break;
        }

        if (ppbentrySrc->pszIpAddress
            && (!(ppbentryDst->pszIpAddress =
                    StrDup( ppbentrySrc->pszIpAddress ))))
        {
            break;
        }

        if (ppbentrySrc->pszIpDnsAddress
            && (!(ppbentryDst->pszIpDnsAddress =
                    StrDup( ppbentrySrc->pszIpDnsAddress ))))
        {
            break;
        }

        if (ppbentrySrc->pszIpDns2Address
            && (!(ppbentryDst->pszIpDns2Address =
                    StrDup( ppbentrySrc->pszIpDns2Address ))))
        {
            break;
        }

        if (ppbentrySrc->pszIpWinsAddress
            && (!(ppbentryDst->pszIpWinsAddress =
                    StrDup( ppbentrySrc->pszIpWinsAddress ))))
        {
            break;
        }

        if (ppbentrySrc->pszIpWins2Address
            && (!(ppbentryDst->pszIpWins2Address =
                    StrDup( ppbentrySrc->pszIpWins2Address ))))
        {
            break;
        }

        if (ppbentrySrc->pszScriptBefore
            && (!(ppbentryDst->pszScriptBefore =
                    StrDup( ppbentrySrc->pszScriptBefore ))))
        {
            break;
        }

        if (ppbentrySrc->pszScriptAfter
            && (!(ppbentryDst->pszScriptAfter =
                    StrDup( ppbentrySrc->pszScriptAfter ))))
        {
            break;
        }

        if (ppbentrySrc->pszX25Network
            && (!(ppbentryDst->pszX25Network =
                    StrDup( ppbentrySrc->pszX25Network ))))
        {
            break;
        }

        if (ppbentrySrc->pszX25Address
            && (!(ppbentryDst->pszX25Address =
                    StrDup( ppbentrySrc->pszX25Address ))))
        {
            break;
        }

        if (ppbentrySrc->pszX25UserData
            && (!(ppbentryDst->pszX25UserData =
                    StrDup( ppbentrySrc->pszX25UserData ))))
        {
            break;
        }

        if (ppbentrySrc->pszX25Facilities
            && (!(ppbentryDst->pszX25Facilities =
                    StrDup( ppbentrySrc->pszX25Facilities ))))
        {
            break;
        }

        if (ppbentrySrc->pszCustomDialDll
            && (!(ppbentryDst->pszCustomDialDll =
                    StrDup( ppbentrySrc->pszCustomDialDll ))))
        {
            break;
        }

        if (ppbentrySrc->pszCustomDialFunc
            && (!(ppbentryDst->pszCustomDialFunc =
                    StrDup( ppbentrySrc->pszCustomDialFunc ))))
        {
            break;
        }

        if (ppbentrySrc->pszOldUser
            && (!(ppbentryDst->pszOldUser =
                    StrDup( ppbentrySrc->pszOldUser ))))
        {
            break;
        }

        if (ppbentrySrc->pszOldDomain
            && (!(ppbentryDst->pszOldDomain =
                    StrDup( ppbentrySrc->pszOldDomain ))))
        {
            break;
        }

        /* Duplicate list of link information.
        */
        if (ppbentrySrc->pdtllistLinks
            &&  (!(ppbentryDst->pdtllistLinks =
                     DtlDuplicateList(
                         ppbentrySrc->pdtllistLinks,
                         DuplicateLinkNode,
                         DestroyLinkNode ))))
        {
            break;
        }

        fDone = TRUE;
    }
    while (FALSE);

    if (!fDone)
    {
        DestroyEntryNode( pdtlnodeDst );
        return NULL;
    }

    /* Since the copy is "new" it is inherently dirty relative to the
    ** phonebook file.
    */
    ppbentryDst->fDirty = TRUE;

    return pdtlnodeDst;
}


DTLNODE*
DuplicateLinkNode(
    IN DTLNODE* pdtlnodeSrc )

    /* Duplicates phonebook entry link node 'pdtlnodeSrc'.  See
    ** DtlDuplicateList.
    **
    ** Returns the address of the allocated node if successful, NULL
    ** otherwise.  It's the caller's responsibility to free the block.
    */
{
    DTLNODE* pdtlnodeDst;
    PBLINK*  ppblinkSrc;
    PBLINK*  ppblinkDst;
    BOOL     fDone;

    TRACE("DuplicateLinkNode");

    pdtlnodeDst = DtlCreateSizedNode( sizeof(PBLINK), 0L );
    if (!pdtlnodeDst)
        return NULL;

    ppblinkSrc = (PBLINK* )DtlGetData( pdtlnodeSrc );
    ppblinkDst = (PBLINK* )DtlGetData( pdtlnodeDst );
    fDone = FALSE;

    CopyMemory( ppblinkDst, ppblinkSrc, sizeof(PBLINK) );

    ppblinkDst->pbport.pszDevice = NULL;
    ppblinkDst->pbport.pszMedia = NULL;
    ppblinkDst->pbport.pszPort = NULL;
    ppblinkDst->pTapiBlob = NULL;
    ppblinkDst->pdtllistPhoneNumbers = NULL;

    do
    {
        /* Duplicate strings.
        */
        if (ppblinkSrc->pbport.pszDevice
            && (!(ppblinkDst->pbport.pszDevice =
                    StrDup( ppblinkSrc->pbport.pszDevice ))))
        {
            break;
        }

        if (ppblinkSrc->pbport.pszMedia
            && (!(ppblinkDst->pbport.pszMedia =
                    StrDup( ppblinkSrc->pbport.pszMedia ))))
        {
            break;
        }


        if (ppblinkSrc->pbport.pszPort
            && (!(ppblinkDst->pbport.pszPort =
                    StrDup( ppblinkSrc->pbport.pszPort ))))
        {
            break;
        }
        /* Duplicate TAPI blob.
        */
        if (ppblinkSrc->pTapiBlob)
        {
            VOID* pTapiBlobDst;

            ppblinkDst->pTapiBlob = (VOID* )Malloc( ppblinkSrc->cbTapiBlob );
            if (!ppblinkDst->pTapiBlob)
                break;

            CopyMemory( ppblinkDst->pTapiBlob, ppblinkSrc->pTapiBlob,
                ppblinkSrc->cbTapiBlob );
        }

        /* Duplicate list of phone numbers.
        */
        if (ppblinkSrc->pdtllistPhoneNumbers
            &&  (!(ppblinkDst->pdtllistPhoneNumbers =
                     DtlDuplicateList(
                         ppblinkSrc->pdtllistPhoneNumbers,
                         DuplicatePszNode,
                         DestroyPszNode ))))
        {
            break;
        }

        fDone = TRUE;
    }
    while (FALSE);

    if (!fDone)
    {
        DestroyLinkNode( pdtlnodeDst );
        return NULL;
    }

    return pdtlnodeDst;
}


DTLNODE*
EntryNodeFromName(
    IN DTLLIST* pdtllistEntries,
    IN TCHAR*   pszName )

    /* Returns the address of the node in the global phonebook entries list
    ** whose Entry Name matches 'pszName' or NULL if none.
    */
{
    DTLNODE* pdtlnode;

    for (pdtlnode = DtlGetFirstNode( pdtllistEntries );
         pdtlnode;
         pdtlnode = DtlGetNextNode( pdtlnode ))
    {
        PBENTRY* ppbentry = (PBENTRY* )DtlGetData( pdtlnode );

        if (lstrcmpi( ppbentry->pszEntryName, pszName ) == 0)
            return pdtlnode;
    }

    return NULL;
}


#if 0
INT
IndexFromName(
    IN DTLLIST* pdtllist,
    IN TCHAR*   pszName )

    /* Returns the 0-based index of the first node that matches 'pszName' in
    ** the linked list of strings, 'pdtllist', or -1 if not found.
    */
{
    DTLNODE* pdtlnode;
    INT      i;

    for (pdtlnode = DtlGetFirstNode( pdtllist ), i = 0;
         pdtlnode;
         pdtlnode = DtlGetNextNode( pdtlnode ), ++i)
    {
        if (lstrcmp( pszName, (TCHAR* )DtlGetData( pdtlnode ) ) == 0)
            break;
    }

    return (pdtlnode) ? i : -1;
}
#endif


#if 0
INT
IndexFromDeviceName(
    IN DTLLIST* pdtllist,
    IN TCHAR*   pszDeviceName )

    /* Returns the 0-based index of the first node that matches
    ** 'pszDeviceName' in the linked list of PBPORTs, 'pdtllist', or -1 if not
    ** found.
    */
{
    DTLNODE* pdtlnode;
    INT      i;

    for (pdtlnode = DtlGetFirstNode( pdtllist ), i = 0;
         pdtlnode;
         pdtlnode = DtlGetNextNode( pdtlnode ), ++i)
    {
        PBPORT* pPort = (PBPORT* )DtlGetData( pdtlnode );

        if (lstrcmp( pszDeviceName, pPort->pszDevice ) == 0)
            break;
    }

    return (pdtlnode) ? i : -1;
}
#endif


DWORD
LoadPadsList(
    OUT DTLLIST** ppdtllistPads )

    /* Build a list of all X.25 PAD devices in '*ppdtllistPads'.
    **
    ** Returns 0 if successful, otherwise a non-zero error code.  It is
    ** caller's responsibility to DtlDestroyList the list when done.
    */
{
    INT            i;
    DWORD          dwErr;
    RASMAN_DEVICE* pDevices;
    WORD           wDevices;

    TRACE("LoadPadsList");

    *ppdtllistPads = NULL;

    dwErr = GetRasPads( &pDevices, &wDevices );
    if (dwErr != 0)
        return dwErr;

    *ppdtllistPads = DtlCreateList( 0L );
    if (!*ppdtllistPads)
    {
        Free( pDevices );
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    qsort( (VOID* )pDevices, (size_t )wDevices, sizeof(RASMAN_DEVICE),
           CompareDevices );

    for (i = 0; i < (INT )wDevices; ++i)
    {
        TCHAR* pszDup;

        pszDup = StrDupTFromA( pDevices[ i ].D_Name );
        dwErr = AppendStringToList( *ppdtllistPads, pszDup );
        Free0( pszDup );

        if (dwErr != 0)
        {
            Free( pDevices );
            DtlDestroyList( *ppdtllistPads, NULL );
            *ppdtllistPads = NULL;
            return dwErr;
        }
    }

    Free( pDevices );

    TRACE("LoadPadsList=0");
    return 0;
}


DWORD
LoadPortsList(
    OUT DTLLIST** ppdtllistPorts )

    /* Build a sorted list of all RAS ports in '*ppdtllistPorts'.
    **
    ** Returns 0 if successful, otherwise a non-zero error code.  It is
    ** caller's responsibility to DtlDestroyList the list when done.
    */
{
    INT          i;
    DWORD        dwErr;
    RASMAN_PORT* pPorts;
    RASMAN_PORT* pPort;
    WORD         wPorts;

    TRACE("LoadPortsList");

    *ppdtllistPorts = NULL;

    dwErr = GetRasPorts( &pPorts, &wPorts );
    if (dwErr != 0)
        return dwErr;

    *ppdtllistPorts = DtlCreateList( 0L );
    if (!*ppdtllistPorts)
        return ERROR_NOT_ENOUGH_MEMORY;

    qsort( (VOID* )pPorts, (size_t )wPorts, sizeof(RASMAN_PORT),
           ComparePorts );

    for (i = 0, pPort = pPorts; i < (INT )wPorts; ++i, ++pPort)
    {
        /* We're only interested in ports you can dial-out on.
        */
        if (pPort->P_ConfiguredUsage != CALL_OUT
            && pPort->P_ConfiguredUsage != CALL_IN_OUT)
        {
            continue;
        }

        dwErr = AppendPbportToList( *ppdtllistPorts, pPort );
        if (dwErr != 0)
        {
            Free( pPorts );
            DtlDestroyList( *ppdtllistPorts, NULL );
            *ppdtllistPorts = NULL;
            return dwErr;
        }
    }

    Free( pPorts );

    TRACE("LoadPortsList=0");
    return 0;
}


DWORD
LoadScriptsList(
    OUT DTLLIST** ppdtllistScripts )

    /* Build a sorted list of all RAS switch devices in '*ppdtllistPorts'.
    **
    ** Returns 0 if successful, otherwise a non-zero error code.  It is
    ** caller's responsibility to DtlDestroyList the list when done.
    */
{
    INT            i;
    DWORD          dwErr;
    RASMAN_DEVICE* pDevices;
    WORD           wDevices;

    TRACE("LoadScriptsList");

    *ppdtllistScripts = NULL;

    dwErr = GetRasSwitches( &pDevices, &wDevices );
    if (dwErr != 0)
        return dwErr;

    *ppdtllistScripts = DtlCreateList( 0L );
    if (!*ppdtllistScripts)
    {
        Free( pDevices );
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    qsort( (VOID* )pDevices, (size_t )wDevices, sizeof(RASMAN_DEVICE),
           CompareDevices );

    for (i = 0; i < (INT )wDevices; ++i)
    {
        TCHAR* pszDup;

        pszDup = StrDupTFromA( pDevices[ i ].D_Name );
        dwErr = AppendStringToList( *ppdtllistScripts, pszDup );
        Free( pszDup );

        if (dwErr != 0)
        {
            Free( pDevices );
            DtlDestroyList( *ppdtllistScripts, NULL );
            *ppdtllistScripts = NULL;
            return dwErr;
        }
    }

    Free( pDevices );

    TRACE("LoadScriptsList=0");
    return 0;
}


#if 0
TCHAR*
NameFromIndex(
    IN DTLLIST* pdtllist,
    IN INT      iToFind )

    /* Returns the name associated with 0-based index 'iToFind' in the linked
    ** list of strings, 'pdtllist', or NULL if not found.
    */
{
    DTLNODE* pdtlnode;

    if (!pdtllist)
        return NULL;

    pdtlnode = DtlGetFirstNode( pdtllist );

    if (iToFind < 0)
        return NULL;

    while (pdtlnode && iToFind--)
        pdtlnode = DtlGetNextNode( pdtlnode );

    return (pdtlnode) ? (TCHAR* )DtlGetData( pdtlnode ) : NULL;
}
#endif


PBDEVICETYPE
PbdevicetypeFromPszType(
    IN TCHAR* pszDeviceType )

    /* Returns the device type corresponding to the device type string,
    ** 'pszDeviceType'.
    */
{
    CHAR*        pszA;
    PBDEVICETYPE pbdt;

    pbdt = PBDT_None;
    pszA = StrDupAFromT( pszDeviceType );
    if (pszA)
    {
        pbdt = PbdevicetypeFromPszTypeA( pszA );
        Free( pszA );
    }
    return pbdt;
}


PBDEVICETYPE
PbdevicetypeFromPszTypeA(
    IN CHAR* pszDeviceType )

    /* Returns the device type corresponding to the ANSI device type string,
    ** 'pszDeviceType'.
    */
{
    if (*pszDeviceType == '\0')
        return PBDT_None;
    else if (lstrcmpiA( pszDeviceType, MXS_MODEM_TXT ) == 0)
        return PBDT_Modem;
    else if (lstrcmpiA( pszDeviceType, MXS_NULL_TXT ) == 0)
        return PBDT_Null;
    else if (lstrcmpiA( pszDeviceType, MXS_PAD_TXT ) == 0)
        return PBDT_Pad;
    else if (lstrcmpiA( pszDeviceType, MXS_SWITCH_TXT ) == 0)
        return PBDT_Switch;
    else if (lstrcmpiA( pszDeviceType, ISDN_TXT ) == 0)
        return PBDT_Isdn;
    else if (lstrcmpiA( pszDeviceType, X25_TXT ) == 0)
        return PBDT_X25;
    else
        return PBDT_Other;
}


CHAR*
PbMedia(
    IN PBDEVICETYPE pbdt,
    IN CHAR*        pszMedia )

    /* The media names stored in the phonebook are not exactly the same as
    ** those returned by RASMAN.  This translates a RASMAN media name to
    ** equivalent phonebook media names given the device type.  The reason for
    ** this is historical and obscure.
    */
{
    if (pbdt == PBDT_Isdn)
        return ISDN_TXT;
    else if (pbdt == PBDT_X25)
        return X25_TXT;
    else if (pbdt == PBDT_Other)
        return pszMedia;
    else
        return SERIAL_TXT;
}


PBPORT*
PpbportFromPortName(
    IN DTLLIST* pdtllistPorts,
    IN TCHAR*   pszPort )

    /* Return port with name 'pszPort' in list of ports 'pdtllistPorts' or
    ** NULL if not found.  'PszPort' may be an old-style name such as
    ** PcImacISDN1, in which case it will match ISDN1.
    */
{
    DTLNODE* pdtlnode;

    if (!pszPort)
        return NULL;

    for (pdtlnode = DtlGetFirstNode( pdtllistPorts );
         pdtlnode;
         pdtlnode = DtlGetNextNode( pdtlnode ))
    {
        PBPORT* ppbport = (PBPORT* )DtlGetData( pdtlnode );

        if (ppbport->pszPort && lstrcmp( ppbport->pszPort, pszPort ) == 0)
            return ppbport;
    }

    /* No match.  Look for the old port name format.
    */
    for (pdtlnode = DtlGetFirstNode( pdtllistPorts );
         pdtlnode;
         pdtlnode = DtlGetNextNode( pdtlnode ))
    {
        TCHAR   szBuf[ MAX_DEVICE_NAME + MAX_DEVICETYPE_NAME + 10 + 1 ];
        PBPORT* ppbport;

        ppbport = (PBPORT* )DtlGetData( pdtlnode );

        /* Skip modems (COM ports) and unconfigured ports, since they do not
        ** follow the same port name formatting rules as other ports.
        */
        if (!ppbport->pszDevice || ppbport->pbdevicetype == PBDT_Modem)
            continue;

        lstrcpy( szBuf, ppbport->pszDevice );
        lstrcat( szBuf, ppbport->pszPort );

        if (lstrcmp( szBuf, pszPort ) == 0)
            return ppbport;
    }

    return NULL;
}


BOOL
SetDefaultModemSettings(
    IN PBLINK* pLink )

    /* Set the MXS modem settings for link 'pLink' to the defaults.
    **
    ** Returns true if something changed, false otherwise.
    */
{
    BOOL  fChange;
    DWORD dwBps;

    fChange = FALSE;

    if (pLink->fHwFlow != pLink->pbport.fHwFlowDefault)
    {
        fChange = TRUE;
        pLink->fHwFlow = pLink->pbport.fHwFlowDefault;
    }

    if (pLink->fEc != pLink->pbport.fEcDefault)
    {
        fChange = TRUE;
        pLink->fEc = pLink->pbport.fEcDefault;
    }

    if (pLink->fEcc != pLink->pbport.fEccDefault)
    {
        fChange = TRUE;
        pLink->fEcc = pLink->pbport.fEccDefault;
    }

    if (pLink->fManualDial)
    {
        fChange = TRUE;
        pLink->fManualDial = FALSE;
    }

    if (!pLink->pbport.fMxsModemPort)
    {
        if (pLink->fSpeaker != pLink->pbport.fSpeakerDefault)
        {
            fChange = TRUE;
            pLink->fSpeaker = pLink->pbport.fSpeakerDefault;
        }
    }

    if (pLink->pbport.fMxsModemPort)
    {
        dwBps = (pLink->fHwFlow)
            ? pLink->pbport.dwMaxConnectBps
            : pLink->pbport.dwMaxCarrierBps;
    }
    else
        dwBps = pLink->pbport.dwBpsDefault;

    if (pLink->dwBps != dwBps)
    {
        fChange = TRUE;
        pLink->dwBps = dwBps;
    }

    TRACE2("SetDefaultModemSettings(bps=%d)=%d",pLink->dwBps,fChange);
    return fChange;
}


BOOL
ValidateAreaCode(
    IN OUT TCHAR* pszAreaCode )

    /* Checks that area code consists of decimal digits only.  If the area
    ** code is all white characters it is reduced to empty string.  Returns
    ** true if 'pszAreaCode' is a valid area code, false if not.
    */
{
    if (IsAllWhite( pszAreaCode ))
    {
        *pszAreaCode = TEXT('\0');
        return TRUE;
    }

    if (lstrlen( pszAreaCode ) > RAS_MaxAreaCode)
        return FALSE;

    while (*pszAreaCode != TEXT('\0'))
    {
        if (*pszAreaCode < TEXT('0') || *pszAreaCode > TEXT('9'))
            return FALSE;

        ++pszAreaCode;
    }

    return TRUE;
}


BOOL
ValidateEntryName(
    IN TCHAR* pszEntry )

    /* Returns true if 'pszEntry' is a valid phonebook entry name, false if
    ** not.
    */
{
    INT nLen = lstrlen( pszEntry );

    if (nLen <= 0
        || nLen > RAS_MaxEntryName
        || IsAllWhite( pszEntry )
        || pszEntry[ 0 ] == TEXT('.'))
    {
        return FALSE;
    }

    return TRUE;
}
