/*++

Copyright (c) 1991-1993  Microsoft Corporation

Module Name:

    device.c

Abstract:

    This module contains the support routines for the APIs that call
    into the NetWare redirector

Author:

    Rita Wong       (ritaw)     20-Feb-1991
    Colin Watson    (colinw)    30-Dec-1992

Revision History:

--*/

#include <nw.h>
#include <nwcons.h>
#include <nwxchg.h>
#include <nwapi32.h>
#include <nwstatus.h>
#include <nwmisc.h>
#include <nwcons.h>
#include <nds.h>
#include <svcguid.h>
#include <tdi.h>

#define NW_LINKAGE_REGISTRY_PATH  L"NWCWorkstation\\Linkage"
#define NW_BIND_VALUENAME         L"Bind"

#define TWO_KB                  2048
#define EIGHT_KB                8192
#define EXTRA_BYTES              256

#define TREECHAR                L'*'
#define BUFFSIZE                1024

//-------------------------------------------------------------------//
//                                                                   //
// Local Function Prototypes                                         //
//                                                                   //
//-------------------------------------------------------------------//


STATIC
NTSTATUS
BindToEachTransport(
    IN PWSTR ValueName,
    IN ULONG ValueType,
    IN PVOID ValueData,
    IN ULONG ValueLength,
    IN PVOID Context,
    IN PVOID EntryContext
    );

DWORD
NwBindTransport(
    IN  LPWSTR TransportName,
    IN  DWORD QualityOfService
    );

DWORD
GetConnectedBinderyServers(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    );

DWORD
GetTreeEntriesFromBindery(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    );

DWORD
NwGetConnectionStatus(
    IN     LPWSTR  pszServerName,
    IN OUT PDWORD  ResumeKey,
    OUT    LPBYTE  *Buffer,
    OUT    PDWORD  EntriesRead
    );

VOID
GetNearestDirServer(
    IN  LPWSTR  TreeName,
    OUT LPDWORD lpdwReplicaAddressSize,
    OUT LPBYTE  lpReplicaAddress
    );

BOOL
NwpCompareTreeNames(
    LPWSTR lpServiceInstanceName,
    LPWSTR lpTreeName
    );

//-------------------------------------------------------------------//
//                                                                   //
// Global variables                                                  //
//                                                                   //
//-------------------------------------------------------------------//

//
// Handle to the Redirector FSD
//
STATIC HANDLE RedirDeviceHandle = NULL;

//
// Redirector name in NT string format
//
STATIC UNICODE_STRING RedirDeviceName;


DWORD
NwInitializeRedirector(
    VOID
    )
/*++

Routine Description:

    This routine initializes the NetWare redirector FSD.

Arguments:

    None.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD error;
    NWR_REQUEST_PACKET Rrp;


    //
    // Initialize global handles
    //
    RedirDeviceHandle = NULL;

    //
    // Initialize the global NT-style redirector device name string.
    //
    RtlInitUnicodeString(&RedirDeviceName, DD_NWFS_DEVICE_NAME_U);

    //
    // Load driver
    //
    error = NwLoadOrUnloadDriver(TRUE);

    if (error != NO_ERROR && error != ERROR_SERVICE_ALREADY_RUNNING) {
        return error;
    }

    if ((error = NwOpenRedirector()) != NO_ERROR) {

        //
        // Unload the redirector driver
        //
        (void) NwLoadOrUnloadDriver(FALSE);
        return error;
    }

    //
    // Send the start FSCTL to the redirector
    //
    Rrp.Version = REQUEST_PACKET_VERSION;

    return NwRedirFsControl(
                RedirDeviceHandle,
                FSCTL_NWR_START,
                &Rrp,
                sizeof(NWR_REQUEST_PACKET),
                NULL,
                0,
                NULL
                );
}



DWORD
NwOpenRedirector(
    VOID
    )
/*++

Routine Description:

    This routine opens the NT NetWare redirector FSD.

Arguments:

    None.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    return RtlNtStatusToDosError(
               NwOpenHandle(&RedirDeviceName, FALSE, &RedirDeviceHandle)
               );
}



DWORD
NwShutdownRedirector(
    VOID
    )
/*++

Routine Description:

    This routine stops the NetWare Redirector FSD and unloads it if
    possible.

Arguments:

    None.

Return Value:

    NO_ERROR or ERROR_REDIRECTOR_HAS_OPEN_HANDLES

--*/
{
    NWR_REQUEST_PACKET Rrp;
    DWORD error;


    Rrp.Version = REQUEST_PACKET_VERSION;

    error = NwRedirFsControl(
                RedirDeviceHandle,
                FSCTL_NWR_STOP,
                &Rrp,
                sizeof(NWR_REQUEST_PACKET),
                NULL,
                0,
                NULL
                );

    (void) NtClose(RedirDeviceHandle);

    RedirDeviceHandle = NULL;

    if (error != ERROR_REDIRECTOR_HAS_OPEN_HANDLES) {

        //
        // Unload the redirector only if all its open handles are closed.
        //
        (void) NwLoadOrUnloadDriver(FALSE);
    }

    return error;
}


DWORD
NwLoadOrUnloadDriver(
    BOOL Load
    )
/*++

Routine Description:

    This routine loads or unloads the NetWare redirector driver.

Arguments:

    Load - Supplies the flag which if TRUE load the driver; otherwise
        unloads the driver.

Return Value:

    NO_ERROR or reason for failure.

--*/
{

    LPWSTR DriverRegistryName;
    UNICODE_STRING DriverRegistryString;
    NTSTATUS ntstatus;
    BOOLEAN WasEnabled;


    DriverRegistryName = (LPWSTR) LocalAlloc(
                                      LMEM_FIXED,
                                      (UINT) (sizeof(SERVICE_REGISTRY_KEY) +
                                              (wcslen(NW_DRIVER_NAME) *
                                               sizeof(WCHAR)))
                                      );

    if (DriverRegistryName == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    ntstatus = RtlAdjustPrivilege(
                   SE_LOAD_DRIVER_PRIVILEGE,
                   TRUE,
                   FALSE,
                   &WasEnabled
                   );

    if (! NT_SUCCESS(ntstatus)) {
        (void) LocalFree(DriverRegistryName);
        return RtlNtStatusToDosError(ntstatus);
    }

    wcscpy(DriverRegistryName, SERVICE_REGISTRY_KEY);
    wcscat(DriverRegistryName, NW_DRIVER_NAME);

    RtlInitUnicodeString(&DriverRegistryString, DriverRegistryName);

    if (Load) {
        ntstatus = NtLoadDriver(&DriverRegistryString);
    }
    else {
        ntstatus = NtUnloadDriver(&DriverRegistryString);
    }

    (void) RtlAdjustPrivilege(
               SE_LOAD_DRIVER_PRIVILEGE,
               WasEnabled,
               FALSE,
               &WasEnabled
               );

    (void) LocalFree(DriverRegistryName);

    if (Load) {
        if (ntstatus != STATUS_SUCCESS && ntstatus != STATUS_IMAGE_ALREADY_LOADED) {
            LPWSTR SubString[1];

            KdPrint(("NWWORKSTATION: NtLoadDriver returned %08lx\n", ntstatus));

            SubString[0] = NW_DRIVER_NAME;

            NwLogEvent(
                EVENT_NWWKSTA_CANT_CREATE_REDIRECTOR,
                1,
                SubString,
                ntstatus
                );
        }
    }

    if (ntstatus == STATUS_OBJECT_NAME_NOT_FOUND) {
        return ERROR_FILE_NOT_FOUND;
    }

    return NwMapStatus(ntstatus);
}


DWORD
NwRedirFsControl(
    IN  HANDLE FileHandle,
    IN  ULONG RedirControlCode,
    IN  PNWR_REQUEST_PACKET Rrp,
    IN  ULONG RrpLength,
    IN  PVOID SecondBuffer OPTIONAL,
    IN  ULONG SecondBufferLength,
    OUT PULONG Information OPTIONAL
    )
/*++

Routine Description:

Arguments:

    FileHandle - Supplies a handle to the file or device on which the service
        is being performed.

    RedirControlCode - Supplies the NtFsControlFile function code given to
        the redirector.

    Rrp - Supplies the redirector request packet.

    RrpLength - Supplies the length of the redirector request packet.

    SecondBuffer - Supplies the second buffer in call to NtFsControlFile.

    SecondBufferLength - Supplies the length of the second buffer.

    Information - Returns the information field of the I/O status block.

Return Value:

    NO_ERROR or reason for failure.

--*/

{
    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;


    //
    // Send the request to the Redirector FSD.
    //
    ntstatus = NtFsControlFile(
                   FileHandle,
                   NULL,
                   NULL,
                   NULL,
                   &IoStatusBlock,
                   RedirControlCode,
                   (PVOID) Rrp,
                   RrpLength,
                   SecondBuffer,
                   SecondBufferLength
                   );

    if (ntstatus == STATUS_SUCCESS) {
        ntstatus = IoStatusBlock.Status;
    }

    if (ARGUMENT_PRESENT(Information)) {
        *Information = IoStatusBlock.Information;
    }

#if DBG
    if (ntstatus != STATUS_SUCCESS) {
        IF_DEBUG(DEVICE) {
            KdPrint(("NWWORKSTATION: fsctl to redir returns %08lx\n", ntstatus));
        }
    }
#endif

    return NwMapStatus(ntstatus);
}


DWORD
NwBindToTransports(
    VOID
    )

/*++

Routine Description:

    This routine binds to every transport specified under the linkage
    key of the NetWare Workstation service.

Arguments:

    None.

Return Value:

    NET_API_STATUS - success/failure of the operation.

--*/

{
    NTSTATUS ntstatus;
    PRTL_QUERY_REGISTRY_TABLE QueryTable;
    ULONG NumberOfBindings = 0;


    //
    // Ask the RTL to call us back for each subvalue in the MULTI_SZ
    // value \NWCWorkstation\Linkage\Bind.
    //

    if ((QueryTable = (PVOID) LocalAlloc(
                                  LMEM_ZEROINIT,
                                  sizeof(RTL_QUERY_REGISTRY_TABLE) * 2
                                  )) == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    QueryTable[0].QueryRoutine = (PRTL_QUERY_REGISTRY_ROUTINE) BindToEachTransport;
    QueryTable[0].Flags = 0;
    QueryTable[0].Name = NW_BIND_VALUENAME;
    QueryTable[0].EntryContext = NULL;
    QueryTable[0].DefaultType = REG_NONE;
    QueryTable[0].DefaultData = NULL;
    QueryTable[0].DefaultLength = 0;

    QueryTable[1].QueryRoutine = NULL;
    QueryTable[1].Flags = 0;
    QueryTable[1].Name = NULL;

    ntstatus = RtlQueryRegistryValues(
                   RTL_REGISTRY_SERVICES,
                   NW_LINKAGE_REGISTRY_PATH,
                   QueryTable,
                   &NumberOfBindings,
                   NULL
                   );

    (void) LocalFree((HLOCAL) QueryTable);

    //
    // If failed to bind to any transports, the workstation will
    // not start.
    //

    if (! NT_SUCCESS(ntstatus)) {
#if DBG
        IF_DEBUG(INIT) {
            KdPrint(("NwBindToTransports: RtlQueryRegistryValues failed: "
                      "%lx\n", ntstatus));
        }
#endif
        return RtlNtStatusToDosError(ntstatus);
    }

    if (NumberOfBindings == 0) {

        NwLogEvent(
            EVENT_NWWKSTA_NO_TRANSPORTS,
            0,
            NULL,
            NO_ERROR
            );

        KdPrint(("NWWORKSTATION: NwBindToTransports: could not bind "
                 "to any transport\n"));

        return ERROR_INVALID_PARAMETER; // BUGBUG: Bad return code.
                                        // Create a NetWare specific set?
    }

    return NO_ERROR;
}


STATIC
NTSTATUS
BindToEachTransport(
    IN PWSTR ValueName,
    IN ULONG ValueType,
    IN PVOID ValueData,
    IN ULONG ValueLength,
    IN PVOID Context,
    IN PVOID EntryContext
    )
{
    DWORD error;
    LPDWORD NumberOfBindings = Context;
    LPWSTR SubStrings[2];
    static DWORD QualityOfService = 65536;


    UNREFERENCED_PARAMETER(ValueName);
    UNREFERENCED_PARAMETER(ValueLength);
    UNREFERENCED_PARAMETER(EntryContext);

    //
    // The value type must be REG_SZ (translated from REG_MULTI_SZ by
    // the RTL).
    //
    if (ValueType != REG_SZ) {

        SubStrings[0] = ValueName;
        SubStrings[1] = NW_LINKAGE_REGISTRY_PATH;

        NwLogEvent(
            EVENT_NWWKSTA_INVALID_REGISTRY_VALUE,
            2,
            SubStrings,
            NO_ERROR
            );

            KdPrint(("NWWORKSTATION: Skipping invalid value %ws\n", ValueName));

        return STATUS_SUCCESS;
    }

    //
    // The value data is the name of the transport device object.
    //

    //
    // Bind to the transport.
    //

#if DBG
    IF_DEBUG(INIT) {
        KdPrint(("NWWORKSTATION: Binding to transport %ws with QOS %lu\n",
                ValueData, QualityOfService));
    }
#endif

    error = NwBindTransport(ValueData, QualityOfService--);

    if (error != NO_ERROR) {

        //
        // If failed to bind to one transport, don't fail starting yet.
        // Try other transports.
        //
        SubStrings[0] = ValueData;

        NwLogEvent(
            EVENT_NWWKSTA_CANT_BIND_TO_TRANSPORT,
            1,
            SubStrings,
            error
            );
    }
    else {
        (*NumberOfBindings)++;
    }

    return STATUS_SUCCESS;
}


DWORD
NwBindTransport(
    IN  LPWSTR TransportName,
    IN  DWORD QualityOfService
    )
/*++

Routine Description:

    This function binds the specified transport to the redirector
    and the datagram receiver.

    NOTE: The transport name length pass to the redirector and
          datagram receiver is the number of bytes.

Arguments:

    TransportName - Supplies the name of the transport to bind to.

    QualityOfService - Supplies a value which specifies the search
        order of the transport with respect to other transports.  The
        highest value is searched first.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    DWORD RequestPacketSize;
    DWORD TransportNameSize = wcslen(TransportName) * sizeof(WCHAR);

    PNWR_REQUEST_PACKET Rrp;


    //
    // Size of request packet buffer
    //
    RequestPacketSize = TransportNameSize + sizeof(NWR_REQUEST_PACKET);

    //
    // Allocate memory for redirector/datagram receiver request packet
    //
    if ((Rrp = (PVOID) LocalAlloc(LMEM_ZEROINIT, (UINT) RequestPacketSize)) == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // Get redirector to bind to transport
    //
    Rrp->Version = REQUEST_PACKET_VERSION;
    Rrp->Parameters.Bind.QualityOfService = QualityOfService;

    Rrp->Parameters.Bind.TransportNameLength = TransportNameSize;
    wcscpy((LPWSTR) Rrp->Parameters.Bind.TransportName, TransportName);

    if ((status = NwRedirFsControl(
                      RedirDeviceHandle,
                      FSCTL_NWR_BIND_TO_TRANSPORT,
                      Rrp,
                      RequestPacketSize,
                      NULL,
                      0,
                      NULL
                      )) != NO_ERROR) {

        KdPrint(("NWWORKSTATION: NwBindTransport fsctl to bind to transport %ws failed\n",
                 TransportName));
    }

    (void) LocalFree((HLOCAL) Rrp);
    return status;
}


DWORD
NwCreateTreeConnectName(
    IN  LPWSTR UncName,
    IN  LPWSTR LocalName OPTIONAL,
    OUT PUNICODE_STRING TreeConnectStr
    )
/*++

Routine Description:

    This function replaces \\ with \Device\NwRdr\LocalName:\ in the
    UncName to form the NT-style tree connection name.  LocalName:\ is part
    of the tree connection name only if LocalName is specified.  A buffer
    is allocated by this function and returned as the output string.

Arguments:

    UncName - Supplies the UNC name of the shared resource.

    LocalName - Supplies the local device name for the redirection.

    TreeConnectStr - Returns a string with a newly allocated buffer that
        contains the NT-style tree connection name.

Return Value:

    NO_ERROR - the operation was successful.

    ERROR_NOT_ENOUGH_MEMORY - Could not allocate output buffer.

--*/
{
    DWORD UncNameLength = wcslen(UncName);


    //
    // Initialize tree connect string maximum length to hold
    //            \Device\NwRdr\LocalName:\Server\Volume\Path
    //
    TreeConnectStr->MaximumLength = RedirDeviceName.Length +
        sizeof(WCHAR) +                                // For '\'
        (ARGUMENT_PRESENT(LocalName) ? (wcslen(LocalName) * sizeof(WCHAR)) : 0) +
        (USHORT) (UncNameLength * sizeof(WCHAR));      // Includes '\' and
                                                       // term char


    if ((TreeConnectStr->Buffer = (PWSTR) LocalAlloc(
                                              LMEM_ZEROINIT,
                                              (UINT) TreeConnectStr->MaximumLength
                                              )) == NULL) {
        KdPrint(("NWWORKSTATION: NwCreateTreeConnectName LocalAlloc failed %lu\n",
                 GetLastError()));
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // Copy \Device\NwRdr
    //
    RtlCopyUnicodeString(TreeConnectStr, &RedirDeviceName);

    //
    // Concatenate \LocalName:
    //
    if (ARGUMENT_PRESENT(LocalName)) {

        wcscat(TreeConnectStr->Buffer, L"\\");
        TreeConnectStr->Length += sizeof(WCHAR);

        wcscat(TreeConnectStr->Buffer, LocalName);

        TreeConnectStr->Length += (USHORT) (wcslen(LocalName) * sizeof(WCHAR));
    }

    //
    // Concatenate \Server\Volume\Path
    //
    wcscat(TreeConnectStr->Buffer, &UncName[1]);
    TreeConnectStr->Length += (USHORT) ((UncNameLength - 1) * sizeof(WCHAR));

#if DBG
    IF_DEBUG(CONNECT) {
        KdPrint(("NWWORKSTATION: NwCreateTreeConnectName %ws, maxlength %u, length %u\n",
                 TreeConnectStr->Buffer, TreeConnectStr->MaximumLength,
                 TreeConnectStr->Length));
    }
#endif

    return NO_ERROR;
}



DWORD
NwOpenCreateConnection(
    IN PUNICODE_STRING TreeConnectionName,
    IN LPWSTR UserName OPTIONAL,
    IN LPWSTR Password OPTIONAL,
    IN LPWSTR UncName,
    IN ACCESS_MASK DesiredAccess,
    IN ULONG CreateDisposition,
    IN ULONG CreateOptions,
    IN ULONG ConnectionType,
    OUT PHANDLE TreeConnectionHandle,
    OUT PULONG Information OPTIONAL
    )
/*++

Routine Description:

    This function asks the redirector to either open an existing tree
    connection (CreateDisposition == FILE_OPEN), or create a new tree
    connection if one does not exist (CreateDisposition == FILE_CREATE).

    The password and user name passed to the redirector via the EA buffer
    in the NtCreateFile call.  The EA buffer is NULL if neither password
    or user name is specified.

    The redirector expects the EA descriptor strings to be in ANSI
    but the password and username themselves are in Unicode.

Arguments:

    TreeConnectionName - Supplies the name of the tree connection in NT-style
        file name format: \Device\NwRdr\Server\Volume\Directory

    UserName - Supplies the user name to create the tree connection with.

    Password - Supplies the password to create the tree connection with.

    DesiredAccess - Supplies the access need on the connection handle.

    CreateDisposition - Supplies the create disposition value to either
        open or create the tree connection.

    CreateOptions - Supplies the options used when creating or opening
        the tree connection.

    ConnectionType - Supplies the type of the connection (DISK, PRINT,
        or ANY).

    TreeConnectionHandle - Returns the handle to the tree connection
        created/opened by the redirector.

    Information - Returns the information field of the I/O status block.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    NTSTATUS ntstatus;

    OBJECT_ATTRIBUTES UncNameAttributes;
    IO_STATUS_BLOCK IoStatusBlock;

    PFILE_FULL_EA_INFORMATION EaBuffer = NULL;
    PFILE_FULL_EA_INFORMATION Ea;
    ULONG EaBufferSize = 0;

    UCHAR EaNamePasswordSize = (UCHAR) (ROUND_UP_COUNT(
                                            strlen(EA_NAME_PASSWORD) + sizeof(CHAR),
                                            ALIGN_WCHAR
                                            ) - sizeof(CHAR));
    UCHAR EaNameUserNameSize = (UCHAR) (ROUND_UP_COUNT(
                                            strlen(EA_NAME_USERNAME) + sizeof(CHAR),
                                            ALIGN_WCHAR
                                            ) - sizeof(CHAR));

    UCHAR EaNameTypeSize = (UCHAR) (ROUND_UP_COUNT(
                                        strlen(EA_NAME_TYPE) + sizeof(CHAR),
                                        ALIGN_DWORD
                                        ) - sizeof(CHAR));

    USHORT PasswordSize = 0;
    USHORT UserNameSize = 0;
    USHORT TypeSize = sizeof(ULONG);



    InitializeObjectAttributes(
        &UncNameAttributes,
        TreeConnectionName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    //
    // Calculate the number of bytes needed for the EA buffer to put the
    // password or user name.
    //
    if (ARGUMENT_PRESENT(Password)) {

#if DBG
        IF_DEBUG(CONNECT) {
            KdPrint(("NWWORKSTATION: NwOpenCreateConnection password is %ws\n",
                     Password));
        }
#endif

        PasswordSize = (USHORT) (wcslen(Password) * sizeof(WCHAR));

        EaBufferSize = ROUND_UP_COUNT(
                           FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                           EaNamePasswordSize + sizeof(CHAR) +
                           PasswordSize,
                           ALIGN_DWORD
                           );
    }

    if (ARGUMENT_PRESENT(UserName)) {

#if DBG
        IF_DEBUG(CONNECT) {
            KdPrint(("NWWORKSTATION: NwOpenCreateConnection username is %ws\n",
                     UserName));
        }
#endif

        UserNameSize = (USHORT) (wcslen(UserName) * sizeof(WCHAR));

        EaBufferSize += ROUND_UP_COUNT(
                            FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                            EaNameUserNameSize + sizeof(CHAR) +
                            UserNameSize,
                            ALIGN_DWORD
                            );
    }

    EaBufferSize += FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                    EaNameTypeSize + sizeof(CHAR) +
                    TypeSize;

    //
    // Allocate the EA buffer
    //
    if ((EaBuffer = (PFILE_FULL_EA_INFORMATION) LocalAlloc(
                                                    LMEM_ZEROINIT,
                                                    (UINT) EaBufferSize
                                                    )) == NULL) {
        status = GetLastError();
        goto FreeMemory;
    }

    Ea = EaBuffer;

    if (ARGUMENT_PRESENT(Password)) {

        //
        // Copy the EA name into EA buffer.  EA name length does not
        // include the zero terminator.
        //
        strcpy((LPSTR) Ea->EaName, EA_NAME_PASSWORD);
        Ea->EaNameLength = EaNamePasswordSize;

        //
        // Copy the EA value into EA buffer.  EA value length does not
        // include the zero terminator.
        //
        wcscpy(
            (LPWSTR) &(Ea->EaName[EaNamePasswordSize + sizeof(CHAR)]),
            Password
            );

        Ea->EaValueLength = PasswordSize;

        Ea->NextEntryOffset = ROUND_UP_COUNT(
                                  FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                                  EaNamePasswordSize + sizeof(CHAR) +
                                  PasswordSize,
                                  ALIGN_DWORD
                                  );

        Ea->Flags = 0;

        (ULONG) Ea += Ea->NextEntryOffset;
    }

    if (ARGUMENT_PRESENT(UserName)) {

        //
        // Copy the EA name into EA buffer.  EA name length does not
        // include the zero terminator.
        //
        strcpy((LPSTR) Ea->EaName, EA_NAME_USERNAME);
        Ea->EaNameLength = EaNameUserNameSize;

        //
        // Copy the EA value into EA buffer.  EA value length does not
        // include the zero terminator.
        //
        wcscpy(
            (LPWSTR) &(Ea->EaName[EaNameUserNameSize + sizeof(CHAR)]),
            UserName
            );

        Ea->EaValueLength = UserNameSize;

        Ea->NextEntryOffset = ROUND_UP_COUNT(
                                  FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                                  EaNameUserNameSize + sizeof(CHAR) +
                                  UserNameSize,
                                  ALIGN_DWORD
                                  );
        Ea->Flags = 0;

        (ULONG) Ea += Ea->NextEntryOffset;

    }

    //
    // Copy the connection type name into EA buffer.  EA name length
    // does not include the zero terminator.
    //
    strcpy((LPSTR) Ea->EaName, EA_NAME_TYPE);
    Ea->EaNameLength = EaNameTypeSize;

    *((PULONG) &(Ea->EaName[EaNameTypeSize + sizeof(CHAR)])) = ConnectionType;

    Ea->EaValueLength = TypeSize;

    //
    // Terminate the EA.
    //
    Ea->NextEntryOffset = 0;
    Ea->Flags = 0;

    //
    // Create or open a tree connection
    //
    ntstatus = NtCreateFile(
                   TreeConnectionHandle,
                   DesiredAccess,
                   &UncNameAttributes,
                   &IoStatusBlock,
                   NULL,
                   FILE_ATTRIBUTE_NORMAL,
                   FILE_SHARE_VALID_FLAGS,
                   CreateDisposition,
                   CreateOptions,
                   (PVOID) EaBuffer,
                   EaBufferSize
                   );

    if (ntstatus == NWRDR_PASSWORD_HAS_EXPIRED) { 
        //
        // wait till other thread is not using the popup data struct.
        // if we timeout, then we just lose the popup.
        //
        switch (WaitForSingleObject(NwPopupDoneEvent, 3000))
        {
            case WAIT_OBJECT_0:
            {
                LPWSTR lpServerStart, lpServerEnd ;
                WCHAR UserNameW[NW_MAX_USERNAME_LEN+1] ;
                DWORD dwUserNameWSize = sizeof(UserNameW)/sizeof(UserNameW[0]) ;
                DWORD dwServerLength, dwGraceLogins ;
                DWORD dwMessageId = NW_PASSWORD_HAS_EXPIRED ;

                //
                // get the current username
                //
                if (UserName)
                {
                    wcscpy(UserNameW, UserName) ;
                }
                else
                {
                    if (!GetUserNameW(UserNameW, &dwUserNameWSize))
                    {
                        SetEvent(NwPopupDoneEvent) ;
                        break ;
                    }
                }

                //
                // allocate string and fill in the username
                //
                if (!(PopupData.InsertStrings[0] =
                    (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT,
                                       sizeof(WCHAR) * (wcslen(UserNameW)+1))))
                {
                    SetEvent(NwPopupDoneEvent) ;
                    break ;
                }
                wcscpy(PopupData.InsertStrings[0], UserNameW) ;
 
                //
                // find the server name from unc name
                //
                lpServerStart = (*UncName == L'\\') ? UncName+2 : UncName ;
                lpServerEnd = wcschr(lpServerStart,L'\\') ;
                dwServerLength = lpServerEnd ? (lpServerEnd-lpServerStart) :
                                 wcslen(lpServerStart) ;

                //
                // allocate string and fill in the server insert string
                //
                if (!(PopupData.InsertStrings[1] =
                    (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT,
                                       sizeof(WCHAR) * (dwServerLength+1))))
                {
                    (void) LocalFree((HLOCAL) PopupData.InsertStrings[0]);
                    SetEvent(NwPopupDoneEvent) ;
                    break ;
                }
                wcsncpy(PopupData.InsertStrings[1],
                        lpServerStart,
                        dwServerLength) ;

                //
                // now call the NCP. if an error occurs while getting
                // the grace login count, dont use it.
                //
                if (NwGetGraceLoginCount(
                                     PopupData.InsertStrings[1],
                                     UserNameW,
                                     &dwGraceLogins) != NO_ERROR)
                {
                    dwMessageId = NW_PASSWORD_HAS_EXPIRED1 ;
                    dwGraceLogins = 0 ;
                }

                //
                // stick the number of grace logins in second insert string.
                //
                if (!(PopupData.InsertStrings[2] =
                    (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT,
                                       sizeof(WCHAR) * 16)))
                {
                    (void) LocalFree((HLOCAL) PopupData.InsertStrings[0]);
                    (void) LocalFree((HLOCAL) PopupData.InsertStrings[1]);
                    SetEvent(NwPopupDoneEvent) ;
                    break ;
                }

                wsprintfW(PopupData.InsertStrings[2], L"%d", dwGraceLogins);
                PopupData.InsertCount = 3 ;
                PopupData.MessageId = dwMessageId ;

                //
                // all done at last, trigger the other thread do the popup
                //
                SetEvent(NwPopupEvent) ;
                break ;

            }

            default:
                break ; // dont bother if we cannot
        }
    }

    if (NT_SUCCESS(ntstatus)) {
        ntstatus = IoStatusBlock.Status;
    }

    if (ntstatus == NWRDR_PASSWORD_HAS_EXPIRED) {
        ntstatus = STATUS_SUCCESS ;
    }

    if (ARGUMENT_PRESENT(Information)) {
        *Information = IoStatusBlock.Information;
    }

#if DBG
    IF_DEBUG(CONNECT) {
        KdPrint(("NWWORKSTATION: NtCreateFile returns %lx\n", ntstatus));
    }
#endif

    status = NwMapStatus(ntstatus);

FreeMemory:
    if (EaBuffer != NULL) {
        RtlZeroMemory( EaBuffer, EaBufferSize );  // Clear the password
        (void) LocalFree((HLOCAL) EaBuffer);
    }

    return status;
}


DWORD
NwNukeConnection(
    IN HANDLE TreeConnection,
    IN DWORD UseForce
    )
/*++

Routine Description:

    This function asks the redirector to delete an existing tree
    connection.

Arguments:

    TreeConnection - Supplies the handle to an existing tree connection.

    UseForce - Supplies the force flag to delete the tree connection.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    NWR_REQUEST_PACKET Rrp;            // Redirector request packet


    //
    // Tell the redirector to delete the tree connection
    //
    Rrp.Version = REQUEST_PACKET_VERSION;
    Rrp.Parameters.DeleteConn.UseForce = (BOOLEAN) UseForce;

    status = NwRedirFsControl(
                 TreeConnection,
                 FSCTL_NWR_DELETE_CONNECTION,
                 &Rrp,
                 sizeof(NWR_REQUEST_PACKET),
                 NULL,
                 0,
                 NULL
                 );

    return status;
}


DWORD
NwGetServerResource(
    IN LPWSTR LocalName,
    IN DWORD LocalNameLength,
    OUT LPWSTR RemoteName,
    IN DWORD RemoteNameLen,
    OUT LPDWORD CharsRequired
    )
/*++

Routine Description:

    This function

Arguments:


Return Value:


--*/
{
    DWORD status = NO_ERROR;

    BYTE Buffer[sizeof(NWR_REQUEST_PACKET) + 2 * sizeof(WCHAR)];
    PNWR_REQUEST_PACKET Rrp = (PNWR_REQUEST_PACKET) Buffer;


    //
    // local device name should not be longer than 4 characters e.g. LPTx, X:
    //
    if ( LocalNameLength > 4 )
        return ERROR_INVALID_PARAMETER;

    Rrp->Version = REQUEST_PACKET_VERSION;

    wcsncpy(Rrp->Parameters.GetConn.DeviceName, LocalName, LocalNameLength);
    Rrp->Parameters.GetConn.DeviceNameLength = LocalNameLength * sizeof(WCHAR);

    status = NwRedirFsControl(
                 RedirDeviceHandle,
                 FSCTL_NWR_GET_CONNECTION,
                 Rrp,
                 sizeof(NWR_REQUEST_PACKET) +
                     Rrp->Parameters.GetConn.DeviceNameLength,
                 RemoteName,
                 RemoteNameLen * sizeof(WCHAR),
                 NULL
                 );

    if (status == ERROR_INSUFFICIENT_BUFFER) {
        *CharsRequired = Rrp->Parameters.GetConn.BytesNeeded / sizeof(WCHAR);
    }
    else if (status == ERROR_FILE_NOT_FOUND) {

        //
        // Redirector could not find the specified LocalName
        //
        status = WN_NOT_CONNECTED;
    }

    return status;

}


DWORD
NwEnumerateConnections(
    IN OUT LPDWORD ResumeId,
    IN DWORD EntriesRequested,
    IN LPBYTE Buffer,
    IN DWORD BufferSize,
    OUT LPDWORD BytesNeeded,
    OUT LPDWORD EntriesRead,
    IN DWORD ConnectionType
    )
/*++

Routine Description:

    This function asks the redirector to enumerate all existing
    connections.

Arguments:

    ResumeId - On input, supplies the resume ID of the next entry
        to begin the enumeration.  This ID is an integer value that
        is either the smaller or the same value as the ID of the
        next entry to return.  On output, this ID indicates the next
        entry to start resuming from for the subsequent call.

    EntriesRequested - Supplies the number of entries to return.  If
        this value is 0xffffffff, return all available entries.

    Buffer - Receives the entries we are listing.

    BufferSize - Supplies the size of the output buffer.

    BytesNeeded - Receives the number of bytes required to get the
        first entry.  This value is returned iff ERROR_MORE_DATA is
        the return code, and Buffer is too small to even fit one
        entry.

    EntriesRead - Receives the number of entries returned in Buffer.
        This value is only returned iff NO_ERROR is the return code.
        NO_ERROR is returned as long as at least one entry was written
        into Buffer but does not necessarily mean that it's the number
        of EntriesRequested.

    ConnectionType - The type of connected resource wanted ( DISK, PRINT, ...)

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    NWR_REQUEST_PACKET Rrp;            // Redirector request packet


    //
    // Tell the redirector to enumerate all connections.
    //
    Rrp.Version = REQUEST_PACKET_VERSION;

    Rrp.Parameters.EnumConn.ResumeKey = *ResumeId;
    Rrp.Parameters.EnumConn.EntriesRequested = EntriesRequested;
    Rrp.Parameters.EnumConn.ConnectionType = ConnectionType;

    status = NwRedirFsControl(
                 RedirDeviceHandle,
                 FSCTL_NWR_ENUMERATE_CONNECTIONS,
                 &Rrp,
                 sizeof(NWR_REQUEST_PACKET),
                 Buffer,                      // User output buffer
                 BufferSize,
                 NULL
                 );

    *EntriesRead = Rrp.Parameters.EnumConn.EntriesReturned;

    if (status == WN_MORE_DATA) {
        *BytesNeeded = Rrp.Parameters.EnumConn.BytesNeeded;

        //
        // NP specs expect WN_SUCCESS in this case.
        //
        if (*EntriesRead)
            status = WN_SUCCESS ;
    }

    *ResumeId = Rrp.Parameters.EnumConn.ResumeKey;

    return status;
}


DWORD
NwGetNextServerEntry(
    IN HANDLE PreferredServer,
    IN OUT LPDWORD LastObjectId,
    OUT LPSTR ServerName
    )
/*++

Routine Description:

    This function uses an opened handle to the preferred server to
    scan it bindery for all file server objects.

Arguments:

    PreferredServer - Supplies the handle to the preferred server on
        which to scan the bindery.

    LastObjectId - On input, supplies the object ID to the last file
        server object returned, which is the resume handle to get the
        next file server object.  On output, receives the object ID
        of the file server object returned.

    ServerName - Receives the name of the returned file server object.

Return Value:

    NO_ERROR - Successfully gotten a file server name.

    WN_NO_MORE_ENTRIES - No other file server object past the one
        specified by LastObjectId.

--*/
{
    NTSTATUS ntstatus;
    WORD ObjectType;


#if DBG
    IF_DEBUG(ENUM) {
        KdPrint(("NWWORKSTATION: NwGetNextServerEntry LastObjectId %lu\n",
                 *LastObjectId));
    }
#endif

    ntstatus = NwlibMakeNcp(
                   PreferredServer,
                   FSCTL_NWR_NCP_E3H,    // Bindery function
                   58,                   // Max request packet size
                   59,                   // Max response packet size
                   "bdwp|dwc",           // Format string
                   0x37,                 // Scan bindery object
                   *LastObjectId,        // Previous ID
                   0x4,                  // File server object
                   "*",                  // Wildcard to match all
                   LastObjectId,         // Current ID
                   &ObjectType,          // Ignore
                   ServerName            // Currently returned server
                   );

#if DBG
    if (ntstatus == STATUS_SUCCESS) {
        IF_DEBUG(ENUM) {
            KdPrint(("NWWORKSTATION: NwGetNextServerEntry NewObjectId %08lx, ServerName %s\n",
                     *LastObjectId, ServerName));
        }
    }
#endif

    return NwMapBinderyCompletionCode(ntstatus);
}


DWORD
GetConnectedBinderyServers(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    )
/*++

Routine Description:

    This function is a helper routine for the function
    NwGetNextServerConnection. It allocates a buffer to cache
    bindery server names returned from calls to the redirector. Since the
    redirector may return duplicate bindery server names, this
    function checks to see if the server name already exist in the buffer
    before adding it.

Arguments:

    ContextHandle - Used to track cached bindery information and the 
                    current server name pointer in the cache buffer.

Return Value:

    NO_ERROR - Successfully returned a server name and cache buffer.

    WN_NO_MORE_ENTRIES - No other server object past the one
        specified by CH->ResumeId.

    ERROR_NOT_ENOUGH_MEMORY - Function was unable to allocate a buffer.

++*/
{
    DWORD  ResumeKey = 0;
    LPBYTE pBuffer = NULL;
    DWORD  EntriesRead = 0;
    BYTE   tokenIter;
    LPWSTR tokenPtr;
    BOOL   fAddToList;
    DWORD  status = NwGetConnectionStatus( NULL,
                                           &ResumeKey,
                                           &pBuffer,
                                           &EntriesRead );

    if ( status == NO_ERROR  && EntriesRead > 0 )
    {
        DWORD i;
        PCONN_STATUS pConnStatus = (PCONN_STATUS) pBuffer;

        ContextHandle->ResumeId = 0;
        ContextHandle->NdsRawDataCount = 0;
        ContextHandle->NdsRawDataSize = (NW_MAX_SERVER_LEN + 2) * EntriesRead;
        ContextHandle->NdsRawDataBuffer =
                    (DWORD) LocalAlloc( LMEM_ZEROINIT,
                                        ContextHandle->NdsRawDataSize );

        if ( ContextHandle->NdsRawDataBuffer == 0 )
        {
            KdPrint(("NWWORKSTATION: GetConnectedBinderyServers LocalAlloc failed %lu\n",
                     GetLastError()));

            ContextHandle->NdsRawDataSize = 0;

            return ERROR_NOT_ENOUGH_MEMORY;
        }

        for ( i = 0; i < EntriesRead ; i++ )
        {
            fAddToList = FALSE;

            if ( pConnStatus->fNds == 0 &&
                 ( pConnStatus->dwConnType == NW_CONN_BINDERY_LOGIN ||
                   pConnStatus->dwConnType == NW_CONN_NDS_AUTHENTICATED_NO_LICENSE ||
                   pConnStatus->dwConnType == NW_CONN_NDS_AUTHENTICATED_LICENSED ||
                   pConnStatus->dwConnType == NW_CONN_DISCONNECTED ) )
            {
                fAddToList = TRUE;
                tokenPtr = (LPWSTR) ContextHandle->NdsRawDataBuffer;
                tokenIter = 0;
    
                //
                // Walk through buffer to see if the tree name already exists.
                //
                while ( tokenIter < ContextHandle->NdsRawDataCount )
                {
                    if ( !wcscmp( tokenPtr, pConnStatus->pszServerName ) )
                    {   
                        fAddToList = FALSE;
                    }

                    tokenPtr = tokenPtr + wcslen( tokenPtr ) + 1;
                    tokenIter++;
                }
            }

            //
            //  Add the new tree name to end of buffer if needed.
            //
            if ( fAddToList )
            {
                wcscpy( tokenPtr, pConnStatus->pszServerName );
                _wcsupr( tokenPtr );
                ContextHandle->NdsRawDataCount += 1;
            }

            pConnStatus = (PCONN_STATUS) ( (DWORD) pConnStatus +
                                           pConnStatus->dwTotalLength );
        }

        if ( pBuffer != NULL )
        {
            LocalFree( pBuffer );
            pBuffer = NULL;
        }

        if ( ContextHandle->NdsRawDataCount > 0 )
        {
            //
            // Set ResumeId to point to the first entry in buffer
            // and have NdsRawDataCount set to the number
            // of tree entries left in buffer (ie. substract 1)
            //
            ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer;
            ContextHandle->NdsRawDataCount -= 1;
        }

        return NO_ERROR;
    }

    return WN_NO_MORE_ENTRIES;
}


DWORD
NwGetNextServerConnection(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    )
/*++

Routine Description:

    This function queries the redirector for bindery server connections

Arguments:

    ContextHandle - Receives the name of the returned bindery server.

Return Value:

    NO_ERROR - Successfully returned a server name.

    WN_NO_MORE_ENTRIES - No other server objects past the one
        specified by CH->ResumeId exist.

--*/
{
#if DBG
    IF_DEBUG(ENUM) {
        KdPrint(("NWWORKSTATION: NwGetNextServerConnection ResumeId %lu\n",
                 ContextHandle->ResumeId));
    }
#endif

    if ( ContextHandle->ResumeId == 0xFFFFFFFF &&
         ContextHandle->NdsRawDataBuffer == 0 &&
         ContextHandle->NdsRawDataCount == 0 )
    {
        //
        // Fill the buffer and point ResumeId to the last
        // server entry name in it. NdsRawDataCount will be
        // set to one less than the number of server names in buffer.
        //
        return GetConnectedBinderyServers( ContextHandle );
    }

    if ( ContextHandle->NdsRawDataBuffer != 0 &&
         ContextHandle->NdsRawDataCount > 0 )
    {
        //
        // Move ResumeId to point to the next entry in the buffer
        // and decrement the NdsRawDataCount by one. Watch for case
        // where we backed up to 0xFFFFFFFF.
        //
        if (ContextHandle->ResumeId == 0xFFFFFFFF) {

            //
            // Reset to start of buffer.
            //
            ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer;
        }
        else {

            //
            // Treat as pointer and advance as need.
            //
            ContextHandle->ResumeId =
                       ContextHandle->ResumeId +
                       ( ( wcslen( (LPWSTR) ContextHandle->ResumeId ) + 1 ) *
                       sizeof(WCHAR) );
        }
        ContextHandle->NdsRawDataCount -= 1;

        return NO_ERROR;
    }
    
    if ( ContextHandle->NdsRawDataBuffer != 0 &&
         ContextHandle->NdsRawDataCount == 0 )
    {
        //
        // We already have a buffer and processed all server names
        // in it, and there is no more data to get.
        // So free the memory used for the buffer and return
        // WN_NO_MORE_ENTRIES to tell WinFile that we are done.
        //
        (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer );

        ContextHandle->NdsRawDataBuffer = 0;
        ContextHandle->NdsRawDataSize = 0;

        return WN_NO_MORE_ENTRIES;
    }

    //
    // Were done
    //
    return WN_NO_MORE_ENTRIES;
}


DWORD
GetTreeEntriesFromBindery(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    )
/*++

Routine Description:

    This function is a helper routine for the function NwGetNextNdsTreeEntry.
    It allocates a buffer (if needed) to cache NDS tree names returned from
    calls to the bindery. Since the bindery often returns duplicates of a
    NDS tree name, this function checks to see if the tree name already
    exist in the buffer before adding it to it if not present.

Arguments:

    ContextHandle - Used to track cached bindery information and the 
                    current tree name pointer in the cache buffer.

Return Value:

    NO_ERROR - Successfully returned a NDS tree name and cache buffer.

    WN_NO_MORE_ENTRIES - No other NDS tree object past the one
        specified by CH->ResumeId.

    ERROR_NOT_ENOUGH_MEMORY - Function was unable to allocate a buffer.

++*/
{
    NTSTATUS ntstatus = STATUS_SUCCESS;
    SERVERNAME TreeName;
    LPWSTR UTreeName = NULL; //Unicode tree name
    DWORD tempDataId;
    WORD ObjectType;
    BYTE iter;
    BYTE tokenIter;
    LPWSTR tokenPtr;
    BOOL fAddToList;

    //
    // Check to see if we need to allocate a buffer for use
    //
    if ( ContextHandle->NdsRawDataBuffer == 0x00000000 )
    {
        ContextHandle->NdsRawDataId = ContextHandle->ResumeId;
        ContextHandle->NdsRawDataSize = EIGHT_KB;
        ContextHandle->NdsRawDataBuffer =
                    (DWORD) LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT,
                                ContextHandle->NdsRawDataSize );

        if ( ContextHandle->NdsRawDataBuffer == 0 )
        {
            KdPrint(("NWWORKSTATION: GetTreeEntriesFromBindery LocalAlloc failed %lu\n",
                     GetLastError()));

            ContextHandle->NdsRawDataSize = 0;
            ContextHandle->NdsRawDataId = 0xFFFFFFFF;

            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    //
    // Repeatedly call bindery to fill buffer with NDS tree names until
    // buffer is full.
    //
    while ( ntstatus == STATUS_SUCCESS )
    {
        RtlZeroMemory( TreeName, sizeof( TreeName ) );

        tempDataId = ContextHandle->NdsRawDataId;

        ntstatus = NwlibMakeNcp(
                   ContextHandle->TreeConnectionHandle,
                   FSCTL_NWR_NCP_E3H,            // Bindery function
                   58,                           // Max request packet size
                   59,                           // Max response packet size
                   "bdwp|dwc",                   // Format string
                   0x37,                         // Scan bindery object
                   ContextHandle->NdsRawDataId,  // Previous ID
                   0x278,                        // Directory server object
                   "*",                          // Wildcard to match all
                   &ContextHandle->NdsRawDataId, // Current ID
                   &ObjectType,                  // Ignore
                   TreeName                      // Currently returned NDS tree
                   );

        //
        // We got a tree name, clean it up (i.e. get rid of underscores ),
        // and add it to buffer if unique.
        //
        if ( ntstatus == STATUS_SUCCESS )
        {
            iter = 31;

            while ( TreeName[iter] == '_' && iter > 0 )
            {
                iter--;
            }

            TreeName[iter + 1] = '\0';

            //
            // Convert tree name to a UNICODE string and proccess it,
            // else just skip it and move on to the next tree name.
            //
            if ( NwConvertToUnicode( &UTreeName, TreeName ) )
            {
               tokenPtr = (LPWSTR) ContextHandle->NdsRawDataBuffer;
               tokenIter = 0;
               fAddToList = TRUE;
    
               //
               // Walk through buffer to see if the tree name already exists.
               //
               while ( tokenIter < ContextHandle->NdsRawDataCount )
               {
                   if ( !wcscmp( tokenPtr, UTreeName ) )
                   {   
                       fAddToList = FALSE;
                   }

                   tokenPtr = tokenPtr + wcslen( tokenPtr ) + 1;
                   tokenIter++;
               }

               //
               //  Add the new tree name to end of buffer if needed.
               //
               if ( fAddToList )
               {
                   DWORD BytesNeededToAddTreeName = (wcslen(UTreeName)+1) * sizeof(WCHAR);
                   DWORD NumberOfBytesAvailable = ContextHandle->NdsRawDataBuffer +
                                            ContextHandle->NdsRawDataSize -
                                            (DWORD) tokenPtr;

                   if ( BytesNeededToAddTreeName < NumberOfBytesAvailable )
                   {
                       wcscpy( tokenPtr, UTreeName );
                       ContextHandle->NdsRawDataCount += 1;
                   }
                   else
                   {
                       ContextHandle->NdsRawDataId = tempDataId;
                       ntstatus = ERROR_NOT_ENOUGH_MEMORY;
                   }
               }

               (void) LocalFree((HLOCAL) UTreeName);
            }
        }
    }

    //
    // We are done filling buffer, and there are no more tree names 
    // to request. Set id to indicate last value.
    //
    if ( ntstatus == STATUS_NO_MORE_ENTRIES )
    {
        ContextHandle->NdsRawDataId = 0xFFFFFFFF;
        ntstatus = STATUS_SUCCESS;
    }

    //
    // We are done because the buffer is full. So we return NO_ERROR to
    // indicate completion, and leave ContextHandle->NdsRawDataId as is
    // to indicate where we left off.
    //
    if ( ntstatus == ERROR_NOT_ENOUGH_MEMORY )
    {
        ntstatus = STATUS_SUCCESS;
    }

    if ( ContextHandle->NdsRawDataCount == 0 )
    {
        if ( ContextHandle->NdsRawDataBuffer )
            (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer );

        ContextHandle->NdsRawDataBuffer = 0;
        ContextHandle->NdsRawDataSize = 0;
        ContextHandle->NdsRawDataId = 0xFFFFFFFF;

        return WN_NO_MORE_ENTRIES;
    }

    if ( ContextHandle->NdsRawDataCount > 0 )
    {
        //
        // Set ResumeId to point to the first entry in buffer
        // and have NdsRawDataCount set to the number
        // of tree entries left in buffer (ie. substract 1)
        //
        ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer;
        ContextHandle->NdsRawDataCount -= 1;

        return NO_ERROR;
    }

    if ( ContextHandle->NdsRawDataBuffer )
        (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer );

    ContextHandle->NdsRawDataBuffer = 0;
    ContextHandle->NdsRawDataSize = 0;
    ContextHandle->NdsRawDataId = 0xFFFFFFFF;

    return NwMapStatus( ntstatus );
}


DWORD
NwGetNextNdsTreeEntry(
    OUT LPNW_ENUM_CONTEXT ContextHandle
    )
/*++

Routine Description:

    This function uses an opened handle to the preferred server to
    scan it bindery for all NDS tree objects.

Arguments:

    ContextHandle - Receives the name of the returned NDS tree object
    given the current preferred server connection and CH->ResumeId.

Return Value:

    NO_ERROR - Successfully returned a NDS tree name.

    WN_NO_MORE_ENTRIES - No other NDS tree objects past the one
        specified by CH->ResumeId exist.

--*/
{
#if DBG
    IF_DEBUG(ENUM) {
        KdPrint(("NWWORKSTATION: NwGetNextNdsTreeEntry ResumeId %lu\n",
                 ContextHandle->ResumeId));
    }
#endif

    if ( ContextHandle->ResumeId == 0xFFFFFFFF &&
         ContextHandle->NdsRawDataBuffer == 0 &&
         ContextHandle->NdsRawDataCount == 0 )
    {
        //
        // Fill the buffer and point ResumeId to the last
        // tree entry name in it. NdsRawDataCount will be
        // set to one less than the number of tree names in buffer.
        //
        return GetTreeEntriesFromBindery( ContextHandle );
    }

    if ( ContextHandle->NdsRawDataBuffer != 0 &&
         ContextHandle->NdsRawDataCount > 0 )
    {
        //
        // Move ResumeId to point to the next entry in the buffer
        // and decrement the NdsRawDataCount by one. Watch for case
        // where we backed up to 0xFFFFFFFF.
        //
        if (ContextHandle->ResumeId == 0xFFFFFFFF) {

            //
            // Reset to start of buffer.
            //
            ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer;
        }
        else {

            //
            // Move ResumeId to point to the next entry in the buffer
            // and decrement the NdsRawDataCount by one
            //
            ContextHandle->ResumeId =
                       ContextHandle->ResumeId +
                       ( ( wcslen( (LPWSTR) ContextHandle->ResumeId ) + 1 ) *
                       sizeof(WCHAR) );
        }

        ContextHandle->NdsRawDataCount -= 1;

        return NO_ERROR;
    }
    
    if ( ContextHandle->NdsRawDataBuffer != 0 &&
         ContextHandle->NdsRawDataCount == 0 &&
         ContextHandle->NdsRawDataId != 0xFFFFFFFF )
    {
        //
        // We already have a buffer and processed all tree names
        // in it, and there is more data in the bindery to get.
        // So go get it and point ResumeId to the last tree
        // entry name in the buffer and set NdsRawDataCount to
        // one less than the number of tree names in buffer.
        //
        return GetTreeEntriesFromBindery( ContextHandle );
    }
    
    if ( ContextHandle->NdsRawDataBuffer != 0 &&
         ContextHandle->NdsRawDataCount == 0 &&
         ContextHandle->NdsRawDataId == 0xFFFFFFFF )
    {
        //
        // We already have a buffer and processed all tree names
        // in it, and there is no more data in the bindery to get.
        // So free the memory used for the buffer and return
        // WN_NO_MORE_ENTRIES to tell WinFile that we are done.
        //
        (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer );

        ContextHandle->NdsRawDataBuffer = 0;
        ContextHandle->NdsRawDataSize = 0;

        return WN_NO_MORE_ENTRIES;
    }

    //
    // We should never hit this area!
    //
    return WN_NO_MORE_ENTRIES;
}


DWORD
NwGetNextVolumeEntry(
    IN HANDLE ServerConnection,
    IN DWORD NextVolumeNumber,
    OUT LPSTR VolumeName
    )
/*++

Routine Description:

    This function lists the volumes on the server specified by
    an opened tree connection handle to the server.

Arguments:

    ServerConnection - Supplies the tree connection handle to the
        server to enumerate volumes from.

    NextVolumeNumber - Supplies the volume number which to look
        up the name.

    VolumeName - Receives the name of the volume associated with
        NextVolumeNumber.

Return Value:

    NO_ERROR - Successfully gotten the volume name.

    WN_NO_MORE_ENTRIES - No other volume name associated with the
         specified volume number.

--*/
{
    NTSTATUS ntstatus;

#if DBG
    IF_DEBUG(ENUM) {
        KdPrint(("NWWORKSTATION: NwGetNextVolumeEntry volume number %lu\n",
                 NextVolumeNumber));
    }
#endif

    ntstatus = NwlibMakeNcp(
                   ServerConnection,
                   FSCTL_NWR_NCP_E2H,       // Directory function
                   4,                       // Max request packet size
                   19,                      // Max response packet size
                   "bb|p",                  // Format string
                   0x6,                     // Get volume name
                   (BYTE) NextVolumeNumber, // Previous ID
                   VolumeName               // Currently returned server
                   );

    return NwMapStatus(ntstatus);
}


DWORD
NwRdrLogonUser(
    IN PLUID LogonId,
    IN LPWSTR UserName,
    IN DWORD UserNameSize,
    IN LPWSTR Password OPTIONAL,
    IN DWORD PasswordSize,
    IN LPWSTR PreferredServer OPTIONAL,
    IN DWORD PreferredServerSize
    )
/*++

Routine Description:

    This function tells the redirector the user logon credential.

Arguments:

    UserName - Supplies the user name.

    UserNameSize - Supplies the size in bytes of the user name string without
        the NULL terminator.

    Password - Supplies the password.

    PasswordSize - Supplies the size in bytes of the password string without
        the NULL terminator.

    PreferredServer - Supplies the preferred server name.

    PreferredServerSize - Supplies the size in bytes of the preferred server
        string without the NULL terminator.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;

    PNWR_REQUEST_PACKET Rrp;            // Redirector request packet

    DWORD RrpSize = sizeof(NWR_REQUEST_PACKET) +
                        UserNameSize +
                        PasswordSize +
                        PreferredServerSize;
    LPBYTE Dest;
    BYTE   lpReplicaAddress[sizeof(TDI_ADDRESS_IPX)];
    DWORD  ReplicaAddressSize = 0;


#if DBG
    IF_DEBUG(LOGON) {
        BYTE PW[128];


        RtlZeroMemory(PW, sizeof(PW));

        if (PasswordSize > (sizeof(PW) - 1)) {
            memcpy(PW, Password, sizeof(PW) - 1);
        }
        else {
            memcpy(PW, Password, PasswordSize);
        }

        KdPrint(("NWWORKSTATION: NwRdrLogonUser: UserName %ws\n", UserName));
        KdPrint(("                               Password %ws\n", PW));
        if ( PreferredServer )
            KdPrint(("                               Server   %ws\n", PreferredServer ));
    }
#endif

    if ( PreferredServer && PreferredServer[0] == TREECHAR )
    {
        WCHAR  TreeName[MAX_NDS_NAME_CHARS + 1];
        LPWSTR lpTemp;

        //
        // Find the nearest dir server for the tree that the user wants to
        // connect to.
        //

        wcscpy( TreeName, PreferredServer + 1 );
        lpTemp = wcschr( TreeName, L'\\' );
        lpTemp[0] = L'\0';

        GetNearestDirServer( TreeName,
                             &ReplicaAddressSize,
                             lpReplicaAddress );

        RrpSize += ReplicaAddressSize;
    }

    //
    // Allocate the request packet
    //
    if ((Rrp = (PVOID) LocalAlloc(
                           LMEM_ZEROINIT,
                           RrpSize
                           )) == NULL) {

        KdPrint(("NWWORKSTATION: NwRdrLogonUser LocalAlloc failed %lu\n",
                 GetLastError()));
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // Tell the redirector the user logon credential.
    //
    Rrp->Version = REQUEST_PACKET_VERSION;

    RtlCopyLuid(&(Rrp->Parameters.Logon.LogonId), LogonId);

#if DBG
    IF_DEBUG(LOGON) {
        KdPrint(("NWWORKSTATION: NwRdrLogonUser passing to Rdr logon ID %lu %lu\n",
                 *LogonId, *((PULONG) ((DWORD) LogonId + sizeof(ULONG)))));
    }
#endif

    Rrp->Parameters.Logon.UserNameLength = UserNameSize;
    Rrp->Parameters.Logon.PasswordLength = PasswordSize;
    Rrp->Parameters.Logon.ServerNameLength = PreferredServerSize;
    Rrp->Parameters.Logon.ReplicaAddrLength = ReplicaAddressSize;

    memcpy(Rrp->Parameters.Logon.UserName, UserName, UserNameSize);
    Dest = (LPBYTE) ((DWORD) Rrp->Parameters.Logon.UserName + UserNameSize);

    if (PasswordSize > 0)
    {
        memcpy(Dest, Password, PasswordSize);
        Dest = (LPBYTE) ((DWORD) Dest + PasswordSize);
    }

    if (PreferredServerSize > 0)
    {
        memcpy(Dest, PreferredServer, PreferredServerSize);

        if (ReplicaAddressSize > 0)
        {
            Dest = (LPBYTE) ((DWORD) Dest + PreferredServerSize);
            memcpy(Dest, lpReplicaAddress, ReplicaAddressSize);
        }
    }

    status = NwRedirFsControl(
                 RedirDeviceHandle,
                 FSCTL_NWR_LOGON,
                 Rrp,
                 RrpSize,
                 NULL,              // No logon script in this release
                 0,
                 NULL
                 );

    RtlZeroMemory(Rrp, RrpSize);   // Clear the password
    (void) LocalFree((HLOCAL) Rrp);

    return status;
}


VOID
NwRdrChangePassword(
    IN PNWR_REQUEST_PACKET Rrp
    )
/*++

Routine Description:

    This function tells the redirector the new password for a user on
    a particular server.

Arguments:

    Rrp - Supplies the username, new password and servername.

    RrpSize - Supplies the size of the request packet.

Return Value:

    None.

--*/
{

    //
    // Tell the redirector the user new password.
    //
    Rrp->Version = REQUEST_PACKET_VERSION;

    (void) NwRedirFsControl(
               RedirDeviceHandle,
               FSCTL_NWR_CHANGE_PASS,
               Rrp,
               sizeof(NWR_REQUEST_PACKET) +
                   Rrp->Parameters.ChangePass.UserNameLength +
                   Rrp->Parameters.ChangePass.PasswordLength +
                   Rrp->Parameters.ChangePass.ServerNameLength,
               NULL,
               0,
               NULL
               );

}


DWORD
NwRdrSetInfo(
    IN DWORD PrintOption,
    IN DWORD PacketBurstSize,
    IN LPWSTR PreferredServer OPTIONAL,
    IN DWORD PreferredServerSize,
    IN LPWSTR ProviderName OPTIONAL,
    IN DWORD ProviderNameSize
    )
/*++

Routine Description:

    This function passes some workstation configuration and current user's
    preference to the redirector. This includes the network provider name, the
    packet burst size, the user's selected preferred server and print option.

Arguments:

    PrintOption  - The current user's print option

    PacketBurstSize - The packet burst size stored in the registry

    PreferredServer - The preferred server the current user selected
    PreferredServerSize - Supplies the size in bytes of the preferred server
                   string without the NULL terminator.

    ProviderName - Supplies the provider name.
    ProviderNameSize - Supplies the size in bytes of the provider name
                   string without the NULL terminator.


Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;

    PNWR_REQUEST_PACKET Rrp;            // Redirector request packet

    DWORD RrpSize = sizeof(NWR_REQUEST_PACKET) +
                        PreferredServerSize +
                        ProviderNameSize;

    LPBYTE Dest;

    //
    // Allocate the request packet
    //
    if ((Rrp = (PVOID) LocalAlloc(
                           LMEM_ZEROINIT,
                           RrpSize
                           )) == NULL) {

        KdPrint(("NWWORKSTATION: NwRdrSetInfo LocalAlloc failed %lu\n",
                 GetLastError()));
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Rrp->Version = REQUEST_PACKET_VERSION;

    Rrp->Parameters.SetInfo.PrintOption = PrintOption;
    Rrp->Parameters.SetInfo.MaximumBurstSize = PacketBurstSize;

    Rrp->Parameters.SetInfo.PreferredServerLength = PreferredServerSize;
    Rrp->Parameters.SetInfo.ProviderNameLength  = ProviderNameSize;

    if (ProviderNameSize > 0) {
        memcpy( Rrp->Parameters.SetInfo.PreferredServer,
                PreferredServer, PreferredServerSize);
    }

    Dest = (LPBYTE) ((DWORD) Rrp->Parameters.SetInfo.PreferredServer
                     + PreferredServerSize);

    if (ProviderNameSize > 0) {
        memcpy(Dest, ProviderName, ProviderNameSize);
    }

    status = NwRedirFsControl(
                 RedirDeviceHandle,
                 FSCTL_NWR_SET_INFO,
                 Rrp,
                 RrpSize,
                 NULL,
                 0,
                 NULL
                 );

    (void) LocalFree((HLOCAL) Rrp);


    if ( status != NO_ERROR )
    {
        KdPrint(("NwRedirFsControl: FSCTL_NWR_SET_INFO failed with %d\n",
                status ));
    }

    return status;
}


DWORD
NwRdrLogoffUser(
    IN PLUID LogonId
    )
/*++

Routine Description:

    This function asks the redirector to log off the interactive user.

Arguments:

    None.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    NWR_REQUEST_PACKET Rrp;            // Redirector request packet


    //
    // Tell the redirector to logoff user.
    //
    Rrp.Version = REQUEST_PACKET_VERSION;

    RtlCopyLuid(&Rrp.Parameters.Logoff.LogonId, LogonId);

    status = NwRedirFsControl(
                 RedirDeviceHandle,
                 FSCTL_NWR_LOGOFF,
                 &Rrp,
                 sizeof(NWR_REQUEST_PACKET),
                 NULL,
                 0,
                 NULL
                 );

    return status;
}


DWORD
NwConnectToServer(
    IN LPWSTR ServerName
    )
/*++

Routine Description:

    This function opens a handle to \Device\Nwrdr\ServerName, given
    ServerName, and then closes the handle if the open was successful.
    It is to validate that the current user credential can access
    the server.

Arguments:

    ServerName - Supplies the name of the server to validate the
        user credential.

Return Value:

    NO_ERROR or reason for failure.

--*/
{
    DWORD status;
    UNICODE_STRING ServerStr;
    HANDLE ServerHandle;



    ServerStr.MaximumLength = (wcslen(ServerName) + 2) *
                                  sizeof(WCHAR) +          // \ServerName0
                                  RedirDeviceName.Length;  // \Device\Nwrdr

    if ((ServerStr.Buffer = (PWSTR) LocalAlloc(
                                        LMEM_ZEROINIT,
                                        (UINT) ServerStr.MaximumLength
                                        )) == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // Copy \Device\NwRdr
    //
    RtlCopyUnicodeString(&ServerStr, &RedirDeviceName);

    //
    // Concatenate \ServerName
    //
    wcscat(ServerStr.Buffer, L"\\");
    ServerStr.Length += sizeof(WCHAR);

    wcscat(ServerStr.Buffer, ServerName);
    ServerStr.Length += (USHORT) (wcslen(ServerName) * sizeof(WCHAR));


    status = NwOpenCreateConnection(
                 &ServerStr,
                 NULL,
                 NULL,
                 ServerName,
                 SYNCHRONIZE | FILE_WRITE_DATA,
                 FILE_OPEN,
                 FILE_SYNCHRONOUS_IO_NONALERT,
                 RESOURCETYPE_DISK,
                 &ServerHandle,
                 NULL
                 );

    if (status == ERROR_FILE_NOT_FOUND) {
        status = ERROR_BAD_NETPATH;
    }

    (void) LocalFree((HLOCAL) ServerStr.Buffer);

    if (status == NO_ERROR || status == NW_PASSWORD_HAS_EXPIRED) {
        (void) NtClose(ServerHandle);
    }

    return status;
}

DWORD
NWPGetConnectionStatus(
    IN     LPWSTR  pszRemoteName,
    IN OUT PDWORD  ResumeKey,
    OUT    LPBYTE  Buffer,
    IN     DWORD   BufferSize,
    OUT    PDWORD  BytesNeeded,
    OUT    PDWORD  EntriesRead
)
{
    NTSTATUS ntstatus = STATUS_SUCCESS;
    HANDLE            handleRdr = NULL;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK   IoStatusBlock;
    UNICODE_STRING    uRdrName;
    WCHAR             RdrPrefix[] = L"\\Device\\NwRdr\\*";

    PNWR_REQUEST_PACKET RequestPacket = NULL;
    DWORD             RequestPacketSize = 0;
    DWORD             dwRemoteNameLen = 0;

    //
    // Set up the object attributes.
    //

    RtlInitUnicodeString( &uRdrName, RdrPrefix );

    InitializeObjectAttributes( &ObjectAttributes,
                                &uRdrName,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL );

    ntstatus = NtOpenFile( &handleRdr,
                           SYNCHRONIZE | FILE_LIST_DIRECTORY,
                           &ObjectAttributes,
                           &IoStatusBlock,
                           FILE_SHARE_VALID_FLAGS,
                           FILE_SYNCHRONOUS_IO_NONALERT );

    if ( !NT_SUCCESS(ntstatus) )
        goto CleanExit;

    dwRemoteNameLen = pszRemoteName? wcslen(pszRemoteName)*sizeof(WCHAR) : 0;

    RequestPacketSize = sizeof( NWR_REQUEST_PACKET ) + dwRemoteNameLen;

    RequestPacket = (PNWR_REQUEST_PACKET) LocalAlloc( LMEM_ZEROINIT,
                                                      RequestPacketSize );

    if ( RequestPacket == NULL )
    {
        ntstatus = STATUS_NO_MEMORY;
        goto CleanExit;
    }

    //
    // Fill out the request packet for FSCTL_NWR_GET_CONN_STATUS.
    //

    RequestPacket->Parameters.GetConnStatus.ResumeKey = *ResumeKey;

    RequestPacket->Version = REQUEST_PACKET_VERSION;
    RequestPacket->Parameters.GetConnStatus.ConnectionNameLength = dwRemoteNameLen;

    RtlCopyMemory( &(RequestPacket->Parameters.GetConnStatus.ConnectionName[0]),
                   pszRemoteName,
                   dwRemoteNameLen );

    ntstatus = NtFsControlFile( handleRdr,
                                NULL,
                                NULL,
                                NULL,
                                &IoStatusBlock,
                                FSCTL_NWR_GET_CONN_STATUS,
                                (PVOID) RequestPacket,
                                RequestPacketSize,
                                (PVOID) Buffer,
                                BufferSize );

    if ( NT_SUCCESS( ntstatus ))
        ntstatus = IoStatusBlock.Status;

    *EntriesRead = RequestPacket->Parameters.GetConnStatus.EntriesReturned;
    *ResumeKey   = RequestPacket->Parameters.GetConnStatus.ResumeKey;
    *BytesNeeded = RequestPacket->Parameters.GetConnStatus.BytesNeeded;

CleanExit:

    if ( handleRdr != NULL )
        NtClose( handleRdr );

    if ( RequestPacket != NULL )
        LocalFree( RequestPacket );

    return RtlNtStatusToDosError( ntstatus );
}

DWORD
NwGetConnectionStatus(
    IN  LPWSTR  pszRemoteName,
    OUT PDWORD  ResumeKey,
    OUT LPBYTE  *Buffer,
    OUT PDWORD  EntriesRead
)
{
    DWORD err = NO_ERROR;
    DWORD dwBytesNeeded = 0;
    DWORD dwBufferSize  = TWO_KB;

    *Buffer = NULL;
    *EntriesRead = 0;

    do {

        *Buffer = (LPBYTE) LocalAlloc( LMEM_ZEROINIT, dwBufferSize );

        if ( *Buffer == NULL )
            return ERROR_NOT_ENOUGH_MEMORY;

        err = NWPGetConnectionStatus( pszRemoteName,
                                      ResumeKey,
                                      *Buffer,
                                      dwBufferSize,
                                      &dwBytesNeeded,
                                      EntriesRead );

        if ( err == ERROR_INSUFFICIENT_BUFFER )
        {
            dwBufferSize = dwBytesNeeded + EXTRA_BYTES;
            LocalFree( *Buffer );
            *Buffer = NULL;
        }

    } while ( err == ERROR_INSUFFICIENT_BUFFER );

    if ( err == ERROR_INVALID_PARAMETER )  // not attached
    {
        err = NO_ERROR;
        *EntriesRead = 0;
    }

    return err;
}

VOID
GetNearestDirServer(
    IN  LPWSTR  TreeName,
    OUT LPDWORD lpdwReplicaAddressSize,
    OUT LPBYTE  lpReplicaAddress
    )
{
    WCHAR Buffer[BUFFSIZE];
    PWSAQUERYSETW Query = (PWSAQUERYSETW)Buffer;
    HANDLE hRnr;
    DWORD dwQuerySize = BUFFSIZE;
    GUID gdService = SVCID_NETWARE(0x278);
    WSADATA wsaData;
    WCHAR  ServiceInstanceName[] = L"*";

    WSAStartup(MAKEWORD(1, 1), &wsaData);

    memset(Query, 0, sizeof(*Query));

    //
    // putting a "*" in the lpszServiceInstanceName causes
    // the query to look for all server instances. Putting a
    // specific name in here will search only for instance of
    // that name. If you have a specific name to look for,
    // put a pointer to the name here.
    //
    Query->lpszServiceInstanceName = ServiceInstanceName;
    Query->dwNameSpace = NS_SAP;
    Query->dwSize = sizeof(*Query);
    Query->lpServiceClassId = &gdService;

    //
    // Find the servers. The flags indicate:
    // LUP_NEAREST: look for nearest servers
    // LUP_DEEP : if none are found on the local segement look
    //            for server using a general query
    // LUP_RETURN_NAME: return the name
    // LUP_RETURN_ADDR: return the server address
    //
    // if only servers on the local segment are acceptable, omit
    // setting LUP_DEEP
    //
    if( WSALookupServiceBegin( Query,
                               LUP_NEAREST |
                               LUP_DEEP |
                               LUP_RETURN_NAME |
                               LUP_RETURN_ADDR,
                               &hRnr ) == SOCKET_ERROR )
    {
        //
        // Something went wrong, return no address. The redirector will
        // have to come up with a dir server on its own.
        //
        *lpdwReplicaAddressSize = 0;
        return ;
    }
    else
    {
        //
        // Ready to actually look for one of the suckers ...
        //
        Query->dwSize = BUFFSIZE;

        while( WSALookupServiceNext( hRnr,
                                     0,
                                     &dwQuerySize,
                                     Query ) == NO_ERROR )
        {
            //
            // Found a dir server, now see if it is a server for the NDS tree
            // TreeName.
            //
            if ( NwpCompareTreeNames( Query->lpszServiceInstanceName,
                                      TreeName ) )
            {
                *lpdwReplicaAddressSize = sizeof(TDI_ADDRESS_IPX);
                memcpy( lpReplicaAddress,
                        Query->lpcsaBuffer->RemoteAddr.lpSockaddr->sa_data,
                        sizeof(TDI_ADDRESS_IPX) );

                WSALookupServiceEnd(hRnr);
                return ;
            }
        }

        //
        // Could not find a dir server, return no address. The redirector will
        // have to come up with a dir server on its own.
        //
        *lpdwReplicaAddressSize = 0;
        WSALookupServiceEnd(hRnr);
    }
}

BOOL
NwpCompareTreeNames(
    LPWSTR lpServiceInstanceName,
    LPWSTR lpTreeName
    )
{
    DWORD  iter = 31;

    while ( lpServiceInstanceName[iter] == '_' && iter > 0 )
    {
        iter--;
    }

    lpServiceInstanceName[iter + 1] = '\0';

    if ( !_wcsicmp( lpServiceInstanceName, lpTreeName ) )
    {
        return TRUE;
    }

    return FALSE;
}