/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    redir.c

Abstract:

    Redirector thread for USRV.

Author:

    David Treadwell (davidtr) 20-Oct-1989
    Chuck Lenzmeier (chuckl)

Revision History:

--*/

#include "usrv.h"

//
// Forward declarations
//

STATIC
NTSTATUS
CreateEndpoint (
    IN OUT PDESCRIPTOR Redir,
    IN CLONG RedirNumber,
    IN PSZ DebugName
    );

STATIC
NTSTATUS
CreateEvents (
    IN OUT PDESCRIPTOR Redir,
    IN PSZ DebugName
    );

STATIC
NTSTATUS
ExecuteBatchFile(
    IN PSZ Name,
    IN PDESCRIPTOR Redir,
    IN PSZ Prompt
    );

STATIC
NTSTATUS
ExecuteCommand(
    IN PSZ Command,
    IN PDESCRIPTOR Redir,
    IN PSZ Prompt
    );

STATIC
NTSTATUS
Negotiate (
    IN OUT PDESCRIPTOR Redir
    );

STATIC
VOID
ParseCommand(
    IN PSZ CommandLine,
    OUT PSZ Argv[],
    OUT PSHORT Argc,
    IN SHORT MaxArgc
    );

STATIC
NTSTATUS
StartAssociate (
    IN HANDLE ConnectionFileHandle,
    IN HANDLE AddressFileHandle,
    IN HANDLE EventHandle,
    IN PIO_STATUS_BLOCK Iosb,
    IN PSZ DebugString
    );

STATIC
NTSTATUS
StartConnect (
    IN PSTRING RemoteAddress,
    IN PVOID Buffer,
    IN HANDLE FileHandle,
    IN HANDLE EventHandle,
    IN PIO_STATUS_BLOCK Iosb,
    IN PSZ DebugString
    );

STATIC
NTSTATUS
WaitForAssociate (
    IN PSZ Operation,
    IN PDESCRIPTOR Redir,
    IN UCHAR EventNumber
    );

STATIC
NTSTATUS
WaitForConnect (
    IN PSZ Operation,
    IN PDESCRIPTOR Redir,
    IN UCHAR EventNumber
    );


NTSTATUS
RedirThreadWrapper(
    IN PVOID Dummy
    )

{
    NTSTATUS status;

    status = RedirThread( (PDESCRIPTOR)Dummy );

    NtTerminateThread( NtCurrentThread(), status );

    return status;      // shouldn't get here

} // RedirThreadWrapper


NTSTATUS
RedirThread(
    IN PDESCRIPTOR Redir
    )

{
#define MAX_ARGC 20

    NTSTATUS status;
    UCHAR i;
    STRING remoteAddress;
    CLONG redirNumber;
    CHAR prompt[11];

    redirNumber = Redir->RedirNumber;
    IF_DEBUG(1) printf( "****Entered redir thread %ld.\n", redirNumber );

    //
    // Allocate data buffers.
    //

    for( i = 0; i < NUMBER_OF_EVENTS; i++ ) {
        Redir->Data[i] = malloc( Redir->MaxBufferSize );
        if ( Redir->Data[i] == NULL ) {
            printf( "malloc (redir data buffer) failed\n" );
            DbgBreakPoint( );
            return STATUS_UNSUCCESSFUL;
        }
        IF_DEBUG(2) printf( "Redir data buffer address: 0x%lx\n", Redir->Data[i] );
    }

    Redir->RawBuffer = NULL;

    //
    // Create redir events.
    //

    status = CreateEvents(
                Redir,
                "Redir"
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    //
    // Create endpoint for Redir.
    //

    status = CreateEndpoint(
                Redir,
                redirNumber,
                "Redir"
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    //
    // Connect redir to server (must be started after server listen).
    //

    RtlInitString( &remoteAddress, ServerName+1 );  // skip comma in server name
    IF_DEBUG(2) printf( "Server name \"%Z\"\n", &remoteAddress );

    status = StartConnect(
                &remoteAddress,
                Redir->Data[0],
                Redir->FileHandle,
                Redir->EventHandle[0],
                &Redir->Iosb[0],
                "Connect"
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    status = WaitForConnect(
                "Connect",
                Redir,
                0
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    if ( DefaultNegotiate ) {
        status = Negotiate( Redir );
        if ( !NT_SUCCESS(status) ) {
            return status;
        }
    }

    IF_DEBUG(1) {
        printf( "****Redir thread %ld initialization complete.\n",
                    redirNumber );
    }

    RtlMoveMemory( prompt, "Redir_", 6 );
    prompt[6] = (CHAR)( redirNumber / 10 + '0' );
    prompt[7] = (CHAR)( redirNumber % 10 + '0' );
    prompt[8] = '>';
    prompt[9] = ' ';
    prompt[10] = 0;

    if ( !NoUsrvInit ) {
        ExecuteBatchFile( "usrvinit", Redir, prompt );
    }

    ExecuteCommand( NULL, Redir, prompt );

    printf( "****Redir thread %ld exiting: %X\n",
                redirNumber, STATUS_SUCCESS );
    return STATUS_SUCCESS;

} // RedirThread


NTSTATUS
CreateEndpoint (
    PDESCRIPTOR Redir,
    CLONG RedirNumber,
    PSZ DebugName
    )
{
    NTSTATUS status;
    CHAR transportName[128];
    CHAR redirName[17];
    CLONG baseNameLength;
    STRING nameString;
    UNICODE_STRING unicodeNameString;
    OBJECT_ATTRIBUTES objectAttributes;
    LARGE_INTEGER allocationSize = { 0, 0 };
    CHAR eaBuffer[sizeof(FILE_FULL_EA_INFORMATION) - 1 +
                  TDI_TRANSPORT_ADDRESS_LENGTH + 1 +
                  sizeof(TA_NETBIOS_ADDRESS)];
    PFILE_FULL_EA_INFORMATION ea;
    CONNECTION_CONTEXT ctx;
    CONNECTION_CONTEXT *pctx;

    //
    // Create the transport and redir names.
    //

    baseNameLength = strlen( REDIR_ADDRESS_PART1 );
    RtlMoveMemory(
        transportName,
        REDIR_ADDRESS_PART1,
        baseNameLength
        );
    RtlMoveMemory(
        transportName + baseNameLength,
        Transport,
        strlen( Transport ) + 1
        );
    IF_DEBUG(1) printf( "Using transport name %s\n", transportName );

    RtlMoveMemory( redirName, "Redir_", 6 );
    redirName[6] = (CHAR)( RedirNumber / 10 + '0' );
    redirName[7] = (CHAR)( RedirNumber % 10 + '0' );
    RtlMoveMemory( redirName+8, "       ", 7 );
    redirName[15] = 0;
    redirName[16] = 0;

    IF_DEBUG(1) printf( "Using redir name \"%s\"\n", redirName );

    //
    // Open the address.
    //

    RtlInitString( &nameString, transportName );
    status = RtlAnsiStringToUnicodeString(
                 &unicodeNameString,
                 &nameString,
                 TRUE
                 );
    ASSERT( NT_SUCCESS(status) );

    IF_DEBUG(2) printf( "Creating %s endpoint\n", DebugName );
    status = TdiOpenNetbiosAddress(
                &Redir->EndpointFileHandle,
                eaBuffer,
                &unicodeNameString,
                redirName
                );

    RtlFreeUnicodeString( &unicodeNameString );

    if ( !NT_SUCCESS(status) ) {
        printf( "TdiOpenNetbiosAddress (%s) failed: %X\n",
                    DebugName, status );
        DbgBreakPoint( );
        return status;
    }

    IF_DEBUG(2) printf( "  %s Address file handle 0x%lx\n",
        DebugName, Redir->EndpointFileHandle );

    //
    // Create a connection.
    //
    // Create the EA for the connection context.
    //

    ASSERT( TDI_CONNECTION_CONTEXT_LENGTH + sizeof(CONNECTION_CONTEXT) <=
            TDI_TRANSPORT_ADDRESS_LENGTH + sizeof(TA_NETBIOS_ADDRESS) );

    ea = (PFILE_FULL_EA_INFORMATION)eaBuffer;
    ea->NextEntryOffset = 0;
    ea->Flags = 0;
    ea->EaNameLength = TDI_CONNECTION_CONTEXT_LENGTH;
    ea->EaValueLength = sizeof( CONNECTION_CONTEXT );

    RtlMoveMemory( ea->EaName, TdiConnectionContext, ea->EaNameLength + 1 );

    ctx = (PVOID)0x22222222;
    pctx = (CONNECTION_CONTEXT *)&ea->EaName[ea->EaNameLength + 1];
    RtlMoveMemory( pctx, &ctx, sizeof(CONNECTION_CONTEXT) );

    //
    // Create the connection file object.
    //

    RtlInitString( &nameString, transportName );
    status = RtlAnsiStringToUnicodeString(
                 &unicodeNameString,
                 &nameString,
                 TRUE
                 );
    ASSERT( NT_SUCCESS(status) );

    InitializeObjectAttributes(
        &objectAttributes,
        &unicodeNameString,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    status = NtCreateFile(
                &Redir->FileHandle,
                FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
                &objectAttributes,
                &Redir->Iosb[0],
                &allocationSize,
                0,
                0,
                0,
                0,
                eaBuffer,
                FIELD_OFFSET( FILE_FULL_EA_INFORMATION, EaName[0] ) +
                            ea->EaNameLength + 1 + ea->EaValueLength
                );

    RtlFreeUnicodeString( &unicodeNameString );

    if ( !NT_SUCCESS(status) ) {
        printf( "NtCreateFile (%s) service failed: %X\n", DebugName, status );
        DbgBreakPoint( );
        NtClose( Redir->EndpointFileHandle );
        return status;
    }

    status = Redir->Iosb[0].Status;
    if ( !NT_SUCCESS(status) ) {
        printf( "NtCreateFile (%s) I/O failed: %X\n", DebugName, status );
        DbgBreakPoint( );
        NtClose( Redir->EndpointFileHandle );
        return status;
    }

    IF_DEBUG(2) printf( "  %s Connection File handle 0x%lx\n",
        DebugName, Redir->FileHandle );

    //
    // Associate the connection with the address.
    //

    status = StartAssociate(
                Redir->FileHandle,
                Redir->EndpointFileHandle,
                Redir->EventHandle[0],
                &Redir->Iosb[0],
                DebugName
                );
    if ( !NT_SUCCESS(status) ) {
        NtClose( Redir->FileHandle );
        NtClose( Redir->EndpointFileHandle );
        return status;
    }

    status = WaitForAssociate(
                DebugName,
                Redir,
                0
                );
    if ( !NT_SUCCESS(status) ) {
        NtClose( Redir->FileHandle );
        NtClose( Redir->EndpointFileHandle );
        return status;
    }

    IF_DEBUG(2) printf( "  connection 0x%lx associated with address 0x%lx\n",
        Redir->FileHandle, Redir->EndpointFileHandle );

    return STATUS_SUCCESS;

} // CreateEndpoint


NTSTATUS
CreateEvents (
    PDESCRIPTOR Redir,
    PSZ DebugName
    )
{
    NTSTATUS status;
    UCHAR i;

    //
    // Create redir events.
    //

    for( i = 0; i < NUMBER_OF_EVENTS; i++ ) {
        status = NtCreateEvent(
                    &Redir->EventHandle[i],
                    EVENT_ALL_ACCESS,
                    NULL,
                    NotificationEvent,
                    FALSE
                    );
        if ( !NT_SUCCESS(status) ) {
            printf( "NtCreateEvent (%s) failed: %X\n", DebugName, status );
            DbgBreakPoint( );
            return status;
        }

        IF_DEBUG(2) printf( "  %s Event handle 0x%lx\n",
            DebugName, Redir->EventHandle[i] );

    } // loop on NUMBER_OF_EVENTS

    return STATUS_SUCCESS;

} // CreateEvents


NTSTATUS
Negotiate(
    IN OUT PDESCRIPTOR Redir
    )
{
    NTSTATUS status;
    ID_SELECTIONS idSelections;
    ULONG smbSize;

    //
    // Create dummy IdSelections.  Required to be present, but not used
    // by Negotiate request/response.
    //

    idSelections.Uid = 0;
    idSelections.Tid = 0;
    idSelections.Fid = 0;

    //
    // Format the Negotiate request.
    //

    status = MakeNegotiateSmb(
                Redir,
                Redir->Data[0],
                NULL,
                SMB_COM_NO_ANDX_COMMAND,
                &idSelections,
                &Redir->IdValues,
                &smbSize
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    //
    // Send the request and receive a response.
    //

    status = SendAndReceiveSmb( Redir, "Negotiate", smbSize, 0, 1 );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    //
    // Verify the response.
    //

    status = VerifyNegotiate(
                Redir,
                NULL,
                SMB_COM_NEGOTIATE,
                &idSelections,
                &Redir->IdValues,
                &smbSize,
                Redir->Data[1]
                );
    if ( !NT_SUCCESS(status) ) {
        return status;
    }

    return STATUS_SUCCESS;

} // Negotiate


VOID
ParseCommand(
    IN PSZ CommandLine,
    OUT PSZ Argv[],
    OUT PSHORT Argc,
    IN SHORT MaxArgc
    )
{
    PSZ cl = CommandLine;
    SHORT ac = 0;

    while ( *cl && (ac < MaxArgc) ) {

        while ( *cl && (*cl <= ' ') ) { // ignore leading blanks
            cl++;
        }

        if ( !*cl ) break;

        *Argv++ = cl;
        ++ac;

        while (*cl > ' ') {
            cl++;
        }

        if ( *cl ) {
            *cl++ = '\0';
        }
    }

    if ( ac < MaxArgc ) {
        *Argv++ = NULL;
    } else if ( *cl ) {
        printf( "Too many tokens in command; \"%s\" ignored\n", cl );
    }

    *Argc = ac;

    return;

} // ParseCommand


NTSTATUS
StartConnect (
    IN PSTRING RemoteAddress,
    IN PVOID Buffer,
    IN HANDLE FileHandle,
    IN HANDLE EventHandle,
    IN PIO_STATUS_BLOCK Iosb,
    IN PSZ DebugString
    )
{
    NTSTATUS status;
    PTDI_REQUEST_CONNECT request;
    PTDI_CONNECTION_INFORMATION conninfo;
    PTA_NETBIOS_ADDRESS address;
    ULONG i;
    SHORT length;
    PSZ remoteAddress;

    remoteAddress = RemoteAddress->Buffer;
    length = RemoteAddress->Length;

    //
    // Inside of Buffer, initialize the TDI_REQUEST_CONNECT,
    // followed immediately by the TDI_CONNECTION_INFORMATION,
    // followed by the TA_NETBIOS_ADDRESS.
    //

    request = (PTDI_REQUEST_CONNECT)Buffer;
    request->RequestConnectionInformation = (PTDI_CONNECTION_INFORMATION)(request + 1);

    conninfo = request->RequestConnectionInformation;
    conninfo->UserDataLength = 0;
    conninfo->OptionsLength = 0;
    conninfo->RemoteAddressLength = sizeof(TA_NETBIOS_ADDRESS);
    conninfo->RemoteAddress = (PTA_NETBIOS_ADDRESS)(conninfo + 1);

    address = conninfo->RemoteAddress;
    address->TAAddressCount = 1;
    address->Address[0].AddressType = TDI_ADDRESS_TYPE_NETBIOS;
    address->Address[0].AddressLength = sizeof(TDI_ADDRESS_NETBIOS);
    address->Address[0].Address[0].NetbiosNameType =
                                    TDI_ADDRESS_NETBIOS_TYPE_UNIQUE;
    RtlMoveMemory(
        address->Address[0].Address[0].NetbiosName,
        remoteAddress,
        MIN( length, 16 )
        );
    for ( i = length; i < 16; i++ ) {
        address->Address[0].Address[0].NetbiosName[i] = 0;
    }

    IF_DEBUG(2) printf( "Starting %s\n", DebugString );
    status = NtDeviceIoControlFile(
                FileHandle,
                EventHandle,
                NULL,
                NULL,
                Iosb,
                IOCTL_TDI_CONNECT,
                (PVOID)Buffer,
                sizeof(*request) + sizeof(*conninfo) + sizeof(*address),
                NULL,
                0
                );

    if ( !NT_SUCCESS(status) ) {
        printf( "NtDeviceIoControlFile (%s) service failed: %X\n",
                    DebugString, status );
        DbgBreakPoint( );
        return status;
    }

    return STATUS_SUCCESS;

} // StartConnect


NTSTATUS
WaitForConnect(
    IN PSZ Operation,
    IN PDESCRIPTOR Redir,
    IN UCHAR EventNumber
    )

{
    NTSTATUS status;

    IF_DEBUG(2) printf( "Waiting for %s\n", Operation );
    status = NtWaitForSingleObject(
                Redir->EventHandle[EventNumber],
                FALSE,
                NULL
                );
    IF_DEBUG(2) printf( "%s complete\n", Operation );
    if ( !NT_SUCCESS(status) ) {
        printf( "NtWaitForSingleObject (%s) failed: %X\n", Operation, status );
        DbgBreakPoint( );
        return status;
    }

    status = Redir->Iosb[EventNumber].Status;
    if ( !NT_SUCCESS(status) ) {
        printf( "%s I/O failed: %X\n", Operation, status );
        DbgBreakPoint( );
        return status;
    }

    return STATUS_SUCCESS;

} // WaitForConnect


NTSTATUS
StartAssociate (
    IN HANDLE ConnectionFileHandle,
    IN HANDLE AddressFileHandle,
    IN HANDLE EventHandle,
    IN PIO_STATUS_BLOCK Iosb,
    IN PSZ DebugString
    )
{
    NTSTATUS status;
    TDI_REQUEST_ASSOCIATE_ADDRESS request;

    request.AddressHandle = AddressFileHandle;

    IF_DEBUG(2) printf( "Starting %s Associate\n", DebugString );
    status = NtDeviceIoControlFile(
                ConnectionFileHandle,
                EventHandle,
                NULL,
                NULL,
                Iosb,
                IOCTL_TDI_ASSOCIATE_ADDRESS,
                (PVOID)&request,
                sizeof(request),
                NULL,
                0
                );

    if ( !NT_SUCCESS(status) ) {
        printf( "NtDeviceIoControlFile (%s Associate) service failed: %X\n",
                    DebugString, status );
        DbgBreakPoint( );
        return status;
    }

    return STATUS_SUCCESS;

} // StartAssociate


NTSTATUS
WaitForAssociate(
    IN PSZ Operation,
    IN PDESCRIPTOR Redir,
    IN UCHAR EventNumber
    )

{
    NTSTATUS status;

    IF_DEBUG(2) printf( "Waiting for %s Associate\n", Operation );
    status = NtWaitForSingleObject(
                Redir->EventHandle[EventNumber],
                FALSE,
                NULL
                );
    IF_DEBUG(2) printf( "%s Associate complete\n", Operation );
    if ( !NT_SUCCESS(status) ) {
        printf( "NtWaitForSingleObject (%s Associate) failed: %X\n",
            Operation, status );
        DbgBreakPoint( );
        return status;
    }

    status = Redir->Iosb[EventNumber].Status;
    if ( !NT_SUCCESS(status) ) {
        printf( "%s Associate I/O failed: %X\n", Operation, status );
        DbgBreakPoint( );
        return status;
    }

    return STATUS_SUCCESS;

} // WaitForAssociate


STATIC
BOOLEAN
ReadWithPrompt(
    PSZ Prompt,
    PSZ Buffer,
    ULONG BufferLength
    )
{
    ULONG length;
    CHAR c;
    PSZ dest;

    ASSERT( BufferLength > 0 );

    printf( Prompt );

    c = 0;
    length = 0;
    dest = Buffer;

    while ( length < BufferLength ) {

        c = (CHAR)getchar( );
        if ( c == EOF ) break;
        if ( c == '\n' ) break;

        *dest++ = c;
        length++;

    }

    *dest = 0;

    return (BOOLEAN)(c == EOF);

} // ReadWithPrompt


STATIC
NTSTATUS
ExecuteCommand(
    IN PSZ Command OPTIONAL,
    IN PDESCRIPTOR Redir,
    IN PSZ Prompt
    )

{
    BOOLEAN done = FALSE;
    NTSTATUS status = STATUS_SUCCESS;
    UCHAR i;
    ULONG SmbSize;
    PREDIR_TEST LocalRedirTest;
    PSMB_TEST smbTest;
    CLONG redirNumber;
    LONG testNumber;
    CHAR debugString[128];
    CLONG testNameLength;

    BOOLEAN again = TRUE;

#define COMMAND_LINE_LENGTH 128
    CHAR commandLineBuffer[COMMAND_LINE_LENGTH];
    PSZ commandLine;
    PSZ localArgv[MAX_ARGC];


    while ( !done ) {

        testNumber = Redir->TestNumber;

        if ( (testNumber < 0) && again ) {

            while ( !done ) {

                if ( Command == NULL ) {

                    done = ReadWithPrompt(
                                Prompt,
                                commandLineBuffer,
                                COMMAND_LINE_LENGTH
                                );
                    commandLine = (PSZ)commandLineBuffer;

                } else {

                    done = TRUE;
                    printf( "%s%s\n", Prompt, Command );
                    commandLine = Command;

                }

                ParseCommand(
                    commandLine,
                    localArgv,
                    &Redir->argc,
                    MAX_ARGC
                    );
                if ( Redir->argc <= 0 ) {
                    continue;
                }

                if ( *localArgv[0] == '@' ) {
                    ExecuteBatchFile( localArgv[0]+1, Redir, Prompt );
                    continue;
                }

                if ( _stricmp( localArgv[0], "break" ) == 0 ) {
                    DbgBreakPoint( );
                    continue;
                }

                if ( _stricmp( localArgv[0], "exit" ) == 0 ) {
                    break;
                }

                if ( _stricmp( localArgv[0], "debug" ) == 0 ) {
                    if ( Redir->argc > 1 ) {
                        DebugParameter = atolx( localArgv[1] );
                    } else {
                        printf( "USRV: Missing argument\n" );
                    }
                    continue;
                }

#if 0
                if ( _stricmp( localArgv[0], "dbg" ) == 0 ) {
                    NTSTATUS netStatus;
                    netStatus = NetLocalSetServerDebug(
                                    (SHORT)(Redir->argc - 1),
                                    &localArgv[1],
                                    NULL,
                                    NULL
                                    );
                    if ( NT_SUCCESS(status) ) {
                        printf( "The command completed successfully\n" );
                    }
                    continue;
                }
#endif
                if ( _stricmp( localArgv[0], "delay" ) == 0 ) {
                    ULONG ms = 1000;
                    LARGE_INTEGER delayTime;
                    if ( Redir->argc > 1 ) {
                        ms = atol( localArgv[1] );
                    }
                    printf( "Delaying for %lu milliseconds\n", ms );
                    delayTime.QuadPart = Int32x32To64( ms, -10000 );
                    NtDelayExecution( TRUE, (PLARGE_INTEGER)&delayTime );
                    continue;
                }

                Redir->argv = localArgv;
                testNumber = MatchTestName( localArgv[0] );

                switch( testNumber ) {
                case (LONG)-1:
                    printf( "Unknown test specified: %s\n", localArgv[0] );
                    continue;
                case (LONG)-2:
                    printf( "Test name ambiguous: %s\n", localArgv[0] );
                    continue;
                }

                Redir->TestNumber = testNumber;
                break;

            }

        }

        if ( testNumber < 0 ) {
            break;
        }

        LocalRedirTest = &RedirTests[testNumber];
        testNameLength = strlen( LocalRedirTest->RedirName );
        RtlMoveMemory( debugString, LocalRedirTest->RedirName, testNameLength );
        debugString[testNameLength] = ' ';

        for( i = 0, smbTest = LocalRedirTest->SmbTests;
             smbTest->SmbMaker != NULL;
             i++, smbTest++ ) {

            RtlMoveMemory(
                debugString + testNameLength + 1,
                smbTest->DebugString,
                strlen( smbTest->DebugString ) + 1
                );
            IF_DEBUG(1) printf( "Starting test #%ld.%ld.%ld - %s\n",
                        redirNumber, testNumber, i, debugString );

            Redir->ErrorInhibit = smbTest->ErrorInhibit;

            if ( smbTest->SmbVerifier == NULL ) {

                status = smbTest->SmbMaker(
                            Redir,
                            debugString,
                            NULL,
                            smbTest->Command,
                            &smbTest->IdSelections,
                            &Redir->IdValues,
                            NULL
                            );

            } else {

                status = smbTest->SmbMaker(
                            Redir,
                            Redir->Data[0],
                            NULL,
                            (UCHAR)(smbTest->SmbMaker == MakeAndXChain ?
                                                smbTest->Command : 0xFF),
                            &smbTest->IdSelections,
                            &Redir->IdValues,
                            &SmbSize
                            );

                if ( NT_SUCCESS(status) && (status != STATUS_PENDING) ) {

                    status = SendAndReceiveSmb(
                                Redir,
                                debugString,
                                SmbSize,
                                0,
                                1
                                );

                    if ( NT_SUCCESS(status) ) {

                        status = smbTest->SmbVerifier(
                                    Redir,
                                    NULL,
                                    smbTest->Command,
                                    &smbTest->IdSelections,
                                    &Redir->IdValues,
                                    &SmbSize,
                                    Redir->Data[1]
                                    );

                    }

                }

            } // if ( smbTest->SmbVerifier == NULL )

            if ( !NT_SUCCESS(status) ) {
                IF_SMB_ERROR_QUIT_TEST {
                    break;
                }
            }

        } // for( i = 0, smbTest = LocalRedirTest->SmbTests; ...

        Redir->TestNumber = -4;
        again = PromptForNextTest;

    } // while ( TRUE )

    return status;

} // ExecuteCommand


STATIC
NTSTATUS
ExecuteBatchFile(
    IN PSZ Name,
    IN PDESCRIPTOR Redir,
    IN PSZ Prompt
    )

{
    NTSTATUS status;
    CHAR nameBuffer[32];
    CHAR commandBuffer[128];
    PCHAR commandBufferPtr;
    STRING fileName;
    UNICODE_STRING unicodeFileName;
    OBJECT_ATTRIBUTES objectAttributes;
    HANDLE fileHandle;
    IO_STATUS_BLOCK ioStatusBlock;
    PCHAR readBuffer;
    ULONG readBufferIndex;

    RtlMoveMemory( nameBuffer, "\\SystemRoot\\", 12 );
    RtlMoveMemory( nameBuffer + 15, Name, strlen( Name ) );
    RtlMoveMemory( nameBuffer + 15 + strlen( Name ), ".CMD", 5 );

    fileName.Buffer = nameBuffer;
    fileName.Length = (SHORT)strlen( nameBuffer );

    status = RtlAnsiStringToUnicodeString(
                 &unicodeFileName,
                 &fileName,
                 TRUE
                 );
    ASSERT( NT_SUCCESS(status) );
    InitializeObjectAttributes(
        &objectAttributes,
        &unicodeFileName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    status = NtOpenFile(
        &fileHandle,
        FILE_READ_DATA | SYNCHRONIZE,
        &objectAttributes,
        &ioStatusBlock,
        FILE_SHARE_READ,
        FILE_SYNCHRONOUS_IO_NONALERT
        );

    RtlFreeUnicodeString( &unicodeFileName );
    if ( !NT_SUCCESS(status) ) {
        if ( status == STATUS_OBJECT_NAME_NOT_FOUND ) {
            printf( "Batch file %Z not found\n", &fileName );
        } else {
            printf( "Error opening batch file %Z: %X\n",
                        &fileName, status );
        }
        return status;
    }

    readBuffer = malloc( 4096 );
    if ( readBuffer == NULL ) {
        NtClose( fileHandle );
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    status = NtReadFile(
                 fileHandle,
                 NULL,
                 NULL,
                 NULL,
                 &ioStatusBlock,
                 readBuffer,
                 4096,
                 NULL,
                 NULL
                 );

    if ( !NT_SUCCESS(status) ) {
        printf( "Error reading batch file %Z: %X\n", &fileName, status );
        ioStatusBlock.Information = 0;
    }

    for ( readBufferIndex = 0, commandBufferPtr = commandBuffer;
          readBufferIndex < ioStatusBlock.Information;
          readBufferIndex++ ) {

        if ( readBuffer[readBufferIndex] == '\n' ) {
            *commandBufferPtr = '\0';
            (VOID)ExecuteCommand( commandBuffer, Redir, Prompt );
            commandBufferPtr = commandBuffer;
            continue;
        }

        *commandBufferPtr++ = readBuffer[readBufferIndex];
    }

    NtClose( fileHandle );
    free( readBuffer );

    return STATUS_SUCCESS;

} // ExecuteBatchFile


NTSTATUS
TdiOpenNetbiosAddress (
    IN OUT PHANDLE FileHandle,
    IN PUCHAR Buffer,
    IN PVOID DeviceName,
    IN PVOID Address)

/*++

Routine Description:

   Opens an address on the given file handle and device.

Arguments:

    FileHandle - the returned handle to the file object that is opened.

    Buffer - pointer to a buffer that the ea is to be built in. This buffer
        must be at least 40 bytes long.

    DeviceName - the Unicode string that points to the device object.

    Name - the address to be registered. If this pointer is NULL, the routine
        will attempt to open a "control channel" to the device; that is, it
        will attempt to open the file object with a null ea pointer, and if the
        transport provider allows for that, will return that handle.

Return Value:

    An informative error code if something goes wrong. STATUS_SUCCESS if the
    returned file handle is valid.

--*/
{
    IO_STATUS_BLOCK IoStatusBlock;
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    PFILE_FULL_EA_INFORMATION EaBuffer;
    TA_NETBIOS_ADDRESS NetbiosAddress;
    PSZ Name;
    ULONG Length;

    if (Address != NULL) {
        Name = (PSZ)Address;
        try {
            Length = FIELD_OFFSET( FILE_FULL_EA_INFORMATION, EaName[0] ) +
                                        TDI_TRANSPORT_ADDRESS_LENGTH + 1 +
                                        sizeof(TA_NETBIOS_ADDRESS);
            EaBuffer = (PFILE_FULL_EA_INFORMATION)Buffer;

            if (EaBuffer == NULL) {
                return STATUS_UNSUCCESSFUL;
            }

            EaBuffer->NextEntryOffset = 0;
            EaBuffer->Flags = 0;
            EaBuffer->EaNameLength = (UCHAR)TDI_TRANSPORT_ADDRESS_LENGTH;
            EaBuffer->EaValueLength = sizeof (TA_NETBIOS_ADDRESS);

            RtlMoveMemory(
                EaBuffer->EaName,
                TdiTransportAddress,
                EaBuffer->EaNameLength + 1
                );

            //
            // Create a copy of the NETBIOS address descriptor in a local
            // first, in order to avoid alignment problems.
            //

            NetbiosAddress.TAAddressCount = 1;
            NetbiosAddress.Address[0].AddressType = TDI_ADDRESS_TYPE_NETBIOS;
            NetbiosAddress.Address[0].AddressLength =
                                                sizeof (TDI_ADDRESS_NETBIOS);
            NetbiosAddress.Address[0].Address[0].NetbiosNameType =
                                            TDI_ADDRESS_NETBIOS_TYPE_UNIQUE;
            RtlMoveMemory(
                NetbiosAddress.Address[0].Address[0].NetbiosName,
                Name,
                16
                );

            RtlMoveMemory (
                &EaBuffer->EaName[EaBuffer->EaNameLength + 1],
                &NetbiosAddress,
                sizeof(TA_NETBIOS_ADDRESS)
                );

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // Couldn't touch the passed parameters; just return an error
            // status.
            //

            return GetExceptionCode();
        }
    } else {
        EaBuffer = NULL;
        Length = 0;
    }

    InitializeObjectAttributes (
        &ObjectAttributes,
        DeviceName,
        0,
        NULL,
        NULL);

    Status = NtCreateFile (
                 FileHandle,
                 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, // desired access.
                 &ObjectAttributes,     // object attributes.
                 &IoStatusBlock,        // returned status information.
                 0,                     // block size (unused).
                 0,                     // file attributes.
                 FILE_SHARE_READ | FILE_SHARE_WRITE, // share access.
                 FILE_CREATE,           // create disposition.
                 0,                     // create options.
                 EaBuffer,                  // EA buffer.
                 Length);                    // EA length.

    if (!NT_SUCCESS( Status )) {
        return Status;
    }

    Status = IoStatusBlock.Status;

    return Status;

} // TdiOpenNetbiosAddress