// Copyright (c) 1995, Microsoft Corporation, all rights reserved
//
// pbook.c
// Remote Access Common Dialog APIs
// RasPhonebookDlg APIs
//
// 06/20/95 Steve Cobb


#include "rasdlgp.h" // Our private header
#include <commdlg.h> // FileOpen dialog
#include <dlgs.h>    // Common dialog resource constants
#include <rnk.h>     // Shortcut file library

#define WM_RASEVENT      (WM_USER+987)
#define WM_NOUSERTIMEOUT (WM_USER+988)

#define RAS_SC_IS_BAD_PIN(_err) \
    (((_err) == SCARD_W_WRONG_CHV) || ((_err) == SCARD_E_INVALID_CHV))

// In no-user mode this is updated on every mouse or keyboard event by our
// window hook.  The monitor thread notices and resets it's inactivity
// timeout.
//
DWORD g_cInput = 0;


//----------------------------------------------------------------------------
// Help maps
//----------------------------------------------------------------------------

static DWORD g_adwDuHelp[] =
{
    CID_DU_ST_Entries,     HID_DU_LB_Entries,
    CID_DU_LB_Entries,     HID_DU_LB_Entries,
    CID_DU_PB_New,         HID_DU_PB_New,
    CID_DU_PB_More,        HID_DU_PB_More,
    CID_DU_PB_Dial,        HID_DU_PB_Dial,
    CID_DU_PB_Close,       HID_DU_PB_Close,
    0, 0
};


//----------------------------------------------------------------------------
// Local datatypes
//----------------------------------------------------------------------------

// Phonebook dialog argument block.
//
typedef struct
_DUARGS
{
    // Caller's  arguments to the RAS API.  Outputs in 'pApiArgs' are visible
    // to the API which has the address of same.  'PszPhonebook' is updated if
    // user changes the phonebook on the Preferences->PhoneList page, though
    // API is unaware of this.
    //
    LPTSTR pszPhonebook;
    LPTSTR pszEntry;
    RASPBDLG* pApiArgs;

    // RAS API return value.  Set true if a connection is established within
    // the dialog.
    //
    BOOL fApiResult;
}
DUARGS;

typedef struct
_DUCONTEXT
{
    LPTSTR  pszPhonebookPath;
    PBENTRY *pEntry;
}
DUCONTEXT;


// Dial-Up Networking dialog context block.
//
typedef struct
_DUINFO
{
    // Caller's arguments to the RAS API.
    //
    DUARGS* pArgs;

    // Handle of this dialog and some of it's controls.
    //
    HWND hwndDlg;
    HWND hwndPbNew;
    HWND hwndPbProperties;
    HWND hwndLbEntries;
    HWND hwndPbDial;

    // Global user preference settings read from the Registry.
    //
    PBUSER user;

    // Phonebook settings read from the phonebook file.
    //
    PBFILE file;

    // No logged on user information retrieved via callback.
    //
    RASNOUSER* pNoUser;

    // Set if in "no user before logon" mode.  Always the same as the
    // RASPBDFLAG but here for convenience.
    //
    BOOL fNoUser;

    // Window hooks used to detect user input in the thread.  Used only when
    // 'fNoUser' is set.
    //
    HHOOK hhookKeyboard;
    HHOOK hhookMouse;

    // TAPI session handle.
    //
    HLINEAPP hlineapp;

    // Handle of the RAS connection associated with the current entry or NULL
    // if none.
    //
    HRASCONN hrasconn;

    // Connect monitor objects.
    //
    HANDLE hThread;
    HANDLE hEvent;
    BOOL fAbortMonitor;
}
DUINFO;


//----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//----------------------------------------------------------------------------

BOOL
DuCommand(
    IN DUINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl );

VOID
DuCreateShortcut(
    IN DUINFO* pInfo );

LRESULT CALLBACK
DuCreateShortcutCallWndRetProc(
    int code,
    WPARAM wparam,
    LPARAM lparam );

INT_PTR CALLBACK
DuDlgProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam );

VOID
DuDeleteSelectedEntry(
    IN DUINFO* pInfo );

VOID
DuDialSelectedEntry(
    IN DUINFO* pInfo );

VOID
DuEditSelectedEntry(
    IN DUINFO* pInfo );

VOID
DuEditSelectedLocation(
    IN DUINFO* pInfo );

DWORD
DuFillLocationList(
    IN DUINFO* pInfo );

VOID
DuFillPreview(
    IN DUINFO* pInfo );

DWORD
DuGetEntry(
    DUINFO* pInfo,
    DUCONTEXT* pContext );

TCHAR*
DuGetPreview(
    IN DUINFO* pInfo );

DWORD
DuHandleConnectFailure(
    IN DUINFO* pInfo,
    IN RASDIALDLG* pDialInfo);
    
VOID
DuHangUpSelectedEntry(
    IN DUINFO* pInfo );

BOOL
DuInit(
    IN HWND    hwndDlg,
    IN DUARGS* pArgs );

LRESULT CALLBACK
DuInputHook(
    IN int nCode,
    IN WPARAM wparam,
    IN LPARAM lparam );

LRESULT APIENTRY
DuLbEntriesProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam );

VOID
DuLocationChange(
    IN DUINFO* pInfo );

DWORD
DuMonitorThread(
    LPVOID pThreadArg );

VOID
DuNewEntry(
    IN DUINFO* pInfo,
    IN BOOL fClone );

VOID
DuOperatorDial(
    IN DUINFO* pInfo );

LRESULT APIENTRY
DuPbMoreProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam );

VOID
DuPopupMoreMenu(
    IN DUINFO* pInfo );

VOID
DuPreferences(
    IN DUINFO* pInfo,
    IN BOOL fLogon );

VOID
DuSetup(
    IN DUINFO* pInfo );

VOID
DuStatus(
    IN DUINFO* pInfo );

VOID
DuTerm(
    IN HWND hwndDlg );

VOID
DuUpdateConnectStatus(
    IN DUINFO* pInfo );

VOID
DuUpdateLbEntries(
    IN DUINFO* pInfo,
    IN TCHAR* pszEntry );

VOID
DuUpdatePreviewAndLocationState(
    IN DUINFO* pInfo );

VOID
DuUpdateTitle(
    IN DUINFO* pInfo );

VOID
DuWriteShortcutFile(
    IN HWND hwnd,
    IN TCHAR* pszRnkPath,
    IN TCHAR* pszPbkPath,
    IN TCHAR* pszEntry );

DWORD
DwGetEapLogonInfo(
    VOID *pv,
    EAPLOGONINFO **ppEapLogonInfo );

VOID WINAPI
RasPbDlgCallbackThunk(
    ULONG_PTR ulpId,
    DWORD dwEvent,
    LPWSTR pszEntry,
    LPVOID pArgs );


//----------------------------------------------------------------------------
// External entry points
//----------------------------------------------------------------------------

BOOL APIENTRY
RasPhonebookDlgA(
    IN LPSTR lpszPhonebook,
    IN LPSTR lpszEntry,
    IN OUT LPRASPBDLGA lpInfo )

    // Win32 ANSI entrypoint that displays the Dial-Up Networking dialog, i.e.
    // the RAS phonebook.  'LpszPhonebook' is the full path the phonebook or
    // NULL indicating the default phonebook.  'LpszEntry' is the entry to
    // highlight on entry or NULL to highlight the first entry in the list.
    // 'LpInfo' is caller's additional input/output parameters.
    //
    // Returns true if user establishes a connection, false otherwise.
    //
{
    WCHAR* pszPhonebookW;
    WCHAR* pszEntryW;
    RASPBDLGW infoW;
    BOOL fStatus;

    TRACE( "RasPhonebookDlgA" );

    if (!lpInfo)
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    if (lpInfo->dwSize != sizeof(RASPBDLGA))
    {
        lpInfo->dwError = ERROR_INVALID_SIZE;
        return FALSE;
    }

    // Thunk "A" arguments to "W" arguments.
    //
    if (lpszPhonebook)
    {
        pszPhonebookW = StrDupTFromAUsingAnsiEncoding( lpszPhonebook );
        if (!pszPhonebookW)
        {
            lpInfo->dwError = ERROR_NOT_ENOUGH_MEMORY;
            return FALSE;
        }
    }
    else
    {
        pszPhonebookW = NULL;
    }

    if (lpszEntry)
    {
        pszEntryW = StrDupTFromAUsingAnsiEncoding( lpszEntry );
        if (!pszEntryW)
        {
            Free0( pszPhonebookW );
            lpInfo->dwError = ERROR_NOT_ENOUGH_MEMORY;
            return FALSE;
        }
    }
    else
    {
        pszEntryW = NULL;
    }

    // Take advantage of the structures currently having the same size and
    // layout.  Only the callback is different.
    //
    ASSERT( sizeof(RASPBDLGA) == sizeof(RASPBDLGW) );
    CopyMemory( &infoW, lpInfo, sizeof(infoW) );

    if (lpInfo->pCallback)
    {
        infoW.dwCallbackId = (ULONG_PTR)lpInfo;
        infoW.pCallback = RasPbDlgCallbackThunk;
    }

    infoW.reserved2 = lpInfo->reserved2;

    // Thunk to the equivalent "W" API.
    //
    fStatus = RasPhonebookDlgW( pszPhonebookW, pszEntryW, &infoW );

    Free0( pszPhonebookW );
    Free0( pszEntryW );

    return fStatus;
}


VOID WINAPI
RasPbDlgCallbackThunk(
    ULONG_PTR ulpId,
    DWORD dwEvent,
    LPWSTR pszEntry,
    LPVOID pArgs )

    // This thunks "W" callbacks to API caller's "A" callback.
    //
{
    CHAR* pszEntryA;
    VOID* pArgsA;
    RASPBDLGA* pInfo;
    RASNOUSERA nuA;

    if (dwEvent == RASPBDEVENT_NoUser || dwEvent == RASPBDEVENT_NoUserEdit)
    {
        RASNOUSERW* pnuW = (RASNOUSERW* )pArgs;
        ASSERT( pnuW );

        ZeroMemory( &nuA, sizeof(nuA) );
        nuA.dwSize = sizeof(nuA);
        nuA.dwFlags = pnuW->dwFlags;
        nuA.dwTimeoutMs = pnuW->dwTimeoutMs;

        StrCpyAFromW(nuA.szUserName, pnuW->szUserName, UNLEN + 1);
        StrCpyAFromW(nuA.szPassword, pnuW->szPassword, UNLEN + 1);
        StrCpyAFromW(nuA.szDomain, pnuW->szDomain, UNLEN + 1);
        
        pArgsA = &nuA;
    }
    else
    {
        pArgsA = NULL;
    }

    pszEntryA = StrDupAFromT( pszEntry );
    pInfo = (RASPBDLGA* )ulpId;
    pInfo->pCallback( pInfo->dwCallbackId, dwEvent, pszEntryA, pArgsA );
    Free0( pszEntryA );

    if (dwEvent == RASPBDEVENT_NoUser || dwEvent == RASPBDEVENT_NoUserEdit)
    {
        RASNOUSERW* pnuW = (RASNOUSERW* )pArgs;

        pnuW->dwFlags = nuA.dwFlags;
        pnuW->dwTimeoutMs = nuA.dwTimeoutMs;

        StrCpyWFromA(pnuW->szUserName, nuA.szUserName, UNLEN + 1);
        StrCpyWFromA(pnuW->szPassword, nuA.szPassword, UNLEN + 1);
        StrCpyWFromA(pnuW->szDomain, nuA.szDomain, UNLEN + 1);
        
        ZeroMemory( nuA.szPassword, PWLEN );
    }
}


BOOL APIENTRY
RasPhonebookDlgW(
    IN LPWSTR lpszPhonebook,
    IN LPWSTR lpszEntry,
    IN OUT LPRASPBDLGW lpInfo )

    // Win32 Unicode entrypoint that displays the Dial-Up Networking dialog,
    // i.e. the RAS phonebook.  'LpszPhonebook' is the full path the phonebook
    // or NULL indicating the default phonebook.  'LpszEntry' is the entry to
    // highlight on entry or NULL to highlight the first entry in the list.
    // 'LpInfo' is caller's additional input/output parameters.
    //
    // Returns true if user establishes a connection, false otherwise.
    //
{
    INT_PTR nStatus;
    DUARGS args;

    TRACE( "RasPhonebookDlgW" );

    if (!lpInfo)
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    if (lpInfo->dwSize != sizeof(RASPBDLGW))
    {
        lpInfo->dwError = ERROR_INVALID_SIZE;
        return FALSE;
    }

    // Initialize OUT parameters.
    //
    lpInfo->dwError = 0;

    // Initialize dialog argument block.
    //
    args.pszPhonebook = lpszPhonebook;
    args.pszEntry = lpszEntry;
    args.pApiArgs = lpInfo;
    args.fApiResult = FALSE;


    // Run the dialog.
    //
    nStatus =
        DialogBoxParam(
            g_hinstDll,
            MAKEINTRESOURCE( DID_DU_DialUpNetworking ),
            lpInfo->hwndOwner,
            DuDlgProc,
            (LPARAM )&args );

    if (nStatus == -1)
    {
        ErrorDlg( lpInfo->hwndOwner, SID_OP_LoadDlg, ERROR_UNKNOWN, NULL );
        lpInfo->dwError = ERROR_UNKNOWN;
        args.fApiResult = FALSE;
    }

    return args.fApiResult;
}


//----------------------------------------------------------------------------
// Dial-Up Networking dialog
// Listed alphabetically following dialog proc
//----------------------------------------------------------------------------

INT_PTR CALLBACK
DuDlgProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam )

    // DialogProc callback for the Dial-Up Networking dialog, i.e. the
    // phonebook dialog.  Parameters and return value are as described for
    // standard windows 'DialogProc's.
    //
{
#if 0
    TRACE4( "DuDlgProc(h=$%x,m=$%x,w=$%x,l=$%x)",
           (DWORD )hwnd, (DWORD )unMsg, (DWORD )wparam, (DWORD )lparam );
#endif

    switch (unMsg)
    {
        case WM_INITDIALOG:
        {
            return DuInit( hwnd, (DUARGS* )lparam );
        }

        case WM_HELP:
        case WM_CONTEXTMENU:
        {
            ContextHelp( g_adwDuHelp, hwnd, unMsg, wparam, lparam );
            return TRUE;
        }

        case WM_COMMAND:
        {
            DUINFO* pInfo = (DUINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            ASSERT( pInfo );

            return DuCommand(
                pInfo, HIWORD( wparam ), LOWORD( wparam ), (HWND )lparam );
        }

        case WM_RASEVENT:
        {
            DUINFO* pInfo = (DUINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            ASSERT( pInfo );

            DuUpdateConnectStatus( pInfo );
            break;
        }

        case WM_NOUSERTIMEOUT:
        {
            DUINFO* pInfo;
            ULONG ulCallbacksActive;

            TRACE( "CancelOwnedWindows" );
            CancelOwnedWindows( hwnd );
            TRACE( "CancelOwnedWindows done" );

            ulCallbacksActive = CallbacksActive( 1, NULL );
            if (ulCallbacksActive > 0)
            {
                TRACE1( "NoUser timeout stall, n=%d", ulCallbacksActive );
                PostMessage( hwnd, WM_NOUSERTIMEOUT, wparam, lparam );
                return TRUE;
            }

            pInfo = (DUINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            if (pInfo)
            {
                pInfo->pArgs->pApiArgs->dwError = STATUS_TIMEOUT;
            }

            EndDialog( hwnd, TRUE );
            CallbacksActive( 0, NULL );
            break;
        }

        case WM_DESTROY:
        {
            DuTerm( hwnd );

            /*
            //We have to wait for Deonb to return us the IID_Dun1 icon
            //For whistler bug 372078 381099
            //Icon returned by GetCurrentIconEntryType() has to be destroyed
            {
                HICON hIcon=NULL;

                //hIcon = (HICON) GetWindowLongPtr(hwnd, GWLP_USERDATA);
                hIcon = GetProp( hwnd, TEXT("TweakTitleBar_Icon"));
                ASSERT(hIcon);
                if( hIcon )
                {
                    DestroyIcon(hIcon);
                }
                else
                {
                    TRACE("DuDlgProc:Destroy Icon failed");
                }
            }
            */
            break;
        }
    }

    return FALSE;
}


BOOL
DuCommand(
    IN DUINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl )

    // Called on WM_COMMAND.  'PInfo' is the dialog context.  'WNotification'
    // is the notification code of the command.  'wId' is the control/menu
    // identifier of the command.  'HwndCtrl' is the control window handle of
    // the command.
    //
    // Returns true if processed message, false otherwise.
    //
{
    TRACE3( "DuCommand(n=%d,i=%d,c=$%x)",
        (DWORD )wNotification, (DWORD )wId, (ULONG_PTR )hwndCtrl );

    switch (wId)
    {
        case CID_DU_PB_Dial:
        {
            if (pInfo->hrasconn)
            {
                DuHangUpSelectedEntry( pInfo );
            }
            else
            {
                DuDialSelectedEntry( pInfo );
            }
            return TRUE;
        }

        case CID_DU_PB_New:
        {
            DuNewEntry( pInfo, FALSE );
            return TRUE;
        }

        case CID_DU_PB_More:
        {
            DuEditSelectedEntry( pInfo );
            return TRUE;
        }

        case CID_DU_LB_Entries:
        {
            if (wNotification == CBN_SELCHANGE)
            {
                PBENTRY *pEntry;
                DWORD  dwErr = SUCCESS;
                DUCONTEXT *pContext;

                pContext = (DUCONTEXT *)
                           ComboBox_GetItemDataPtr(
                                pInfo->hwndLbEntries,
                                ComboBox_GetCurSel(pInfo->hwndLbEntries));

                ASSERT(NULL != pContext);

                if(NULL == pContext)
                {
                    return TRUE;
                }

                //
                // Update the phonebook information
                //
                dwErr = DuGetEntry(pInfo, pContext);

                if(ERROR_SUCCESS == dwErr)
                {
                    ComboBox_SetItemData(
                            pInfo->hwndLbEntries,
                            ComboBox_GetCurSel(pInfo->hwndLbEntries),
                            pContext);
                }
                else
                {
                    ComboBox_DeleteString(
                            pInfo->hwndLbEntries,
                            ComboBox_GetCurSel(pInfo->hwndLbEntries) );
                }

                DuUpdateConnectStatus( pInfo );
                return TRUE;
            }
            break;
        }

        case IDCANCEL:
        case CID_DU_PB_Close:
        {
            EndDialog( pInfo->hwndDlg, TRUE );
            return TRUE;
        }
    }

    return FALSE;
}
    
VOID
DuDialSelectedEntry(
    IN DUINFO* pInfo )

    // Called when user presses the "Dial" button.
    //
{
    DWORD dwErr;
    BOOL fConnected;
    BOOL fAutoLogon;
    TCHAR* pszEbNumber;
    TCHAR* pszEbPreview;
    TCHAR* pszOrgPreview;
    TCHAR* pszOverride;
    TCHAR* pszEntryName;
    RASDIALDLG info;
    INTERNALARGS iargs;
    PBENTRY* pEntry;
    DTLNODE *pdtlnode;
    PBFILE file;
    DUCONTEXT *pContext;

    TRACE( "DuDialSelectedEntry" );

    // Look up the selected entry.
    //
    pContext = (DUCONTEXT *) ComboBox_GetItemDataPtr(
                                pInfo->hwndLbEntries,
                                ComboBox_GetCurSel(pInfo->hwndLbEntries));

    if (!pContext)
    {
        MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
        SetFocus( pInfo->hwndPbNew );
        return;
    }

    pEntry = pContext->pEntry;

    if (!pEntry)
    {
        MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
        SetFocus( pInfo->hwndPbNew );
        return;
    }


    pszOverride = NULL;
    pszOrgPreview = NULL;
    pszEbPreview = NULL;
    pszEbNumber = NULL;

    // Set up API argument block.
    //
    ZeroMemory( &info, sizeof(info) );
    info.dwSize = sizeof(info);
    info.hwndOwner = pInfo->hwndDlg;

    // The secret hack to share information already loaded with the entry API.
    //
    ZeroMemory( &iargs, sizeof(iargs) );
    iargs.pFile = &pInfo->file;
    iargs.pUser = &pInfo->user;
    iargs.pNoUser = pInfo->pNoUser;
    iargs.fNoUser = pInfo->fNoUser;
    iargs.fForceCloseOnDial =
        (pInfo->pArgs->pApiArgs->dwFlags & RASPBDFLAG_ForceCloseOnDial);

    iargs.pvEapInfo = NULL;

    if(0 != pInfo->pArgs->pApiArgs->reserved2)
    {
        DWORD retcode;
        EAPLOGONINFO *pEapInfo = NULL;

        retcode = DwGetEapLogonInfo(
                    (VOID *) pInfo->pArgs->pApiArgs->reserved2,
                    &pEapInfo);

        if(SUCCESS == retcode)
        {
            iargs.pvEapInfo = (VOID *) pEapInfo;
        }
    }

    iargs.fMoveOwnerOffDesktop =
        (iargs.fForceCloseOnDial || pInfo->user.fCloseOnDial);
    info.reserved = (ULONG_PTR ) &iargs;

    // Call the Win32 API to run the connect status dialog.  Make a copy of
    // the entry name and auto-logon flag first, because RasDialDlg may
    // re-read the entry node to pick up RASAPI changes.
    //
    pszEntryName = StrDup( pEntry->pszEntryName );
    fAutoLogon = pEntry->fAutoLogon;

    TRACEW1( "RasDialDlg,o=\"%s\"", (pszOverride) ? pszOverride : TEXT("") );
    fConnected = RasDialDlg(
        pContext->pszPhonebookPath, pEntry->pszEntryName, pszOverride, &info );
    TRACE1( "RasDialDlg=%d", fConnected );

    Free0( pszEbPreview );
    Free0( pszOrgPreview );

    if(NULL != iargs.pvEapInfo)
    {
        Free0(iargs.pvEapInfo);
        iargs.pvEapInfo = NULL;
    }

    if (fConnected)
    {
        pInfo->pArgs->fApiResult = TRUE;

        if (pInfo->pArgs->pApiArgs->pCallback)
        {
            RASPBDLGFUNCW pfunc = pInfo->pArgs->pApiArgs->pCallback;

            if (pInfo->pNoUser && iargs.fNoUserChanged && fAutoLogon)
            {
                // Whistler bug 254385 encode password when not being used
                // Need to Decode password before callback function
                // Assumed password was encoded previously by DuInit()
                //
                DecodePassword( pInfo->pNoUser->szPassword );
                TRACE( "Callback(NoUserEdit)" );
                pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
                    RASPBDEVENT_NoUserEdit, NULL, pInfo->pNoUser );
                TRACE( "Callback(NoUserEdit) done" );
                EncodePassword( pInfo->pNoUser->szPassword );
            }

            TRACE( "Callback(DialEntry)" );
            pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
                RASPBDEVENT_DialEntry, pszEntryName, NULL );
            TRACE( "Callback(DialEntry) done" );
        }

        if (pInfo->user.fCloseOnDial
            || (pInfo->pArgs->pApiArgs->dwFlags & RASPBDFLAG_ForceCloseOnDial))
        {
            EndDialog( pInfo->hwndDlg, TRUE );
        }
    }

    else
    {
        DuHandleConnectFailure(pInfo, &info);
    }

    if (pInfo->pNoUser && !pInfo->hThread)
    {
        TRACE( "Taking shortcut to exit" );
        return;
    }

    // Reload the list even if the Dial was cancelled as user may have changed
    // the current PBENTRY with the Properties button on the dialer which
    // commits changes even if user cancels the dial itself.  See bug 363710.
    //
    DuUpdateLbEntries( pInfo, pszEntryName );
    SetFocus( pInfo->hwndLbEntries );

    Free0( pszEntryName );
}


VOID
DuEditSelectedEntry(
    IN DUINFO* pInfo )

    // Called when user selects "Edit entry" from the menu.  'PInfo' is the
    // dialog context.  'PszEntry' is the name of the entry to edit.
    //
{
    BOOL fOk;
    RASENTRYDLG info;
    INTERNALARGS iargs;
    PBENTRY* pEntry;
    LPTSTR pszEntryName;
    DTLNODE *pdtlnode;
    PBFILE file;
    DWORD dwErr;
    DUCONTEXT *pContext;
    INT iSel;

    TRACE( "DuEditSelectedEntry" );

    // Look up the selected entry.
    //
    iSel = ComboBox_GetCurSel( pInfo->hwndLbEntries );
    if (iSel < 0)
    {
        return;
    }

    pContext = (DUCONTEXT * )ComboBox_GetItemDataPtr(
        pInfo->hwndLbEntries, iSel );

    ASSERT(NULL != pContext);

    if(NULL == pContext)
    {
        return;
    }
    
    ASSERT(NULL != pContext->pszPhonebookPath);

    pEntry = pContext->pEntry;

    if (!pEntry)
    {
        MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
        SetFocus( pInfo->hwndPbNew );
        return;
    }

    // Set up API argument block.
    //
    ZeroMemory( &info, sizeof(info) );
    info.dwSize = sizeof(info);
    info.hwndOwner = pInfo->hwndDlg;

    {
        RECT rect;

        info.dwFlags = RASEDFLAG_PositionDlg;
        GetWindowRect( pInfo->hwndDlg, &rect );
        info.xDlg = rect.left + DXSHEET;
        info.yDlg = rect.top + DYSHEET;
    }

    // The secret hack to share information already loaded with the entry API.
    //
    ZeroMemory( &iargs, sizeof(iargs) );
    iargs.pFile = &pInfo->file;
    iargs.pUser = &pInfo->user;
    iargs.pNoUser = pInfo->pNoUser;
    iargs.fNoUser = pInfo->fNoUser;
    info.reserved = (ULONG_PTR ) &iargs;

    // Call the Win32 API to run the entry property sheet.
    //
    TRACE( "RasEntryDlg" );
    fOk = RasEntryDlg(
              pContext->pszPhonebookPath, pEntry->pszEntryName, &info );
    TRACE1( "RasEntryDlg=%d", fOk );

    if (pInfo->pNoUser && !pInfo->hThread)
    {
        TRACE( "Taking shortcut to exit" );
        return;
    }

    if (fOk)
    {
        TRACEW1( "OK pressed,e=\"%s\"", info.szEntry );

        if (pInfo->pArgs->pApiArgs->pCallback)
        {
            RASPBDLGFUNCW pfunc = pInfo->pArgs->pApiArgs->pCallback;

            TRACE( "Callback(EditEntry)" );
            pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
                RASPBDEVENT_AddEntry, info.szEntry, NULL );
            TRACE( "Callback(EditEntry) done" );
        }

        DuUpdateLbEntries( pInfo, info.szEntry );
        SetFocus( pInfo->hwndLbEntries );
    }
    else
    {
        TRACE( "Cancel pressed or error" );
    }
}

// 
// Helper function called by DuDialSelectedEntry to handle errors
// returned from RasDialDlgW
//
DWORD
DuHandleConnectFailure(
    IN DUINFO* pInfo,
    IN RASDIALDLG* pDialInfo)
{
    TRACE3( 
        "DuHandleConnectFailure: nu=%x, r2=%x, de=%x",
        (pInfo->pNoUser),
        (pInfo->pArgs->pApiArgs->reserved2),
        (pDialInfo->dwError));
        
    // XP: 384968
    //
    // Handle the bad-PIN error from winlogon
    //
    // Normally, the smart card PIN is gotten by calling EAP-TLS's identity
    // api.  This API raises UI and validates the PIN entered.
    //
    // During winlogon, however, the smart card PIN is passed to us from GINA.
    // In this case it is not validated until we call EAP API's.  (actually, 
    // it's until we call the eap identity api with RAS_EAP_FLAG_LOGON.
    // This flag tells EAP not to raise any UI but instead to use the info 
    // passed from GINA)
    //
    // GINA is not able to validate the PIN itself because it does not call any
    // CAPI's directly.  Oh well.
    //
    // If RasDialDlg returns a bad pin error, then we should gracefully fail 
    // back to winlogon.
    //

    if ((pInfo->pNoUser)                        &&  // called by winlogon
        (pInfo->pArgs->pApiArgs->reserved2)     &&  // for smart card
        (RAS_SC_IS_BAD_PIN(pDialInfo->dwError)))    // but pin is bad
    {
        pInfo->pArgs->pApiArgs->dwError = pDialInfo->dwError;
        EndDialog( pInfo->hwndDlg, TRUE );
    }

    return NO_ERROR;
}

VOID
DuHangUpSelectedEntry(
    IN DUINFO* pInfo )

    // Hang up the selected entry after confirming with user.  'Pinfo' is the
    // dialog context block.
    //
{
    DWORD dwErr;
    PBENTRY* pEntry;
    INT iSel;
    INT nResponse;
    MSGARGS msgargs;
    LPTSTR pszEntryName;
    DTLNODE *pdtlnode;
    DUCONTEXT *pContext;

    TRACE( "DuHangUpSelectedEntry" );

    // Look up the selected entry.
    //
    iSel = ComboBox_GetCurSel( pInfo->hwndLbEntries );
    ASSERT( iSel >= 0 );
    pContext = (DUCONTEXT * )ComboBox_GetItemDataPtr( pInfo->hwndLbEntries, iSel );

    ASSERT(NULL != pContext);
    
    if (!pContext)
    {
        MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
        SetFocus( pInfo->hwndPbNew );
        return;
    }
    
    pEntry = pContext->pEntry;
    ASSERT( pEntry );

    if (!pEntry)
    {
        MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
        SetFocus( pInfo->hwndPbNew );
        return;
    }

    ZeroMemory( &msgargs, sizeof(msgargs) );
    msgargs.apszArgs[ 0 ] = pEntry->pszEntryName;
    msgargs.dwFlags = MB_YESNO | MB_ICONEXCLAMATION;
    nResponse = MsgDlg( pInfo->hwndDlg, SID_ConfirmHangUp, &msgargs );

    if (nResponse == IDYES)
    {
        ASSERT( g_pRasHangUp );
        TRACE( "RasHangUp" );
        dwErr = g_pRasHangUp( pInfo->hrasconn );
        TRACE1( "RasHangUp=%d", dwErr );
        if ( dwErr == ERROR_HANGUP_FAILED )
        {
            MsgDlg( pInfo->hwndDlg, SID_CantHangUpRouter, NULL );
        }
    }
}


BOOL
DuInit(
    IN HWND    hwndDlg,
    IN DUARGS* pArgs )

    // Called on WM_INITDIALOG.  'hwndDlg' is the handle of the phonebook
    // dialog window.  'pArgs' points at caller's arguments as passed to the
    // API (or thunk).
    //
    // Return false if focus was set, true otherwise, i.e. as defined for
    // WM_INITDIALOG.
    //
{
    DWORD dwErr;
    DWORD dwThreadId;
    DWORD dwReadPbkFlags = 0;
    DUINFO* pInfo;

    TRACE( "DuInit" );

    // Allocate the dialog context block.  Initialize minimally for proper
    // cleanup, then attach to the dialog window.
    //
    {
        pInfo = Malloc( sizeof(*pInfo) );
        if (!pInfo)
        {
            ErrorDlg( hwndDlg, SID_OP_LoadDlg, ERROR_NOT_ENOUGH_MEMORY, NULL );
            pArgs->pApiArgs->dwError = ERROR_NOT_ENOUGH_MEMORY;
            EndDialog( hwndDlg, TRUE );
            return TRUE;
        }

        ZeroMemory( pInfo, sizeof(*pInfo) );
        pInfo->file.hrasfile = -1;

        SetWindowLongPtr( hwndDlg, DWLP_USER, (ULONG_PTR )pInfo );
        TRACE( "Context set" );
    }

    pInfo->pArgs = pArgs;
    pInfo->hwndDlg = hwndDlg;

    // Position the dialog per caller's instructions.
    //
    PositionDlg( hwndDlg,
        pArgs->pApiArgs->dwFlags & RASPBDFLAG_PositionDlg,
        pArgs->pApiArgs->xDlg, pArgs->pApiArgs->yDlg );

    // Load RAS DLL entrypoints which starts RASMAN, if necessary.  There must
    // be no API calls that require RASAPI32 or RASMAN prior to this point.
    //
    dwErr = LoadRas( g_hinstDll, hwndDlg );
    if (dwErr != 0)
    {
        ErrorDlg( hwndDlg, SID_OP_LoadRas, dwErr, NULL );
        pArgs->pApiArgs->dwError = dwErr;
        EndDialog( hwndDlg, TRUE );
        return TRUE;
    }


    if(0 != (pArgs->pApiArgs->dwFlags & RASPBDFLAG_NoUser))
    {
        // Popup TAPI's "first location" dialog if they are uninitialized.
        //
        dwErr = TapiNoLocationDlg( g_hinstDll, &pInfo->hlineapp, hwndDlg );
        if (dwErr != 0)
        {
            // Error here is treated as a "cancel" per bug 288385.
            //
            pArgs->pApiArgs->dwError = 0;
            EndDialog( hwndDlg, TRUE );
            return TRUE;
        }
    }

    pInfo->hwndLbEntries = GetDlgItem( hwndDlg, CID_DU_LB_Entries );
    ASSERT( pInfo->hwndLbEntries );
    pInfo->hwndPbDial = GetDlgItem( hwndDlg, CID_DU_PB_Dial );
    ASSERT( pInfo->hwndPbDial );
    pInfo->hwndPbNew = GetDlgItem( hwndDlg, CID_DU_PB_New );
    ASSERT( pInfo->hwndPbNew );
    pInfo->hwndPbProperties = GetDlgItem( hwndDlg, CID_DU_PB_More );
    ASSERT( pInfo->hwndPbProperties );

    pInfo->fNoUser = (pArgs->pApiArgs->dwFlags & RASPBDFLAG_NoUser );

    // Setting this global flag indicates that WinHelp will not work in the
    // current mode.  See common\uiutil\ui.c.  We assume here that only the
    // WinLogon process makes use of this.
    //
    {
        extern BOOL g_fNoWinHelp;
        g_fNoWinHelp = pInfo->fNoUser;
    }

    // Read user preferences from registry.
    //
    dwErr = g_pGetUserPreferences(
        NULL, &pInfo->user, pInfo->fNoUser ? UPM_Logon : UPM_Normal);
    if (dwErr != 0)
    {
        //
        // The following free causes a crash in DuTerm. This context will be
        // freed in DuTerm - raos.
        //
        // Free( pInfo );
        ErrorDlg( hwndDlg, SID_OP_LoadPrefs, dwErr, NULL );
        EndDialog( hwndDlg, TRUE );
        return TRUE;
    }

    // Load and parse phonebook file.
    //
    if (pInfo->fNoUser)
    {
        dwReadPbkFlags |= RPBF_NoUser;
    }
    dwErr = ReadPhonebookFile(
                pArgs->pszPhonebook,
                &pInfo->user,
                NULL,
                dwReadPbkFlags,
                &pInfo->file );
    if (dwErr != 0)
    {
        // The following free causes a crash in DuTerm. This context will be
        // freed in DuTerm - raos.
        //
        // Free( pInfo );
        ErrorDlg( hwndDlg, SID_OP_LoadPhonebook, dwErr, NULL );
        EndDialog( hwndDlg, TRUE );
        return TRUE;
    }

    if (pArgs->pApiArgs->pCallback && !pArgs->pszPhonebook)
    {
        RASPBDLGFUNCW pfunc = pInfo->pArgs->pApiArgs->pCallback;

        // Tell user the path to the default phonebook file.
        //
        TRACE( "Callback(EditGlobals)" );
        pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
            RASPBDEVENT_EditGlobals, pInfo->file.pszPath, NULL );
        TRACE( "Callback(EditGlobals) done" );
    }

    if (pInfo->fNoUser)
    {
        // Retrieve logon information from caller via callback.
        //
        if (pArgs->pApiArgs->pCallback)
        {
            RASPBDLGFUNCW pfunc = pArgs->pApiArgs->pCallback;

            pInfo->pNoUser = Malloc( sizeof(RASNOUSERW) );
            if (pInfo->pNoUser)
            {
                ZeroMemory( pInfo->pNoUser, sizeof(*pInfo->pNoUser) );
                pInfo->pNoUser->dwSize = sizeof(*pInfo->pNoUser);

                TRACE( "Callback(NoUser)" );
                pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
                    RASPBDEVENT_NoUser, NULL, pInfo->pNoUser );
                TRACE1( "Callback(NoUser) done,to=%d",
                    pInfo->pNoUser->dwTimeoutMs );
                TRACEW1( "U=%s",pInfo->pNoUser->szUserName );
                TRACEW1( "D=%s",pInfo->pNoUser->szDomain );

                // Whistler bug 254385 encode password when not being used
                // Assumed password was not encoded during callback
                //
                EncodePassword( pInfo->pNoUser->szPassword );

                // Install input detection hooks.
                //
                if (pInfo->pNoUser->dwTimeoutMs > 0)
                {
                    pInfo->hhookMouse = SetWindowsHookEx(
                        WH_MOUSE, DuInputHook, g_hinstDll,
                        GetCurrentThreadId() );

                    pInfo->hhookKeyboard = SetWindowsHookEx(
                        WH_KEYBOARD, DuInputHook, g_hinstDll,
                        GetCurrentThreadId() );
                }
            }
        }

        if (!pInfo->user.fAllowLogonPhonebookEdits)
        {
            // Disable new button.  See also similar logic for the Properties
            // button occurs in DuUpdateLbEntries.
            //
            EnableWindow( pInfo->hwndPbNew, FALSE );
        }
    }

    // Load the list of phonebook entries and set selection.
    //
    DuUpdateLbEntries( pInfo, pInfo->pArgs->pszEntry );

    if (!pInfo->pArgs->pszEntry)
    {
        if (ComboBox_GetCount( pInfo->hwndLbEntries ) > 0)
        {
            ComboBox_SetCurSelNotify( pInfo->hwndLbEntries, 0 );
        }
    }

    // Update the title to reflect the phonebook mode.
    //
    DuUpdateTitle( pInfo );

    // Adjust the title bar widgets and create the wizard bitmap.
    //
    TweakTitleBar( hwndDlg );
    AddContextHelpButton( hwndDlg );

    // Start the connect monitor.
    //
    if ((pInfo->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ))
        && (pInfo->hThread = CreateThread(
                NULL, 0, DuMonitorThread, (LPVOID )pInfo, 0,
                (LPDWORD )&dwThreadId )))
    {
        ASSERT( g_pRasConnectionNotification );
        TRACE( "RasConnectionNotification" );
        dwErr = g_pRasConnectionNotification(
            INVALID_HANDLE_VALUE, pInfo->hEvent,
            RASCN_Connection | RASCN_Disconnection );
        TRACE1( "RasConnectionNotification=%d", dwErr );
    }
    else
        TRACE( "Monitor DOA" );

    if (ComboBox_GetCount( pInfo->hwndLbEntries ) == 0)
    {
        // The phonebook is empty.
        //
        if (pInfo->fNoUser
            && !pInfo->user.fAllowLogonPhonebookEdits
            )
        {
            // Tell the user you can't create an entry or locations during
            // startup.
            //
            MsgDlg( hwndDlg, SID_EmptyLogonPb, NULL );
            EndDialog( hwndDlg, TRUE );
            return TRUE;
        }
        else
        {
            if(pInfo->fNoUser)
            {
                dwErr = TapiNoLocationDlg( g_hinstDll, 
                                           &pInfo->hlineapp, hwndDlg );
                if (dwErr != 0)
                {
                    // Error here is treated as a "cancel" per bug 288385.
                    //
                    pArgs->pApiArgs->dwError = 0;
                    EndDialog( hwndDlg, TRUE );
                    return TRUE;
                }
            }
        
            // Tell the user, then automatically start him into adding a new
            // entry.  Set initial focus to "New" button first, in case user
            // cancels out.
            //
            SetFocus( pInfo->hwndPbNew );
            MsgDlg( hwndDlg, SID_EmptyPhonebook, NULL );
            DuNewEntry( pInfo, FALSE );
        }
    }
    else
    {
        // Set initial focus to the non-empty entry listbox.
        //
        SetFocus( pInfo->hwndLbEntries );
    }

    return FALSE;
}


LRESULT CALLBACK
DuInputHook(
    IN int    nCode,
    IN WPARAM wparam,
    IN LPARAM lparam )

    // Standard Win32 'MouseProc' or 'KeyboardProc' callback.  For our simple
    // processing we can take advantage of them having identical arguments and
    // 'nCode' definitions.
    //
{
    if (nCode == HC_ACTION)
    {
        ++g_cInput;
    }
    return 0;
}

VOID
DuNewEntry(
    IN DUINFO* pInfo,
    IN BOOL fClone )

    // Called when user presses the "New" button or "Clone" menu item.
    // 'PInfo' is the dialog context.  'FClone' is set to clone the selected
    // entry, otherwise an empty entry is created.
    //
{
    BOOL fOk;
    TCHAR* pszEntry;
    RASENTRYDLG info;
    INTERNALARGS iargs;
    PBENTRY* pEntry;

    TRACE1( "DuNewEntry(f=%d)", fClone );

    ZeroMemory( &info, sizeof(info) );
    info.dwSize = sizeof(info);
    info.hwndOwner = pInfo->hwndDlg;

    if (fClone)
    {
        DUCONTEXT *pContext;

        // Look up the selected entry.
        //
        pContext = (DUCONTEXT* )ComboBox_GetItemDataPtr(
            pInfo->hwndLbEntries, ComboBox_GetCurSel( pInfo->hwndLbEntries ) );

        if (!pContext)
        {
            MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
            SetFocus( pInfo->hwndPbNew );
            return;
        }

        pEntry = pContext->pEntry;

        if (!pEntry)
        {
            MsgDlg( pInfo->hwndDlg, SID_NoEntrySelected, NULL );
            SetFocus( pInfo->hwndPbNew );
            return;
        }

        pszEntry = pEntry->pszEntryName;
        info.dwFlags = RASEDFLAG_CloneEntry;
    }
    else
    {
        pszEntry = NULL;
        info.dwFlags = RASEDFLAG_NewEntry;
    }

    {
        RECT rect;

        GetWindowRect( pInfo->hwndDlg, &rect );
        info.dwFlags += RASEDFLAG_PositionDlg;
        info.xDlg = rect.left + DXSHEET;
        info.yDlg = rect.top + DYSHEET;
    }

    // The secret hack to share information already loaded with the entry API.
    //
    ZeroMemory( &iargs, sizeof(iargs) );
    iargs.pFile = &pInfo->file;
    iargs.pUser = &pInfo->user;
    iargs.pNoUser = pInfo->pNoUser;
    iargs.fNoUser = pInfo->fNoUser;
    info.reserved = (ULONG_PTR ) &iargs;

    // Call the Win32 API to run the add entry wizard.
    //
    TRACE( "RasEntryDlg" );
    fOk = RasEntryDlg( pInfo->pArgs->pszPhonebook, pszEntry, &info );
    TRACE1( "RasEntryDlg=%d", fOk );

    if (pInfo->pNoUser && !pInfo->hThread)
    {
        TRACE( "Taking shortcut to exit" );
        return;
    }

    if (fOk)
    {
        TRACEW1( "OK pressed, e=\"%s\"", info.szEntry );

        if (pInfo->pArgs->pApiArgs->pCallback)
        {
            RASPBDLGFUNCW pfunc = pInfo->pArgs->pApiArgs->pCallback;

            TRACE( "Callback(AddEntry)" );
            pfunc( pInfo->pArgs->pApiArgs->dwCallbackId,
                RASPBDEVENT_AddEntry, info.szEntry, NULL );
            TRACE( "Callback(AddEntry) done" );
        }

        DuUpdateLbEntries( pInfo, info.szEntry );
        Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbDial );
        SetFocus( pInfo->hwndLbEntries );
    }
    else
    {
        TRACE( "Cancel pressed or error" );
    }
}

VOID
DuUpdateConnectStatus(
    IN DUINFO* pInfo )

    // Called to update connect status of the selected entry and the text of
    // the Dial/HangUp button.  'PInfo' is the dialog context block.
    //
{
    TCHAR* pszPhonebook;
    TCHAR* pszEntry;
    INT iSel;
    TCHAR* psz;
    DUCONTEXT *pContext;

    TRACE( "DuUpdateConnectStatus" );

    // pszPhonebook = pInfo->file.pszPath;
    iSel = ComboBox_GetCurSel( pInfo->hwndLbEntries );
    if (iSel < 0)
    {
        return;
    }

    pContext = (DUCONTEXT *) ComboBox_GetItemDataPtr(
                                pInfo->hwndLbEntries,
                                iSel);

    ASSERT(NULL != pContext);

    pszEntry = ComboBox_GetPsz( pInfo->hwndLbEntries, iSel );
    pInfo->hrasconn = HrasconnFromEntry(
                                    pContext->pszPhonebookPath,
                                    pszEntry );

    psz = PszFromId( g_hinstDll,
              (pInfo->hrasconn) ? SID_DU_HangUp : SID_DU_Dial );
    if (psz)
    {
        SetWindowText( pInfo->hwndPbDial, psz );
        Free( psz );
    }
}


VOID
DuUpdateLbEntries(
    IN DUINFO* pInfo,
    IN TCHAR* pszEntry )

    // Update the contents of the entry listbox and set the selection to
    // 'pszEntry'.  If there are entries the Properties button is enabled,
    // otherwise it is disabled.  'PInfo' is the dialog context.
    //
{
    DTLNODE* pNode;
    RASENTRYNAME *pRasEntryNames = NULL;
    DWORD cEntries = 0;
    DWORD cb;
    DWORD dwErr;
    DWORD i;
    RASENTRYNAME ren;
    DUCONTEXT *pContext;
    INT iSel;

    TRACE( "DuUpdateLbEntries" );

    iSel = -1;
    ComboBox_ResetContent( pInfo->hwndLbEntries );

    cb = ren.dwSize = sizeof(RASENTRYNAME);

    //
    // Enumerate entries across all phonebooks. Fix for bug 206467
    //
    dwErr = g_pRasEnumEntries(NULL,
                              pInfo->pArgs->pszPhonebook,
                              &ren,
                              &cb,
                              &cEntries);

    if(     (   (ERROR_BUFFER_TOO_SMALL == dwErr)
            ||  (SUCCESS == dwErr))
        &&  (cb >= sizeof(RASENTRYNAME)))
    {
        pRasEntryNames = (RASENTRYNAME *) Malloc(cb);

        if(NULL == pRasEntryNames)
        {
            // Nothing else can be done in this case
            //
            goto done;
        }

        pRasEntryNames->dwSize = sizeof(RASENTRYNAME);

        dwErr = g_pRasEnumEntries(NULL,
                                  pInfo->pArgs->pszPhonebook,
                                  pRasEntryNames,
                                  &cb,
                                  &cEntries);

        if(dwErr)
        {
            goto done;
        }
    }
    else
    {
        goto done;
    }


    for(i = 0; i < cEntries; i++)
    {
        pContext = (DUCONTEXT *) Malloc(sizeof(DUCONTEXT));

        if(NULL == pContext)
        {
            dwErr = GetLastError();
            goto done;
        }

        ZeroMemory(pContext, sizeof(DUCONTEXT));

        pContext->pszPhonebookPath = 
                            StrDup(
                                pRasEntryNames[i].szPhonebookPath
                                );
                                
        ComboBox_AddItem(pInfo->hwndLbEntries,
                         pRasEntryNames[i].szEntryName,
                         pContext);


    }

    if (ComboBox_GetCount( pInfo->hwndLbEntries ) >= 0)
    {
        if (pszEntry)
        {
            // Select entry specified by API caller.
            //
            iSel = ComboBox_FindStringExact(
                pInfo->hwndLbEntries, -1, pszEntry );
        }

        if (iSel < 0)
        {
            // Entry not found so default to first item selected.
            //
            iSel = 0;
        }

        if(ComboBox_GetCount(pInfo->hwndLbEntries) > 0)
        {
            ComboBox_SetCurSelNotify( pInfo->hwndLbEntries, iSel );
        }
    }

done:

    // Enable/disable Properties button based on existence of an entry.  See
    // bug 313037.
    //
    if (ComboBox_GetCurSel( pInfo->hwndLbEntries ) >= 0
        && (!pInfo->fNoUser || pInfo->user.fAllowLogonPhonebookEdits))
    {
        EnableWindow( pInfo->hwndPbProperties, TRUE );
    }
    else
    {
        if (GetFocus() == pInfo->hwndPbProperties)
        {
            SetFocus( pInfo->hwndPbDial );
        }

        EnableWindow( pInfo->hwndPbProperties, FALSE );
    }

    ComboBox_AutoSizeDroppedWidth( pInfo->hwndLbEntries );
    Free0(pRasEntryNames);
}

VOID
DuUpdateTitle(
    IN DUINFO* pInfo )

    // Called to update the dialog title to reflect the current phonebook.
    // 'PInfo' is the dialog context.
    //
{
    TCHAR szBuf[ 256 ];
    TCHAR* psz;

    psz = PszFromId( g_hinstDll, SID_PopupTitle );
    if (psz)
    {
        lstrcpyn( szBuf, psz, sizeof(szBuf) / sizeof(TCHAR) );
        Free( psz );
    }
    else
    {
        *szBuf = TEXT('0');
    }

    if (pInfo->pArgs->pszPhonebook
        || pInfo->user.dwPhonebookMode != PBM_System)
    {
        INT iSel;

        iSel = ComboBox_GetCurSel(pInfo->hwndLbEntries);
        if (iSel >= 0)
        {
            DUCONTEXT *pContext;

            pContext = (DUCONTEXT *) ComboBox_GetItemDataPtr(
                pInfo->hwndLbEntries, iSel);

            ASSERT( pContext );

            if(NULL != pContext)
            {
                lstrcat( szBuf, TEXT(" - ") );
                lstrcat( szBuf, StripPath( pContext->pszPhonebookPath ) );
            }
        }
    }

    SetWindowText( pInfo->hwndDlg, szBuf );
}


VOID
DuTerm(
    IN HWND hwndDlg )

    // Called on WM_DESTROY.  'HwndDlg' is that handle of the dialog window.
    //
{
    DUINFO* pInfo;

    DWORD i;
    DWORD cEntries;

    TRACE( "DuTerm" );

    pInfo = (DUINFO* )GetWindowLongPtr( hwndDlg, DWLP_USER );
    if (pInfo)
    {
        // Close ReceiveMonitorThread resources.
        //
        if (pInfo->hThread)
        {
            TRACE( "Set abort event" );

            // Tell thread to wake up and quit...
            //
            pInfo->fAbortMonitor = TRUE;
            CloseHandle( pInfo->hThread );

            // Don't SetEvent before closing the thread handle.  On
            // multi-proc systems, the thread will exit so fast (and
            // set hThread to NULL) that CloseHandle will then close
            // an invalid handle.
            //
            SetEvent( pInfo->hEvent );

            // ...and wait for that to happen.  A message API (such as
            // PeekMessage) must be called to prevent the thread-to-thread
            // SendMessage in the thread from blocking.
            //
            {
                MSG msg;

                TRACE( "Termination spin..." );
                for (;;)
                {
                    PeekMessage( &msg, hwndDlg, 0, 0, PM_NOREMOVE );
                    if (!pInfo->hThread)
                    {
                        break;
                    }
                    Sleep( 500L );
                }
                TRACE( "Termination spin ends" );
            }
        }

        if (pInfo->hEvent)
        {
            CloseHandle( pInfo->hEvent );
        }

        if (pInfo->pNoUser)
        {
            // Don't leave caller's password floating around in memory.
            //
            ZeroMemory( pInfo->pNoUser->szPassword, PWLEN * sizeof(TCHAR) );
            Free( pInfo->pNoUser );

            // Uninstall input event hooks.
            //
            if (pInfo->hhookMouse)
            {
                UnhookWindowsHookEx( pInfo->hhookMouse );
            }
            if (pInfo->hhookKeyboard)
            {
                UnhookWindowsHookEx( pInfo->hhookKeyboard );
            }
        }
        else if ((pInfo->pArgs->pApiArgs->dwFlags & RASPBDFLAG_UpdateDefaults)
                 && pInfo->hwndLbEntries && pInfo->user.fInitialized)
        {
            INT iSel;
            RECT rect;

            // Caller said to update default settings so save the name of the
            // selected entry and the current window position.
            //
            iSel = ComboBox_GetCurSel( pInfo->hwndLbEntries );
            if (iSel >= 0)
            {
                DUCONTEXT *pContext;
                PBENTRY* pEntry;

                pContext = (DUCONTEXT* )ComboBox_GetItemDataPtr(
                    pInfo->hwndLbEntries, iSel );

                if(     (NULL != pContext)
                    &&  (NULL != (pEntry = pContext->pEntry)))
                {
                    Free0( pInfo->user.pszDefaultEntry );
                    pInfo->user.pszDefaultEntry =
                        StrDup( pEntry->pszEntryName );
                }
            }

            if (!SetOffDesktop( pInfo->hwndDlg, SOD_GetOrgRect, &rect ))
            {
                GetWindowRect( pInfo->hwndDlg, &rect );
            }
            pInfo->user.dwXPhonebook = rect.left;
            pInfo->user.dwYPhonebook = rect.top;

            pInfo->user.fDirty = TRUE;
            g_pSetUserPreferences(
                NULL, &pInfo->user, pInfo->fNoUser ? UPM_Logon : UPM_Normal );
        }

        if(NULL != pInfo->hwndLbEntries)
        {
            DUCONTEXT *pContext;

            cEntries = ComboBox_GetCount(pInfo->hwndLbEntries);

            //
            // Free the context stored in the list box
            //
            for(i = 0; i < cEntries; i++)
            {
                pContext = ComboBox_GetItemDataPtr(
                                pInfo->hwndLbEntries, i);

                if(NULL != pContext)
                {
                    Free0(pContext->pszPhonebookPath);
                }

                Free0(pContext);
            }
        }

        TapiShutdown( pInfo->hlineapp );
        ClosePhonebookFile( &pInfo->file );
        DestroyUserPreferences( &pInfo->user );
        Free( pInfo );
    }
}

DWORD
DuMonitorThread(
    LPVOID pThreadArg )

    // The "main" of the "connect monitor" thread.  This thread simply
    // converts Win32 RasConnectionNotification events int WM_RASEVENT style
    // notfications.
    //
{
    DUINFO* pInfo;
    DWORD dwErr;
    DWORD dwTimeoutMs;
    DWORD dwQuitTick;
    DWORD cInput = 0;

    TRACE( "DuMonitor starting" );

    pInfo = (DUINFO* )pThreadArg;

    if (pInfo->pNoUser && pInfo->pNoUser->dwTimeoutMs != 0)
    {
        TRACE( "DuMonitor quit timer set" );
        dwTimeoutMs = 5000L;
        dwQuitTick = GetTickCount() + pInfo->pNoUser->dwTimeoutMs;
        cInput = g_cInput;
    }
    else
    {
        dwTimeoutMs = INFINITE;
        dwQuitTick = 0;
    }

    // Trigger the event so the other thread has the correct state as of the
    // monitor starting.
    //
    SetEvent( pInfo->hEvent );

    for (;;)
    {
        dwErr = WaitForSingleObject( pInfo->hEvent, dwTimeoutMs );

        if (pInfo->fAbortMonitor)
        {
            break;
        }

        if (dwErr == WAIT_TIMEOUT)
        {
            if (g_cInput > cInput)
            {
                TRACE( "Input restarts timer" );
                cInput = g_cInput;
                dwQuitTick = GetTickCount() + pInfo->pNoUser->dwTimeoutMs;
            }
            else if (GetTickCount() >= dwQuitTick)
            {
                TRACE( "/DuMonitor SendMessage(WM_NOUSERTIMEOUT)" );
                SendMessage( pInfo->hwndDlg, WM_NOUSERTIMEOUT, 0, 0 );
                TRACE( "\\DuMonitor SendMessage(WM_NOUSERTIMEOUT) done" );
                break;
            }
        }
        else
        {
            TRACE( "/DuMonitor SendMessage(WM_RASEVENT)" );
            SendMessage( pInfo->hwndDlg, WM_RASEVENT, 0, 0 );
            TRACE( "\\DuMonitor SendMessage(WM_RASEVENT) done" );
        }
    }

    // This clues the other thread that all interesting work has been done.
    //
    pInfo->hThread = NULL;

    TRACE( "DuMonitor terminating" );
    return 0;
}


DWORD
DuGetEntry(
    DUINFO* pInfo,
    DUCONTEXT* pContext )
{
    DWORD dwErr = ERROR_SUCCESS;
    DWORD dwReadPbkFlags = 0;
    LPTSTR pszEntryName;
    DTLNODE *pdtlnode;
    PBFILE file;

    ASSERT(NULL != pContext);

    pContext->pEntry = NULL;

    pszEntryName = ComboBox_GetPsz(pInfo->hwndLbEntries,
                                   ComboBox_GetCurSel(pInfo->hwndLbEntries));

    if (pInfo->fNoUser)
    {
        dwReadPbkFlags |= RPBF_NoUser;
    }

    if(     (NULL != pInfo->file.pszPath)
        &&  (0 == lstrcmpi(pContext->pszPhonebookPath,
                     pInfo->file.pszPath)))
    {
        //
        // We already have the phonebook file open
        //
        pdtlnode = EntryNodeFromName(
                        pInfo->file.pdtllistEntries,
                        pszEntryName);

        ASSERT(NULL != pdtlnode);
    }
    else
    {
        //
        // phonebook file changed. So close the existing phone
        // book file and open the one in  which the entry
        // belongs
        //
        if(NULL != pInfo->file.pszPath)
        {
            ClosePhonebookFile(&pInfo->file);
        }

        dwErr = GetPbkAndEntryName(pContext->pszPhonebookPath,
                                   pszEntryName,
                                   dwReadPbkFlags,
                                   &file,
                                   &pdtlnode);

        if(dwErr)
        {
            goto done;
        }

        ASSERT(NULL != pdtlnode);

        CopyMemory(&pInfo->file, &file, sizeof(PBFILE));
    }

    if (pdtlnode)
    {
        pContext->pEntry = (PBENTRY *) DtlGetData(pdtlnode);
    }
    else
    {
        dwErr = ERROR_CANNOT_FIND_PHONEBOOK_ENTRY;
    }

done:
    return dwErr;
}


DWORD
DwGetEapLogonInfo(
    VOID *pv,
    EAPLOGONINFO **ppEapLogonInfo )
{
    EAPLOGONINFO *pEapLogonInfo = NULL;

    DWORD retcode = SUCCESS;

    struct EAPINFO
    {
        DWORD dwSizeofEapInfo;
        PBYTE pbEapInfo;
        DWORD dwSizeofPINInfo;
        PBYTE pbPINInfo;
    };

    struct EAPINFO *pEapInfo = (struct EAPINFO *) pv;

    DWORD dwSize;

    if(NULL == pv)
    {
        retcode = E_INVALIDARG;
        goto done;
    }

    dwSize = sizeof(EAPLOGONINFO)
           + pEapInfo->dwSizeofEapInfo
           + pEapInfo->dwSizeofPINInfo;

    pEapLogonInfo = (EAPLOGONINFO *) Malloc(dwSize);

    if(NULL == pEapLogonInfo)
    {

        retcode = GetLastError();

        TRACE1("Failed to Allocate EapLogonInfo. rc=0x%x",
                 retcode);
        goto done;
    }

    ZeroMemory(pEapLogonInfo, dwSize);

    //
    // Set up the fields in pEapLogonInfo by
    // flattening out the information passed
    // in.
    //
    pEapLogonInfo->dwSize = dwSize;

    pEapLogonInfo->dwLogonInfoSize =
        pEapInfo->dwSizeofEapInfo;

    pEapLogonInfo->dwOffsetLogonInfo =
        FIELD_OFFSET(EAPLOGONINFO, abdata);

    memcpy( pEapLogonInfo->abdata,
            pEapInfo->pbEapInfo,
            pEapInfo->dwSizeofEapInfo);

    pEapLogonInfo->dwPINInfoSize =
        pEapInfo->dwSizeofPINInfo;

    pEapLogonInfo->dwOffsetPINInfo =
        FIELD_OFFSET(EAPLOGONINFO, abdata)
        + pEapInfo->dwSizeofEapInfo;

    memcpy(    (PBYTE)
               ((PBYTE) pEapLogonInfo
             + pEapLogonInfo->dwOffsetPINInfo),

            pEapInfo->pbPINInfo,

            pEapInfo->dwSizeofPINInfo);

done:
    *ppEapLogonInfo = pEapLogonInfo;

    return retcode;
}