/*
 * Copyright (c) 2008, 2009, 2010, 2011 Kernel Drivers, LLC.
 * Copyright (c) 2009, 2010, 2011 Your File System, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice,
 *   this list of conditions and the following disclaimer in the
 *   documentation
 *   and/or other materials provided with the distribution.
 * - Neither the names of Kernel Drivers, LLC and Your File System, Inc.
 *   nor the names of their contributors may be used to endorse or promote
 *   products derived from this software without specific prior written
 *   permission from Kernel Drivers, LLC and Your File System, Inc.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif

#include <windows.h>
#include <windef.h>
#include <winbase.h>
#include <winreg.h>
#include <winsvc.h>
#include <winnetwk.h>
#include <npapi.h>
#include <winioctl.h>
#include <strsafe.h>

#define AFS_DEBUG_TRACE 1

#ifndef WNNC_NET_OPENAFS
#define WNNC_NET_OPENAFS     0x00390000
#endif

#include "AFSUserDefines.h"
#include "AFSUserIoctl.h"
#include "AFSUserStructs.h"
#include "AFSProvider.h"
#include "AFS_Npdll.h"

#include "stdio.h"

#ifndef MAX_PATH
#define MAX_PATH 260
#endif

#define SCRATCHSZ 1024

//
// Common information
//

ULONG _cdecl AFSDbgPrint( PWCHAR Format, ... );

#define WNNC_DRIVER( major, minor ) ( major * 0x00010000 + minor )

#define OPENAFS_PROVIDER_NAME           L"OpenAFS Network"
#define OPENAFS_PROVIDER_NAME_LENGTH    30

#define MAX_PROVIDER_NAME_LENGTH        256

static ULONG cbProviderNameLength = OPENAFS_PROVIDER_NAME_LENGTH;

static wchar_t wszProviderName[MAX_PROVIDER_NAME_LENGTH+1] = OPENAFS_PROVIDER_NAME;

static BOOL bProviderNameRead = FALSE;

#define OPENAFS_SERVER_NAME             L"AFS"
#define OPENAFS_SERVER_NAME_LENGTH      6

#define OPENAFS_SERVER_COMMENT          L"AFS Root"
#define OPENAFS_SERVER_COMMENT_LENGTH   16

#define MAX_SERVER_NAME_LENGTH         30

static ULONG cbServerNameLength = 0;

static ULONG cbServerNameUNCLength = 0;

static ULONG cbServerCommentLength = OPENAFS_SERVER_COMMENT_LENGTH;

static wchar_t wszServerName[MAX_SERVER_NAME_LENGTH+1];

static wchar_t wszServerNameUNC[MAX_SERVER_NAME_LENGTH+3];

static wchar_t wszServerComment[] = OPENAFS_SERVER_COMMENT;

static BOOL bServerNameRead = FALSE;

LARGE_INTEGER
AFSRetrieveAuthId( void);

void
ReadProviderNameString( void)
{
    HKEY hk;
    DWORD code;
    DWORD dwLen = 0;

    if ( bProviderNameRead )
        return;

    code = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
                         L"SYSTEM\\CurrentControlSet\\Services\\AFSRedirector\\NetworkProvider",
                         0, KEY_QUERY_VALUE, &hk);

    if ( code == ERROR_SUCCESS) {

        dwLen = sizeof(wszProviderName);

        code = RegQueryValueExW( hk, L"Name", NULL, NULL,
                                 (LPBYTE) wszProviderName, &dwLen);

        if ( code == ERROR_SUCCESS)
        {

            wszProviderName[MAX_PROVIDER_NAME_LENGTH] = '\0';

            cbProviderNameLength = wcslen( wszProviderName) * sizeof( WCHAR);
        }

        RegCloseKey( hk);
    }

    bProviderNameRead = TRUE;
}

void
ReadServerNameString( void)
{
    HKEY hk;
    DWORD code;
    DWORD dwLen = 0;

    if ( bServerNameRead )
        return;

    code = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
                         L"SYSTEM\\CurrentControlSet\\Services\\TransarcAFSDaemon\\Parameters",
                         0, KEY_QUERY_VALUE, &hk);

    if ( code == ERROR_SUCCESS) {

        dwLen = sizeof(wszProviderName);

        code = RegQueryValueExW( hk, L"NetbiosName", NULL, NULL,
                                 (LPBYTE) wszServerName, &dwLen);

        if ( code == ERROR_SUCCESS)
        {

            wszServerName[MAX_SERVER_NAME_LENGTH] = '\0';

            cbServerNameLength = wcslen( wszServerName) * sizeof( WCHAR);

            wszServerNameUNC[0] = wszServerNameUNC[1] = L'\\';

            memcpy(&wszServerNameUNC[2], wszServerName, (cbServerNameLength + 1) * sizeof( WCHAR));

            cbServerNameUNCLength = cbServerNameLength + 2 * sizeof( WCHAR);
        }

        RegCloseKey( hk);
    }

    bServerNameRead = TRUE;
}



/* returns TRUE if the file system is disabled or not installed */
BOOL
NPIsFSDisabled( void)
{
    HKEY hk;
    DWORD code;
    DWORD dwLen = 0;
    DWORD dwStart = SERVICE_DISABLED;

    code = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
                         L"SYSTEM\\CurrentControlSet\\Services\\AFSRedirector",
                         0, KEY_QUERY_VALUE, &hk);

    if ( code != ERROR_SUCCESS)

        return TRUE;


    dwLen = sizeof(dwStart);

    code = RegQueryValueExW( hk, L"Start", NULL, NULL,
                             (LPBYTE) &dwStart, &dwLen);

    RegCloseKey( hk);

    return ( dwStart == SERVICE_DISABLED);
}


#define try_return(S) { S; goto try_exit; }

#define __Enter

#define NOTHING

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

HANDLE
OpenRedirector( void);

typedef struct _AFS_ENUM_CB
{

    DWORD       CurrentIndex;

    DWORD       Scope;

    DWORD       Type;

    WCHAR      *RemoteName;

} AFSEnumerationCB;


//
// Recursively evaluate drivestr to find the final
// dos drive letter to which the source is mapped.
//
static BOOL
DriveSubstitution(LPCWSTR drivestr, LPWSTR subststr, size_t substlen)
{
    WCHAR drive[3];
    WCHAR device[MAX_PATH + 1];
    HRESULT hr = S_OK;

    memset( subststr, 0, substlen);
    drive[0] = drivestr[0];
    drive[1] = drivestr[1];
    drive[2] = 0;

    if ( QueryDosDevice(drive, device, MAX_PATH) )
    {
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"DriveSubstitution QueryDosDevice %s [%s -> %s]\n",
                     drivestr,
                     drive,
                     device);
#endif
        if ( device[0] == L'\\' &&
             device[1] == L'?' &&
             device[2] == L'?' &&
             device[3] == L'\\' &&
             iswalpha(device[4]) &&
             device[5] == L':')
        {
            drive[0] = device[4];
            drive[1] = L':';
            drive[2] = L'\0';

            if ( !DriveSubstitution(drive, subststr, substlen) )
            {

                subststr[0] = drive[0];
                subststr[1] = L':';
                subststr[2] = L'\0';

            }

            hr = S_OK;

            if ( device[6] )
            {
                hr = StringCchCat( subststr, substlen, &device[6]);
            }
            if ( SUCCEEDED(hr) && drivestr[2] )
            {
                hr = StringCchCat( subststr, substlen, &drivestr[2]);
            }

            if ( SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
            {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"DriveSubstitution %s -> %s\n",
                                 drivestr,
                                 subststr);
#endif
                return TRUE;
            }
        }
        else if ( device[0] == L'\\' &&
                  device[1] == L'?' &&
                  device[2] == L'?' &&
                  device[3] == L'\\' &&
                  device[4] == L'U' &&
                  device[5] == L'N' &&
                  device[6] == L'C' &&
                  device[7] == L'\\')
        {

            subststr[0] = L'\\';

            hr = StringCbCopyN(&subststr[1], substlen - sizeof(WCHAR), &device[7], MAX_PATH);

            if ( SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
            {
                if ( drivestr[2] )
                {
                    hr = StringCchCat( subststr, substlen, &drivestr[2]);
                }
                else
                {
                    hr = S_OK;
                }

                if ( SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"DriveSubstitution %s -> %s\n",
                                 drivestr,
                                 subststr);
#endif
                    return TRUE;
                }
            }

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"DriveSubstitution StringCbCopyN 1 hr 0x%X\n",
                         hr);
#endif
        }
        else if ( _wcsnicmp( AFS_RDR_DEVICE_NAME, device, sizeof( AFS_RDR_DEVICE_NAME) / sizeof( WCHAR) - 1) == 0)
        {
            //
            // \Device\AFSRedirector\;X:\\afs\cellname
            //

            hr = StringCbCopyN( subststr, substlen,
                                 &device[3 + sizeof( AFS_RDR_DEVICE_NAME) / sizeof( WCHAR)],
                                 MAX_PATH * sizeof( WCHAR));

            if ( SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
            {

                if ( drivestr[2] )
                {
                    hr = StringCchCat( subststr, substlen, &drivestr[2]);
                }
                else
                {
                    hr = S_OK;
                }

                if ( SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"DriveSubstitution %s -> %s\n",
                                 drivestr,
                                 subststr);
#endif
                    return TRUE;
                }
            }

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"DriveSubstitution StringCbCopyN 2 hr 0x%X\n",
                         hr);
#endif
        }

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"DriveSubstitution no substitution or match %s !! %s\n",
                     drivestr,
                     device);
#endif
    }
    else
    {
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"DriveSubstitution QueryDosDevice failed %s gle 0x%X\n",
                     drivestr,
                     GetLastError());
#endif
    }



    return FALSE;
}


static const WCHAR *
NPGetCapsQueryString( DWORD nIndex)
{
    switch ( nIndex) {
    case WNNC_SPEC_VERSION:
        return L"WNNC_SPEC_VERSION";

    case WNNC_NET_TYPE:
        return L"WNNC_NET_TYPE";

    case WNNC_DRIVER_VERSION:
        return L"WNNC_DRIVER_VERSION";

    case WNNC_USER:
        return L"WNNC_USER";

    case WNNC_CONNECTION:
        return L"WNNC_CONNECTION";

    case WNNC_DIALOG:
        return L"WNNC_DIALOG";

    case WNNC_ADMIN:
        return L"WNNC_ADMIN";

    case WNNC_ENUMERATION:
        return L"WNNC_ENUMERATION";

    case WNNC_START:
        return L"WNNC_START";

    case WNNC_CONNECTION_FLAGS:
        return L"WNNC_CONNECTION_FLAGS";

    default:
        return L"UNKNOWN";
    }
}

//
// This is the only function which must be exported, everything else is optional
//

DWORD
APIENTRY
NPGetCaps( DWORD nIndex )
{

    DWORD rc = 0;

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPGetCaps Index %u %s\n", nIndex,
                 NPGetCapsQueryString( nIndex));
#endif
    switch( nIndex)
    {
        case WNNC_SPEC_VERSION:
        {

            rc = WNNC_SPEC_VERSION51;
            break;
        }

        case WNNC_NET_TYPE:
        {
            rc = WNNC_NET_OPENAFS;
            break;
        }

        case WNNC_DRIVER_VERSION:
        {

            rc = WNNC_DRIVER(1, 0);
            break;
        }

        case WNNC_CONNECTION:
        {

            //
            // No support for:
            //   WNNC_CON_GETPERFORMANCE
            //   WNNC_CON_DEFER
            //

            rc = WNNC_CON_GETCONNECTIONS |
                 WNNC_CON_CANCELCONNECTION |
                 WNNC_CON_ADDCONNECTION |
                 WNNC_CON_ADDCONNECTION3;

            break;
        }

        case WNNC_ENUMERATION:
        {
            rc = WNNC_ENUM_LOCAL |
                 WNNC_ENUM_CONTEXT |
                 WNNC_ENUM_GLOBAL |
                 WNNC_ENUM_SHAREABLE;
            break;
        }

        case WNNC_START:
        {

            rc = WNNC_WAIT_FOR_START;

            break;
        }

        case WNNC_DIALOG:
        {

            //
            // No support for:
            //    WNNC_DLG_DEVICEMODE
            //    WNNC_DLG_PROPERTYDIALOG
            //    WNNC_DLG_SEARCHDIALOG
            //    WNNC_DLG_PERMISSIONEDITOR
            //

            rc = WNNC_DLG_FORMATNETWORKNAME |
                 WNNC_DLG_GETRESOURCEINFORMATION |
                 WNNC_DLG_GETRESOURCEPARENT;

            break;
        }

        case WNNC_USER:
        {
            //
            // No support for:
            //    WNNC_USR_GETUSER
            //

            break;
        }

        case WNNC_ADMIN:
        {
            //
            // No support for:
            //    WNNC_ADM_GETDIRECTORYTYPE
            //    WNNC_ADM_DIRECTORYNOTIFY
            // used by the old File Manager
            //
            break;
        }
    }

    return rc;
}

DWORD
APIENTRY
NPAddConnection( LPNETRESOURCE   lpNetResource,
                 LPWSTR          lpPassword,
                 LPWSTR          lpUserName )
{

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPAddConnection forwarding to NPAddConnection3\n");
#endif
    return NPAddConnection3( NULL, lpNetResource, lpPassword, lpUserName, 0 );
}

DWORD
APIENTRY
NPAddConnection3( HWND            hwndOwner,
                  LPNETRESOURCE   lpNetResource,
                  LPWSTR          lpPassword,
                  LPWSTR          lpUserName,
                  DWORD           dwFlags )
{

    DWORD    dwStatus = WN_SUCCESS;
    WCHAR    wchRemoteName[MAX_PATH+1];
    WCHAR    wchLocalName[3];
    DWORD    dwCopyBytes = 0;
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    HANDLE   hControlDevice = NULL;
    HANDLE   hToken = NULL;
    LARGE_INTEGER liAuthId = {0,0};

    __Enter
    {
        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 AFSRDFS is disabled, returning WN_BAD_NETNAME\n");
#endif

            return WN_BAD_NETNAME;
        }

        if ((lpNetResource->lpRemoteName == NULL) ||
            (lpNetResource->lpRemoteName[0] != L'\\') ||
            (lpNetResource->lpRemoteName[1] != L'\\') ||
            ((lpNetResource->dwType != RESOURCETYPE_DISK) &&
             (lpNetResource->dwType != RESOURCETYPE_ANY)))
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 invalid input, returning WN_BAD_NETNAME\n");
#endif
            return WN_BAD_NETNAME;
        }

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPAddConnection3 processing\n");
#endif
        if( lpNetResource->lpLocalName != NULL)
        {

            wchLocalName[0] = towupper(lpNetResource->lpLocalName[0]);
            wchLocalName[1] = L':';
            wchLocalName[2] = L'\0';
        }

        StringCchCopy(wchRemoteName, MAX_PATH+1, lpNetResource->lpRemoteName);
        wchRemoteName[MAX_PATH] = L'\0';

        //
        // Allocate our buffer to pass to the redirector filter
        //

        dwBufferSize = sizeof( AFSNetworkProviderConnectionCB) + (wcslen( wchRemoteName) * sizeof( WCHAR));

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        if( lpNetResource->lpLocalName != NULL)
        {

            pConnectCB->LocalName = towupper(wchLocalName[0]);

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 Adding mapping for drive %s remote name %s\n",
                         wchLocalName,
                         wchRemoteName);
#endif
        }
        else
        {

            pConnectCB->LocalName = L'\0';

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 Adding mapping for NO drive remote name %s\n",
                         wchRemoteName);
#endif
        }

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->RemoteNameLength = wcslen( wchRemoteName) * sizeof( WCHAR);

        memcpy( pConnectCB->RemoteName,
                wchRemoteName,
                pConnectCB->RemoteNameLength);

        pConnectCB->Type = lpNetResource->dwType;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPAddConnection3 Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_ADD_CONNECTION,
                                   pConnectCB,
                                   dwBufferSize,
                                   &dwStatus,
                                   sizeof( DWORD),
                                   &dwCopyBytes,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPAddConnection3 Failed to add connection to file system %d\n", GetLastError());
#endif
            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        //
        // The status returned from the driver will indicate how it was handled
        //

        if( dwStatus == WN_SUCCESS &&
            lpNetResource->lpLocalName != NULL)
        {

            WCHAR TempBuf[MAX_PATH+1];

            if( !QueryDosDeviceW( wchLocalName,
                                  TempBuf,
                                  MAX_PATH+1))
            {

                if( GetLastError() != ERROR_FILE_NOT_FOUND)
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPAddConnection3 QueryDosDeviceW failed with file not found\n");
#endif
                    NPCancelConnection( wchLocalName, TRUE);

                    dwStatus = ERROR_ALREADY_ASSIGNED;
                }
                else
                {

                    UNICODE_STRING uniConnectionName;
                    UNICODE_STRING uniDeviceName;

                    uniDeviceName.Length = (wcslen( AFS_RDR_DEVICE_NAME) * sizeof( WCHAR));
                    uniDeviceName.MaximumLength = uniDeviceName.Length;
                    uniDeviceName.Buffer = AFS_RDR_DEVICE_NAME;

                    //
                    // Create a symbolic link object to the device we are redirecting
                    //

                    uniConnectionName.MaximumLength = (USHORT)( uniDeviceName.Length +
                                                                pConnectCB->RemoteNameLength +
                                                                8 +              // Local name and \;
                                                                sizeof(WCHAR));   //  Space for NULL-termination.

                    //
                    //  Don't include NULL-termination.
                    //

                    uniConnectionName.Length = uniConnectionName.MaximumLength - sizeof(WCHAR);

                    uniConnectionName.Buffer = LocalAlloc( LMEM_ZEROINIT,
                                                           uniConnectionName.MaximumLength);

                    if( uniConnectionName.Buffer == NULL)
                    {

                        try_return( dwStatus = GetLastError());
                    }

                    CopyMemory( uniConnectionName.Buffer,
                                uniDeviceName.Buffer,
                                uniDeviceName.Length);

                    StringCchCatW( uniConnectionName.Buffer,
                                   uniConnectionName.MaximumLength,
                                   L"\\;" );

                    StringCchCatW( uniConnectionName.Buffer,
                                   uniConnectionName.MaximumLength,
                                   wchLocalName);

                    StringCchCatW( uniConnectionName.Buffer,
                                   uniConnectionName.MaximumLength,
                                   wchRemoteName);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPAddConnection3 DefineDosDevice Local %s connection name %s\n",
                                 wchLocalName,
                                 uniConnectionName.Buffer);
#endif

                    if( !DefineDosDeviceW( DDD_RAW_TARGET_PATH |
                                           DDD_NO_BROADCAST_SYSTEM,
                                           wchLocalName,
                                           uniConnectionName.Buffer))
                    {
#ifdef AFS_DEBUG_TRACE
                        AFSDbgPrint( L"NPAddConnection3 Failed to assign drive\n");
#endif
                        dwStatus = GetLastError();
                    }
                    else
                    {

                        dwStatus = WN_SUCCESS;
                    }

                    LocalFree( uniConnectionName.Buffer);
                }
            }
            else
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPAddConnection3 QueryDosDeviceW %Z already existed\n", TempBuf);
#endif
                NPCancelConnection( wchLocalName, TRUE);

                dwStatus = ERROR_ALREADY_ASSIGNED;
            }
        }

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwStatus;
}

DWORD
APIENTRY
NPCancelConnection( LPWSTR  lpName,
                    BOOL    fForce)
{

    WCHAR    wchRemoteName[MAX_PATH+1];
    DWORD    dwRemoteNameLength = (MAX_PATH+1) * sizeof(WCHAR);
    DWORD    dwStatus = WN_NOT_CONNECTED;
    DWORD    dwCopyBytes = 0;
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    AFSCancelConnectionResultCB stCancelConn;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    BOOL     bLocalName = TRUE;
    HANDLE   hControlDevice = NULL;
    WCHAR   *pwchLocalName = NULL;

    __Enter
    {

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPCancelConnection AFSRDFS is disabled, returning WN_NOT_CONNECTED\n");
#endif

            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        if( *lpName == L'\\' &&
            *(lpName + 1) == L'\\')
        {

            bLocalName = FALSE;
        }

        if( bLocalName)
        {

            //
            // Get the remote name for the connection, if we are handling it
            //

            dwStatus = NPGetConnection( lpName,
                                        wchRemoteName,
                                        &dwRemoteNameLength);

            if( dwStatus != WN_SUCCESS ||
                dwRemoteNameLength == 0)
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPCancelConnection Status 0x%x NameLength %u, returning WN_NOT_CONNECTED\n",
                             dwStatus, dwRemoteNameLength);
#endif
                try_return( dwStatus = WN_NOT_CONNECTED);
            }

            //
            // NPGetConnection returns the buffer size not the length without NUL
            //
            dwRemoteNameLength -= sizeof( WCHAR);
        }
        else
        {

            StringCchCopyW( wchRemoteName, MAX_PATH+1, lpName);

            dwRemoteNameLength = (wcslen( wchRemoteName) * sizeof( WCHAR));
        }

        wchRemoteName[ dwRemoteNameLength/sizeof( WCHAR)] = L'\0';

        dwBufferSize = sizeof( AFSNetworkProviderConnectionCB) + dwRemoteNameLength;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        if( bLocalName)
        {

            pConnectCB->LocalName = towupper(lpName[0]);
        }
        else
        {

            pConnectCB->LocalName = L'\0';
        }

        pConnectCB->RemoteNameLength = (USHORT)dwRemoteNameLength;

        StringCchCopyW( pConnectCB->RemoteName,
                        MAX_PATH+1,
                        wchRemoteName);

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPCancelConnection Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPCancelConnection OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        memset( &stCancelConn,
                '\0',
                sizeof( AFSCancelConnectionResultCB));

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_CANCEL_CONNECTION,
                                   pConnectCB,
                                   dwBufferSize,
                                   &stCancelConn,
                                   sizeof( AFSCancelConnectionResultCB),
                                   &dwCopyBytes,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPCancelConnection Failed to cancel connection to file system - gle 0x%x\n", gle);
#endif
            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        dwStatus = stCancelConn.Status;

#ifdef AFS_DEBUG_TRACE

        AFSDbgPrint( L"NPCancelConnection Cancel connection to file system - Name %s Status %08lX\n",
                     lpName,
                     dwStatus);
#endif

        if( dwStatus == WN_SUCCESS &&
            ( bLocalName ||
              stCancelConn.LocalName != L'\0'))
        {

            UNICODE_STRING uniConnectionName;
            UNICODE_STRING uniDeviceName;

            uniDeviceName.Length = (wcslen( AFS_RDR_DEVICE_NAME) * sizeof( WCHAR));
            uniDeviceName.MaximumLength = uniDeviceName.Length;
            uniDeviceName.Buffer = AFS_RDR_DEVICE_NAME;

            //
            // Create a symbolic link object to the device we are redirecting
            //

            uniConnectionName.MaximumLength = (USHORT)( uniDeviceName.Length +
                                                        dwRemoteNameLength +
                                                        8 +             // Local name and \;
                                                        sizeof(WCHAR)); //  Space for NULL-termination.

            //
            //  Don't include NULL-termination.
            //

            uniConnectionName.Length = uniConnectionName.MaximumLength - sizeof(WCHAR);

            uniConnectionName.Buffer = LocalAlloc( LMEM_ZEROINIT,
                                                   uniConnectionName.MaximumLength);

            if( uniConnectionName.Buffer == NULL)
            {

                try_return( dwStatus = GetLastError());
            }

            CopyMemory( uniConnectionName.Buffer,
                        uniDeviceName.Buffer,
                        uniDeviceName.Length);

            StringCchCatW( uniConnectionName.Buffer,
                           uniConnectionName.MaximumLength,
                           L"\\;" );

            if( !bLocalName)
            {

                WCHAR wchLocalName[ 3];

                wchLocalName[ 0] = stCancelConn.LocalName;

                wchLocalName[ 1] = L':';

                wchLocalName[ 2] = L'\0';

                StringCchCatW( uniConnectionName.Buffer,
                               uniConnectionName.MaximumLength,
                               wchLocalName);

                pwchLocalName = wchLocalName;
            }
            else
            {

                StringCchCatW( uniConnectionName.Buffer,
                               uniConnectionName.MaximumLength,
                               lpName);

                pwchLocalName = lpName;
            }

            StringCchCatW( uniConnectionName.Buffer,
                           uniConnectionName.MaximumLength,
                           wchRemoteName);

            if( !DefineDosDevice( DDD_REMOVE_DEFINITION | DDD_RAW_TARGET_PATH | DDD_EXACT_MATCH_ON_REMOVE,
                                  pwchLocalName,
                                  uniConnectionName.Buffer))
            {

#ifdef AFS_DEBUG_TRACE
                DWORD gle = GetLastError();

                AFSDbgPrint( L"NPCancelConnection Failed to cancel connection to system - gle 0x%x Name %s connection %wZ\n",
                             gle,
                             pwchLocalName,
                             &uniConnectionName);
#endif
            }
            else
            {
#ifdef AFS_DEBUG_TRACE

                AFSDbgPrint( L"NPCancelConnection Canceled connection to system - Name %s connection %wZ\n",
                             pwchLocalName,
                             &uniConnectionName);
#endif
            }
        }

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }


        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }

    }

    return dwStatus;
}

DWORD
APIENTRY
NPGetConnection( LPWSTR  lpLocalName,
                 LPWSTR  lpRemoteName,
                 LPDWORD lpBufferSize)
{

    DWORD    dwStatus = WN_NOT_CONNECTED;
    WCHAR    wchLocalName[3];
    WCHAR    wchSubstName[MAX_PATH + 1];
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    HANDLE   hControlDevice = NULL;
    DWORD    dwPassedSize;

    __Enter
    {

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection AFSRDFS is disabled, returning WN_NOT_CONNECTED\n");
#endif

            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        if( lstrlen( lpLocalName) == 0)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection No local name, returning WN_BAD_LOCALNAME\n");
#endif
            try_return( dwStatus = WN_BAD_LOCALNAME);
        }

        if ( lpBufferSize == NULL)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection No output size, returning WN_BAD_LOCALNAME\n");
#endif
            try_return( dwStatus = WN_BAD_VALUE);
        }

        dwPassedSize = *lpBufferSize;

        if ( !DriveSubstitution( lpLocalName, wchSubstName, sizeof( wchSubstName)))
        {
            wchLocalName[0] = towupper(lpLocalName[0]);
            wchLocalName[1] = L':';
            wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection Requesting connection for %s\n",
                         wchLocalName);
#endif
        }
        else
        {
            ReadServerNameString();

            if ( wchSubstName[0] != L'\\' &&
                 wchSubstName[1] == L':')
            {

                wchLocalName[0] = towupper(wchSubstName[0]);
                wchLocalName[1] = L':';
                wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection Requesting connection for drive substitution %s -> %s\n",
                             wchSubstName,
                             wchLocalName);
#endif
            }
            else if ( _wcsnicmp( wchSubstName, wszServerNameUNC, cbServerNameUNCLength / sizeof( WCHAR)) == 0 &&
                      ( wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == L'\\' ||
                        wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == 0))
            {
                HRESULT hr;
                WCHAR  *pwch;
                DWORD   dwCount = 0;

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection drive substitution %s is AFS\n",
                             wchSubstName);
#endif

                if ( lpRemoteName == NULL ||
                     dwPassedSize == 0)
                {

                    *lpBufferSize = wcslen( wchSubstName) * sizeof( WCHAR) + sizeof( WCHAR);

                    try_return( dwStatus = WN_MORE_DATA);

                }

                hr = StringCbCopyN(lpRemoteName, *lpBufferSize, wchSubstName, sizeof( wchSubstName));

                if ( SUCCEEDED(hr))
                {

                    for ( dwCount = 0, pwch = lpRemoteName; *pwch; pwch++ )
                    {
                        if ( *pwch == L'\\' )
                        {
                            dwCount++;

                            if ( dwCount == 4)
                            {
                                *pwch = L'\0';

                                break;
                            }
                        }

                    }

                    *lpBufferSize = wcslen( lpRemoteName) * sizeof( WCHAR) + sizeof( WCHAR);

                    try_return( dwStatus = WN_SUCCESS);
                }
                else if ( hr == STRSAFE_E_INSUFFICIENT_BUFFER)
                {

                    *lpBufferSize = wcslen( wchSubstName) * sizeof( WCHAR) + sizeof( WCHAR);

                    for ( dwCount = 0, pwch = lpRemoteName; *pwch; pwch++ )
                    {
                        if ( *pwch == L'\\' )
                        {
                            dwCount++;

                            if ( dwCount == 4)
                            {
                                *pwch = L'\0';

                                *lpBufferSize = wcslen( lpRemoteName) * sizeof( WCHAR) + sizeof( WCHAR);

                                try_return( dwStatus = WN_SUCCESS);
                            }
                        }

                    }

                    try_return( dwStatus = WN_MORE_DATA);
                }
                else
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetConnection StringCbCopyN failure 0x%X\n",
                                 hr);
#endif
                    try_return( dwStatus = WN_NET_ERROR);
                }
            }
            else
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection drive substitution %s is not AFS\n",
                             wchSubstName);
#endif
                try_return( dwStatus = WN_NOT_CONNECTED);
            }
        }

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetConnection Requesting connection for %s\n",
                     wchLocalName);
#endif

        dwBufferSize = 0x1000;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        pConnectCB->LocalName = towupper(wchLocalName[0]);

        pConnectCB->RemoteNameLength = 0;

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetConnection Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_GET_CONNECTION,
                                   pConnectCB,
                                   dwBufferSize,
                                   pConnectCB,
                                   dwBufferSize,
                                   lpBufferSize,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPGetConnection Failed to get connection from file system for local %s gle 0x%x\n",
                         wchLocalName, gle);
#endif
            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        //
        // IOCTL_AFS_GET_CONNECTION returns a counted string
        //

        if( lpRemoteName == NULL ||
            *lpBufferSize + sizeof( WCHAR) > dwPassedSize)
        {

            *lpBufferSize += sizeof( WCHAR);

            try_return( dwStatus = WN_MORE_DATA);
        }

        memcpy( lpRemoteName,
                (void *)pConnectCB,
                *lpBufferSize);

        lpRemoteName[ *lpBufferSize/sizeof( WCHAR)] = L'\0';

        *lpBufferSize += sizeof( WCHAR);

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetConnection local %s remote %s\n",
                     wchLocalName,
                     lpRemoteName);
#endif
        dwStatus = WN_SUCCESS;

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwStatus;
}

DWORD
APIENTRY
NPGetConnection3( IN     LPCWSTR lpLocalName,
                  IN     DWORD dwLevel,
                  OUT    LPVOID lpBuffer,
                  IN OUT LPDWORD lpBufferSize)
{

    DWORD    dwStatus = WN_NOT_CONNECTED;
    WCHAR    wchLocalName[3];
    WCHAR    wchSubstName[MAX_PATH + 1];
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    HANDLE   hControlDevice = NULL;
    DWORD    dwPassedSize;
    DWORD   *pConnectState =(DWORD *)lpBuffer;

    __Enter
    {

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 AFSRDFS is disabled, returning WN_NOT_CONNECTED\n");
#endif

            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        if( lstrlen( lpLocalName) == 0)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 No local name, returning WN_BAD_LOCALNAME\n");
#endif
            try_return( dwStatus = WN_BAD_LOCALNAME);
        }

        //
        // LanMan NPGetConnection3 only responds to level 1
        //

        if ( dwLevel != 0x1)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 Level 0x%X returning WN_BAD_LEVEL\n", dwLevel);
#endif
            try_return( dwStatus = WN_BAD_LEVEL);
        }

        if ( lpBufferSize == NULL)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 No output size, returning WN_BAD_VALUE\n");
#endif
            try_return( dwStatus = WN_BAD_VALUE);
        }

        dwPassedSize = *lpBufferSize;

        if ( dwPassedSize == 0 ||
             lpBuffer == NULL)
        {

            *lpBufferSize = sizeof( DWORD);

            try_return( dwStatus = WN_MORE_DATA);
        }

        if ( !DriveSubstitution( lpLocalName, wchSubstName, sizeof( wchSubstName)))
        {
            wchLocalName[0] = towupper(lpLocalName[0]);
            wchLocalName[1] = L':';
            wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 Requesting connection for %s level 0x%X\n",
                         wchLocalName,
                         dwLevel);
#endif
        }
        else
        {

            ReadServerNameString();

            if ( wchSubstName[0] != L'\\' &&
                 wchSubstName[1] == L':')
            {

                wchLocalName[0] = towupper(wchSubstName[0]);
                wchLocalName[1] = L':';
                wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection3 Requesting connection for drive substitution %s -> %s level 0x%x\n",
                             wchSubstName,
                             wchLocalName,
                             dwLevel);
#endif
            }
            else if ( _wcsnicmp( wchSubstName, wszServerNameUNC, cbServerNameUNCLength / sizeof( WCHAR)) == 0 &&
                      ( wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == L'\\' ||
                        wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == 0))
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection3 drive substitution %s is AFS return connected\n",
                             wchSubstName);
#endif
                *pConnectState = WNGETCON_CONNECTED;

                *lpBufferSize = sizeof( DWORD);

                try_return( dwStatus = WN_SUCCESS);
            }
            else
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetConnection3 drive substitution %s is not AFS return not connected\n",
                             wchSubstName);
#endif
                try_return( dwStatus = WN_NOT_CONNECTED);
            }
        }

        dwBufferSize = 0x1000;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        pConnectCB->LocalName = towupper(wchLocalName[0]);

        pConnectCB->RemoteNameLength = 0;

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetConnection3 Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnection3 OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_GET_CONNECTION,
                                   pConnectCB,
                                   dwBufferSize,
                                   pConnectCB,
                                   dwBufferSize,
                                   &dwBufferSize,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPGetConnection3 Failed to get connection from file system for local %s gle 0x%x\n",
                         wchLocalName, gle);
#endif
            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        *lpBufferSize = sizeof( DWORD);

        if( sizeof( DWORD) > dwPassedSize)
        {

            try_return( dwStatus = WN_MORE_DATA);
        }

        *pConnectState = WNGETCON_CONNECTED;

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetConnection3 local %s connect-state 0x%x\n",
                     wchLocalName,
                     *pConnectState);
#endif
        dwStatus = WN_SUCCESS;

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwStatus;
}

DWORD
APIENTRY
NPGetConnectionPerformance( LPCWSTR lpRemoteName,
                            LPNETCONNECTINFOSTRUCT lpNetConnectInfo)
{

    DWORD dwReturn = WN_SUCCESS;
    AFSNetworkProviderConnectionCB *pConnectCB = NULL;
    DWORD dwBufferSize = 0;
    HANDLE hControlDevice = NULL;
    DWORD dwError = 0;

    __Enter
    {

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetConnectionPerformance AFSRDFS is disabled, returning WN_BAD_NETNAME\n");
#endif

            return WN_NO_NETWORK;
        }

        AFSDbgPrint( L"NPGetConnectionPerformance Entry for remote connection %S\n",
                     lpRemoteName);

        dwBufferSize = 0x1000;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {
            try_return( dwReturn = WN_OUT_OF_MEMORY);
        }

        pConnectCB->RemoteNameLength = wcslen( lpRemoteName) * sizeof( WCHAR);

        StringCbCopy( pConnectCB->RemoteName,
                      dwBufferSize - sizeof(AFSNetworkProviderConnectionCB),
                      lpRemoteName);

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {
            AFSDbgPrint( L"NPGetConnectionPerformance OpenRedirector failure, returning WN_NET_ERROR\n");

            try_return( dwReturn = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_GET_CONNECTION_INFORMATION,
                                   pConnectCB,
                                   dwBufferSize,
                                   pConnectCB,
                                   dwBufferSize,
                                   &dwBufferSize,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPGetConnectionPerformance Failed to get connection info from file system for remote %S gle 0x%x\n",
                         lpRemoteName,
                         gle);
#endif
            try_return( dwReturn = WN_NOT_CONNECTED);
        }

        lpNetConnectInfo->dwFlags = WNCON_DYNAMIC;

        lpNetConnectInfo->dwSpeed = 500;

        lpNetConnectInfo->dwDelay = 0;

        lpNetConnectInfo->dwOptDataSize = 0x1000;

        AFSDbgPrint( L"NPGetConnectionPerformance Successfully returned information for remote connection %S\n",
                     lpRemoteName);

try_exit:

        if ( hControlDevice != NULL)
        {
            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {
            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwReturn;
}

static LPCWSTR
GetUsageString( DWORD dwUsage)
{
    static WCHAR Buffer[128] = L"";
    //
    // RESOURCEUSAGE_CONNECTABLE   0x00000001
    // RESOURCEUSAGE_CONTAINER     0x00000002
    // RESOURCEUSAGE_NOLOCALDEVICE 0x00000004
    // RESOURCEUSAGE_SIBLING       0x00000008
    // RESOURCEUSAGE_ATTACHED      0x00000010
    // RESOURCEUSAGE_ALL           (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED)
    // RESOURCEUSAGE_RESERVED      0x80000000
    //

    Buffer[0] = L'\0';

    if ( dwUsage == RESOURCEUSAGE_ALL )
    {
        return L"ALL";
    }

    if ( dwUsage == 0 )
    {
        return L"NONE";
    }

    if ( dwUsage & RESOURCEUSAGE_CONNECTABLE )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"CONNECTABLE|");
    }

    if ( dwUsage & RESOURCEUSAGE_CONTAINER )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"CONTAINER|");
    }

    if ( dwUsage & RESOURCEUSAGE_NOLOCALDEVICE )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"NOLOCALDEVICE|");
    }

    if ( dwUsage & RESOURCEUSAGE_SIBLING )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"SIBLING|");
    }

    if ( dwUsage & RESOURCEUSAGE_ATTACHED )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"ATTACHED|");
    }

    if ( dwUsage & RESOURCEUSAGE_RESERVED )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"RESERVED|");
    }

    if ( dwUsage & ~(RESOURCEUSAGE_ALL|RESOURCEUSAGE_NOLOCALDEVICE|RESOURCEUSAGE_SIBLING|RESOURCEUSAGE_RESERVED) )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"UNKNOWN|");
    }

    Buffer[lstrlen(Buffer)-1] = L'\0';

    return Buffer;
}

static LPCWSTR
GetTypeString( DWORD dwType)
{
    static WCHAR Buffer[128] = L"";

    //
    // RESOURCETYPE_ANY        0x00000000
    // RESOURCETYPE_DISK       0x00000001
    // RESOURCETYPE_PRINT      0x00000002
    // RESOURCETYPE_RESERVED   0x00000008
    // RESOURCETYPE_UNKNOWN    0xFFFFFFFF
    //

    Buffer[0] = L'\0';

    if ( dwType == RESOURCETYPE_ANY )
    {
        return L"ANY";
    }

    if ( dwType == RESOURCETYPE_UNKNOWN )
    {
        return L"UNKNOWN";
    }

    if ( dwType & RESOURCETYPE_DISK )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"DISK|");
    }

    if ( dwType & RESOURCETYPE_PRINT )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"PRINT|");
    }

    if ( dwType & RESOURCETYPE_RESERVED )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"RESERVED|");
    }

    if ( dwType & ~(RESOURCETYPE_DISK|RESOURCETYPE_PRINT|RESOURCETYPE_RESERVED) )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"UNKNOWN|");
    }

    Buffer[lstrlen(Buffer)-1] = L'\0';

    return Buffer;
}

static LPCWSTR
GetScopeString( DWORD dwScope)
{
    static WCHAR Buffer[128] = L"";

    //
    // RESOURCE_CONNECTED      0x00000001
    // RESOURCE_GLOBALNET      0x00000002
    // RESOURCE_REMEMBERED     0x00000003
    // RESOURCE_RECENT         0x00000004
    // RESOURCE_CONTEXT        0x00000005
    //

    Buffer[0] = L'\0';

    if ( dwScope == RESOURCE_CONNECTED )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"CONNECTED|");
    }

    if ( dwScope == RESOURCE_GLOBALNET )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"GLOBALNET|");
    }

    if ( dwScope == RESOURCE_REMEMBERED )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"REMEMBERED|");
    }

    if ( dwScope == RESOURCE_RECENT )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"RECENT|");
    }

    if ( dwScope == RESOURCE_CONTEXT )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"CONTEXT|");
    }

    if ( dwScope & ~(RESOURCE_CONNECTED|RESOURCE_GLOBALNET|RESOURCE_REMEMBERED|RESOURCE_RECENT|RESOURCE_CONTEXT) )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"UNKNOWN|");
    }

    Buffer[lstrlen(Buffer)-1] = L'\0';

    return Buffer;
}

static LPCWSTR
GetDisplayString( DWORD dwDisplay)
{
    //
    // RESOURCEDISPLAYTYPE_GENERIC        0x00000000
    // RESOURCEDISPLAYTYPE_DOMAIN         0x00000001
    // RESOURCEDISPLAYTYPE_SERVER         0x00000002
    // RESOURCEDISPLAYTYPE_SHARE          0x00000003
    // RESOURCEDISPLAYTYPE_FILE           0x00000004
    // RESOURCEDISPLAYTYPE_GROUP          0x00000005
    // RESOURCEDISPLAYTYPE_NETWORK        0x00000006
    // RESOURCEDISPLAYTYPE_ROOT           0x00000007
    // RESOURCEDISPLAYTYPE_SHAREADMIN     0x00000008
    // RESOURCEDISPLAYTYPE_DIRECTORY      0x00000009
    // RESOURCEDISPLAYTYPE_TREE           0x0000000A
    // RESOURCEDISPLAYTYPE_NDSCONTAINER   0x0000000B
    //

    switch ( dwDisplay ) {
    case RESOURCEDISPLAYTYPE_GENERIC:
        return L"GENERIC";
    case RESOURCEDISPLAYTYPE_DOMAIN:
        return L"DOMAIN";
    case RESOURCEDISPLAYTYPE_SERVER:
        return L"SERVER";
    case RESOURCEDISPLAYTYPE_SHARE:
        return L"SHARE";
    case RESOURCEDISPLAYTYPE_FILE:
        return L"FILE";
    case RESOURCEDISPLAYTYPE_GROUP:
        return L"GROUP";
    case RESOURCEDISPLAYTYPE_NETWORK:
        return L"NETWORK";
    case RESOURCEDISPLAYTYPE_ROOT:
        return L"ROOT";
    case RESOURCEDISPLAYTYPE_SHAREADMIN:
        return L"SHAREADMIN";
    case RESOURCEDISPLAYTYPE_DIRECTORY:
        return L"DIRECTORY";
    case RESOURCEDISPLAYTYPE_TREE:
        return L"TREE";
    case RESOURCEDISPLAYTYPE_NDSCONTAINER:
        return L"NDSCONTAINER";
    default:
        return L"UNKNOWN";
    }
}

DWORD
APIENTRY
NPOpenEnum( DWORD          dwScope,
            DWORD          dwType,
            DWORD          dwUsage,
            LPNETRESOURCE  lpNetResource,
            LPHANDLE       lphEnum )
{

    DWORD   dwStatus = WN_SUCCESS;
    AFSEnumerationCB *pEnumCB = NULL;

#ifdef AFS_DEBUG_TRACE
    if ( lpNetResource == NULL)
    {
        AFSDbgPrint( L"NPOpenEnum Scope %s Type %s Usage %s NetResource: (Null)\n",
                     GetScopeString(dwScope), GetTypeString(dwType), GetUsageString(dwUsage));
    }
    else
    {
        AFSDbgPrint( L"NPOpenEnum Scope %s Type %s Usage %s NetResource (0x%p): Scope %s Type %s Display %s Usage %s Local %s Remote \"%s\" Comment \"%s\"\n",
                     GetScopeString(dwScope),
                     GetTypeString(dwType),
                     GetUsageString(dwUsage),
                     lpNetResource,
                     GetScopeString(lpNetResource->dwScope),
                     GetTypeString(lpNetResource->dwType),
                     GetDisplayString(lpNetResource->dwDisplayType),
                     GetUsageString(lpNetResource->dwUsage),
                     lpNetResource->lpLocalName,
                     lpNetResource->lpRemoteName,
                     lpNetResource->lpComment);
    }
#endif

    if ( dwUsage == 0 )
    {
        dwUsage = RESOURCEUSAGE_ALL;
    }

#if 0
    if ( dwType == 0 || dwType == RESOURCEUSAGE_ATTACHED)
    {
        dwType |= RESOURCETYPE_DISK | RESOURCETYPE_PRINT;
    }
#endif

    *lphEnum = HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, sizeof( AFSEnumerationCB));

    if( *lphEnum == NULL)
    {

        return WN_OUT_OF_MEMORY;
    }

    pEnumCB = (AFSEnumerationCB *)*lphEnum;

    pEnumCB->CurrentIndex = 0;

    pEnumCB->Type = dwType;

    switch( dwScope )
    {
        case RESOURCE_CONNECTED:
        {

            pEnumCB->Scope = RESOURCE_CONNECTED;

            break;
        }

        case RESOURCE_CONTEXT:
        {

            pEnumCB->Scope = RESOURCE_CONTEXT;

            break;
        }

        case RESOURCE_GLOBALNET:
        {

            if( lpNetResource != NULL &&
                lpNetResource->lpRemoteName != NULL)
            {

                pEnumCB->RemoteName = (WCHAR *)HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, 0x1000);

                if( pEnumCB->RemoteName == NULL)
                {

                    dwStatus = WN_OUT_OF_MEMORY;
                    HeapFree( GetProcessHeap( ), 0, (PVOID) *lphEnum );
                    *lphEnum = NULL;
                }
                else
                {

                    StringCbCopy( pEnumCB->RemoteName,
                                  0x1000,
                                  lpNetResource->lpRemoteName);

                }
            }

            pEnumCB->Scope = RESOURCE_GLOBALNET;

            break;
        }

        default:

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPOpenEnum Processing (Scope %s 0x%x) Type %s Usage %s, returning WN_NOT_SUPPORTED\n",
                         GetScopeString(dwScope), dwScope, GetTypeString(dwType), GetUsageString(dwUsage));
#endif

            dwStatus  = WN_NOT_SUPPORTED;
            HeapFree( GetProcessHeap( ), 0, (PVOID) *lphEnum );
            *lphEnum = NULL;

            break;
    }

    return dwStatus;
}


DWORD
APIENTRY
NPEnumResource( HANDLE  hEnum,
                LPDWORD lpcCount,
                LPVOID  lpBuffer,
                LPDWORD lpBufferSize)
{

    DWORD            dwStatus = WN_NO_MORE_ENTRIES; //WN_SUCCESS;
    ULONG            dwCopyBytes;
    ULONG            EntriesCopied;
    ULONG            EntriesRequested;
    ULONG            dwIndex;
    LPNETRESOURCE    pNetResource;
    ULONG            SpaceNeeded;
    ULONG            SpaceAvailable;
    PWCHAR           StringZone;
    AFSNetworkProviderConnectionCB *pConnectionCB = NULL;
    void            *pConnectionCBBase = NULL;
    DWORD            dwError = 0;
    UNICODE_STRING   uniRemoteName;
    HANDLE           hControlDevice = NULL;
    AFSEnumerationCB *pEnumCB = (AFSEnumerationCB *)hEnum;

    __Enter
    {

        if ( lpBufferSize == NULL)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource No output size, returning WN_BAD_VALUE\n");
#endif
            try_return( dwStatus = WN_BAD_VALUE);
        }

        ReadProviderNameString();

        pNetResource = (LPNETRESOURCE) lpBuffer;
        SpaceAvailable = *lpBufferSize;
        EntriesRequested = *lpcCount;
        *lpcCount = EntriesCopied = 0;
        StringZone = (PWCHAR) ((char *)lpBuffer + *lpBufferSize);

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPEnumResource Processing Remote name %s Scope %s Type %s Usage %s Index %d SpaceAvailable 0x%lX RequestedEntries %lu\n",
                     pEnumCB->RemoteName ? pEnumCB->RemoteName : L"(Null)",
                     GetScopeString(pEnumCB->Scope),
                     GetTypeString(pEnumCB->Type),
                     GetUsageString(pEnumCB->Type),
                     pEnumCB->CurrentIndex,
                     SpaceAvailable,
                     EntriesRequested);
#endif

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource AFSRDFS is disabled, returning WN_NO_MORE_ENTRIES\n");
#endif

            try_return( dwStatus = WN_NO_MORE_ENTRIES);
        }

        pConnectionCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000);

        if( pConnectionCB == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource Out of Memory\n");
#endif

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        pConnectionCBBase = (void *)pConnectionCB;

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        //
        // Handle the special cases here
        //   0. Provider Network Root
        //   1. Server Root
        //

#if 0
        if ( pEnumCB->Scope == RESOURCE_GLOBALNET)
        {

            ReadServerNameString();

            if ( pEnumCB->CurrentIndex == 0 &&
                 pEnumCB->RemoteName == NULL)
            {

                // Determine the space needed for this entry...

                SpaceNeeded = 2 * ( cbProviderNameLength + sizeof( WCHAR));

                uniRemoteName.Length = (USHORT)cbProviderNameLength;
                uniRemoteName.MaximumLength = uniRemoteName.Length;
                uniRemoteName.Buffer = wszProviderName;

                if( SpaceNeeded + sizeof( NETRESOURCE) > SpaceAvailable)
                {

                    *lpBufferSize = SpaceNeeded + sizeof( NETRESOURCE);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPEnumResource Request MORE_DATA for entry %s Len %d\n",
                                 &uniRemoteName,
                                 *lpBufferSize);
#endif
                    try_return( dwStatus = WN_MORE_DATA);
                }

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPEnumResource Processing Entry %wZ\n",
                             &uniRemoteName);
#endif

                SpaceAvailable -= (SpaceNeeded + sizeof( NETRESOURCE));

                pNetResource->dwScope       = RESOURCE_GLOBALNET;
                pNetResource->dwType        = RESOURCETYPE_ANY;
                pNetResource->dwDisplayType = RESOURCEDISPLAYTYPE_NETWORK;
                pNetResource->dwUsage       = RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_RESERVED;

                // setup string area at opposite end of buffer
                StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

                pNetResource->lpLocalName = NULL;

                // copy remote name
                pNetResource->lpRemoteName = StringZone;

                StringCbCopy( StringZone,
                              cbProviderNameLength + sizeof( WCHAR),
                              wszProviderName);

                StringZone += cbProviderNameLength / sizeof(WCHAR) + 1;

                pNetResource->lpComment = NULL;

                // copy provider name
                pNetResource->lpProvider = StringZone;
                StringCbCopy( StringZone,
                              cbProviderNameLength + sizeof( WCHAR),
                              wszProviderName);

                StringZone += cbProviderNameLength / sizeof( WCHAR) + 1;

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPEnumResource Entry (0x%p) Scope %s Type %s Display %s Usage %s Local %s Remote \"%s\" Comment \"%s\"\n",
                             pNetResource,
                             GetScopeString(pNetResource->dwScope),
                             GetTypeString(pNetResource->dwType),
                             GetDisplayString(pNetResource->dwDisplayType),
                             GetUsageString(pNetResource->dwUsage),
                             pNetResource->lpLocalName,
                             pNetResource->lpRemoteName,
                             pNetResource->lpComment);
#endif

                // setup the new end of buffer
                StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

                EntriesCopied++;

                pNetResource++;

                // do not change the index since we did not query the redirector
                pEnumCB->CurrentIndex = 0;

                // remember that we returned the provider name
                pEnumCB->RemoteName = (WCHAR *)HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, 0x1000);

                if( pEnumCB->RemoteName == NULL)
                {

                    try_return( dwStatus = WN_OUT_OF_MEMORY);
                }
                else
                {

                    StringCbCopy( pEnumCB->RemoteName,
                                   0x1000,
                                   wszProviderName);
                }
            }

            if ( pEnumCB->CurrentIndex == 0 &&
                 lstrlen( pEnumCB->RemoteName) == cbProviderNameLength / sizeof( WCHAR) &&
                 _wcsnicmp( pEnumCB->RemoteName, wszProviderName, cbProviderNameLength / sizeof( WCHAR)) == 0 &&
                 EntriesCopied < EntriesRequested)
            {

                //
                // After the network provider entry comes the server entry
                //

                // Determine the space needed for this entry...

                SpaceNeeded = cbProviderNameLength + cbServerNameUNCLength + cbServerCommentLength + 3 * sizeof( WCHAR);

                uniRemoteName.Length = (USHORT)cbServerNameUNCLength;
                uniRemoteName.MaximumLength = uniRemoteName.Length;
                uniRemoteName.Buffer = wszServerNameUNC;

                if( SpaceNeeded + sizeof( NETRESOURCE) > SpaceAvailable)
                {

                    *lpBufferSize = SpaceNeeded + sizeof( NETRESOURCE);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPEnumResource Request MORE_DATA for entry %s Len %d\n",
                                 &uniRemoteName,
                                 *lpBufferSize);
#endif
                    try_return( dwStatus = WN_MORE_DATA);
                }

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPEnumResource Processing Entry %wZ\n",
                             &uniRemoteName);
#endif

                SpaceAvailable -= (SpaceNeeded + sizeof( NETRESOURCE));

                pNetResource->dwScope       = 0;
                pNetResource->dwType        = RESOURCETYPE_ANY;
                pNetResource->dwDisplayType = RESOURCEDISPLAYTYPE_SERVER;
                pNetResource->dwUsage       = RESOURCEUSAGE_CONTAINER;

                // setup string area at opposite end of buffer
                StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

                pNetResource->lpLocalName = NULL;

                // copy remote name
                pNetResource->lpRemoteName = StringZone;

                StringCbCopy( StringZone,
                              cbServerNameUNCLength + sizeof( WCHAR),
                              wszServerNameUNC);

                StringZone += cbServerNameUNCLength / sizeof(WCHAR) + 1;

                // copy comment
                pNetResource->lpComment = StringZone;

                StringCbCopy( StringZone,
                              cbServerCommentLength + sizeof( WCHAR),
                              wszServerComment);

                StringZone += cbServerCommentLength / sizeof( WCHAR) + 1;

                // copy provider name
                pNetResource->lpProvider = StringZone;
                StringCbCopy( StringZone,
                              cbProviderNameLength + sizeof( WCHAR),
                              wszProviderName);

                StringZone += cbProviderNameLength / sizeof( WCHAR) + 1;

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPEnumResource Entry (0x%p) Scope %s Type %s Display %s Usage %s Local %s Remote \"%s\" Comment \"%s\"\n",
                             pNetResource,
                             GetScopeString(pNetResource->dwScope),
                             GetTypeString(pNetResource->dwType),
                             GetDisplayString(pNetResource->dwDisplayType),
                             GetUsageString(pNetResource->dwUsage),
                             pNetResource->lpLocalName,
                             pNetResource->lpRemoteName,
                             pNetResource->lpComment);
#endif

                // setup the new end of buffer
                StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

                EntriesCopied++;

                pNetResource++;

                // do not update the index because we did not query the redirector
                pEnumCB->CurrentIndex = 0;

                // remember that we returned the server
                StringCbCopy( pEnumCB->RemoteName,
                              0x1000,
                              wszServerNameUNC);
            }
        }
#endif

        //
        // Setup what we are going to ask for
        //

        pConnectionCB->Scope = pEnumCB->Scope;

        pConnectionCB->Type = pEnumCB->Type;

        pConnectionCB->CurrentIndex = pEnumCB->CurrentIndex;

        pConnectionCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        //
        // If this is a RESOURCE_GLOBALNET enumeration then pass down the remote name if
        // there is one
        //

        pConnectionCB->RemoteNameLength = 0;

        if( pEnumCB->Scope == RESOURCE_GLOBALNET &&
            pEnumCB->RemoteName != NULL)
        {

            pConnectionCB->RemoteNameLength = wcslen( pEnumCB->RemoteName) * sizeof( WCHAR);

            StringCbCopy( pConnectionCB->RemoteName,
                          (0x1000 - sizeof(AFSNetworkProviderConnectionCB)) + sizeof(WCHAR),
                          pEnumCB->RemoteName);
        }

        pConnectionCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPEnumResource Retrieved authentication id %08lX-%08lX\n",
                     pConnectionCB->AuthenticationId.HighPart,
                     pConnectionCB->AuthenticationId.LowPart);
#endif

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_LIST_CONNECTIONS,
                                   pConnectionCB,
                                   0x1000,
                                   pConnectionCB,
                                   0x1000,
                                   &dwCopyBytes,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPEnumResource Failed to list connections from file system - gle 0x%x\n",
                         gle);
#endif
            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        if( dwCopyBytes == 0)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource No More Entries\n");
#endif
            try_return( dwStatus = WN_NO_MORE_ENTRIES);
        }

        dwIndex = pEnumCB->CurrentIndex;

        while( EntriesCopied < EntriesRequested)
        {

            uniRemoteName.Length = (USHORT)pConnectionCB->RemoteNameLength;
            uniRemoteName.MaximumLength = uniRemoteName.Length;
            uniRemoteName.Buffer = pConnectionCB->RemoteName;

            // Determine the space needed for this entry...

            SpaceNeeded  = 0;

            if( pConnectionCB->LocalName != 0)
            {

                SpaceNeeded += 3 * sizeof(WCHAR);                // local name
            }

            SpaceNeeded += pConnectionCB->RemoteNameLength + sizeof( WCHAR);        // remote name

            if( pConnectionCB->CommentLength > 0)
            {

                SpaceNeeded += pConnectionCB->CommentLength + sizeof( WCHAR);           // comment
            }

            SpaceNeeded += cbProviderNameLength + sizeof( WCHAR);           // provider name

            if( SpaceNeeded + sizeof( NETRESOURCE) > SpaceAvailable)
            {

                if (EntriesCopied == 0) {

                    dwStatus = WN_MORE_DATA;

                    *lpBufferSize = SpaceNeeded + sizeof( NETRESOURCE);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPEnumResource Request MORE_DATA for entry %s Len %d\n",
                                 &uniRemoteName,
                                 *lpBufferSize);
#endif

                } else {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPEnumResource Return SUCCESS but more entries Index %d\n",
                                 dwIndex);
#endif

                    dwStatus = WN_SUCCESS;
                }

                break;
            }

            SpaceAvailable -= (SpaceNeeded + sizeof( NETRESOURCE));

            pNetResource->dwScope       = pConnectionCB->Scope;
            pNetResource->dwType        = pConnectionCB->Type;

            pNetResource->dwDisplayType = pConnectionCB->DisplayType;

            if ( pNetResource->dwType == RESOURCETYPE_ANY &&
                 pNetResource->dwDisplayType == RESOURCEDISPLAYTYPE_SHARE)
            {

                pNetResource->dwType = RESOURCETYPE_DISK;
            }
            pNetResource->dwUsage       = pConnectionCB->Usage;

            // setup string area at opposite end of buffer
            StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

            // copy local name
            if( pConnectionCB->LocalName != 0)
            {

                pNetResource->lpLocalName = StringZone;
                *StringZone++ = towupper(pConnectionCB->LocalName);
                *StringZone++ = L':';
                *StringZone++ = L'\0';
            }
            else
            {

                pNetResource->lpLocalName = NULL;
            }

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource Processing Entry %wZ\n",
                         &uniRemoteName);
#endif

            // copy remote name
            pNetResource->lpRemoteName = StringZone;

            CopyMemory( StringZone,
                        pConnectionCB->RemoteName,
                        pConnectionCB->RemoteNameLength);

            StringZone += (pConnectionCB->RemoteNameLength / sizeof(WCHAR));

            *StringZone++ = L'\0';

            // copy comment
            if( pConnectionCB->CommentLength > 0)
            {

                pNetResource->lpComment = StringZone;

                CopyMemory( StringZone,
                            (void *)((char *)pConnectionCB + pConnectionCB->CommentOffset),
                            pConnectionCB->CommentLength);

                StringZone += (pConnectionCB->CommentLength / sizeof(WCHAR));

                *StringZone++ = L'\0';
            }
            else
            {

                pNetResource->lpComment = NULL;
            }

            // copy provider name
            pNetResource->lpProvider = StringZone;
            StringCbCopy( StringZone,
                          cbProviderNameLength + sizeof( WCHAR),
                          wszProviderName);

            StringZone += (cbProviderNameLength / sizeof( WCHAR) + 1);

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPEnumResource Entry (0x%p) Scope %s Type %s Display %s Usage %s Local %s Remote \"%s\" Comment \"%s\"\n",
                         pNetResource,
                         GetScopeString(pNetResource->dwScope),
                         GetTypeString(pNetResource->dwType),
                         GetDisplayString(pNetResource->dwDisplayType),
                         GetUsageString(pNetResource->dwUsage),
                         pNetResource->lpLocalName,
                         pNetResource->lpRemoteName,
                         pNetResource->lpComment);
#endif

            // setup the new end of buffer
            StringZone = (PWCHAR)( (PBYTE) StringZone - SpaceNeeded);

            EntriesCopied++;

            pNetResource++;

            dwIndex++;

            dwCopyBytes -= FIELD_OFFSET( AFSNetworkProviderConnectionCB, RemoteName) +
                           pConnectionCB->RemoteNameLength +
                           pConnectionCB->CommentLength;

            if( dwCopyBytes == 0)
            {

                dwStatus = WN_SUCCESS;

                break;
            }

            pConnectionCB = (AFSNetworkProviderConnectionCB *)((char *)pConnectionCB +
                            FIELD_OFFSET( AFSNetworkProviderConnectionCB, RemoteName) +
                            pConnectionCB->RemoteNameLength +
                            pConnectionCB->CommentLength);
        }

        *lpcCount = EntriesCopied;

        // update entry index
        pEnumCB->CurrentIndex = dwIndex;

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPEnumResource Completed Count %d Index %d\n",
                     EntriesCopied,
                     dwIndex);
#endif

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectionCBBase != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectionCBBase);
        }
    }

    return dwStatus;
}

/*++

Routine Description:

    This routine closes the handle for enumeration of resources.

Arguments:

    hEnum  - the enumeration handle

Return Value:

    WN_SUCCESS if successful, otherwise the appropriate error

Notes:

    The sample only supports the notion of enumerating connected shares

--*/

DWORD APIENTRY
NPCloseEnum( HANDLE hEnum )
{

    AFSEnumerationCB *pEnumCB = (AFSEnumerationCB *)hEnum;

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPCloseEnum\n");
#endif

    if( pEnumCB->RemoteName != NULL)
    {

        HeapFree( GetProcessHeap( ), 0, (PVOID) pEnumCB->RemoteName);
    }

    HeapFree( GetProcessHeap( ), 0, (PVOID) hEnum );

    return WN_SUCCESS;
}

DWORD APIENTRY
NPGetResourceParent( LPNETRESOURCE   lpNetResource,
                     LPVOID  lpBuffer,
                     LPDWORD lpBufferSize )
{

    DWORD    dwStatus = WN_ACCESS_DENIED;
    WCHAR   *pwchRemoteName = NULL, *pwchSearch = NULL, *pwchSystem = NULL;
    LPNETRESOURCE lpOutResource = (LPNETRESOURCE) lpBuffer;

    if ( lpNetResource == NULL)
    {
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceParent NULL NETRESOURCE\n");
#endif
        return WN_MORE_DATA;
    }

    if( lpNetResource->lpRemoteName == NULL)
    {
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceParent NULL NETRESOURCE\n");
#endif
        return WN_BAD_NETNAME;
    }

    if ( lpNetResource->dwType != 0 &&
         lpNetResource->dwType != RESOURCETYPE_DISK)
    {
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceParent Bad dwType\n");
#endif
        return WN_BAD_VALUE;
    }

    if ( lpBufferSize == NULL )
    {

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceParent Null lpBufferSize\n");
#endif
        return WN_BAD_VALUE;
    }

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPGetResourceParent For remote name %s\n",
                 lpNetResource->lpRemoteName);
#endif

    pwchRemoteName = lpNetResource->lpRemoteName;

    pwchSearch = pwchRemoteName + (wcslen( pwchRemoteName) - 1);

    while( pwchSearch != pwchRemoteName)
    {

        if( *pwchSearch == L'\\')
        {

            *pwchSearch = L'\0';

            break;
        }

        pwchSearch--;
    }

    if( pwchSearch != pwchRemoteName)
    {

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceParent Processing parent %s\n",
                     lpNetResource->lpRemoteName);
#endif

        dwStatus = NPGetResourceInformation( lpNetResource,
                                             lpBuffer,
                                             lpBufferSize,
                                             &pwchSystem);
    }
    else
    {
        if ( lpOutResource == NULL ||
             *lpBufferSize < sizeof( NETRESOURCE) )
        {
            *lpBufferSize = sizeof( NETRESOURCE);

            return WN_MORE_DATA;
        }

        memset( lpOutResource, 0, sizeof( NETRESOURCE));

        return WN_SUCCESS;

    }

    return dwStatus;
}

DWORD APIENTRY
NPGetResourceInformation( LPNETRESOURCE   lpNetResource,
                          LPVOID  lpBuffer,
                          LPDWORD lpBufferSize,
                          LPWSTR  *lplpSystem )
{

    DWORD    dwStatus = WN_NOT_CONNECTED;
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    HANDLE   hControlDevice = NULL;
    NETRESOURCE *pNetResource = (NETRESOURCE *)lpBuffer;
    PWCHAR   pStringZone = NULL;
    UNICODE_STRING uniRemoteName;
    DWORD    ulRequiredLen = 0;
    DWORD    dwPassedSize;


    __Enter
    {
        if ( lplpSystem)
        {
            *lplpSystem = NULL;
        }

        ReadProviderNameString();

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetResourceInformation AFSRDFS is disabled, returning WN_BAD_NETNAME\n");
#endif

            try_return( dwStatus = WN_BAD_NETNAME);
        }

        if ( lpNetResource == NULL ||
             lpBufferSize == NULL )
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetResourceInformaton Null lpNetResource or lpBufferSize\n");
#endif
            return WN_BAD_VALUE;
        }

        if( lpNetResource->lpRemoteName == NULL)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetResourceInformation No resource name\n");
#endif

            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        dwPassedSize = *lpBufferSize;

        dwBufferSize = 0x1000;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {

            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        pConnectCB->RemoteNameLength = wcslen( lpNetResource->lpRemoteName) * sizeof( WCHAR);

        StringCbCopy( pConnectCB->RemoteName,
                      dwBufferSize - sizeof(AFSNetworkProviderConnectionCB),
                      lpNetResource->lpRemoteName);

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceInformation Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetResourceInformation OpenRedirector failure, returning WN_NET_ERROR\n");
#endif

            try_return( dwStatus = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_GET_CONNECTION_INFORMATION,
                                   pConnectCB,
                                   dwBufferSize,
                                   pConnectCB,
                                   dwBufferSize,
                                   lpBufferSize,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPGetResourceInformation Failed to get connection info from file system for local %s gle 0x%x\n",
                         lpNetResource->lpRemoteName, gle);
#endif
            try_return( dwStatus = WN_BAD_NETNAME);
        }

        uniRemoteName.Length = (USHORT)pConnectCB->RemoteNameLength;
        uniRemoteName.MaximumLength = uniRemoteName.Length;
        uniRemoteName.Buffer = pConnectCB->RemoteName;

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetResourceInformation For remote name %wZ Scope %08lX Type %08lX Usage %08lX\n",
                     &uniRemoteName,
                     pConnectCB->Scope,
                     pConnectCB->Type,
                     pConnectCB->Usage);
#endif

        // Determine the space needed for this entry...

        ulRequiredLen = sizeof( NETRESOURCE);

        ulRequiredLen += pConnectCB->RemoteNameLength + sizeof( WCHAR);

        ulRequiredLen += pConnectCB->CommentLength + sizeof( WCHAR);

        ulRequiredLen += cbProviderNameLength + sizeof( WCHAR);

        ulRequiredLen += pConnectCB->RemainingPathLength + sizeof( WCHAR);

        if( pNetResource == NULL ||
            ulRequiredLen > dwPassedSize)
        {

            *lpBufferSize = ulRequiredLen;

            try_return( dwStatus = WN_MORE_DATA);
        }

        pStringZone = (PWCHAR) ((char *)lpBuffer + sizeof( NETRESOURCE));

        pNetResource->dwScope       = 0 /* pConnectCB->Scope*/;
        pNetResource->dwType        = 0 /* pConnectCB->Type */;

        pNetResource->dwDisplayType = pConnectCB->DisplayType;

        pNetResource->dwUsage       = pConnectCB->Usage;

        pNetResource->lpLocalName = NULL;

        // copy remote name
        pNetResource->lpRemoteName = pStringZone;

        CopyMemory( pStringZone,
                    pConnectCB->RemoteName,
                    pConnectCB->RemoteNameLength);

        pStringZone += (pConnectCB->RemoteNameLength / sizeof(WCHAR));

        *pStringZone++ = L'\0';

        // copy comment
        pNetResource->lpComment = pStringZone;

        CopyMemory( pStringZone,
                    (void *)((char *)pConnectCB + pConnectCB->CommentOffset),
                    pConnectCB->CommentLength);

        pStringZone += (pConnectCB->CommentLength / sizeof(WCHAR));

        *pStringZone++ = L'\0';

        // copy remaining path
        if (pConnectCB->RemainingPathLength > 0)
        {
            *lplpSystem = pStringZone;

            CopyMemory( pStringZone,
                        (void *)((char *)pConnectCB + pConnectCB->RemainingPathOffset),
                        pConnectCB->RemainingPathLength);

            pStringZone += (pConnectCB->RemainingPathLength / sizeof(WCHAR));

            *pStringZone++ = L'\0';

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetResourceInformation For remote name %s returning remaining path %s\n",
                         pNetResource->lpRemoteName,
                         *lplpSystem);
#endif
        }

        // copy provider name
        pNetResource->lpProvider = pStringZone;

        StringCbCopy( pStringZone,
                      cbProviderNameLength + sizeof( WCHAR),
                      wszProviderName);

        pStringZone += (cbProviderNameLength / sizeof( WCHAR) + 1);

        *lpBufferSize = ulRequiredLen;

        dwStatus = WN_SUCCESS;

try_exit:

        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwStatus;
}

static VOID
SeparateRemainingPath( WCHAR * lpConnectionName, WCHAR **lppRemainingPath)
{
    WCHAR *pwch;
    WCHAR wch1, wch2;
    DWORD  dwCount;

    //
    // at this point the lpConnectionName contains the full name.  We need to
    // truncate it to \\server\share and move the remaining path back one position.
    //

    for ( pwch = lpConnectionName, dwCount = 0; *pwch; pwch++)
    {
        if ( *pwch == L'\\')
        {
            dwCount++;
        }

        if ( dwCount == 4)
        {
            break;
        }
    }

    if (*pwch == L'\\')
    {
        //
        // Found the remaining path that must be moved
        //

        *lppRemainingPath = pwch + 1;

        *pwch++ = 0;

        //
        // Find the end
        //
        for ( ; *pwch; pwch++);

        //
        // and work backwards moving the string
        // and then make sure that there is at least
        // a path separator.
        //

        *(pwch + 1) = 0;

        for ( ;pwch > *lppRemainingPath; pwch--)
        {
            *pwch = *(pwch - 1);
        }

        *pwch = L'\\';
    }
}

DWORD APIENTRY
NPGetUniversalName( LPCWSTR lpLocalPath,
                    DWORD   dwInfoLevel,
                    LPVOID  lpBuffer,
                    LPDWORD lpBufferSize )
{
    DWORD    dwStatus = WN_NOT_CONNECTED;
    WCHAR    wchLocalName[3];
    WCHAR    wchSubstName[MAX_PATH + 1];
    AFSNetworkProviderConnectionCB   *pConnectCB = NULL;
    DWORD    dwError = 0;
    DWORD    dwBufferSize = 0;
    DWORD    dwPassedSize = *lpBufferSize;
    DWORD    dwRemainingLength = *lpBufferSize;
    HANDLE   hControlDevice = NULL;
    DWORD    dwLocalPathLength = 0;
    DWORD    dwRemainingPathLength = 0;
    CHAR    *pch;

    __Enter
    {

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetUniversalName local path %s level 0x%X\n",
                     lpLocalPath ? lpLocalPath : L"(Null)",
                     dwInfoLevel);
#endif

        if ( NPIsFSDisabled())
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetUniversalName AFSRDFS is disabled, returning WN_NOT_CONNECTED\n");
#endif

            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        dwLocalPathLength = lstrlen( lpLocalPath);

        dwRemainingPathLength = dwLocalPathLength - 2;          // no drive letter

        if( dwLocalPathLength == 0)
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetUniversalName AFSRDFS is disabled, returning WN_BAD_LOCALNAME\n");
#endif

            try_return( dwStatus = WN_BAD_LOCALNAME);
        }

        if( lpBuffer == NULL ||
            lpBufferSize == NULL)
        {
#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetUniversalName No output buffer or size\n");
#endif
            try_return( dwStatus = WN_BAD_VALUE);
        }

        memset(lpBuffer, 0, dwPassedSize);

        if ( !DriveSubstitution( lpLocalPath, wchSubstName, sizeof( wchSubstName)))
        {
            wchLocalName[0] = towupper(lpLocalPath[0]);
            wchLocalName[1] = L':';
            wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"NPGetUniversalName Requesting UNC for %s level 0x%X\n",
                         wchLocalName,
                         dwInfoLevel);
#endif
        }
        else
        {

            ReadServerNameString();

            if ( wchSubstName[0] != L'\\' &&
                 wchSubstName[1] == L':')
            {

                wchLocalName[0] = towupper(wchSubstName[0]);
                wchLocalName[1] = L':';
                wchLocalName[2] = L'\0';

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName Requesting UNC for drive substitution %s -> %s\n",
                             wchSubstName,
                             wchLocalName);
#endif
            }
            else if ( _wcsnicmp( wchSubstName, wszServerNameUNC, cbServerNameUNCLength / sizeof( WCHAR)) == 0 &&
                      ( wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == L'\\' ||
                        wchSubstName[cbServerNameUNCLength / sizeof( WCHAR)] == 0))
            {
                HRESULT hr;

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName drive substitution %s is AFS; Level 0x%x BufferSize 0x%x\n",
                             wchSubstName,
                             dwInfoLevel,
                             dwPassedSize);
#endif

                dwBufferSize = (wcslen( wchSubstName) + 1) * sizeof( WCHAR);

                switch( dwInfoLevel)
                {

                case UNIVERSAL_NAME_INFO_LEVEL:
                {

                    UNIVERSAL_NAME_INFO *pUniversalInfo = (UNIVERSAL_NAME_INFO *)lpBuffer;

                    *lpBufferSize = sizeof( UNIVERSAL_NAME_INFO) + dwBufferSize;

                    if( dwPassedSize <= sizeof( UNIVERSAL_NAME_INFO))
                    {

#ifdef AFS_DEBUG_TRACE
                        AFSDbgPrint( L"NPGetUniversalName (UNIVERSAL_NAME_INFO) WN_MORE_DATA\n");
#endif

                        try_return( dwStatus = WN_MORE_DATA);
                    }

                    dwRemainingLength -= sizeof( UNIVERSAL_NAME_INFO);

                    pUniversalInfo->lpUniversalName = (LPTSTR)((char *)lpBuffer + sizeof( UNIVERSAL_NAME_INFO));

                    memcpy( pUniversalInfo->lpUniversalName,
                            wchSubstName,
                            min( dwBufferSize, dwRemainingLength));

                    dwRemainingLength -= min( dwBufferSize, dwRemainingLength);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (UNIVERSAL_NAME_INFO_LEVEL) lpBuffer: %p Name: (%p) \"%s\"\n",
                                 lpBuffer,
                                 pUniversalInfo->lpUniversalName,
                                 pUniversalInfo->lpUniversalName);
#endif

                    if ( dwPassedSize < *lpBufferSize)
                    {

                        try_return( dwStatus = WN_MORE_DATA);
                    }

                    try_return( dwStatus = WN_SUCCESS);
                }

                case REMOTE_NAME_INFO_LEVEL:
                {

                    REMOTE_NAME_INFO *pRemoteInfo = (REMOTE_NAME_INFO *)lpBuffer;

                    *lpBufferSize = sizeof( REMOTE_NAME_INFO) + 2 * dwBufferSize + sizeof( WCHAR);

                    if( dwPassedSize <= sizeof( REMOTE_NAME_INFO))
                    {

#ifdef AFS_DEBUG_TRACE
                        AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO) WN_MORE_DATA\n");
#endif

                        try_return( dwStatus = WN_MORE_DATA);
                    }

                    dwRemainingLength -= sizeof( REMOTE_NAME_INFO);

                    pRemoteInfo->lpUniversalName = (LPTSTR)((char *)lpBuffer + sizeof( REMOTE_NAME_INFO));

                    memcpy( pRemoteInfo->lpUniversalName,
                            wchSubstName,
                            min( dwRemainingLength, dwBufferSize));

                    dwRemainingLength -= min( dwRemainingLength, dwBufferSize);

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) UNI lpBuffer: %p Name: (%p) \"%s\"\n",
                                 lpBuffer,
                                 pRemoteInfo->lpUniversalName,
                                 pRemoteInfo->lpUniversalName);
#endif

                    if ( dwRemainingLength > dwBufferSize + sizeof( WCHAR))
                    {
                        pRemoteInfo->lpConnectionName = (LPTSTR)((char *)pRemoteInfo->lpUniversalName + dwBufferSize);

                        memcpy( pRemoteInfo->lpConnectionName,
                                wchSubstName,
                                min( dwRemainingLength, dwBufferSize));

                        dwRemainingLength -= min( dwRemainingLength, dwBufferSize) - sizeof( WCHAR);

                        SeparateRemainingPath( pRemoteInfo->lpConnectionName,
                                               &pRemoteInfo->lpRemainingPath);
                    }

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) CONN lpBuffer: %p Name: (%p) \"%s\"\n",
                                 lpBuffer,
                                 pRemoteInfo->lpConnectionName,
                                 pRemoteInfo->lpConnectionName ? pRemoteInfo->lpConnectionName : L"(null)");

                    AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) REMAIN lpBuffer: %p Name: (%p) \"%s\"\n",
                                 lpBuffer,
                                 pRemoteInfo->lpRemainingPath,
                                 pRemoteInfo->lpRemainingPath ? pRemoteInfo->lpRemainingPath : L"(null)");
#endif

                    if ( dwPassedSize < *lpBufferSize)
                    {

                        try_return( dwStatus = WN_MORE_DATA);
                    }

                    try_return( dwStatus = WN_SUCCESS);
                }

                default:
#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (UNKNOWN: 0x%X) WN_BAD_VALUE\n",
                                 dwInfoLevel);
#endif
                    try_return( dwStatus = WN_BAD_VALUE);
                }
            }
            else
            {

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName drive substitution %s is not AFS\n",
                             wchSubstName);
#endif
                try_return( dwStatus = WN_NOT_CONNECTED);
            }
        }

        dwBufferSize = 0x1000;

        pConnectCB = (AFSNetworkProviderConnectionCB *)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);

        if( pConnectCB == NULL)
        {
            try_return( dwStatus = WN_OUT_OF_MEMORY);
        }

        pConnectCB->LocalName = towupper(wchLocalName[0]);

        pConnectCB->RemoteNameLength = 0;

        pConnectCB->Version = AFS_NETWORKPROVIDER_INTERFACE_VERSION_1;

        pConnectCB->AuthenticationId = AFSRetrieveAuthId();

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetUniversalName Retrieved authentication id %08lX-%08lX\n",
                     pConnectCB->AuthenticationId.HighPart,
                     pConnectCB->AuthenticationId.LowPart);
#endif

        hControlDevice = OpenRedirector();

        if( hControlDevice == NULL)
        {

            try_return( dwStatus = WN_NET_ERROR);
        }

        dwError = DeviceIoControl( hControlDevice,
                                   IOCTL_AFS_GET_CONNECTION,
                                   pConnectCB,
                                   dwBufferSize,
                                   pConnectCB,
                                   dwBufferSize,
                                   &dwBufferSize,
                                   NULL);

        if( !dwError)
        {
#ifdef AFS_DEBUG_TRACE
            DWORD gle = GetLastError();

            AFSDbgPrint( L"NPGetUniversalName Failed to get connection from file system for local %s gle 0x%x\n",
                         wchLocalName, gle);
#endif
            try_return( dwStatus = WN_NOT_CONNECTED);
        }

        switch( dwInfoLevel)
        {

            case UNIVERSAL_NAME_INFO_LEVEL:
            {

                UNIVERSAL_NAME_INFO *pUniversalInfo = (UNIVERSAL_NAME_INFO *)lpBuffer;

                *lpBufferSize = sizeof( UNIVERSAL_NAME_INFO) + dwBufferSize + sizeof( WCHAR);

                *lpBufferSize += dwRemainingPathLength * sizeof( WCHAR);

                if( dwPassedSize <= sizeof( UNIVERSAL_NAME_INFO))
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (UNIVERSAL_NAME_INFO) WN_MORE_DATA\n");
#endif

                    try_return( dwStatus = WN_MORE_DATA);
                }

                dwRemainingLength -= sizeof( UNIVERSAL_NAME_INFO);

                pUniversalInfo->lpUniversalName = (LPTSTR)((char *)lpBuffer + sizeof( UNIVERSAL_NAME_INFO));

                pch = (char *)pUniversalInfo->lpUniversalName;

                memcpy( pch,
                        pConnectCB,
                        min( dwBufferSize, dwRemainingLength));

                pch += min( dwBufferSize, dwRemainingLength);

                dwRemainingLength -= min( dwBufferSize + sizeof(WCHAR), dwRemainingLength);

                memcpy( pch,
                        &lpLocalPath[2],
                        min(dwRemainingPathLength * sizeof( WCHAR), dwRemainingLength));

                pch += min(dwRemainingPathLength * sizeof( WCHAR), dwRemainingLength);

                dwRemainingLength -= min(dwRemainingPathLength * sizeof( WCHAR), dwRemainingLength);

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName (UNIVERSAL_NAME_INFO_LEVEL) lpBuffer: %p Name: (%p) \"%s\"\n",
                             lpBuffer,
                             pUniversalInfo->lpUniversalName,
                             pUniversalInfo->lpUniversalName);
#endif

                if ( dwPassedSize < *lpBufferSize)
                {

                    try_return( dwStatus = WN_MORE_DATA);
                }

                try_return( dwStatus = WN_SUCCESS);
            }

            case REMOTE_NAME_INFO_LEVEL:
            {

                REMOTE_NAME_INFO *pRemoteInfo = (REMOTE_NAME_INFO *)lpBuffer;

                *lpBufferSize = sizeof( REMOTE_NAME_INFO) + (2 * dwBufferSize + sizeof( WCHAR)) + 2 * sizeof( WCHAR);

                *lpBufferSize += 2 * dwRemainingPathLength * sizeof( WCHAR);

                if( dwPassedSize <= sizeof( REMOTE_NAME_INFO))
                {

#ifdef AFS_DEBUG_TRACE
                    AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO) WN_MORE_DATA\n");
#endif

                    try_return( dwStatus = WN_MORE_DATA);
                }

                dwRemainingLength -= sizeof( REMOTE_NAME_INFO);

                pRemoteInfo->lpUniversalName = (LPTSTR)((char *)lpBuffer + sizeof( REMOTE_NAME_INFO));

                pch = (char *)pRemoteInfo->lpUniversalName;

                memcpy( pch,
                        pConnectCB,
                        min( dwBufferSize, dwRemainingLength));

                pch += min( dwBufferSize, dwRemainingLength);

                dwRemainingLength -= min( dwBufferSize + sizeof( WCHAR), dwRemainingLength);

                memcpy( pch,
                        &lpLocalPath[2],
                        min(dwRemainingPathLength * sizeof( WCHAR), dwRemainingLength));

                pch += min((dwRemainingPathLength + 1) * sizeof( WCHAR), dwRemainingLength);

                dwRemainingLength -= min((dwRemainingPathLength + 1) * sizeof( WCHAR), dwRemainingLength);

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) UNI lpBuffer: %p Name: (%p) \"%s\"\n",
                             lpBuffer,
                             pRemoteInfo->lpUniversalName,
                             pRemoteInfo->lpUniversalName);
#endif

                if ( dwRemainingLength > dwBufferSize + sizeof( WCHAR))
                {
                    pRemoteInfo->lpConnectionName = (LPWSTR)pch;

                    memcpy( pch,
                            pConnectCB,
                            min( dwBufferSize, dwRemainingLength));

                    pch += min( dwBufferSize + sizeof( WCHAR), dwRemainingLength);

                    dwRemainingLength -= min( dwBufferSize + sizeof( WCHAR), dwRemainingLength);
                }


                if ( dwRemainingLength > dwRemainingPathLength + sizeof( WCHAR))
                {
                    pRemoteInfo->lpRemainingPath = (LPWSTR)pch;

                    memcpy( pch,
                            &lpLocalPath[2],
                            min((dwRemainingPathLength + 1) * sizeof( WCHAR), dwRemainingLength));

                    pch += min((dwRemainingPathLength + 1) * sizeof( WCHAR), dwRemainingLength);

                    dwRemainingLength -= min((dwLocalPathLength + 1) * sizeof( WCHAR), dwRemainingLength);
                }

#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) CONN lpBuffer: %p Name: (%p) \"%s\"\n",
                             lpBuffer,
                             pRemoteInfo->lpConnectionName,
                             pRemoteInfo->lpConnectionName ? pRemoteInfo->lpConnectionName : L"(null)");

                AFSDbgPrint( L"NPGetUniversalName (REMOTE_NAME_INFO_LEVEL) REMAIN lpBuffer: %p Name: (%p) \"%s\"\n",
                             lpBuffer,
                             pRemoteInfo->lpRemainingPath,
                             pRemoteInfo->lpRemainingPath ? pRemoteInfo->lpRemainingPath : L"(null)");
#endif

                if ( dwPassedSize < *lpBufferSize)
                {

                    try_return( dwStatus = WN_MORE_DATA);
                }

                try_return( dwStatus = WN_SUCCESS);
            }

            default:
#ifdef AFS_DEBUG_TRACE
                AFSDbgPrint( L"NPGetUniversalName (UNKNOWN: 0x%X) WN_BAD_VALUE\n",
                             dwInfoLevel);
#endif
                try_return( dwStatus = WN_BAD_VALUE);
        }

try_exit:

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPGetUniversalName BufferSize 0x%X\n",
                     *lpBufferSize);
#endif
        if ( hControlDevice != NULL)
        {

            CloseHandle( hControlDevice);
        }

        if( pConnectCB != NULL)
        {

            HeapFree( GetProcessHeap( ), 0, (PVOID) pConnectCB);
        }
    }

    return dwStatus;
}


static LPCWSTR
GetFormatFlags( DWORD dwFlags)
{
    static WCHAR Buffer[128] = L"";

    //
    // WNFMT_MULTILINE         0x01
    // WNFMT_ABBREVIATED       0x02
    // WNFMT_INENUM            0x10
    // WNFMT_CONNECTION        0x20
    //

    Buffer[0] = L'\0';

    if ( dwFlags & WNFMT_MULTILINE )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"MULTILINE|");
    }

    if ( dwFlags & WNFMT_INENUM )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"ABBREVIATED|");
    }

    if ( dwFlags & WNFMT_INENUM )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"INENUM|");
    }

    if ( dwFlags & WNFMT_CONNECTION )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"CONNECTION|");
    }

    if ( dwFlags & ~(WNFMT_MULTILINE|WNFMT_ABBREVIATED|WNFMT_INENUM|WNFMT_CONNECTION) )
    {
        StringCbCat( Buffer, sizeof(Buffer), L"UNKNOWN|");
    }

    Buffer[lstrlen(Buffer)-1] = L'\0';

    return Buffer;
}

DWORD
NPFormatNetworkName( LPTSTR  lpRemoteName,
                     LPTSTR  lpFormattedName,
                     LPDWORD lpnLength,
                     DWORD dwFlags,
                     DWORD dwAveCharPerLine)
{

    DWORD dwLen = 0, dwCurrentLen = 0;
    LPTSTR pCurrentName = lpRemoteName;

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPFormatNetworkName Remote %s Flags %s (0x%x) CharsPerLine %u\n",
                 lpRemoteName,
                 dwFlags,
                 GetFormatFlags( dwFlags),
                 dwAveCharPerLine);
#endif


    //
    // Walk back in the name until we hit a \
    //

    dwLen = wcslen( lpRemoteName);

    pCurrentName += (dwLen - 1);

    if ( pCurrentName[ 0] != L'\\')
    {

        while( dwLen > 0)
        {

            if( pCurrentName[ 0] == L'\\')
            {

                pCurrentName++;

                break;
            }

            pCurrentName--;

            dwLen--;

            dwCurrentLen++;
        }
    }

    if( *lpnLength  < dwCurrentLen * sizeof( WCHAR))
    {

        *lpnLength = dwCurrentLen * sizeof( WCHAR);

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"NPFormatNetworkName remote name %s WN_MORE_DATA\n",
                     lpRemoteName);
#endif

        return WN_MORE_DATA;
    }

    StringCbCopy( lpFormattedName,
                  *lpnLength,
                  pCurrentName);

    *lpnLength = dwCurrentLen * sizeof( WCHAR);

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPFormatNetworkName remote name %s as %s\n",
                 lpRemoteName,
                 lpFormattedName);
#endif

    return WN_SUCCESS;
}

/************************************************************
/       Unsupported entry points
/************************************************************/

//
// AuthGroup processing is implemented in src/WINNT/afsd/afslogon.c
//
DWORD APIENTRY
NPLogonNotify(
    PLUID   lpLogonId,
    LPCWSTR lpAuthentInfoType,
    LPVOID  lpAuthentInfo,
    LPCWSTR lpPreviousAuthentInfoType,
    LPVOID  lpPreviousAuthentInfo,
    LPWSTR  lpStationName,
    LPVOID  StationHandle,
    LPWSTR  *lpLogonScript)
{

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPLogonNotify, returning WN_NOT_SUPPORTED\n");
#endif

    return WN_NOT_SUPPORTED;
}

DWORD APIENTRY
NPPasswordChangeNotify (
    LPCWSTR    lpAuthentInfoType,
    LPVOID    lpAuthentInfo,
    LPCWSTR    lpPreviousAuthentInfoType,
    LPVOID    lpPreviousAuthentInfo,
    LPWSTR    lpStationName,
    LPVOID    StationHandle,
    DWORD    dwChangeInfo )
{

#ifdef AFS_DEBUG_TRACE
    AFSDbgPrint( L"NPPasswordChangeNotify, returning WN_NOT_SUPPORTED\n");
#endif

    SetLastError( WN_NOT_SUPPORTED );

    return WN_NOT_SUPPORTED;
}

DWORD APIENTRY
NPGetUser( LPTSTR lpName,
           LPTSTR lpUserName,
           LPDWORD lpBufferSize)
{

    DWORD rc = WN_NOT_SUPPORTED;

    AFSDbgPrint( L"NPGetUser Entry Name %s\n", lpName);

    return rc;
}


DWORD
APIENTRY
NPGetReconnectFlags( LPWSTR  lpRemoteName,
                     unsigned char *Parameter2)
{

    DWORD    dwStatus = WN_NOT_SUPPORTED;

    AFSDbgPrint( L"NPGetReconnectFlags RemoteName %s\n",
                 lpRemoteName);

    return dwStatus;
}


DWORD
APIENTRY
I_SystemFocusDialog( VOID)
{

    DWORD    dwStatus = WN_NOT_SUPPORTED;

    AFSDbgPrint( L"I_SystemFocusDialog\n");

    return dwStatus;
}

/************************************************************
/       END Unsupported entry points
/************************************************************/


HANDLE
OpenRedirector()
{

    HANDLE hControlDevice = NULL;
    WCHAR wchError[ 256];

    hControlDevice = CreateFile( AFS_SYMLINK_W,
                                 GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL,
                                 OPEN_EXISTING,
                                 0,
                                 NULL );

    if( hControlDevice == INVALID_HANDLE_VALUE)
    {

        hControlDevice = NULL;
#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"Failed to open control device error: %d\n",
                     GetLastError());
#endif
    }
#if 0
    //
    // only do this if you want network shares to fail to mount
    // when the file system is not yet ready
    //
    else {

        AFSDriverStatusRespCB   respCB;
        DWORD                   dwBytes;

        memset( &respCB, '\0', sizeof( AFSDriverStatusRespCB));

        if ( !DeviceIoControl( hControlDevice,
                               IOCTL_AFS_STATUS_REQUEST,
                               NULL,
                               0,
                               (void *)&respCB,
                               sizeof( AFSDriverStatusRespCB),
                               &dwBytes,
                               NULL) ||
             dwBytes != sizeof(AFSDriverStatusRespCB) ||
             respCB.Status != AFS_DRIVER_STATUS_READY )
        {

            CloseHandle( hControlDevice);

            hControlDevice = NULL;
        }
    }
#endif

    return hControlDevice;
}

LARGE_INTEGER
AFSRetrieveAuthId()
{

    LARGE_INTEGER liAuthId = {0,0};
    HANDLE hToken = NULL;
    TOKEN_STATISTICS stTokenInfo;
    DWORD dwCopyBytes = 0;

    if ( !OpenThreadToken( GetCurrentThread(),
                           TOKEN_QUERY,
                           FALSE,       // Impersonation
                           &hToken))
    {
        if( !OpenProcessToken( GetCurrentProcess(),
                               TOKEN_QUERY,
                               &hToken))
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"AFSRetrieveAuthId Failed to retrieve Thread and Process tokens 0x%X\n",
                         GetLastError());
#endif
        }
        else
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"AFSRetrieveAuthId Retrieved Process Token\n");
#endif
        }
    }
    else
    {

#ifdef AFS_DEBUG_TRACE
        AFSDbgPrint( L"AFSRetrieveAuthId Retrieved Thread Token\n");
#endif
    }

    if ( hToken != NULL)
    {

        if( !GetTokenInformation( hToken,
                                  TokenStatistics,
                                  &stTokenInfo,
                                  sizeof( TOKEN_STATISTICS),
                                  &dwCopyBytes))
        {

#ifdef AFS_DEBUG_TRACE
            AFSDbgPrint( L"AFSRetrieveAuthId Failed to retrieve token information 0x%X\n",
                         GetLastError());
#endif
        }
        else
        {

            liAuthId.HighPart = stTokenInfo.AuthenticationId.HighPart;
            liAuthId.LowPart = stTokenInfo.AuthenticationId.LowPart;
        }

        CloseHandle( hToken);
    }

    return liAuthId;
}

static BOOL
Debug(void)
{
    static int init = 0;
    static BOOL debug = 0;

    if ( !init ) {
        HKEY hk;

        if (RegOpenKey (HKEY_LOCAL_MACHINE,
                         TEXT("SYSTEM\\CurrentControlSet\\Services\\AFSRedirector\\NetworkProvider"), &hk) == 0)
        {
            DWORD dwSize = sizeof(BOOL);
            DWORD dwType = REG_DWORD;
            RegQueryValueEx (hk, TEXT("Debug"), NULL, &dwType, (PBYTE)&debug, &dwSize);
            RegCloseKey (hk);
        }
        init = 1;
    }

    return debug;
}

ULONG
_cdecl
AFSDbgPrint(
    PWCHAR Format,
    ...
    )
{
    HRESULT rc = S_OK;
    WCHAR wszbuffer[512];
    va_list marker;

    if ( !Debug() )
        return 0;

    va_start( marker, Format );
    {
        StringCbPrintf( wszbuffer, sizeof(wszbuffer), L"[%d-%08X] ",
#ifdef AMD64
                           64,
#else
                           32,
#endif
                           GetCurrentThreadId());

        rc = StringCbVPrintfW( &wszbuffer[ 14], sizeof(wszbuffer) - 14, Format, marker);

        if (SUCCEEDED(rc))
            OutputDebugString( wszbuffer );
        else
            OutputDebugString(L"AFSDbgPrint Failed to create string\n");
    }
    return SUCCEEDED(rc) ? 1 : 0;
}
