/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    NdsLib32.c

Abstract:

    This module implements the exposed user-mode link to
    Netware NDS support in the Netware redirector.  For
    more comments, see ndslib32.h.

Author:

    Cory West    [CoryWest]    23-Feb-1995

--*/

#include <procs.h>

NTSTATUS
NwNdsOpenTreeHandle(
    IN PUNICODE_STRING puNdsTree,
    OUT PHANDLE  phNwRdrHandle
) {

    NTSTATUS ntstatus, OpenStatus;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;
    ACCESS_MASK DesiredAccess = SYNCHRONIZE | FILE_LIST_DIRECTORY;

    WCHAR DevicePreamble[] = L"\\Device\\Nwrdr\\";
    UINT PreambleLength = 14;

    WCHAR NameStr[128];
    UNICODE_STRING uOpenName;
    UINT i;

    PNWR_NDS_REQUEST_PACKET Rrp;
    BYTE RrpData[1024];

    //
    // Prepare the open name.
    //

    uOpenName.MaximumLength = sizeof( NameStr );

    for ( i = 0; i < PreambleLength ; i++ )
        NameStr[i] = DevicePreamble[i];

    try {

        for ( i = 0 ; i < ( puNdsTree->Length / sizeof( WCHAR ) ) ; i++ ) {
            NameStr[i + PreambleLength] = puNdsTree->Buffer[i];
        }

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;

    }

    uOpenName.Length = ( i * sizeof( WCHAR ) ) +
                       ( PreambleLength * sizeof( WCHAR ) );
    uOpenName.Buffer = NameStr;

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

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

    ntstatus = NtOpenFile(
                   phNwRdrHandle,
                   DesiredAccess,
                   &ObjectAttributes,
                   &IoStatusBlock,
                   FILE_SHARE_VALID_FLAGS,
                   FILE_SYNCHRONOUS_IO_NONALERT
                   );

    if ( !NT_SUCCESS(ntstatus) )
        return ntstatus;

    OpenStatus = IoStatusBlock.Status;

    //
    // Verify that this is a tree handle, not a server handle.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET)RrpData;

    Rrp->Version = 0;

    RtlCopyMemory( &(Rrp->Parameters).VerifyTree,
                   puNdsTree,
                   sizeof( UNICODE_STRING ) );

    RtlCopyMemory( (BYTE *)(&(Rrp->Parameters).VerifyTree) + sizeof( UNICODE_STRING ),
                   puNdsTree->Buffer,
                   puNdsTree->Length );

    try {

        ntstatus = NtFsControlFile( *phNwRdrHandle,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_VERIFY_TREE,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {
        ntstatus = GetExceptionCode();
        goto CloseAndExit;
    }

    if ( !NT_SUCCESS( ntstatus )) {
        goto CloseAndExit;
    }

    //
    // Otherwise, all is well!
    //

    return OpenStatus;

CloseAndExit:

    NtClose( *phNwRdrHandle );
    *phNwRdrHandle = NULL;

    return ntstatus;
}

NTSTATUS
NwNdsOpenGenericHandle(
    IN PUNICODE_STRING puNdsTree,
    OUT LPDWORD  lpdwHandleType,
    OUT PHANDLE  phNwRdrHandle
) {

    NTSTATUS ntstatus, OpenStatus;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;
    ACCESS_MASK DesiredAccess = SYNCHRONIZE | FILE_LIST_DIRECTORY;

    WCHAR DevicePreamble[] = L"\\Device\\Nwrdr\\";
    UINT PreambleLength = 14;

    WCHAR NameStr[128];
    UNICODE_STRING uOpenName;
    UINT i;

    PNWR_NDS_REQUEST_PACKET Rrp;
    BYTE RrpData[1024];

    //
    // Prepare the open name.
    //

    uOpenName.MaximumLength = sizeof( NameStr );

    for ( i = 0; i < PreambleLength ; i++ )
        NameStr[i] = DevicePreamble[i];

    try {

        for ( i = 0 ; i < ( puNdsTree->Length / sizeof( WCHAR ) ) ; i++ ) {
            NameStr[i + PreambleLength] = puNdsTree->Buffer[i];
        }

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;

    }

    uOpenName.Length = ( i * sizeof( WCHAR ) ) +
                       ( PreambleLength * sizeof( WCHAR ) );
    uOpenName.Buffer = NameStr;

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

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

    ntstatus = NtOpenFile(
                   phNwRdrHandle,
                   DesiredAccess,
                   &ObjectAttributes,
                   &IoStatusBlock,
                   FILE_SHARE_VALID_FLAGS,
                   FILE_SYNCHRONOUS_IO_NONALERT
                   );

    if ( !NT_SUCCESS(ntstatus) )
        return ntstatus;

    OpenStatus = IoStatusBlock.Status;

    //
    // Verify that this is a tree handle, not a server handle.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET)RrpData;

    Rrp->Version = 0;

    RtlCopyMemory( &(Rrp->Parameters).VerifyTree,
                   puNdsTree,
                   sizeof( UNICODE_STRING ) );

    RtlCopyMemory( (BYTE *)(&(Rrp->Parameters).VerifyTree) + sizeof( UNICODE_STRING ),
                   puNdsTree->Buffer,
                   puNdsTree->Length );

    try {

        ntstatus = NtFsControlFile( *phNwRdrHandle,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_VERIFY_TREE,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {
        ntstatus = GetExceptionCode();
        goto CloseAndExit2;
    }

    if ( !NT_SUCCESS( ntstatus ))
    {
        *lpdwHandleType = HANDLE_TYPE_NCP_SERVER;
    }
    else
    {
        *lpdwHandleType = HANDLE_TYPE_NDS_TREE;
    }

    return OpenStatus;

CloseAndExit2:

    NtClose( *phNwRdrHandle );
    *phNwRdrHandle = NULL;

    return ntstatus;
}

NTSTATUS
NwOpenHandleWithSupplementalCredentials(
    IN PUNICODE_STRING puResourceName,
    IN PUNICODE_STRING puUserName,
    IN PUNICODE_STRING puPassword,
    OUT LPDWORD  lpdwHandleType,
    OUT PHANDLE  phNwHandle
) {

    NTSTATUS ntstatus, OpenStatus;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;
    ACCESS_MASK DesiredAccess = FILE_GENERIC_READ | FILE_GENERIC_WRITE;

    WCHAR DevicePreamble[] = L"\\Device\\Nwrdr\\";
    UINT PreambleLength = 14;

    WCHAR NameStr[128];
    UNICODE_STRING uOpenName;
    UINT i;

    PFILE_FULL_EA_INFORMATION pEaEntry;
    PBYTE EaBuffer;
    ULONG EaLength, EaNameLength, EaTotalLength;
    ULONG UserNameLen, PasswordLen, TypeLen, CredLen;

    PNWR_NDS_REQUEST_PACKET Rrp;
    BYTE RrpData[1024];

    //
    // Prepare the open name.
    //

    uOpenName.MaximumLength = sizeof( NameStr );

    for ( i = 0; i < PreambleLength ; i++ )
        NameStr[i] = DevicePreamble[i];

    try {

        for ( i = 0 ; i < ( puResourceName->Length / sizeof( WCHAR ) ) ; i++ ) {
            NameStr[i + PreambleLength] = puResourceName->Buffer[i];
        }

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;

    }

    uOpenName.Length = ( i * sizeof( WCHAR ) ) +
                       ( PreambleLength * sizeof( WCHAR ) );
    uOpenName.Buffer = NameStr;

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

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

    //
    // Allocate the EA buffer - be a little generous.
    //

    UserNameLen = strlen( EA_NAME_USERNAME );
    PasswordLen = strlen( EA_NAME_PASSWORD );
    TypeLen = strlen( EA_NAME_TYPE );
    CredLen = strlen( EA_NAME_CREDENTIAL_EX );

    EaLength = 4 * sizeof( FILE_FULL_EA_INFORMATION );
    EaLength += 4 * sizeof( ULONG );
    EaLength += ROUNDUP4( UserNameLen );
    EaLength += ROUNDUP4( PasswordLen );
    EaLength += ROUNDUP4( TypeLen );
    EaLength += ROUNDUP4( CredLen  );
    EaLength += ROUNDUP4( puUserName->Length );
    EaLength += ROUNDUP4( puPassword->Length );

    EaBuffer = LocalAlloc( LMEM_ZEROINIT, EaLength );

    if ( !EaBuffer ) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Pack in the first EA: UserName.
    //

    pEaEntry = (PFILE_FULL_EA_INFORMATION) EaBuffer;

    EaNameLength = UserNameLen + sizeof( CHAR );

    pEaEntry->EaNameLength = (UCHAR) EaNameLength;
    pEaEntry->EaValueLength = puUserName->Length;

    RtlCopyMemory( &(pEaEntry->EaName[0]),
                   EA_NAME_USERNAME,
                   EaNameLength );

    EaNameLength = ROUNDUP2( EaNameLength + sizeof( CHAR ) );

    RtlCopyMemory( &(pEaEntry->EaName[EaNameLength]),
                   puUserName->Buffer,
                   puUserName->Length );

    EaLength = ( 2 * sizeof( DWORD ) ) +
               EaNameLength +
               puUserName->Length;

    EaLength = ROUNDUP4( EaLength );

    EaTotalLength = EaLength;

    pEaEntry->NextEntryOffset = EaLength;

    //
    // Pack in the second EA: Password.
    //

    pEaEntry = (PFILE_FULL_EA_INFORMATION)
               ( ( (PBYTE)pEaEntry ) + pEaEntry->NextEntryOffset );

    EaNameLength = PasswordLen + sizeof( CHAR );

    pEaEntry->EaNameLength = (UCHAR) EaNameLength;
    pEaEntry->EaValueLength = puPassword->Length;

    RtlCopyMemory( &(pEaEntry->EaName[0]),
                   EA_NAME_PASSWORD,
                   EaNameLength );

    EaNameLength = ROUNDUP2( EaNameLength + sizeof( CHAR ) );

    RtlCopyMemory( &(pEaEntry->EaName[EaNameLength]),
                   puPassword->Buffer,
                   puPassword->Length );

    EaLength = ( 2 * sizeof( DWORD ) ) +
               EaNameLength +
               puPassword->Length;

    EaLength = ROUNDUP4( EaLength );

    EaTotalLength += EaLength;

    pEaEntry->NextEntryOffset = EaLength;

    //
    // Pack in the third EA: Type.
    //

    pEaEntry = (PFILE_FULL_EA_INFORMATION)
               ( ( (PBYTE)pEaEntry ) + pEaEntry->NextEntryOffset );

    EaNameLength = TypeLen + sizeof( CHAR );

    pEaEntry->EaNameLength = (UCHAR) EaNameLength;
    pEaEntry->EaValueLength = sizeof( ULONG );

    RtlCopyMemory( &(pEaEntry->EaName[0]),
                   EA_NAME_TYPE,
                   EaNameLength );

    EaNameLength = ROUNDUP2( EaNameLength + sizeof( CHAR ) );

    EaLength = ( 2 * sizeof( DWORD ) ) +
               EaNameLength +
               sizeof( ULONG );

    EaLength = ROUNDUP4( EaLength );

    EaTotalLength += EaLength;

    pEaEntry->NextEntryOffset = EaLength;

    //
    // Pack in the fourth EA: the CredentialEx flag.
    //

    pEaEntry = (PFILE_FULL_EA_INFORMATION)
               ( ( (PBYTE)pEaEntry ) + pEaEntry->NextEntryOffset );

    EaNameLength = CredLen + sizeof( CHAR );

    pEaEntry->EaNameLength = (UCHAR) EaNameLength;
    pEaEntry->EaValueLength = sizeof( ULONG );

    RtlCopyMemory( &(pEaEntry->EaName[0]),
                   EA_NAME_CREDENTIAL_EX,
                   EaNameLength );

    EaNameLength = ROUNDUP2( EaNameLength + sizeof( CHAR ) );

    EaLength = ( 2 * sizeof( DWORD ) ) +
               EaNameLength +
               sizeof( ULONG );

    EaLength = ROUNDUP4( EaLength );
    EaTotalLength += EaLength;

    pEaEntry->NextEntryOffset = 0;

    //
    // Do the open.
    //

    ntstatus = NtCreateFile( phNwHandle,              // File handle (OUT)
                             DesiredAccess,           // Access mask
                             &ObjectAttributes,       // Object attributes
                             &IoStatusBlock,          // Io status
                             NULL,                    // Optional Allocation size
                             FILE_ATTRIBUTE_NORMAL,   // File attributes
                             FILE_SHARE_VALID_FLAGS,  // File share access
                             FILE_OPEN,               // Create disposition
                             0,                       // Create options
                             (PVOID) EaBuffer,        // Our EA buffer
                             EaTotalLength );         // Ea buffer length

    LocalFree( EaBuffer );

    if ( !NT_SUCCESS(ntstatus) )
        return ntstatus;

    OpenStatus = IoStatusBlock.Status;

    //
    // Check the handle type.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET)RrpData;

    Rrp->Version = 0;

    RtlCopyMemory( &(Rrp->Parameters).VerifyTree,
                   puResourceName,
                   sizeof( UNICODE_STRING ) );

    RtlCopyMemory( (BYTE *)(&(Rrp->Parameters).VerifyTree) + sizeof( UNICODE_STRING ),
                   puResourceName->Buffer,
                   puResourceName->Length );

    try {

        ntstatus = NtFsControlFile( *phNwHandle,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_VERIFY_TREE,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {
        ntstatus = GetExceptionCode();
        goto CloseAndExit2;
    }

    if ( !NT_SUCCESS( ntstatus ))
    {
        *lpdwHandleType = HANDLE_TYPE_NCP_SERVER;
    }
    else
    {
        *lpdwHandleType = HANDLE_TYPE_NDS_TREE;
    }

    return OpenStatus;

CloseAndExit2:

    NtClose( *phNwHandle );
    *phNwHandle = NULL;

    return ntstatus;
}

NTSTATUS
NwNdsSetTreeContext (
    IN HANDLE hNdsRdr,
    IN PUNICODE_STRING puTree,
    IN PUNICODE_STRING puContext
)
/*+++

    This sets the current context in the requested tree.

---*/
{

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    DWORD RrpSize;
    BYTE *CurrentString;

    //
    // Set up the request.
    //

    RrpSize = sizeof( NWR_NDS_REQUEST_PACKET ) +
              puTree->Length +
              puContext->Length;

    Rrp = LocalAlloc( LMEM_ZEROINIT, RrpSize );

    if ( !Rrp ) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    try {

        (Rrp->Parameters).SetContext.TreeNameLen = puTree->Length;
        (Rrp->Parameters).SetContext.ContextLen = puContext->Length;

        CurrentString = (BYTE *)(Rrp->Parameters).SetContext.TreeAndContextString;

        RtlCopyMemory( CurrentString, puTree->Buffer, puTree->Length );
        CurrentString += puTree->Length;
        RtlCopyMemory( CurrentString, puContext->Buffer, puContext->Length );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        ntstatus = STATUS_INVALID_PARAMETER;
        goto ExitWithCleanup;
    }

    try {

        ntstatus = NtFsControlFile( hNdsRdr,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_SETCONTEXT,
                                    (PVOID) Rrp,
                                    RrpSize,
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        ntstatus = GetExceptionCode();
        goto ExitWithCleanup;
    }

ExitWithCleanup:

    LocalFree( Rrp );
    return ntstatus;
}

NTSTATUS
NwNdsGetTreeContext (
    IN HANDLE hNdsRdr,
    IN PUNICODE_STRING puTree,
    OUT PUNICODE_STRING puContext
)
/*+++

    This gets the current context of the requested tree.

---*/
{

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    DWORD RrpSize;

    //
    // Set up the request.
    //

    RrpSize = sizeof( NWR_NDS_REQUEST_PACKET ) + puTree->Length;

    Rrp = LocalAlloc( LMEM_ZEROINIT, RrpSize );

    if ( !Rrp ) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    try {

        (Rrp->Parameters).GetContext.TreeNameLen = puTree->Length;

        RtlCopyMemory( (BYTE *)(Rrp->Parameters).GetContext.TreeNameString,
                       puTree->Buffer,
                       puTree->Length );

        (Rrp->Parameters).GetContext.Context.MaximumLength = puContext->MaximumLength;
        (Rrp->Parameters).GetContext.Context.Length = 0;
        (Rrp->Parameters).GetContext.Context.Buffer = puContext->Buffer;

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        ntstatus = STATUS_INVALID_PARAMETER;
        goto ExitWithCleanup;
    }

    try {

        ntstatus = NtFsControlFile( hNdsRdr,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_GETCONTEXT,
                                    (PVOID) Rrp,
                                    RrpSize,
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        ntstatus = GetExceptionCode();
        goto ExitWithCleanup;
    }

    //
    // Copy out the length; the buffer has already been written.
    //

    puContext->Length = (Rrp->Parameters).GetContext.Context.Length;

ExitWithCleanup:

    LocalFree( Rrp );
    return ntstatus;
}

NTSTATUS
NwNdsResolveName (
    IN HANDLE           hNdsTree,
    IN PUNICODE_STRING  puObjectName,
    OUT DWORD           *dwObjectId,
    OUT PUNICODE_STRING puReferredServer,
    OUT PBYTE           pbRawResponse,
    IN DWORD            dwResponseBufferLen
) {

    //
    // BUGBUG: Do I want or need to expose the flags parameter?
    //

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    PNDS_RESPONSE_RESOLVE_NAME Rsp;
    DWORD dwRspBufferLen, dwNameLen, dwPadding;

    BYTE RrpData[1024];
    BYTE RspData[256];

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;

    RtlZeroMemory( Rrp, 1024 );

    //
    // NW NDS strings are null terminated, so we make sure we
    // report the correct length...
    //

    dwNameLen = puObjectName->Length + sizeof( WCHAR );

    Rrp->Version = 0;
    Rrp->Parameters.ResolveName.ObjectNameLength = ROUNDUP4( dwNameLen );
    Rrp->Parameters.ResolveName.ResolverFlags = RSLV_DEREF_ALIASES |
                                                RSLV_WALK_TREE |
                                                RSLV_WRITABLE;

    try {

        //
        // But don't try to copy more than the user gave us.
        //

        memcpy( Rrp->Parameters.ResolveName.ObjectName,
                puObjectName->Buffer,
                puObjectName->Length );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;
    }

    //
    // Send the request to the Redirector FSD.
    //

    if ( dwResponseBufferLen != 0 &&
         pbRawResponse != NULL ) {

        Rsp = ( PNDS_RESPONSE_RESOLVE_NAME ) pbRawResponse;
        dwRspBufferLen = dwResponseBufferLen;

    } else {

        Rsp = ( PNDS_RESPONSE_RESOLVE_NAME ) RspData;
        dwRspBufferLen = 256;

    }

    try {

        ntstatus = NtFsControlFile( hNdsTree,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_RESOLVE_NAME,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    (PVOID) Rsp,
                                    dwRspBufferLen );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return GetExceptionCode();
    }

    //
    // Dig out the object id and referred server.
    //

    if ( NT_SUCCESS( ntstatus ) ) {

        try {

            *dwObjectId = Rsp->EntryId;

            if ( Rsp->ServerNameLength > puReferredServer->MaximumLength ) {

                ntstatus = STATUS_BUFFER_TOO_SMALL;

            } else {

                RtlCopyMemory( puReferredServer->Buffer,
                               Rsp->ReferredServer,
                               Rsp->ServerNameLength );

                puReferredServer->Length = (USHORT)Rsp->ServerNameLength;

            }

        } except ( EXCEPTION_EXECUTE_HANDLER ) {

            return ntstatus;

        }

    }

    return ntstatus;

}

NTSTATUS
NwNdsList (
   IN HANDLE   hNdsTree,
   IN DWORD    dwObjectId,
   OUT DWORD   *dwIterHandle,
   OUT BYTE    *pbReplyBuf,
   IN DWORD    dwReplyBufLen
) {

    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;

    PNDS_RESPONSE_SUBORDINATE_LIST Rsp;
    DWORD dwRspBufferLen;

    BYTE RrpData[256];
    BYTE RspData[1024];

    //
    // BUGBUG: I should make the reply buffer small and start testing
    // fragment assembly stuff.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;

    Rrp->Parameters.ListSubordinates.ObjectId = dwObjectId;
    Rrp->Parameters.ListSubordinates.IterHandle = *dwIterHandle;

   if ( dwReplyBufLen != 0 &&
        pbReplyBuf != NULL ) {

       Rsp = ( PNDS_RESPONSE_SUBORDINATE_LIST ) pbReplyBuf;
       dwRspBufferLen = dwReplyBufLen;

   } else {

       Rsp = ( PNDS_RESPONSE_SUBORDINATE_LIST ) RspData;
       dwRspBufferLen = 1024;

   }

   try {

       Status = NtFsControlFile( hNdsTree,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &IoStatusBlock,
                                 FSCTL_NWR_NDS_LIST_SUBS,
                                 (PVOID) Rrp,
                                 sizeof( NWR_NDS_REQUEST_PACKET ),
                                 (PVOID) Rsp,
                                 dwRspBufferLen );

   } except ( EXCEPTION_EXECUTE_HANDLER ) {

       return GetExceptionCode();
   }

   return Status;

}

NTSTATUS
NwNdsReadObjectInfo(
    IN HANDLE  hNdsTree,
    IN DWORD   dwObjectId,
    OUT BYTE   *pbRawReply,
    IN DWORD   dwReplyBufLen
)
{

    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;

    PNDS_RESPONSE_GET_OBJECT_INFO Rsp;
    DWORD dwRspBufferLen;

    BYTE RrpData[256];
    BYTE RspData[1024];

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;

    Rrp->Parameters.GetObjectInfo.ObjectId = dwObjectId;

    if ( dwReplyBufLen != 0 &&
         pbRawReply != NULL ) {

        Rsp = ( PNDS_RESPONSE_GET_OBJECT_INFO ) pbRawReply;
        dwRspBufferLen = dwReplyBufLen;

    } else {

        Rsp = ( PNDS_RESPONSE_GET_OBJECT_INFO ) RspData;
        dwRspBufferLen = 1024;

    }

    try {

        Status = NtFsControlFile( hNdsTree,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &IoStatusBlock,
                                  FSCTL_NWR_NDS_READ_INFO,
                                  (PVOID) Rrp,
                                  sizeof( NWR_NDS_REQUEST_PACKET ),
                                  (PVOID) Rsp,
                                  dwRspBufferLen );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return GetExceptionCode();
    }

    return Status;

}

NTSTATUS
NwNdsReadAttribute (
   IN HANDLE          hNdsTree,
   IN DWORD           dwObjectId,
   IN DWORD           *dwIterHandle,
   IN PUNICODE_STRING puAttrName,
   OUT BYTE           *pbReplyBuf,
   IN DWORD           dwReplyBufLen
) {

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    PNDS_RESPONSE_READ_ATTRIBUTE Rsp;

    DWORD dwAttributeNameLen;

    BYTE RrpData[1024];

    //
    // Check the incoming buffer.
    //
    if ( !dwReplyBufLen ||
         !pbReplyBuf ) {

        return STATUS_INVALID_PARAMETER;
    }

    //
    // Set up the request.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;
    RtlZeroMemory( Rrp, 1024 );

    (Rrp->Parameters).ReadAttribute.ObjectId = dwObjectId;
    (Rrp->Parameters).ReadAttribute.IterHandle = *dwIterHandle;

    //
    // Nds strings are NULL terminated; watch the size.
    //

    dwAttributeNameLen = puAttrName->Length + sizeof( WCHAR );

    (Rrp->Parameters).ReadAttribute.AttributeNameLength = dwAttributeNameLen;

    try {

        //
        // But don't try to copy more than the user gave us.
        //

        memcpy( (Rrp->Parameters).ReadAttribute.AttributeName,
                puAttrName->Buffer,
                puAttrName->Length );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;
    }

   //
   // Send the request to the Redirector FSD.
   //

   try {

       ntstatus = NtFsControlFile( hNdsTree,
                                   NULL,
                                   NULL,
                                   NULL,
                                   &IoStatusBlock,
                                   FSCTL_NWR_NDS_READ_ATTR,
                                   (PVOID) Rrp,
                                   sizeof( NWR_NDS_REQUEST_PACKET ),
                                   (PVOID) pbReplyBuf,
                                   dwReplyBufLen );

   } except ( EXCEPTION_EXECUTE_HANDLER ) {

       return GetExceptionCode();
   }

   //
   // There's no buffer post processing on this one.
   //

   return ntstatus;

}

NTSTATUS
NwNdsOpenStream (
    IN HANDLE          hNdsTree,
    IN DWORD           dwObjectId,
    IN PUNICODE_STRING puStreamName,
    IN DWORD           dwOpenFlags,
    OUT DWORD          *pdwFileLength
) {

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    BYTE RrpData[1024];

    //
    // Set up the request.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;
    RtlZeroMemory( Rrp, 1024 );

    (Rrp->Parameters).OpenStream.StreamAccess = dwOpenFlags;
    (Rrp->Parameters).OpenStream.ObjectOid = dwObjectId;

    (Rrp->Parameters).OpenStream.StreamName.Length = puStreamName->Length;
    (Rrp->Parameters).OpenStream.StreamName.MaximumLength =
        sizeof( RrpData ) - sizeof( (Rrp->Parameters).OpenStream );
    (Rrp->Parameters).OpenStream.StreamName.Buffer =
        (Rrp->Parameters).OpenStream.StreamNameString;

    //
    // Make sure we're not trashing memory.
    //

    if ( (Rrp->Parameters).OpenStream.StreamName.Length >
         (Rrp->Parameters).OpenStream.StreamName.MaximumLength ) {

        return STATUS_INVALID_PARAMETER;
    }

    try {

        //
        // But don't try to copy more than the user gave us.
        //

        memcpy( (Rrp->Parameters).OpenStream.StreamNameString,
                puStreamName->Buffer,
                puStreamName->Length );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;
    }

    //
    // Send the request to the Redirector FSD.
    //

    try {

        ntstatus = NtFsControlFile( hNdsTree,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_OPEN_STREAM,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return GetExceptionCode();
    }

    if ( pdwFileLength ) {
        *pdwFileLength = (Rrp->Parameters).OpenStream.FileLength;
    }

    return ntstatus;
}

NTSTATUS
NwNdsGetQueueInformation(
    IN HANDLE            hNdsTree,
    IN PUNICODE_STRING   puQueueName,
    OUT PUNICODE_STRING  puHostServer,
    OUT PDWORD           pdwQueueId
) {

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    BYTE RrpData[1024];

    //
    // Set up the request.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;
    RtlZeroMemory( Rrp, sizeof( RrpData ) );

    if ( puQueueName ) {
        (Rrp->Parameters).GetQueueInfo.QueueName.Length = puQueueName->Length;
        (Rrp->Parameters).GetQueueInfo.QueueName.MaximumLength = puQueueName->MaximumLength;
        (Rrp->Parameters).GetQueueInfo.QueueName.Buffer = puQueueName->Buffer;
    }

    if ( puHostServer ) {
        (Rrp->Parameters).GetQueueInfo.HostServer.Length = 0;
        (Rrp->Parameters).GetQueueInfo.HostServer.MaximumLength = puHostServer->MaximumLength;
        (Rrp->Parameters).GetQueueInfo.HostServer.Buffer = puHostServer->Buffer;
    }

    //
    // Send the request to the Redirector FSD.
    //

    try {

        ntstatus = NtFsControlFile( hNdsTree,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_GET_QUEUE_INFO,
                                    (PVOID) Rrp,
                                    sizeof( NWR_NDS_REQUEST_PACKET ),
                                    NULL,
                                    0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return GetExceptionCode();
    }

    if ( NT_SUCCESS( ntstatus ) ) {

        if ( pdwQueueId ) {
            *pdwQueueId = (Rrp->Parameters).GetQueueInfo.QueueId;
        }

        puHostServer->Length = (Rrp->Parameters).GetQueueInfo.HostServer.Length;

    }

    return ntstatus;
}

NTSTATUS
NwNdsGetVolumeInformation(
    IN HANDLE            hNdsTree,
    IN PUNICODE_STRING   puVolumeName,
    OUT PUNICODE_STRING  puHostServer,
    OUT PUNICODE_STRING  puHostVolume
) {

    NTSTATUS ntstatus;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET Rrp;
    DWORD RequestSize;
    BYTE RrpData[1024];
    BYTE ReplyData[1024];
    PBYTE NameStr;

    //
    // Set up the request.
    //

    Rrp = (PNWR_NDS_REQUEST_PACKET) RrpData;
    RtlZeroMemory( Rrp, sizeof( RrpData ) );

    if ( !puVolumeName ||
         puVolumeName->Length > MAX_NDS_NAME_SIZE ||
         !puHostServer ||
         !puHostVolume ) {

        return STATUS_INVALID_PARAMETER;
    }

    try {

        (Rrp->Parameters).GetVolumeInfo.VolumeNameLen = puVolumeName->Length;
        RtlCopyMemory( &((Rrp->Parameters).GetVolumeInfo.VolumeName[0]),
                       puVolumeName->Buffer,
                       puVolumeName->Length );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return STATUS_INVALID_PARAMETER;
    }

    //
    // Send the request to the Redirector FSD.
    //

    RequestSize = sizeof( NWR_NDS_REQUEST_PACKET ) +
                  (Rrp->Parameters).GetVolumeInfo.VolumeNameLen;

    try {

        ntstatus = NtFsControlFile( hNdsTree,
                                    NULL,
                                    NULL,
                                    NULL,
                                    &IoStatusBlock,
                                    FSCTL_NWR_NDS_GET_VOLUME_INFO,
                                    (PVOID) Rrp,
                                    RequestSize,
                                    ReplyData,
                                    sizeof( ReplyData ) );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        return GetExceptionCode();
    }

    if ( NT_SUCCESS( ntstatus ) ) {

        try {

            if ( ( puHostServer->MaximumLength < (Rrp->Parameters).GetVolumeInfo.ServerNameLen ) ||
                 ( puHostVolume->MaximumLength < (Rrp->Parameters).GetVolumeInfo.TargetVolNameLen ) ) {

                return STATUS_BUFFER_TOO_SMALL;
            }

            puHostServer->Length = (USHORT)(Rrp->Parameters).GetVolumeInfo.ServerNameLen;
            puHostVolume->Length = (USHORT)(Rrp->Parameters).GetVolumeInfo.TargetVolNameLen;

            NameStr = &ReplyData[0];

            RtlCopyMemory( puHostServer->Buffer, NameStr, puHostServer->Length );
            NameStr += puHostServer->Length;
            RtlCopyMemory( puHostVolume->Buffer, NameStr, puHostVolume->Length );

        } except ( EXCEPTION_EXECUTE_HANDLER ) {

            return STATUS_INVALID_PARAMETER;
        }

    }

    return ntstatus;

}

//
// User mode fragment exchange.
//

int
_cdecl
FormatBuf(
    char *buf,
    int bufLen,
    const char *format,
    va_list args
);

NTSTATUS
_cdecl
FragExWithWait(
    IN HANDLE  hNdsServer,
    IN DWORD   NdsVerb,
    IN BYTE    *pReplyBuffer,
    IN DWORD   pReplyBufferLen,
    IN OUT DWORD *pdwReplyLen,
    IN BYTE    *NdsRequestStr,
    ...
)
/*

Routine Description:

    Exchanges an NDS request in fragments and collects the fragments
    of the response and writes them to the reply buffer.

Routine Arguments:

    hNdsServer      - A handle to the server you want to talk to.
    NdsVerb         - The verb for that indicates the request.

    pReplyBuffer    - The reply buffer.
    pReplyBufferLen - The length of the reply buffer.

    NdsReqestStr    - The format string for the arguments to this NDS request.
    Arguments       - The arguments that satisfy the NDS format string.

Return Value:

    NTSTATUS - Status of the exchange, but not the result code in the packet.

*/
{

    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    PNWR_NDS_REQUEST_PACKET RawRequest;

    BYTE  *NdsRequestBuf;
    DWORD NdsRequestLen;

    va_list Arguments;

    //
    // Allocate a request buffer.
    //

    RawRequest = LocalAlloc( LMEM_ZEROINIT, NDS_BUFFER_SIZE );

    if ( !RawRequest ) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Build the request in our local buffer.  The first DWORD
    // is the verb and the rest is the formatted request.
    //

    RawRequest->Parameters.RawRequest.NdsVerb = NdsVerb;
    NdsRequestBuf = &RawRequest->Parameters.RawRequest.Request[0];

    if ( NdsRequestStr != NULL ) {

        va_start( Arguments, NdsRequestStr );

        NdsRequestLen = FormatBuf( NdsRequestBuf,
                                   NDS_BUFFER_SIZE - sizeof( NWR_NDS_REQUEST_PACKET ),
                                   NdsRequestStr,
                                   Arguments );

        if ( !NdsRequestLen ) {

           Status = STATUS_INVALID_PARAMETER;
           goto ExitWithCleanup;

        }

        va_end( Arguments );

    } else {

        NdsRequestLen = 0;
    }

    RawRequest->Parameters.RawRequest.RequestLength = NdsRequestLen;

    //
    // Pass this buffer to kernel mode via FSCTL.
    //

    try {

        Status = NtFsControlFile( hNdsServer,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &IoStatusBlock,
                                  FSCTL_NWR_NDS_RAW_FRAGEX,
                                  (PVOID) RawRequest,
                                  NdsRequestLen + sizeof( NWR_NDS_REQUEST_PACKET ),
                                  (PVOID) pReplyBuffer,
                                  pReplyBufferLen );

        if ( NT_SUCCESS( Status ) ) {
            *pdwReplyLen = RawRequest->Parameters.RawRequest.ReplyLength;
        }

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        Status = GetExceptionCode();
    }

ExitWithCleanup:

    if ( RawRequest ) {
        LocalFree( RawRequest );
    }

    return Status;

}

int
_cdecl
FormatBuf(
    char *buf,
    int bufLen,
    const char *format,
    va_list args
)
/*

Routine Description:

    Formats a buffer according to supplied the format string.

    FormatString - Supplies an ANSI string which describes how to
       convert from the input arguments into NCP request fields, and
       from the NCP response fields into the output arguments.

         Field types, request/response:

            'b'      byte              ( byte   /  byte* )
            'w'      hi-lo word        ( word   /  word* )
            'd'      hi-lo dword       ( dword  /  dword* )
            'W'      lo-hi word        ( word  /   word*)
            'D'      lo-hi dword       ( dword  /  dword*)
            '-'      zero/skip byte    ( void )
            '='      zero/skip word    ( void )
            ._.      zero/skip string  ( word )
            'p'      pstring           ( char* )
            'c'      cstring           ( char* )
            'C'      cstring followed skip word ( char*, word )
            'V'      sized NDS value   ( byte *, dword / byte **, dword *)
            'S'      p unicode string copy as NDS_STRING (UNICODE_STRING *)
            's'      cstring copy as NDS_STRING (char* / char *, word)
            'r'      raw bytes         ( byte*, word )
            'u'      p unicode string  ( UNICODE_STRING * )
            'U'      p uppercase string( UNICODE_STRING * )

Routine Arguments:

    char *buf - destination buffer.
    int buflen - length of the destination buffer.
    char *format - format string.
    args - args to the format string.

Implementation Notes:

   This comes verbatim from kernel mode.

*/
{
    ULONG ix;

    NTSTATUS status;
    const char *z = format;

    //
    // Convert the input arguments into request packet.
    //

    ix = 0;

    while ( *z )
    {
        switch ( *z )
        {
        case '=':
            buf[ix++] = 0;
        case '-':
            buf[ix++] = 0;
            break;

        case '_':
        {
            WORD l = va_arg ( args, WORD );
            if (ix + (ULONG)l > (ULONG)bufLen)
            {
                goto ErrorExit;
            }
            while ( l-- )
                buf[ix++] = 0;
            break;
        }

        case 'b':
            buf[ix++] = va_arg ( args, BYTE );
            break;

        case 'w':
        {
            WORD w = va_arg ( args, WORD );
            buf[ix++] = (BYTE) (w >> 8);
            buf[ix++] = (BYTE) (w >> 0);
            break;
        }

        case 'd':
        {
            DWORD d = va_arg ( args, DWORD );
            buf[ix++] = (BYTE) (d >> 24);
            buf[ix++] = (BYTE) (d >> 16);
            buf[ix++] = (BYTE) (d >>  8);
            buf[ix++] = (BYTE) (d >>  0);
            break;
        }

        case 'W':
        {
            WORD w = va_arg(args, WORD);
            (* (WORD *)&buf[ix]) = w;
            ix += 2;
            break;
        }

        case 'D':
        {
            DWORD d = va_arg (args, DWORD);
            (* (DWORD *)&buf[ix]) = d;
            ix += 4;
            break;
        }

        case 'c':
        {
            char* c = va_arg ( args, char* );
            WORD  l = strlen( c );
            if (ix + (ULONG)l > (ULONG)bufLen)
            {
                goto ErrorExit;
            }
            RtlCopyMemory( &buf[ix], c, l+1 );
            ix += l + 1;
            break;
        }

        case 'C':
        {
            char* c = va_arg ( args, char* );
            WORD l = va_arg ( args, WORD );
            WORD len = strlen( c ) + 1;
            if (ix + (ULONG)l > (ULONG)bufLen)
            {
                goto ErrorExit;
            }

            RtlCopyMemory( &buf[ix], c, len > l? l : len);
            ix += l;
            buf[ix-1] = 0;
            break;
        }

        case 'p':
        {
            char* c = va_arg ( args, char* );
            BYTE  l = strlen( c );
            if (ix + (ULONG)l +1 > (ULONG)bufLen)
            {
                goto ErrorExit;
            }
            buf[ix++] = l;
            RtlCopyMemory( &buf[ix], c, l );
            ix += l;
            break;
        }

        case 'u':
        {
            PUNICODE_STRING pUString = va_arg ( args, PUNICODE_STRING );
            OEM_STRING OemString;
            ULONG Length;

            //
            //  Calculate required string length, excluding trailing NUL.
            //

            Length = RtlUnicodeStringToOemSize( pUString ) - 1;
            ASSERT( Length < 0x100 );

            if ( ix + Length > (ULONG)bufLen ) {
                goto ErrorExit;
            }

            buf[ix++] = (UCHAR)Length;
            OemString.Buffer = &buf[ix];
            OemString.MaximumLength = (USHORT)Length + 1;

            status = RtlUnicodeStringToOemString( &OemString, pUString, FALSE );
            ASSERT( NT_SUCCESS( status ));
            ix += (USHORT)Length;
            break;
        }

        case 'S':
        {
            PUNICODE_STRING pUString = va_arg (args, PUNICODE_STRING);
            ULONG Length, rLength;

            Length = pUString->Length;
            if (ix + Length + sizeof(Length) + sizeof( WCHAR ) > (ULONG)bufLen) {
                goto ErrorExit;
            }

            //
            // The VLM client uses the rounded up length and it seems to
            // make a difference!  Also, don't forget that NDS strings have
            // to be NULL terminated.
            //

            rLength = ROUNDUP4(Length + sizeof( WCHAR ));
            *((DWORD *)&buf[ix]) = rLength;
            ix += 4;
            RtlCopyMemory(&buf[ix], pUString->Buffer, Length);
            ix += Length;
            rLength -= Length;
            RtlFillMemory( &buf[ix], rLength, '\0' );
            ix += rLength;
            break;

        }

        case 's':
        {
           PUNICODE_STRING pUString = va_arg (args, PUNICODE_STRING);
           ULONG Length, rLength;

           Length = pUString->Length;
           if (ix + Length + sizeof(Length) + sizeof( WCHAR ) > (ULONG)bufLen) {
               // DebugTrace( 0, Dbg, "FormatBuf: case 's' request buffer too small.\n", 0 );
               goto ErrorExit;
           }

           //
           // Don't use the padded size here, only the NDS null terminator.
           //

           rLength = Length + sizeof( WCHAR );
           *((DWORD *)&buf[ix]) = rLength;
           ix += 4;
           RtlCopyMemory(&buf[ix], pUString->Buffer, Length);
           ix += Length;
           rLength -= Length;
           RtlFillMemory( &buf[ix], rLength, '\0' );
           ix += rLength;
           break;


        }

        case 'V':
        {
            // too similar to 'S' - should be combined
            BYTE* b = va_arg ( args, BYTE* );
            DWORD  l = va_arg ( args, DWORD );
            if ( ix + l + sizeof(DWORD) > (ULONG)
               bufLen )
            {
                goto ErrorExit;
            }
            *((DWORD *)&buf[ix]) = l;
            ix += sizeof(DWORD);
            RtlCopyMemory( &buf[ix], b, l );
                        l = ROUNDUP4(l);
            ix += l;
            break;
        }

        case 'r':
        {
            BYTE* b = va_arg ( args, BYTE* );
            WORD  l = va_arg ( args, WORD );
            if ( b == NULL || l == 0 )
            {
                break;
            }
            if ( ix + l > (ULONG)bufLen )
            {
                goto ErrorExit;
            }
            RtlCopyMemory( &buf[ix], b, l );
            ix += l;
            break;
        }

        default:

        ;

        }

        if ( ix > (ULONG)bufLen )
        {
            goto ErrorExit;
        }


        z++;
    }

    return(ix);

ErrorExit:
    return 0;
}

NTSTATUS
_cdecl
ParseResponse(
    PUCHAR  Response,
    ULONG ResponseLength,
    char*  FormatString,
    ...                       //  format specific parameters
    )
/*++

Routine Description:

    This routine parse an NCP response.

    Packet types:

            'G'      Generic packet            ( )

         Field types, request/response:

            'b'      byte              ( byte* )
            'w'      hi-lo word        ( word* )
            'x'      ordered word      ( word* )
            'd'      hi-lo dword       ( dword* )
            'e'      ordered dword     ( dword* )
            '-'      zero/skip byte    ( void )
            '='      zero/skip word    ( void )
            ._.      zero/skip string  ( word )
            'p'      pstring           ( char* )
            'c'      cstring           ( char* )
            'r'      raw bytes         ( byte*, word )

            Added 3/29/95 by CoryWest:

            'W'      lo-hi word        ( word  /   word*)
            'D'      lo-hi dword       ( dword  /  dword*)
            'S'      unicode string copy as NDS_STRING (UNICODE_STRING *)
            'T'      terminal unicode string copy as NDS_STRING (UNICODE_STRING *)

            't'      terminal unicode string with the nds null copied
                     as NDS_STRING (UNICODE_STRING *) (for GetUseName)

Return Value:

    STATUS - Success or failure, depending on the response.

--*/

{
    NTSTATUS Status = STATUS_SUCCESS;

    PCHAR FormatByte;
    va_list Arguments;
    ULONG Length = 0;

    va_start( Arguments, FormatString );

    //
    // User mode parse response handles only generic packets.
    //

    if ( *FormatString != 'G' ) {
        return STATUS_INVALID_PARAMETER;
    }

    FormatByte = FormatString + 1;
    while ( *FormatByte ) {

        switch ( *FormatByte ) {

        case '-':
            Length += 1;
            break;

        case '=':
            Length += 2;
            break;

        case '_':
        {
            WORD l = va_arg ( Arguments, WORD );
            Length += l;
            break;
        }

        case 'b':
        {
            BYTE* b = va_arg ( Arguments, BYTE* );
            *b = Response[Length++];
            break;
        }

        case 'w':
        {
            BYTE* b = va_arg ( Arguments, BYTE* );
            b[1] = Response[Length++];
            b[0] = Response[Length++];
            break;
        }

        case 'x':
        {
            WORD* w = va_arg ( Arguments, WORD* );
            *w = *(WORD UNALIGNED *)&Response[Length];
            Length += 2;
            break;
        }

        case 'd':
        {
            BYTE* b = va_arg ( Arguments, BYTE* );
            b[3] = Response[Length++];
            b[2] = Response[Length++];
            b[1] = Response[Length++];
            b[0] = Response[Length++];
            break;
        }

        case 'e':
        {
            DWORD UNALIGNED * d = va_arg ( Arguments, DWORD* );
            *d = *(DWORD UNALIGNED *)&Response[Length];
            Length += 4;
            break;
        }

        case 'c':
        {
            char* c = va_arg ( Arguments, char* );
            WORD  l = strlen( &Response[Length] );
            memcpy ( c, &Response[Length], l+1 );
            Length += l+1;
            break;
        }

        case 'p':
        {
            char* c = va_arg ( Arguments, char* );
            BYTE  l = Response[Length++];
            memcpy ( c, &Response[Length], l );
            c[l+1] = 0;
            break;
        }

        case 'r':
        {
            BYTE* b = va_arg ( Arguments, BYTE* );
            WORD  l = va_arg ( Arguments, WORD );
            RtlCopyMemory( b, &Response[Length], l );
            Length += l;
            break;
        }

        case 'W':
        {

            WORD *w = va_arg ( Arguments, WORD* );
            *w = (* (WORD *)&Response[Length]);
            Length += 2;
            break;

        }

        case 'D':
        {

            DWORD *d = va_arg ( Arguments, DWORD* );
            *d = (* (DWORD *)&Response[Length]);
            Length += 4;
            break;

        }

        case 'S':
        {

            PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
            USHORT strl;

            if (pU) {

               strl = (USHORT)(* (DWORD *)&Response[Length]);

                //
                // Don't count the null terminator that is part of
                // Novell's counted unicode string.
                //

                pU->Length = strl - sizeof( WCHAR );
                Length += 4;
                RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
                Length += ROUNDUP4(strl);

            } else {

                //
                // Skip over the string since we don't want it.
                //

                Length += ROUNDUP4((* (DWORD *)&Response[Length] ));
                Length += 4;
            }


            break;

        }

        case 's':
        {

            PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
            USHORT strl;

            if (pU) {

                strl = (USHORT)(* (DWORD *)&Response[Length]);
                pU->Length = strl;
                Length += 4;
                RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );
                Length += ROUNDUP4(strl);

            } else {

                //
                // Skip over the string since we don't want it.
                //

                Length += ROUNDUP4((* (DWORD *)&Response[Length] ));
                Length += 4;
            }


            break;

        }

        case 'T':
        {

            PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
            USHORT strl;

            if (pU) {

                strl = (USHORT)(* (DWORD *)&Response[Length] );
                strl -= sizeof( WCHAR );  // Don't count the NULL from NDS.

                if ( strl <= pU->MaximumLength ) {

                   pU->Length = strl;
                   Length += 4;
                   RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );

                   //
                   // No need to advance the pointers since this is
                   // specifically a termination case!
                   //

                } else {

                    pU->Length = 0;
                }

            }

            break;

        }

        case 't':
        {

            PUNICODE_STRING pU = va_arg( Arguments, PUNICODE_STRING );
            USHORT strl;

            if (pU) {

                strl = (USHORT)(* (DWORD *)&Response[Length] );

                if ( strl <= pU->MaximumLength ) {

                   pU->Length = strl;
                   Length += 4;
                   RtlCopyMemory( pU->Buffer, &Response[Length], pU->Length );

                   //
                   // No need to advance the pointers since this is
                   // specifically a termination case!
                   //

                } else {

                   pU->Length = 0;

                }

            }

            break;

        }

    }

    if ( Length > ResponseLength ) {
        return( STATUS_INVALID_PARAMETER );
    }

    FormatByte++;

    }

    va_end( Arguments );
    return( Status );

}

NTSTATUS
NwNdsChangePassword(
    IN HANDLE          hNwRdr,
    IN PUNICODE_STRING puTreeName,
    IN PUNICODE_STRING puUserName,
    IN PUNICODE_STRING puCurrentPassword,
    IN PUNICODE_STRING puNewPassword
) {

    NTSTATUS Status;
    PNWR_NDS_REQUEST_PACKET pNdsRequest;
    DWORD dwRequestLength;
    PBYTE CurrentString;
    IO_STATUS_BLOCK IoStatusBlock;

    //
    // Allocate the request.
    //

    dwRequestLength =  sizeof( NWR_NDS_REQUEST_PACKET ) +
                       puTreeName->Length +
                       puUserName->Length +
                       puCurrentPassword->Length +
                       puNewPassword->Length;

    pNdsRequest = LocalAlloc( LMEM_ZEROINIT, dwRequestLength );

    if ( !pNdsRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Copy the parameters into the request buffer.
    //

    try {

        (pNdsRequest->Parameters).ChangePass.NdsTreeNameLength =
            puTreeName->Length;
        (pNdsRequest->Parameters).ChangePass.UserNameLength =
            puUserName->Length;
        (pNdsRequest->Parameters).ChangePass.CurrentPasswordLength =
            puCurrentPassword->Length;
        (pNdsRequest->Parameters).ChangePass.NewPasswordLength =
            puNewPassword->Length;

        CurrentString = ( PBYTE ) &((pNdsRequest->Parameters).ChangePass.StringBuffer[0]);
        RtlCopyMemory( CurrentString, puTreeName->Buffer, puTreeName->Length );

        CurrentString += puTreeName->Length;
        RtlCopyMemory( CurrentString, puUserName->Buffer, puUserName->Length );

        CurrentString += puUserName->Length;
        RtlCopyMemory( CurrentString, puCurrentPassword->Buffer, puCurrentPassword->Length );

        CurrentString += puCurrentPassword->Length;
        RtlCopyMemory( CurrentString, puNewPassword->Buffer, puNewPassword->Length );

        Status = NtFsControlFile( hNwRdr,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &IoStatusBlock,
                                  FSCTL_NWR_NDS_CHANGE_PASS,
                                  (PVOID) pNdsRequest,
                                  dwRequestLength,
                                  NULL,
                                  0 );

    } except ( EXCEPTION_EXECUTE_HANDLER ) {

        Status = STATUS_INVALID_PARAMETER;
    }

    LocalFree( pNdsRequest );
    return Status;

}