/*++

Copyright (c) 1992-1996 Microsoft Corporation

Module Name:

    tftpd.c

Abstract:

    This implements an RFC 783 tftp daemon.  The tftp daemon listens on it's
    well-known port waiting for requests.  When a valid request comes in, it
    spawns a thread to process the request.

Functions Defined:

        TftpdErrorPacket - sends an error reply.

        TftpdDoRead      - read from file and convert.
        TftpdHandleRead  - incoming read file request.
                           read file => sendto.

        TftpdDoWrite     - convert and write to file.
        TftpdHandleWrite - incoming write file request, calls TftpdDoWrite().

Author: Sam Patton (sampa) 08-apr-1992

Revision History:
        MohsinA,   02-Dec-96.

--*/

#include "tftpd.h"
#if defined(REMOTE_BOOT_SECURITY)
#include <ipsec.h>
#endif // defined(REMOTE_BOOT_SECURITY)

extern TFTP_GLOBALS Globals;

char * ErrorString[NUM_TFTP_ERROR_CODES] =
{
    "Error undefined",
    "File not found",
    "Access violation",
    "Disk full or allocation exceeded",
    "Illegal TFTP operation",
    "Unknown transfer ID",
    "File already exists",
    "No such user",
    "Option negotiation failure"
};

#if defined(REMOTE_BOOT_SECURITY)
//
// These routines manage the security info structures for clients
// thay have logged in. I put all the code that deals with how the
// structures are actually stored in these functions, in case I
// change it.
//

PTFTPD_SECURITY SecurityArray = NULL;
USHORT SecurityArrayLength = 0;
USHORT SecurityValidation;
CRITICAL_SECTION SecurityCriticalSection;

#define INITIAL_SECURITY_ARRAY_SIZE   8

UCHAR
TftpdHexDigitToChar(
    PUCHAR HexDigit
    )
{
    UCHAR Nibble;
    UCHAR ReturnValue = 0;
    int i;

    for (i = 0; i < 2; i++) {

        if ((HexDigit[i] >= '0') && (HexDigit[i] <= '9')) {
            Nibble = (UCHAR)(HexDigit[i] - '0');
        } else if ((HexDigit[i] >= 'a') && (HexDigit[i] <= 'f')) {
            Nibble = (UCHAR)(HexDigit[i] - 'a' + 10);
        } else if ((HexDigit[i] >= 'A') && (HexDigit[i] <= 'F')) {
            Nibble = (UCHAR)(HexDigit[i] - 'A' + 10);
        } else {
            Nibble = 0;
        }

        ReturnValue = (UCHAR)((ReturnValue << 4) + Nibble);
    }

    return ReturnValue;
}


BOOL
TftpdInitSecurityArray(
    VOID
    )
{
    int i;

    SecurityArray = (PTFTPD_SECURITY)malloc(sizeof(TFTPD_SECURITY) * INITIAL_SECURITY_ARRAY_SIZE);

    if (SecurityArray == NULL) {
        DbgPrint("TftpdInitSecurityArray: cannot allocate security array\n");
        return FALSE;
    }

    for (i = 0; i < INITIAL_SECURITY_ARRAY_SIZE; i++) {
        SecurityArray[i].Validation = 0;   // means this entry is free
        SecurityArray[i].LastFileRead[0] = '\0';
    }

    SecurityArrayLength = INITIAL_SECURITY_ARRAY_SIZE;
    srand((unsigned)time(NULL));
    SecurityValidation = (USHORT)rand();
    RtlInitializeCriticalSection(&SecurityCriticalSection);

    return TRUE;

}

VOID
TftpdUninitSecurityArray(
    VOID
    )
{
    free(SecurityArray);
}

BOOL
TftpdAllocateSecurityEntry(
    PUSHORT Index,
    PTFTPD_SECURITY Security
    )
{
    USHORT i, j;

    RtlEnterCriticalSection (&SecurityCriticalSection);

    for (i = 0; i < SecurityArrayLength; i++) {
        if (SecurityArray[i].Validation == 0) {
            break;
        }
    }

    if (i == SecurityArrayLength) {

        PTFTPD_SECURITY NewSecurity;
        USHORT NewSecurityLength;

        //
        // Could not find a spot, double the array.
        //

        if (SecurityArrayLength < 0x8000) {
            NewSecurityLength = SecurityArrayLength * 2;
        } else {
            NewSecurityLength = 0xffff;
        }

        NewSecurity = malloc(sizeof(TFTPD_SECURITY) * NewSecurityLength);
        if (NewSecurity == NULL) {
            RtlLeaveCriticalSection (&SecurityCriticalSection);
            return FALSE;
        }

        memcpy(NewSecurity, SecurityArray, sizeof(TFTPD_SECURITY) * SecurityArrayLength);

        i = SecurityArrayLength;

        for (j = SecurityArrayLength; j < NewSecurityLength; j++) {
            NewSecurity[j].Validation = 0;   // means this entry is free
            NewSecurity[j].LastFileRead[0] = '\0';
        }

        SecurityArray = NewSecurity;
        SecurityArrayLength = NewSecurityLength;

    }

    SecurityArray[i].Validation = SecurityValidation;
    SecurityArray[i].CredentialsHandleValid = FALSE;
    SecurityArray[i].ServerContextHandleValid = FALSE;
    SecurityArray[i].GeneratedKey = FALSE;
    SecurityValidation = (SecurityValidation % 10000) + 1;

    *Security = SecurityArray[i];

    RtlLeaveCriticalSection (&SecurityCriticalSection);

    *Index = i;
    return TRUE;

}

VOID
TftpdFreeSecurityEntry(
    USHORT Index
    )
{
    TFTPD_SECURITY TempSecurity;   // save it so we can leave the critical section

    RtlEnterCriticalSection (&SecurityCriticalSection);

    if (Index < SecurityArrayLength) {

        TempSecurity = SecurityArray[Index];
        SecurityArray[Index].Validation = 0;   // this marks it as free

        RtlLeaveCriticalSection (&SecurityCriticalSection);

        if (TempSecurity.ServerContextHandleValid) {
            DeleteSecurityContext(&TempSecurity.ServerContextHandle);
        }

        if (TempSecurity.CredentialsHandleValid) {
            FreeCredentialsHandle(&TempSecurity.CredentialsHandle);
        }

    } else {

        RtlLeaveCriticalSection (&SecurityCriticalSection);
    }
}

VOID
TftpdGetSecurityEntry(
    USHORT Index,
    PTFTPD_SECURITY Security
    )
{
    RtlEnterCriticalSection (&SecurityCriticalSection);

    if (Index < SecurityArrayLength) {

        *Security = SecurityArray[Index];

    } else {

        memset(Security, 0, sizeof(TFTPD_SECURITY));
    }


    RtlLeaveCriticalSection (&SecurityCriticalSection);

}

VOID
TftpdStoreSecurityEntry(
    USHORT Index,
    PTFTPD_SECURITY Security
    )
{
    RtlEnterCriticalSection (&SecurityCriticalSection);

    if (Index < SecurityArrayLength) {

        SecurityArray[Index] = *Security;

    }

    RtlLeaveCriticalSection (&SecurityCriticalSection);

}

VOID
TftpdGenerateKeyForSecurityEntry(
    USHORT Index,
    PTFTPD_SECURITY Security
    )
{
    SecBufferDesc      SignMessage;
    SecBuffer          SigBuffers[2];
    SECURITY_STATUS    SecStatus;
    LARGE_INTEGER      SystemTime;

    RtlEnterCriticalSection (&SecurityCriticalSection);

    if (Index < SecurityArrayLength) {

        if (!SecurityArray[Index].GeneratedKey) {

            //
            // Generate and sign a key.
            //

            NtQuerySystemTime(&SystemTime);
            SecurityArray[Index].Key = (ULONG)(SystemTime.QuadPart % SecurityArray[Index].ForeignAddress.sin_addr.s_addr);
            *(PULONG)(SecurityArray[Index].SignedKey) = SecurityArray[Index].Key;

            SigBuffers[0].pvBuffer = SecurityArray[Index].SignedKey;
            SigBuffers[0].cbBuffer = sizeof(SecurityArray[Index].SignedKey);
            SigBuffers[0].BufferType = SECBUFFER_DATA;

            SigBuffers[1].pvBuffer = SecurityArray[Index].Sign;
            SigBuffers[1].cbBuffer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
            SigBuffers[1].BufferType = SECBUFFER_TOKEN;

            SignMessage.pBuffers = SigBuffers;
            SignMessage.cBuffers = 2;
            SignMessage.ulVersion = 0;

            SecStatus = SealMessage(
                                &(SecurityArray[Index].ServerContextHandle),
                                0,
                                &SignMessage,
                                0 );

            if (SecStatus == STATUS_SUCCESS) {
                SecurityArray[Index].GeneratedKey = TRUE;
            }

        }

        *Security = SecurityArray[Index];

    } else {

        memset(Security, 0, sizeof(TFTPD_SECURITY));
    }

    RtlLeaveCriticalSection (&SecurityCriticalSection);

}

SECURITY_STATUS
TftpdVerifyFileSignature(
    USHORT Index,
    USHORT Validation,
    PTFTPD_SECURITY Security,
    char * FileName,
    char * Sign,
    USHORT ClientPort
    )
{
    unsigned long      FileNameLength;
    char *             CompareFileName;
    SecBufferDesc      SignMessage;
    SecBuffer          SigBuffers[2];
    SECURITY_STATUS    SecStatus;
    PTFTPD_SECURITY    TmpSecurity;   // points to the real location in the array

    //
    // First figure out where the last 64 characters of the
    // requested filename are since that is all we save.
    //

    FileNameLength = strlen(FileName);

    if (FileNameLength < sizeof(Security->LastFileRead)) {
        CompareFileName = FileName;
    } else {
        CompareFileName = FileName + (FileNameLength + 1 - sizeof(Security->LastFileRead));
    }

    //
    // Make sure that the sign for the filename is valid. If this
    // is the same as the last filename requested for this security
    // entry, and it is coming in on the same port as before,
    // then we assume the client is retransmitting the request,
    // so therefore has not re-generated the sign, so we just compare
    // the sign with the one he sent last time instead of calling
    // VerifySignature again (to prevent us getting unbalanced with
    // his MakeSignature call).
    //

    RtlEnterCriticalSection (&SecurityCriticalSection);

    if ((Index < SecurityArrayLength) &&
        (SecurityArray[Index].Validation == Validation)) {

        TmpSecurity = &SecurityArray[Index];

    } else {

        memset(Security, 0, sizeof(TFTPD_SECURITY));
        return (SECURITY_STATUS)STATUS_INVALID_HANDLE;

    }

    if ((strcmp(CompareFileName, TmpSecurity->LastFileRead) == 0) &&
        (ClientPort == TmpSecurity->LastFileReadPort)) {

        //
        // Compare them, and fake a security error if they don't match.
        //

        if (memcmp(TmpSecurity->LastFileSign, Sign, NTLMSSP_MESSAGE_SIGNATURE_SIZE) == 0) {
            SecStatus = SEC_E_OK;
        } else {
            SecStatus = SEC_E_MESSAGE_ALTERED;
        }

    } else {

        //
        // Save the values in case this request is resent.
        //

        strcpy(TmpSecurity->LastFileRead, CompareFileName);
        memcpy(TmpSecurity->LastFileSign, Sign, NTLMSSP_MESSAGE_SIGNATURE_SIZE);
        TmpSecurity->LastFileReadPort = ClientPort;

        //
        // Now make sure the signature is correct.
        //

        SigBuffers[1].pvBuffer = Sign;
        SigBuffers[1].cbBuffer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
        SigBuffers[1].BufferType = SECBUFFER_TOKEN;

        SigBuffers[0].pvBuffer = FileName;
        SigBuffers[0].cbBuffer = FileNameLength;
        SigBuffers[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY;

        SignMessage.pBuffers = SigBuffers;
        SignMessage.cBuffers = 2;
        SignMessage.ulVersion = 0;

        SecStatus = VerifySignature(
                            &TmpSecurity->ServerContextHandle,
                            &SignMessage,
                            0,
                            0 );

    }

    *Security = *TmpSecurity;

    RtlLeaveCriticalSection (&SecurityCriticalSection);

    return SecStatus;

}

#endif // defined(REMOTE_BOOT_SECURITY)


// ========================================================================

VOID
TftpdErrorPacket(
    struct sockaddr * PeerAddress,
    char *            RequestPacket,
    SOCKET            LocalSocket,
    unsigned short    ErrorCode,
    char *            ErrorMessage OPTIONAL
)

/*++

Routine Description:

    This sends an error packet back to the person who sent the request.  The
    RequestPacket is used to select an appropriate error code.

Arguments:

    PeerAddress - The remote address
    RequestPacket - packet making the request
    LocalSocket - socket to send error from

Return Value:

    None
    Error?

--*/

{
    char ErrorPacket[MAX_TFTP_DATAGRAM];
    int  err;
    int  errorLength;

    ((unsigned short *) ErrorPacket)[0] = htons(TFTPD_ERROR);
    ((unsigned short *) ErrorPacket)[1] = htons(ErrorCode);

    DbgPrint("TftpdError: Sending error packet Error Code: %d",ErrorCode);

    if ( ErrorMessage != NULL ) {
        strcpy(&ErrorPacket[4], ErrorMessage);
        errorLength = strlen(ErrorMessage);
    } else {
        if (ErrorCode >= NUM_TFTP_ERROR_CODES) {
            DbgPrint("TftpdErrorPacket: Unknown ErrorCode=%d.\n",
                     ErrorCode );
            ErrorCode = 0;
        }
        strcpy(&ErrorPacket[4], ErrorString[ErrorCode]);
        errorLength = strlen(ErrorString[ErrorCode]);
    }

    err = sendto(
        LocalSocket,
        ErrorPacket,
        5 + errorLength,
        0,
        PeerAddress,
        sizeof(struct sockaddr_in));

    if( SOCKET_ERROR == err ){
        DbgPrint("TftpdErrorPacket: sendto failed=%d\n",
                 WSAGetLastError() );
    }

    return;
}


#if defined(REMOTE_BOOT_SECURITY)
int
TftpdProcessOptionsPhase1(
    PTFTP_REQUEST Request,
    PUCHAR Options,
    int Opcode
    )
{
    int i;

    //
    // Assume default values.
    //

    Request->SecurityHandle = 0;

    //
    // Walk through the remainder of the request packet, looking for options
    // that we need to process in phase 1.
    //

    while ( *Options != 0 ) {

        if ( _stricmp(Options, "security") == 0 ) {

            Options += sizeof("security");

            if ( Opcode == TFTPD_RRQ ) {

                Request->SecurityHandle = atoi(Options);

            }

            Options += strlen(Options) + 1;

        } else if ( _stricmp(Options, "sign") == 0 ) {

            Options += sizeof("sign");

            if ( Opcode == TFTPD_RRQ ) {

                for (i = 0; i < NTLMSSP_MESSAGE_SIGNATURE_SIZE; i++) {
                    Request->Sign[i] = TftpdHexDigitToChar(&Options[i*2]);
                }

            }

            Options += strlen(Options) + 1;

        } else {

            //
            // Unrecognized option.  Skip the option ID and the value.
            //

            Options += strlen(Options) + 1;
            if ( *Options != 0 ) {
                Options += strlen(Options) + 1;
            }
        }
    }

    return 0;
}
#endif // defined(REMOTE_BOOT_SECURITY)

int
TftpdProcessOptionsPhase2(
    PTFTP_REQUEST Request,
    PUCHAR Options,
    int Opcode,
    int *OackLength,
    char *PacketBuffer,
    BOOL *ReceivedTimeoutOption
    )
{
    PUCHAR oack;

    //
    // Assume default values.
    //

    Request->BlockSize = MAX_OACK_PACKET_LENGTH - 4; // Save an alloc mem by default to the current packet size.
    Request->Timeout = 10;
    *ReceivedTimeoutOption=FALSE;

    //
    // Build the OACK header.
    //

    memset( PacketBuffer, 0, MAX_OACK_PACKET_LENGTH );
    ((unsigned short *)PacketBuffer)[0] = htons(TFTPD_OACK);
    oack = &PacketBuffer[2];

    //
    // Walk through the remainder of the request packet, looking for options
    // that we understand.
    //

    while ( *Options != 0 ) {

        if ( _stricmp(Options, "blksize") == 0 ) {

            strcpy( oack, Options );
            oack += sizeof("blksize");
            Options += sizeof("blksize");

            Request->BlockSize = atoi(Options);
            if ( (Request->BlockSize < 8) ||
                 (Request->BlockSize > 65464) ) {
                DbgPrint("TftpdProcessOptionsPhase2: invalid blksize=%s\n", Options );
                TftpdErrorPacket(
                    (struct sockaddr *)&Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_OPTION_NEGOT_FAILED,
                    NULL);
                return -1;
            }

            //
            // Workaround for problem in .98 version of ROM, which
            // doesn't like our OACK response. If the requested blksize is
            // 1456, pretend that the option wasn't specified. In the case
            // of the ROM's TFTP layer, this is the only option specified,
            // so ignoring it will mean that we don't send an OACK, and the
            // ROM will deign to talk to us. Note that our TFTP code uses
            // a blksize of 1432, so this workaround won't affect us.
            //

            if ( Request->BlockSize == 1456 ) {
                Request->BlockSize = MAX_OACK_PACKET_LENGTH - 4;
                oack -= sizeof("blksize");
                Options += strlen(Options) + 1;
                continue;
            }

            if ( Request->BlockSize > MAX_TFTP_DATA ) {
                Request->BlockSize = MAX_TFTP_DATA;
            }

            _itoa( Request->BlockSize, oack, 10 );
            oack += strlen(oack) + 1;
            Options += strlen(Options) + 1;

        } else if ( _stricmp(Options, "timeout") == 0 ) {

            strcpy( oack, Options );
            oack += sizeof("timeout");
            Options += sizeof("timeout");

            Request->Timeout = atoi(Options);
            if ( (Request->Timeout < 1) ||
                 (Request->Timeout > 255) ) {
                DbgPrint("TftpdProcessOptionsPhase2: invalid timeout=%s\n", Options );
                TftpdErrorPacket(
                    (struct sockaddr *)&Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_OPTION_NEGOT_FAILED,
                    NULL);
                return -1;
            }
            *ReceivedTimeoutOption = TRUE;

            strcpy( oack, Options );
            oack += strlen(Options) + 1;
            Options += strlen(Options) + 1;

        } else if ( _stricmp(Options, "tsize") == 0 ) {

            strcpy( oack, Options );
            oack += sizeof("tsize");
            Options += sizeof("tsize");

            if ( Opcode == TFTPD_WRQ ) {

                strcpy( oack, Options );
                oack += strlen(Options) + 1;
                Options += strlen(Options) + 1;

            } else {

                _itoa( Request->FileSize, oack, 10 );
                oack += strlen(oack) + 1;
                Options += strlen(Options) + 1;
            }

#if defined(REMOTE_BOOT_SECURITY)
        } else if ( _stricmp(Options, "security") == 0 ) {

            //
            // We process this just so that we can copy it to the OACK.
            //
            // Should really copy over Request->Security, in case
            // it has since become 0, to show the client we reject the
            // security option for some reason.
            //

            strcpy( oack, Options );
            oack += sizeof("security");
            Options += sizeof("security");

            if ( Opcode == TFTPD_RRQ ) {

                strcpy( oack, Options );
                oack += strlen(Options) + 1;

            }

            Options += strlen(Options) + 1;
#endif //defined (REMOTE_BOOT_SECURITY)

        } else {

            //
            // Unrecognized option.  Skip the option ID and the value.
            //

            Options += strlen(Options) + 1;
            if ( *Options != 0 ) {
                Options += strlen(Options) + 1;
            }
        }
    }

    *OackLength =(int)(oack - PacketBuffer);
    if ( *OackLength == 2 ) {
        *OackLength = 0;
    }

    return 0;
}

#define IS_SEPARATOR(c) (((c) == '\\') || ((c) == '/'))

BOOL
TftpdCanonicalizeFileName(
    IN OUT PUCHAR FileName
    )
{
    PUCHAR destination;
    PUCHAR source;
    PUCHAR lastComponent;

    //
    // The canonicalization is done in place.  Initialize the source and
    // destination pointers to point to the same place.
    //

    source = FileName;
    destination = FileName;

    //
    // The lastComponent variable is used as a placeholder when
    // backtracking over trailing blanks and dots.  It points to the
    // first character after the last directory separator or the
    // beginning of the pathname.
    //

    lastComponent = FileName;

    //
    // Get rid of leading directory separators.
    //

    while ( (*source != 0) && IS_SEPARATOR(*source) ) {
        source++;
    }

    //
    // Walk through the pathname until we reach the zero terminator.  At
    // the start of this loop, Input points to the first charaecter
    // after a directory separator or the first character of the
    // pathname.
    //

    while ( *source != 0 ) {

        if ( *source == '.' ) {

            //
            // If we see a dot, look at the next character.
            //

            if ( IS_SEPARATOR(*(source+1)) ) {

                //
                // If the next character is a directory separator,
                // advance the source pointer to the directory
                // separator.
                //

                source++;

            } else if ( (*(source+1) == '.') && IS_SEPARATOR(*(source+2)) ) {

                //
                // If the following characters are ".\", we have a "..\".
                // Advance the source pointer to the "\".
                //

                source += 2;

                //
                // Move the destination pointer to the character before the
                // last directory separator in order to prepare for backing
                // up.  This may move the pointer before the beginning of
                // the name pointer.
                //

                destination -= 2;

                //
                // If destination points before the beginning of the name
                // pointer, fail because the user is attempting to go
                // to a higher directory than the TFTPD root.  This is
                // the equivalent of a leading "..\", but may result from
                // a case like "dir\..\..\file".
                //

                if ( destination <= FileName ) {
                    return FALSE;
                }

                //
                // Back up the destination pointer to after the last
                // directory separator or to the beginning of the pathname.
                // Backup to the beginning of the pathname will occur
                // in a case like "dir\..\file".
                //

                while ( destination >= FileName && !IS_SEPARATOR(*destination) ) {
                    destination--;
                }

                //
                // destination points to \ or character before name; we
                // want it to point to character after last \.
                //

                destination++;

            } else {

                //
                // The characters after the dot are not "\" or ".\", so
                // so just copy source to destination until we reach a
                // directory separator character.  This will occur in
                // a case like ".file" (filename starts with a dot).
                //

                do {
                    *destination++ = *source++;
                } while ( (*source != 0) && !IS_SEPARATOR(*source) );

            }

        } else {             // if ( *source == '.' )

            //
            // source does not point to a dot, so copy source to
            // destination until we get to a directory separator.
            //

            while ( (*source != 0) && !IS_SEPARATOR(*source) ) {
                *destination++ = *source++;
            }

        }

        //
        // Truncate trailing blanks.  destination should point to the last
        // character before the directory separator, so back up over blanks.
        //

        while ( (destination > lastComponent) && (*(destination-1) == ' ') ) {
            destination--;
        }

        //
        // At this point, source points to a directory separator or to
        // a zero terminator.  If it is a directory separator, put one
        // in the destination.
        //

        if ( IS_SEPARATOR(*source) ) {

            //
            // If we haven't put the directory separator in the path name,
            // put it in.
            //

            if ( (destination != FileName) && !IS_SEPARATOR(*(destination-1)) ) {
                *destination++ = '\\';
            }

            //
            // It is legal to have multiple directory separators, so get
            // rid of them here.  Example: "dir\\\\\\\\file".
            //

            do {
                source++;
            } while ( (source != 0) && IS_SEPARATOR(*source) );

            //
            // Make lastComponent point to the character after the directory
            // separator.
            //

            lastComponent = destination;

        }

    }

    //
    // We're just about done.  If there was a trailing ..  (example:
    // "file\.."), trailing .  ("file\."), or multiple trailing
    // separators ("file\\\\"), then back up one since separators are
    // illegal at the end of a pathname.
    //

    if ( (destination != FileName) && IS_SEPARATOR(*(destination-1)) ) {
        destination--;
    }

    //
    // Terminate the destination string.
    //

    *destination = L'\0';

    return TRUE;
}

BOOL
TftpdPrependStringToFileName(
    IN OUT PUCHAR FileName,
    IN ULONG FileNameLength,
    IN PCHAR Prefix
    )
{
    BOOL prefixHasSeparator;
    BOOL currentFileNameHasSeparator;
    ULONG prefixLength;
    ULONG separatorLength;
    ULONG currentFileNameLength;

    prefixLength = strlen( Prefix );
    currentFileNameLength = strlen( FileName );

    prefixHasSeparator = (BOOL)(Prefix[prefixLength - 1] == '\\');
    currentFileNameHasSeparator = (BOOL)(FileName[0] == '\\');

    if ( prefixHasSeparator || currentFileNameHasSeparator ) {
        separatorLength = 0;
        if ( prefixHasSeparator && currentFileNameHasSeparator ) {
            prefixLength--;
        }
    } else {
        separatorLength = 1;
    }

    if ( (prefixLength + separatorLength + currentFileNameLength) > (FileNameLength - 1) ) {
        return FALSE;
    }

    //
    // Move the existing string down to make room for the prefix.
    //

    memmove( FileName + prefixLength + separatorLength, FileName, currentFileNameLength + 1 );

    //
    // Move the prefix into place.
    //

    memcpy( FileName, Prefix, prefixLength );

    //
    // If necessary, insert a backslash between the prefix and the file name.
    //

    if ( separatorLength != 0 ) {
        FileName[prefixLength] = '\\';
    }

    return TRUE;
}



BOOL
TftpdGetNextReadPacket(
    PTFTP_READ_CONTEXT Context,
    PTFTP_REQUEST Request
)
/*++

Routine Description:

Arguments:

Return Value:

    TRUE: got next packet into Context-Packet
    FALSE : error packet in Request->Packet2



--*/



{

#if defined(REMOTE_BOOT_SECURITY)
    SECURITY_STATUS SecStatus;
#endif  //defined(REMOTE_BOOT_SECURITY)


    if ( Context->oackLength != 0 ) {

        //
        // The first "data packet" sent will really be the OACK.
        //

        Context->packetLength = Context->oackLength;
        Context->oackLength = 0;
        Context->BlockNumber = 0;
        Context->BytesRead = Request->BlockSize;  // to prevent exit condition from being true

    } else {

        ((unsigned short *) Context->Packet)[0] = htons(TFTPD_DATA);
        ((unsigned short *) Context->Packet)[1] = htons(Context->BlockNumber);

#if defined(REMOTE_BOOT_SECURITY)
        if (Request->SecurityHandle) {

            if (Context->EncryptBytesSent == 0) {

                //
                // Read the file before sending the first data packet.
                //

                Context->BytesRead = _read(
                    Context->fd,
                    Context->EncryptFileBuffer + NTLMSSP_MESSAGE_SIGNATURE_SIZE,
                    Request->FileSize);

                if (Context->BytesRead != Request->FileSize) {
                    DbgPrint("TftpdHandleRead: Could not read EncryptFileBuffer=%d.\n", errno);
                    TftpdErrorPacket(
                        (struct sockaddr *) &Request->ForeignAddress,
                        Request->Packet2,
                        Request->TftpdPort,
                        TFTPD_ERROR_UNDEFINED,
                        "Insufficient resources");
                    goto cleanup;
                }

                //
                // We have it in memory, so encrypt it.
                //

                Context->SigBuffers[0].pvBuffer = Context->EncryptFileBuffer + NTLMSSP_MESSAGE_SIGNATURE_SIZE;
                Context->SigBuffers[0].cbBuffer = Request->FileSize;
                Context->SigBuffers[0].BufferType = SECBUFFER_DATA;

                Context->SigBuffers[1].pvBuffer = Context->EncryptFileBuffer;
                Context->SigBuffers[1].cbBuffer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
                Context->SigBuffers[1].BufferType = SECBUFFER_TOKEN;

                Context->SignMessage.pBuffers = Context->SigBuffers;
                Context->SignMessage.cBuffers = 2;
                Context->SignMessage.ulVersion = 0;

                SecStatus = SealMessage(
                    &Context->Security.ServerContextHandle,
                    0,
                    &Context->SignMessage,
                    0 );

                if (SecStatus != STATUS_SUCCESS) {
                    DbgPrint("TftpdHandleRead: Could not seal message=%d.\n", SecStatus);
                    TftpdErrorPacket(
                        (struct sockaddr *) &Request->ForeignAddress,
                        Request->Packet2,
                        Request->TftpdPort,
                        TFTPD_ERROR_UNDEFINED,
                        "Encryption error");
                    goto cleanup;

                }

            }

            if ((Context->EncryptBytesSent + Request->BlockSize) <= (int)(Request->FileSize + NTLMSSP_MESSAGE_SIGNATURE_SIZE)) {

                Context->BytesRead = Request->BlockSize;

            } else {

                Context->BytesRead = (Request->FileSize + NTLMSSP_MESSAGE_SIGNATURE_SIZE) - Context->EncryptBytesSent;
            }

            memcpy(
                &Context->Packet[4],
                Context->EncryptFileBuffer + Context->EncryptBytesSent,
                Context->BytesRead);

            Context->EncryptBytesSent += Context->BytesRead;

        } else
#endif //defined(REMOTE_BOOT_SECURITY)

        {

            //
            // read BlockSize bytes (or whatever's left)
            //

            Context->BytesRead = _read(
                Context->fd,
                &Context->Packet[4],
                Request->BlockSize);

            if( Context->BytesRead == -1 ){
                DbgPrint("TftpdHandleRead: read failed=%d\n", errno );
                SetLastError( errno );
                goto cleanup;
            }

            if (Context->BytesRead != Request->BlockSize) {
                DbgPrint("GetNextReadPacket read %d bytes\n",Context->BytesRead);
            }

        }

        Context->packetLength = 4 + Context->BytesRead;
    }

    return TRUE;

cleanup:
    return FALSE;

}

DWORD
TftpdAddContextToList(PLIST_ENTRY pEntry)
{

    EnterCriticalSection(&Globals.Lock);
    InsertHeadList(&Globals.WorkList,pEntry);
    DbgPrint("Adding 0x%X to global list\n", pEntry);
    LeaveCriticalSection(&Globals.Lock);

    return TRUE;
}

PVOID
TftpdFindContextInList(SOCKET Sock)

/*++

Routine Description:

    Look for context based upon Socket descriptor.  If found, return pointer to context with lock held
    
    You must release the lock via a call to TftpdReleaseContextLock().


    For now, simple linked list walk.  Move to hash table if time permits.

Arguments:

    Argument - socket


Return Value:

    NULL, failed to find context

--*/

{

    PLIST_ENTRY pEntry;
    PTFTP_CONTEXT_HEADER Context;


    EnterCriticalSection(&Globals.Lock);


    for (   pEntry = Globals.WorkList.Flink;
            pEntry != &Globals.WorkList;
            pEntry = pEntry->Flink) {

        Context=CONTAINING_RECORD(pEntry, TFTP_CONTEXT_HEADER, ContextLinkage);

        if (Context->Sock == Sock) {
            // Found it
            EnterCriticalSection(&Context->Lock);

            if (!Context->Closing) {
                Context->RefCount++;
            } else {
                LeaveCriticalSection(&Context->Lock);
                Context = NULL;
            }

            LeaveCriticalSection(&Globals.Lock);
            return (Context);
        }

    }

    LeaveCriticalSection(&Globals.Lock);
    return(NULL);


}



void
TftpdReleaseContextLock(
    PTFTP_CONTEXT_HEADER Context
    )

/*++

Routine Description:

    Used to leave any context critical section entered via TftpdFindContextInList().

Arguments:

    Argument - Context


Return Value:

    None.

--*/

{
    assert(Context->RefCount > 0);

    Context->RefCount--;

    if (Context->Closing && (Context->RefCount == 0)) {
        
        Context->IdleCount=0;
        LeaveCriticalSection(&Context->Lock);                

        TftpdRemoveContextFromList((PTFTP_CONTEXT_HEADER)Context);

    } else {

        LeaveCriticalSection(&Context->Lock);                

    }

}


VOID
TftpdRemoveContextFromList(PTFTP_CONTEXT_HEADER Context)

/*++

Routine Description:

    Look for context.  If found, remove it, free all resources
    For now, simple linked list walk.  Move to hash table if time permits.

Arguments:

    Argument - socket


Return Value:



--*/

{

    PLIST_ENTRY pEntry;
    PLIST_ENTRY pNextEntry;
    PTFTP_CONTEXT_HEADER LocalContext;


    EnterCriticalSection(&Globals.Lock);

    pEntry=Globals.WorkList.Flink;

    while (pEntry != &Globals.WorkList)

    {


        LocalContext=CONTAINING_RECORD(pEntry, TFTP_CONTEXT_HEADER, ContextLinkage);
        pNextEntry=pEntry->Flink;

        if (Context == LocalContext) {
            
            // Found it

            assert(Context->Closing);
            assert(Context->RefCount == 0);

            RemoveEntryList(pEntry);

            DbgPrint("Removing 0x%X from global list\n", pEntry);
            
            LeaveCriticalSection(&Globals.Lock);

            DbgPrint("Removing connection to port %d\n",htons(Context->ForeignAddress.sin_port));

            TftpdFreeContext(Context);
            
            return;

        }

        pEntry=pNextEntry;

    }

    LeaveCriticalSection(&Globals.Lock);
}


VOID
TftpdFreeGeneralContextFields(PTFTP_CONTEXT_HEADER Context)
{

    if (Context->Sock != INVALID_SOCKET) {
        closesocket(Context->Sock);
        DbgPrint("TftpdFreeGeneralContextFields: Close Socket %d\n",Context->Sock);
    }


    if (Context->TimerHandle) {
        RtlDeleteTimer(Globals.TimerQueueHandle,Context->TimerHandle,NULL);
    }

    Context->TimerHandle = 0;

    if (Context->Packet != NULL) {
        free(Context->Packet);
        Context->Packet = NULL;
    }

    RtlDeregisterWaitEx(Context->WaitEvent,NULL);
    CloseHandle(Context->SocketEvent);
    DeleteCriticalSection(&Context->Lock);

}

VOID
TftpdFreeReadContext(PTFTP_READ_CONTEXT Context)
{


#if defined(REMOTE_BOOT_SECURITY)
    if (Context->EncryptFileBuffer) {
        free(Context->EncryptFileBuffer);
    }
#endif //defined(REMOTE_BOOT_SECURITY)
    if (Context->fd != -1) {
        _close(Context->fd);
    }
    free(Context);


}


VOID
TftpdFreeWriteContext(PTFTP_WRITE_CONTEXT Context)
{

    if (Context->fd != -1) {
        _close(Context->fd);
    }
    free(Context);

}

VOID
TftpdFreeLoginContext(PTFTP_LOGIN_CONTEXT Context)
{

}

VOID
TftpdFreeKeyContext(PTFTP_KEY_CONTEXT Context)
{

}



VOID
TftpdFreeContext(PTFTP_CONTEXT_HEADER Context)
{

    if (Context == NULL) {
        DbgPrint("TftpdFreeContext: Called with Null context");
        return;
    }

    TftpdFreeGeneralContextFields(Context);

    switch (Context->ContextType) {
    case READ_CONTEXT:
        TftpdFreeReadContext((PTFTP_READ_CONTEXT)Context);
        break;

    case WRITE_CONTEXT:
        TftpdFreeWriteContext((PTFTP_WRITE_CONTEXT)Context);
        break;

    case LOGIN_CONTEXT:
        TftpdFreeLoginContext((PTFTP_LOGIN_CONTEXT)Context);
        break;
    case KEY_CONTEXT:
        TftpdFreeKeyContext((PTFTP_KEY_CONTEXT)Context);
        break;


    }

}




VOID
TftpdReaper(PVOID ReaperContext,
                BOOLEAN Flag)

/*++

Routine Description:

   Walk WorkList looking for inactive Contexts

Arguments:


Return Value:

--*/


{

    PLIST_ENTRY pEntry;
    PLIST_ENTRY pNextEntry;
    PTFTP_CONTEXT_HEADER Context;


    EnterCriticalSection(&Globals.Lock);

    pEntry = Globals.WorkList.Flink;

    while (pEntry != &Globals.WorkList)

    {


        Context=CONTAINING_RECORD(pEntry, TFTP_CONTEXT_HEADER, ContextLinkage);
        EnterCriticalSection(&Context->Lock);

        pNextEntry=pEntry->Flink;
        Context->IdleCount++;


        if ((Context->IdleCount >= DEAD_CONTEXT_COUNT) &&
            !Context->Closing &&
            (Context->RefCount == 0)) {

            // Context is dead.
            Context->Closing = TRUE;

            DbgPrint("Reaping connection to port %d",htons(Context->ForeignAddress.sin_port));

            LeaveCriticalSection(&Context->Lock);                
            
            LeaveCriticalSection(&Globals.Lock);

            TftpdRemoveContextFromList((PTFTP_CONTEXT_HEADER)Context);

            EnterCriticalSection(&Globals.Lock);
            
        } else {

            LeaveCriticalSection(&Context->Lock);

        }

        pEntry=pNextEntry;

    }


    LeaveCriticalSection(&Globals.Lock);

    TftpdCleanHeap();

}


VOID
TftpdRetransmit(PVOID RetransContext,
                BOOLEAN Flag)
{

    PTFTP_READ_WRITE_CONTEXT_HEADER Context;
    BOOL Status;
    NTSTATUS ntStatus;



    Context=(PTFTP_READ_WRITE_CONTEXT_HEADER)TftpdFindContextInList((SOCKET)RetransContext);

    if (Context == NULL) {
        DbgPrint("TftpdRetransmit:  Unable to find context\n");
        return;
    }

    if (Context->RetransmissionCount < MAX_TFTPD_RETRIES) {


        if (Context->RetransmissionCount > 5) {

            SYSTEMTIME _st;
            GetLocalTime(&_st);


            DbgPrint("%2d-%02d: %02d:%02d:%02d TftpdRetransmit: Socket %d DstPort %d Count %d BlkNum %d\n",
                          _st.wMonth,_st.wDay,_st.wHour,_st.wMinute,_st.wSecond,             
                          (DWORD)((DWORD_PTR)RetransContext),
                          ntohs(Context->ForeignAddress.sin_port),
                          Context->RetransmissionCount,
                          htons(((unsigned short*)(Context->Packet))[1]));
        }

        Status = sendto(
            Context->Sock,
            Context->Packet,
            Context->packetLength,
            0,
            (struct sockaddr *) &Context->ForeignAddress,
            sizeof(struct sockaddr_in));


        if( SOCKET_ERROR == Status ){

            DbgPrint("TftpdHandleRead: sendto failed=%d\n",
                     WSAGetLastError() );
        }

        Context->RetransmissionCount++;
        Context->IdleCount = 0;  // don't accidently reap this connection during retransmit tries.

        if (Context->TimerHandle) {

            if (!Context->FixedTimer) {
                Context->DueTime *= 2;
                if (Context->DueTime > (TFTPD_MAX_TIMEOUT * 1000)) {
                    Context->DueTime = (TFTPD_MAX_TIMEOUT * 1000);
                }
            }
            ntStatus=RtlUpdateTimer(Globals.TimerQueueHandle,
                                    Context->TimerHandle,
                                    Context->DueTime,
                                    Context->DueTime);
            if (ntStatus != STATUS_SUCCESS) {
                DbgPrint("TftpdRetransmit: UpdateTimerFailed %d",GetLastError());
            }
        }
    } else {

        //Send timeout

        TftpdErrorPacket((struct sockaddr *) &Context->ForeignAddress,
                         NULL,
                         Context->Sock,
                         TFTPD_ERROR_UNDEFINED,
                         "Timeout"
                        );

        Context->Closing = TRUE;
    }
        
    TftpdReleaseContextLock((PTFTP_CONTEXT_HEADER)Context);        
}


DWORD
TftpdResumeRead(
    PTFTP_READ_CONTEXT Context,
    PTFTP_REQUEST Request
)

/*++

Routine Description:

Resumes processing of existing read request.  Context lock held when function is called.

Arguments:

    Argument - buffer containing the read request datagram

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

s--*/
{

    BOOL Acked=FALSE;
    BOOL Status=FALSE;
    int SendStatus=0;
    BOOL Retrans=FALSE;
    NTSTATUS Stat;


    //
    // Parse the request
    //

    DbgPrint("TftpdResumeRead BlockNum %d\n",Context->BlockNumber);
    Request->BlockSize=Context->BlockSize;

    if (CHECK_ACK(Request->Packet1, TFTPD_ACK, Context->BlockNumber)) {
        Acked = TRUE;
        Context->RetransmissionCount=0;
    } else {
        DbgPrint("Ack failed:  Expect Blk %d Received Blk %d OpCode %d",
                 Context->BlockNumber,
                 ntohs((((unsigned short *)Request->Packet1)[1])),
                 htons(*((unsigned short *) (Request->Packet1))));
        if (CHECK_ACK(Request->Packet1, TFTPD_ACK, Context->BlockNumber-1)) {
            Retrans=TRUE;
        }
    }


        if (Acked) {

            if (Context->Done) {
                Context->Closing = TRUE;
                
                return 0;
            }

            if (++Context->BlockNumber == 0)
                Context->BlockNumber = 1; // 32 MB file roll-over.

            Status=TftpdGetNextReadPacket(Context,Request);

            if (!Status) {
                DbgPrint("GetNextPacketFailed %d",ntohs(Request->ForeignAddress.sin_port));
                return 0;


            }

            Context->RetransmissionCount=0;
            Context->IdleCount=0;

            if (!Context->FixedTimer) {
                // received new packet, reset timer
                Context->DueTime=TFTPD_INITIAL_TIMEOUT*1000;
            }

            if (Context->TimerHandle) {


                Stat=RtlUpdateTimer(Globals.TimerQueueHandle,
                                    Context->TimerHandle,
                                    Context->DueTime,
                                    Context->DueTime);
                if (!NT_SUCCESS(Stat)) {
                    DbgPrint("Failed to Update Timer");
                }
            }

        //
        // If we've sent the whole file, exit the loop.  Note that we
        // don't send an error packet if there is a timeout on the last
        // data packet, because the receiver might have only sent the
        // ACK once, then forgotten about this transfer.
        //


            if (Context->BytesRead < Request->BlockSize) {
                SOCKET Sock;


                DbgPrint("We're done with %d\n",ntohs(Request->ForeignAddress.sin_port));

                Context->Done=TRUE;



            }
        }

        if (Status) {
            // Got a valid packet to send

            DbgPrint("TftpdResumeRead: Sending data BlkNumber %d Socket %d PeerPort %d Size %d\n",Context->BlockNumber, Context->Sock, ntohs(Request->ForeignAddress.sin_port), Context->packetLength);


                SendStatus = sendto(
                    Context->Sock,
                    Context->Packet,
                    Context->packetLength,
                    0,
                    (struct sockaddr *) &Request->ForeignAddress,
                    sizeof(struct sockaddr_in));


                
                if( SOCKET_ERROR == SendStatus ){
                    
                    DbgPrint("TftpdHandleRead: sendto failed=%d\n",
                             WSAGetLastError() );

                    goto cleanup;
                }
        }

        return 0;

cleanup:
        return 1;

}


DWORD
TftpdResumeWrite(
    PTFTP_WRITE_CONTEXT Context,
    PTFTP_REQUEST Request
)

/*++

Routine Description:

Resumes processing of existing write request.  Context lock held when function is called.

Arguments:

    Argument - buffer containing the write request datagram

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/
{

    BOOL NewData=FALSE;
    char State;
    int BytesWritten;
    int Status;
    NTSTATUS Stat;

    DbgPrint("Request Blocksize  %d Context Blocksize %d\n",Request->BlockSize,Context->BlockSize);

    Request->BlockSize=Context->BlockSize;

    DbgPrint("TftpdResumeWrite: PktNum %d DataSize %d\n",Context->BlockNumber+1,Request->DataSize-4);

    if (CHECK_ACK(Request->Packet1, TFTPD_DATA, Context->BlockNumber+1)) {
        NewData = TRUE;
        Context->BlockNumber++;
        Context->IdleCount=0;
    } else {
        if (CHECK_ACK(Request->Packet1, TFTPD_DATA, Context->BlockNumber)) {
            // resend ack

            ((unsigned short *) Context->Packet)[0] = htons(TFTPD_ACK);
            ((unsigned short *) Context->Packet)[1] = htons(Context->BlockNumber);

            Status =
                sendto(
                    Context->Sock,
                    Context->Packet,
                    4,
                    0,
                    (struct sockaddr *) &Request->ForeignAddress,
                    sizeof(struct sockaddr_in));

            DbgPrint("TftpdResumeWrite:  Resending Ack %d\n",Context->BlockNumber);

            if( SOCKET_ERROR == Status ){
                DbgPrint("TftpdHandleWrite: sendto failed=%d\n",
                         WSAGetLastError() );
            }

            return 0;


        }
    }


    State = '\0';
    if (NewData) {
        BytesWritten =
            TftpdDoWrite(Context->fd, &Request->Packet1[4], Request->DataSize - 4, Context->FileMode, &State);
    }

    if (!NewData) {
        DbgPrint("TftpdHandleWrite: Timed out waiting for ack\n");
/*
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Timeout");
            */
        goto cleanup;

    } else if (BytesWritten < 0) {
        DbgPrint("TftpdHandleWrite: disk full?\n");

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_DISK_FULL,
            NULL);
        goto cleanup;

    } else if (Request->DataSize - 4 <= Request->BlockSize ) {

        //
        // Ack the last packet
        //

        ((unsigned short *) Context->Packet)[0] = htons(TFTPD_ACK);
        ((unsigned short *) Context->Packet)[1] = htons(Context->BlockNumber);

        Status =
            sendto(
                Context->Sock,
                Context->Packet,
                4,
                0,
                (struct sockaddr *) &Request->ForeignAddress,
                sizeof(struct sockaddr_in));

        DbgPrint("TftpdResumeWrite:  Sending Ack %d\n",Context->BlockNumber);

        if (Context->TimerHandle) {
            Context->RetransmissionCount=0;

            if (!Context->FixedTimer) {
                // received new packet, reset timer
                Context->DueTime=TFTPD_INITIAL_TIMEOUT*1000;
            }

            Stat=RtlUpdateTimer(Globals.TimerQueueHandle,
                                Context->TimerHandle,
                                Context->DueTime,
                                Context->DueTime);
            if (!NT_SUCCESS(Stat)) {
                DbgPrint("Failed to Update Timer");
            }
        }

        if( SOCKET_ERROR == Status ){
            DbgPrint("TftpdHandleWrite: sendto failed=%d\n",
                     WSAGetLastError() );
        }

        if (Request->DataSize - 4 < Request->BlockSize ) {
            // we're done.  flag for speedy cleanup
            Context->Closing = TRUE;
            
        }

    }
    return 0;

cleanup:

    return 1;

}

DWORD
TftpdResumeLogin(
    PTFTP_LOGIN_CONTEXT Context,
    PTFTP_REQUEST Request
)

/*++

Routine Description:

Resumes processing of existing login request.  Context lock held when function is called.  Lock released upon exiting.

Arguments:

    Argument - buffer containing the login request datagram

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/
{

    return 0;

}


DWORD
TftpdResumeKey(
    PTFTP_KEY_CONTEXT Context,
    PTFTP_REQUEST Request
)

/*++

Routine Description:

Resumes processing of existing key request.  Context lock held when function is called.  Lock released upon exiting.

Arguments:

    Argument - buffer containing the key request datagram

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/
{

    return 0;
}

VOID
TftpdResumeProcessing(PVOID Argument)
/*++

Routine Description:

    Resume work, if possible

Arguments:

    Argument - buffer containing the incoming datagram

Return Value:

--*/
{
    PTFTP_REQUEST      Request=(PTFTP_REQUEST)Argument;
    PTFTP_CONTEXT_HEADER  Context;
    DWORD Status;

    Context=(PTFTP_CONTEXT_HEADER)TftpdFindContextInList(Request->TftpdPort);

    if (Context == NULL) {
        DbgPrint("Invalid request on port %d", Request->TftpdPort);
        return;
    }

    if ((Context->ForeignAddress.sin_family != Request->ForeignAddress.sin_family) ||
        (Context->ForeignAddress.sin_addr.s_addr != Request->ForeignAddress.sin_addr.s_addr) ||
		(Context->ForeignAddress.sin_port != Request->ForeignAddress.sin_port)) {
                
        TftpdReleaseContextLock(Context);
        DbgPrint("Invalid request on port %d", Request->TftpdPort);
        return;
    }

    switch (Context->ContextType) {
    case READ_CONTEXT:
        Status=TftpdResumeRead((PTFTP_READ_CONTEXT)Context, Request);
        break;

    case WRITE_CONTEXT:
        TftpdResumeWrite((PTFTP_WRITE_CONTEXT)Context, Request);
        break;

    case LOGIN_CONTEXT:
        TftpdResumeLogin((PTFTP_LOGIN_CONTEXT)Context, Request);
        break;
    case KEY_CONTEXT:
        TftpdResumeKey((PTFTP_KEY_CONTEXT)Context, Request);
        break;
    }

    TftpdReleaseContextLock(Context);

    return;
}


/*
  Make sure incoming name is null terminated

 */
BOOL IsFileNameValid(char* FileName, DWORD MaxLen)
{
    DWORD i;

    // Make sure Filename has null terminator
    for (i=0; i < MaxLen; i++) {
        if (FileName[i] == (char)0 ) {
            return TRUE;
        }
    }

    return FALSE;

}


// ========================================================================
DWORD
TftpdHandleRead(
    PVOID Argument
)

/*++

Routine Description:

    This handles an incoming read file request.

Arguments:

    Argument - buffer containing the read request datagram

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/

{
    BOOL               Acked;
    int                AddressLength;
    int                BytesAck;
    int                BytesRead;
    char *             CharPtr;
    struct fd_set      exceptfds;
    int                FileMode;
    char *             FileName;
    char *             ReadMode;
    char *             NewPacket;
    struct sockaddr_in ReadAddress;
    struct fd_set      readfds;
    SOCKET             ReadPort = INVALID_SOCKET;
    PTFTP_REQUEST      Request;
    int                Status, err;
    struct timeval     timeval;
    char             * client_ipaddr;
    short              client_port;
    BOOL LockHeld=FALSE;
    BOOL AddedContext=FALSE;
    int                length;

#if defined(REMOTE_BOOT_SECURITY)
    SECURITY_STATUS    SecStatus;
#endif //REMOTE_BOOT_SECURITY)

    PTFTP_READ_CONTEXT  Context = NULL;
    NTSTATUS ntStatus;

    //
    // Parse the request
    //

    DbgPrint("Entered Handle read\n");

    Request = (PTFTP_REQUEST) Argument;

    FileName = &Request->Packet1[2];

    if (!IsFileNameValid(FileName,MAX_TFTP_DATAGRAM-2)) {
        goto cleanup;
    }

    ReadMode = FileName + (length = strlen(FileName)) + 1;
    
    // Make sure ReadMode is NUL terminated.
    if (!IsFileNameValid(ReadMode, MAX_TFTP_DATAGRAM - (length + 1))) {
        DbgPrint("TftpdHandleRead: invalid ReadMode\n");
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;
    }

    // Set up context.
    Context=(PTFTP_READ_CONTEXT)malloc(sizeof(TFTP_READ_CONTEXT));
    if (Context == NULL) {
        goto cleanup;
    }

    memset(Context,0,sizeof(TFTP_READ_CONTEXT));

    Context->Packet = (char *)malloc(MAX_OACK_PACKET_LENGTH);
    if (Context->Packet == NULL) {
        goto cleanup;
    }

    //
    // Profile data.
    //

    client_ipaddr = inet_ntoa( Request->ForeignAddress.sin_addr );
    if (client_ipaddr == NULL)
        client_ipaddr = "";
    client_port   = htons(     Request->ForeignAddress.sin_port );

    DbgPrint("TftpdHandleRead: FileName=%s, ReadMode=%s, from=%s:%d.\n",
             FileName, ReadMode,
             client_ipaddr,
             client_port   );

    //
    // Convert the mode to all lower case for comparison
    //

    for (CharPtr = ReadMode; *CharPtr; CharPtr++) {
        *CharPtr = (char)tolower(*CharPtr);
    }

    if (strcmp(ReadMode, "netascii") == 0) {
        FileMode = O_TEXT;
        DbgPrint("TftpdHandleRead: netascii mode.\n");
    } else if (strcmp(ReadMode, "octet") == 0) {
        FileMode = O_BINARY;
        DbgPrint("TftpdHandleRead: binary mode.\n");
    } else {
        DbgPrint("TftpdHandleRead: invalid ReadMode=%s?\n", ReadMode );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;
    }

#if defined(REMOTE_BOOT_SECURITY)
    err = TftpdProcessOptionsPhase1( Request, CharPtr + 1, TFTPD_RRQ );
    if ( err != 0 ) {
        goto cleanup;
    }

    if (Request->SecurityHandle) {

        //
        // This returns TRUE (and the security entry) if the sign
        // for this file is valid.
        //

        SecStatus = TftpdVerifyFileSignature(
                        (USHORT)(Request->SecurityHandle >> 16),    // index
                        (USHORT)(Request->SecurityHandle & 0xffff), // validation
                        &Context->Security,
                        FileName,
                        Request->Sign,
                        client_port);

        //
        // This error code is known to mean an invalid security handle.
        //

        if ( SecStatus == (SECURITY_STATUS)STATUS_INVALID_HANDLE ) {

            DbgPrint("TftpdHandleRead: SecurityHandle %x is invalid.\n",
                    Request->SecurityHandle);
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Invalid security handle");
            goto cleanup;

        } else if ( SecStatus != SEC_E_OK ) {

            DbgPrint("TftpdHandleRead: sign is invalid.\n");
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Invalid sign");
            goto cleanup;

        }

    }
#endif // defined(REMOTE_BOOT_SECURITY)

    //
    // Canonicalize the file name.
    //

    DbgPrint("TftpdHandleRead: Canonicalizing name.\n");
    strcpy( Request->Packet3, FileName );

    if ( !TftpdCanonicalizeFileName(Request->Packet3) ) {
        DbgPrint("TftpdHandleRead: invalid FileName=%s\n", FileName );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Malformed file name");
        goto cleanup;
    }

    //
    // Check whether this access is permitted.
    //

    if(    !(  match( ValidClients, client_ipaddr )
            || match( ValidMasters, client_ipaddr ) )
        || !match( ValidReadFiles, Request->Packet3 )
    ){
        DbgPrint("TftpdHandleRead: cannot open file=%s, errno=%d.\n"
                 "  client %s:%d,\n"
                 "  ValidReadFiles=%s, ValidClients=%s, ValidMasters=%s,\n"
                 ,
                 Request->Packet3, errno,
                 client_ipaddr, client_port,
                 ValidReadFiles, ValidClients, ValidMasters
        );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ACCESS_VIOLATION,
            NULL);
        goto cleanup;
    }

    //
    // Prepend the start directory to the file name.
    //

    DbgPrint("TftpdHandleRead: Prepending directory name.\n");

    if ( !TftpdPrependStringToFileName(
            Request->Packet3,
            sizeof(Request->Packet3),
            StartDirectory) ) {
        DbgPrint("TftpdHandleRead: too long FileName=%s\n", FileName );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "File name too long");
        goto cleanup;

    }

    //
    // Open the file.
    //

    DbgPrint("TftpdHandleRead: opening file <%s>\n", Request->Packet3 );

    Context->fd = _open(Request->Packet3, O_RDONLY | O_BINARY);

    if (Context->fd == -1) {

        SetLastError( errno );
        DbgPrint("TftpdHandleRead: cannot open file %s, errno=%d.\n", Request->Packet3, errno );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_FILE_NOT_FOUND,
            NULL);
        goto cleanup;
    }

    err = _lseek(Context->fd, 0, SEEK_END);
    if ( err != -1 ) {
        Request->FileSize = err;
        err = _lseek(Context->fd, 0, SEEK_SET);
    }

    if( err == -1 ){
        DbgPrint("TftpdHandleRead: lseek failed, errno=%d\n",
                 errno );
        SetLastError( errno );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Insufficient resources");
        goto cleanup;
    }

    //
    // Open a new socket for this request
    //

    ReadPort =
    socket(
        AF_INET,
        SOCK_DGRAM,
        0);

    DbgPrint("TftpdHandleRead:  New Socket %d\n",ReadPort);

    if (ReadPort == INVALID_SOCKET) {
        DbgPrint("TftpdHandleRead: cannot open socket, Error=%d\n",
                 WSAGetLastError() );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Insufficient resources");
        goto cleanup;
    }

    //
    // Bind to a random address
    //

    ReadAddress.sin_family = AF_INET;
    ReadAddress.sin_port = 0;

    ReadAddress.sin_addr.s_addr = Request->MyAddr;

    Status =
    bind(
        ReadPort,
        (struct sockaddr *) &ReadAddress,
        sizeof(ReadAddress)
    );

    if (Status) {
        DbgPrint("TftpdHandleRead: cannot bind socket, error=%d.\n",
                 WSAGetLastError() );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Insufficient resources");
        goto cleanup;
    }

    Request->TftpdPort = ReadPort;

    // Enter Context into list now that we know the port
    InitializeCriticalSection(&Context->Lock);
    Context->Sock=ReadPort;
    memcpy(&Context->ForeignAddress,&Request->ForeignAddress,sizeof(struct sockaddr_in));


    Context->SocketEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
    if (Context->SocketEvent == NULL) {
        DbgPrint("Failed to create socket event %d",GetLastError());
        goto cleanup;
    }

    Context->WaitEvent=RegisterSocket(ReadPort,Context->SocketEvent,REG_CONTINUE_SOCKET);
    if (Context->WaitEvent == NULL) {
        DbgPrint("Failed to create socket event %d",GetLastError());
        goto cleanup;
    }

    // Insert Context
    TftpdAddContextToList(&Context->ContextLinkage);
    AddedContext=TRUE;

    Context=(PTFTP_READ_CONTEXT)TftpdFindContextInList(ReadPort);
    
    if (Context == NULL) {
        DbgPrint("Failed to Lookup ReadContext");
        goto cleanup;
    }

    LockHeld=TRUE;
    err = TftpdProcessOptionsPhase2( Request, CharPtr + 1, TFTPD_RRQ, &Context->oackLength,Context->Packet,
                                     &Context->FixedTimer);
    if ( err != 0 ) {
        goto cleanup;
    }

    // Start retransmission timer

    if (Context->FixedTimer) {
        Context->DueTime=Request->Timeout*1000;
    } else {
        Context->DueTime=TFTPD_INITIAL_TIMEOUT*1000;
    }


    DbgPrint("TftpdHandleRead: Timer Interval %d msecs",Context->DueTime);

    ntStatus=RtlCreateTimer(Globals.TimerQueueHandle,
                         &Context->TimerHandle,
                         TftpdRetransmit,
                         (PVOID)Context->Sock,
                         Context->DueTime,
                         Context->DueTime,
                         0);

    if (!NT_SUCCESS(ntStatus)) {
        DbgPrint("Failed to Arm Timer %d",ntStatus);
    }

    Context->ContextType=READ_CONTEXT;



    Context->BlockSize=Request->BlockSize;

    if (Context->BlockSize > MAX_OACK_PACKET_LENGTH - 4) {
        
        DbgPrint("TftpdHandleRead: Reallocating packet.\n");

        NewPacket = (char *)realloc(Context->Packet, Context->BlockSize + 4);
        if (NewPacket == NULL) {
            goto cleanup;
        }

        Context->Packet = NewPacket;
    }

#if defined(REMOTE_BOOT_SECURITY)
    if (Request->SecurityHandle) {

        //
        // For secure mode, we read the whole file in at once so
        // we can encrypt it. For large files like ntoskrnl.exe,
        // will this work? If we get errors here, we could
        // just change the oack to say "security 0" and then send
        // down the unencrypted file -- maybe we should also do this
        // for files beyond a certain size.
        //

        Context->EncryptFileBuffer = malloc(Request->FileSize + NTLMSSP_MESSAGE_SIGNATURE_SIZE);

        if (Context->EncryptFileBuffer == NULL) {
            DbgPrint("TftpdHandleRead: Could not allocate EncryptFileBuffer length %d.\n",
                    Request->FileSize + NTLMSSP_MESSAGE_SIGNATURE_SIZE);
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        //
        // We don't actually read/seal until later -- this is so we can
        // send the OACK out right now to prevent more threads from
        // being spawned if he resends the initial request.
        //

        Context->EncryptBytesSent = 0;

    }

#endif //defined(REMOTE_BOOT_SECURITY)


    //
    // Ready to read and send file in blocks.
    //

    Context->BlockNumber = 1;

    Status=TftpdGetNextReadPacket(Context,Request);
    Context->RetransmissionCount=0;


    if (Status) {
        // Got a valid packet to send
        Status = sendto(
            ReadPort,
            Context->Packet,
            Context->packetLength,
            0,
            (struct sockaddr *) &Request->ForeignAddress,
            sizeof(struct sockaddr_in));

        if (Context->BytesRead < Request->BlockSize) {
            Context->Done=TRUE;
        }

    } else {
        // send error packet
        Status = sendto(
            ReadPort,
            Request->Packet2,
            Context->packetLength,
            0,
            (struct sockaddr *) &Request->ForeignAddress,
            sizeof(struct sockaddr_in));
    }

    if( SOCKET_ERROR == Status ){

        DbgPrint("TftpdHandleRead: sendto failed=%d\n",
                 WSAGetLastError() );

        goto cleanup;
    }




cleanup:

    if (Context != NULL) {
        if (LockHeld) {

            TftpdReleaseContextLock((PTFTP_CONTEXT_HEADER)Context);
        } 
        if (!AddedContext) {
            free(Context);
        }
    }

    return 0;
}

// ========================================================================





/*++

Routine Description:

    This handles an incoming write file request.

Arguments:

    Argument - buffer containing the write request

Return Value:

    Exit status
    0 == success
    >0 == failure

--*/


DWORD
TftpdHandleWrite(
    PVOID Argument
)
{
    int                AddressLength;
    int                BytesRead;
    int                BytesWritten;
    char *             CharPtr;
    struct fd_set      exceptfds;
    char *             FileName;
    char *             NewPacket;
    BOOL               NewData;
    struct sockaddr_in ReadAddress;
    struct fd_set      readfds;
    SOCKET             ReadPort = INVALID_SOCKET;
    int                Retry;
    char               State;
    int                Status, err;
    struct timeval     timeval;
    char *             WriteMode;
    PTFTP_REQUEST      Request;
    int                oackLength;
    int                packetLength;

    char             * client_ipaddr;
    short              client_port;
    BOOL  LockHeld=FALSE;
    BOOL AddedContext=FALSE;
    int length;

    PTFTP_WRITE_CONTEXT  Context = NULL;
    NTSTATUS ntStatus;

    // Set up context.
    Context=(PTFTP_WRITE_CONTEXT)malloc(sizeof(TFTP_WRITE_CONTEXT));
    if (Context == NULL) {
        goto cleanup;
    }

    memset(Context,0,sizeof(TFTP_WRITE_CONTEXT));

    Context->Packet = (char *)malloc(MAX_OACK_PACKET_LENGTH);
    if (Context->Packet == NULL) {
        goto cleanup;
    }


    //
    // Parse the request
    //

    Request = (PTFTP_REQUEST) Argument;

    FileName = &Request->Packet1[2];

    if (!IsFileNameValid(FileName,MAX_TFTP_DATAGRAM-2)) {
        goto cleanup;
    }

    WriteMode = FileName + (length = strlen(FileName)) + 1;

    // Make sure WriteMode is NUL terminated.
    if (!IsFileNameValid(WriteMode, MAX_TFTP_DATAGRAM - (length + 1))) {
        DbgPrint("TftpdHandleWrite: invalid WriteMode\n");
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;
    }

    //
    // Profile data.
    //

    client_ipaddr = inet_ntoa( Request->ForeignAddress.sin_addr );
    if (client_ipaddr == NULL)
        client_ipaddr = "";
    client_port   = htons(     Request->ForeignAddress.sin_port );

    DbgPrint("TftpdHandleWrite: FileName=%s, WriteMode=%s, from=%s:%d.\n",
             FileName, WriteMode,
             client_ipaddr, client_port
    );


    for (CharPtr = WriteMode; *CharPtr; CharPtr ++) {
        *CharPtr = isupper(*CharPtr) ? tolower(*CharPtr) : *CharPtr;
    }

    if (strcmp(WriteMode, "netascii") == 0) {
        Context->FileMode = O_TEXT;
    } else if (strcmp(WriteMode, "octet") == 0) {
        Context->FileMode = O_BINARY;
    } else {
        DbgPrint("TftpdHandleWrite: invalid WriteMode=%s\n", WriteMode );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;
    }
#if defined(REMOTE_BOOT_SECURITY)
    err = TftpdProcessOptionsPhase1( Request, CharPtr + 1, TFTPD_WRQ );
    if ( err != 0 ) {
        goto cleanup;
    }

#endif //defined(REMOTE_BOOT_SECURITY)
    //
    // Canonicalize the file name.
    //

    strcpy( Request->Packet3, FileName );

    if ( !TftpdCanonicalizeFileName(Request->Packet3) ) {
        DbgPrint("TftpdHandleWrite: invalid FileName=%s\n", FileName );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Malformed file name");
        goto cleanup;
    }

    //
    // Check whether this access is permitted.
    //

    if(    !match( ValidMasters, client_ipaddr )
        || !match( ValidWriteFiles, FileName )
    ){
        DbgPrint("TftpdHandleWrite: cannot open file=%s, errno=%d.\n"
                 "  client %s:%d,\n"
                 "  ValidWriteFiles=%s, ValidClients=%s, ValidMasters=%s,\n"
                 ,
                 Request->Packet3, errno,
                 client_ipaddr, client_port,
                 ValidWriteFiles, ValidClients, ValidMasters
        );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ACCESS_VIOLATION,
            NULL);
        goto cleanup;
    }

    //
    // Prepend the start directory to the file name.
    //

    if ( !TftpdPrependStringToFileName(
            Request->Packet3,
            sizeof(Request->Packet3),
            StartDirectory) ) {
        DbgPrint("TftpdHandleWrite: too long FileName=%s\n", FileName );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "File name too long");
        goto cleanup;
    }

    //
    // Open the file.
    //

    DbgPrint("TftpdHandleWrite: opening file <%s>\n", Request->Packet3 );

    Context->fd = _open(Request->Packet3, _O_WRONLY | _O_CREAT | _O_BINARY | _O_TRUNC,
                        _S_IREAD | _S_IWRITE);
    
    if (Context->fd == -1) {
        DbgPrint("TftpdHandleWrite: cannot open file=%s, errno=%d.\n"
                 "  client %s:%d,\n"
                 "  ValidWriteFiles=%s, ValidClients=%s, ValidMasters=%s,\n"
                 ,
                 FileName, errno,
                 client_ipaddr, client_port,
                 ValidWriteFiles, ValidClients, ValidMasters
        );

        SetLastError( errno );

        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ACCESS_VIOLATION,
            NULL);
        goto cleanup;
    }

    //
    // Open a new socket for this request
    //

    ReadPort =
    socket(
        AF_INET,
        SOCK_DGRAM,
        0);

    if( ReadPort == INVALID_SOCKET ){
        DbgPrint("TftpdHandleWrite: cannot open socket, Error=%d.\n",
                 WSAGetLastError() );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Insufficient resources");
        goto cleanup;
    }

    //
    // Bind to a random address
    //

    ReadAddress.sin_family = AF_INET;
    ReadAddress.sin_port = 0;

    ReadAddress.sin_addr.s_addr = Request->MyAddr;

    Status = bind(
        ReadPort,
        (struct sockaddr *) &ReadAddress,
        sizeof(ReadAddress));

    if (Status) {
        DbgPrint("TftpdHandleWrite: cannot bind socket, Error=%d.\n",
                 WSAGetLastError() );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_UNDEFINED,
            "Insufficient resources");
        goto cleanup;
    }

    Request->TftpdPort = ReadPort;

    err = TftpdProcessOptionsPhase2( Request, CharPtr + 1, TFTPD_WRQ, &Context->oackLength,Context->Packet,
                                     &Context->FixedTimer);
    if ( err != 0 ) {
        goto cleanup;
    }

    State = '\0';

    // Enter Context into list now that we know the port
    InitializeCriticalSection(&Context->Lock);
    Context->Sock=ReadPort;
    memcpy(&Context->ForeignAddress,&Request->ForeignAddress,sizeof(struct sockaddr_in));


    Context->SocketEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
    if (Context->SocketEvent == NULL) {
        DbgPrint("Failed to create socket event %d",GetLastError());
        goto cleanup;
    }

    Context->WaitEvent=RegisterSocket(ReadPort,Context->SocketEvent,REG_CONTINUE_SOCKET);
    if (Context->WaitEvent == NULL) {
        DbgPrint("Failed to create socket event %d",GetLastError());
        goto cleanup;
    }

    // Insert Context
    TftpdAddContextToList(&Context->ContextLinkage);

    AddedContext=TRUE;

    Context=(PTFTP_WRITE_CONTEXT)TftpdFindContextInList(ReadPort);
    if (Context == NULL) {
        DbgPrint("Failed to Lookup ReadContext");
        goto cleanup;
    }

    LockHeld=TRUE;

    // Start retransmission timer

    if (Context->FixedTimer) {

        Context->DueTime=Request->Timeout*1000;
    } else {
        Context->DueTime=TFTPD_INITIAL_TIMEOUT*1000;
    }

    DbgPrint("TftpdHandleWrite: Timer Interval %d msecs\n",Context->DueTime);

    ntStatus=RtlCreateTimer(Globals.TimerQueueHandle,
                         &Context->TimerHandle,
                         TftpdRetransmit,
                         (PVOID)Context->Sock,
                         Context->DueTime,
                         Context->DueTime,
                         0);

    if (!NT_SUCCESS(ntStatus)) {
        DbgPrint("Failed to Arm Timer %d",ntStatus);
    }

    Context->ContextType=WRITE_CONTEXT;
    Context->BlockSize=Request->BlockSize;

    if (Context->BlockSize > MAX_OACK_PACKET_LENGTH - 4) {
        
        NewPacket = (char *)realloc(Context->Packet, Context->BlockSize + 4);
        if (NewPacket == NULL) {
            goto cleanup;
        }

        Context->Packet = NewPacket;
    }


    if ( Context->oackLength != 0 ) {
        Context->packetLength = Context->oackLength;
        Context->oackLength = 0;
    } else {
        ((unsigned short *) Context->Packet)[0] = htons(TFTPD_ACK);
        ((unsigned short *) Context->Packet)[1] = htons(Context->BlockNumber);
        Context->packetLength = 4;
    }

    Status =
        sendto(
            ReadPort,
            Context->Packet,
            Context->packetLength,
            0,
            (struct sockaddr *) &Request->ForeignAddress,
            sizeof(struct sockaddr_in)
            );


    if( SOCKET_ERROR == Status ){
        DbgPrint("TftpdHandleWrite: sendto failed=%d\n",
                 WSAGetLastError() );
        goto cleanup;
    }



cleanup:

    if (Context != NULL) {
        if (LockHeld) {

            TftpdReleaseContextLock((PTFTP_CONTEXT_HEADER)Context);
            
        }
        if (!AddedContext) {
            free(Context);
        }
    }
//        _chmod(Request->Packet3, _S_IWRITE);

        return 0;
}

// End function TftpdHandleWrite.
// ========================================================================

#if defined(REMOTE_BOOT_SECURITY)
DWORD
TftpdHandleLogin(
    PVOID Argument
)

/*++

Routine Description:

    This handles an incoming login request.

Arguments:

    Argument - buffer containing the read request datagram
               freed when done.

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/

{
    int                packetLength;
    int                Retry;
    int                Status, err;
    struct timeval     timeval;
    struct sockaddr_in LoginAddress;
    BOOL               Acked;
    int                AddressLength;
    char *             CharPtr;
    PTFTP_REQUEST      Request;
    char *             OperationType;
    char *             PackageName;
    char *             SecurityString;
    char *             Options;
    SECURITY_STATUS    SecStatus;
    PSecPkgInfo        PackageInfo = NULL;
    USHORT             Index = -1;
    ULONG              MaxToken;
    TFTPD_SECURITY     Security;
    ULONG              SecurityHandle;
    USHORT             LastMessageSequence;   // sequence number of the last message sent
    SOCKET             LoginPort = INVALID_SOCKET;
    char *             IncomingMessage;
    SecBufferDesc      IncomingDesc;
    SecBuffer          IncomingBuffer;
    SecBufferDesc      OutgoingDesc;
    SecBuffer          OutgoingBuffer;
    BOOL               FirstChallenge;
    struct fd_set      exceptfds;
    struct fd_set      loginfds;
    TimeStamp          Lifetime;
    int                BytesAck;

    //
    // Parse the request. The initial request should always be a
    // "login".
    //

    Request = (PTFTP_REQUEST) Argument;

    OperationType = &Request->Packet1[2];

    //
    // Convert the operation to all lower case for comparison
    //

    for (CharPtr = OperationType; *CharPtr; CharPtr ++) {
        *CharPtr = (char)tolower(*CharPtr);
    }

    if (strcmp(OperationType, "login") == 0) {

        PackageName = OperationType + strlen(OperationType) + 1;

        //
        // Profile data.
        //

        DbgPrint("TftpdHandleLogin: OperationType=%s, Package=%s, from=%s.\n",
                 OperationType, PackageName,
                 inet_ntoa( Request->ForeignAddress.sin_addr )
        );

        //
        // Check that the security package is known.
        //

        SecStatus = QuerySecurityPackageInfoA( PackageName, &PackageInfo );

        if (SecStatus != STATUS_SUCCESS) {

            DbgPrint("TftpdHandleLogin: invalid PackageName=%s?\n", PackageName );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_ILLEGAL_OPERATION,
                "invalid security package");
            goto cleanup;

        }

        MaxToken = PackageInfo->cbMaxToken;

        FreeContextBuffer(PackageInfo);

        //
        // Things look OK so far, so let's find a spot in the array of
        // security information to store this client.
        //

        if (!TftpdAllocateSecurityEntry(&Index, &Security)) {

            DbgPrint("TftpdHandleLogin: could not allocate security entry\n" );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "insufficient resources");
            goto cleanup;

        }

        //
        // Acquire a credential handle for the server side.
        //

        SecStatus = AcquireCredentialsHandleA(
                        NULL,           // New principal
                        PackageName,    // Package Name
                        SECPKG_CRED_INBOUND,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        &(Security.CredentialsHandle),
                        &Lifetime );

        if ( SecStatus != STATUS_SUCCESS ) {

            DbgPrint("TftpdHandleLogin: AcquireCredentialsHandle failed %x\n", SecStatus );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "insufficient resources");
            goto cleanup;

        }

        Security.CredentialsHandleValid = TRUE;


        //
        // Open a new socket for this request
        //

        LoginPort =
        socket(
            AF_INET,
            SOCK_DGRAM,
            0);

        if (LoginPort == INVALID_SOCKET) {
            DbgPrint("TftpdHandleLogin: cannot open socket, Error=%d\n",
                     WSAGetLastError() );

            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        //
        // Bind to a random address
        //

        LoginAddress.sin_family = AF_INET;
        LoginAddress.sin_port = 0;

        LoginAddress.sin_addr.s_addr = INADDR_ANY;

        Status =
        bind(
            LoginPort,
            (struct sockaddr *) &LoginAddress,
            sizeof(LoginAddress)
        );

        if (Status) {
            DbgPrint("TftpdHandleLogin: cannot bind socket, error=%d.\n",
                     WSAGetLastError() );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        Request->TftpdPort = LoginPort;

        Request->Timeout = 10;   // let client set this in login packet?

        //
        // Ready to do exchanges until login is complete.
        //

        LastMessageSequence = (USHORT)-1;
        FirstChallenge = TRUE;
        IncomingMessage = PackageName + strlen(PackageName) + 1;

        while (1) {

            IncomingDesc.ulVersion = 0;
            IncomingDesc.cBuffers = 1;
            IncomingDesc.pBuffers = &IncomingBuffer;

            IncomingBuffer.cbBuffer = (ntohl)(((unsigned long UNALIGNED *)IncomingMessage)[0]);
            IncomingBuffer.BufferType = SECBUFFER_TOKEN | SECBUFFER_READONLY;
            IncomingBuffer.pvBuffer = IncomingMessage + 4;

            OutgoingDesc.ulVersion = 0;
            OutgoingDesc.cBuffers = 1;
            OutgoingDesc.pBuffers = &OutgoingBuffer;

            OutgoingBuffer.cbBuffer = MaxToken;
            OutgoingBuffer.BufferType = SECBUFFER_TOKEN;
            OutgoingBuffer.pvBuffer = Request->Packet2 + 8;

            //
            // Pass the client buffer to the security system -- the first time
            // we don't have a valid SecurityContextHandle, so we pass the
            // CredentialsHandle instead.
            //

            SecStatus = AcceptSecurityContext(
                            FirstChallenge ? &(Security.CredentialsHandle) : NULL,
                            FirstChallenge ? NULL : &(Security.ServerContextHandle),
                            &IncomingDesc,
                            FirstChallenge ?
                                ISC_REQ_SEQUENCE_DETECT | ASC_REQ_ALLOW_NON_USER_LOGONS :
                                ASC_REQ_ALLOW_NON_USER_LOGONS,
                            SECURITY_NATIVE_DREP,
                            &(Security.ServerContextHandle),
                            &OutgoingDesc,
                            &(Security.ContextAttributes),
                            &Lifetime );

            if (FirstChallenge) {
                Security.ServerContextHandleValid = TRUE;
            }

            FirstChallenge = FALSE;

            if (SecStatus != SEC_I_CONTINUE_NEEDED) {

                //
                // The login has been accepted or rejected.
                //

                ((unsigned short *) Request->Packet2)[0] = htons(TFTPD_LOGIN);
                ((unsigned short *) Request->Packet2)[1] = htons((USHORT)-1);

                if (SecStatus == STATUS_SUCCESS) {
                    sprintf(Request->Packet2+4, "status %u handle %d ",
                        SecStatus, (Index << 16) + Security.Validation);
                } else {
                    sprintf(Request->Packet2+4, "status %u ", SecStatus);
                }

                packetLength = 4 + strlen(Request->Packet2+4);

                for (CharPtr = Request->Packet2+4; *CharPtr; CharPtr ++) {
                    if (*CharPtr == ' ') {
                        *CharPtr = '\0';
                    }
                }

                Security.LoginComplete = TRUE;
                Security.LoginStatus = SecStatus;
                Security.ForeignAddress = Request->ForeignAddress;

                TftpdStoreSecurityEntry(Index, &Security);

                LastMessageSequence = (USHORT)-1;

            } else if (SecStatus == SEC_I_CONTINUE_NEEDED) {

                //
                // Need to exchange with the client. Note that the response
                // message has already been stored at Request->Packet2 + 8.
                //

                ++LastMessageSequence;

                ((unsigned short *) Request->Packet2)[0] = htons(TFTPD_LOGIN);
                ((unsigned short *) Request->Packet2)[1] = htons(LastMessageSequence);

                ((unsigned long UNALIGNED *) Request->Packet2)[1] = htonl(OutgoingBuffer.cbBuffer);

                packetLength = 8 + OutgoingBuffer.cbBuffer;

            }

            Acked = FALSE;
            Retry = 0;

            while (!Acked && (Retry < MAX_TFTPD_RETRIES) ){

                //
                // send the data
                //

                Status = sendto(
                    LoginPort,
                    Request->Packet2,
                    packetLength,
                    0,
                    (struct sockaddr *) &Request->ForeignAddress,
                    sizeof(struct sockaddr_in));


                if( SOCKET_ERROR == Status ){

                    DbgPrint("TftpdHandleLogin: sendto failed=%d\n",
                             WSAGetLastError() );

                    goto cleanup;
                }

                //
                // wait for the ack
                //

                FD_ZERO( &loginfds );
                FD_ZERO( &exceptfds );

                FD_SET( LoginPort, &loginfds );
                FD_SET( LoginPort, &exceptfds );

                timeval.tv_sec = Request->Timeout;
                timeval.tv_usec = 0;

                Status = select(0, &loginfds, NULL, &exceptfds, &timeval);

                if( SOCKET_ERROR == Status ){
                    DbgPrint("TftpdHandleLogin: select failed=%d\n",
                             WSAGetLastError() );
                    goto cleanup;
                }

                if ((Status > 0) && (FD_ISSET(LoginPort, &loginfds))) {

                    //
                    // Got response, maybe
                    //

                    AddressLength = sizeof(LoginAddress);

                    BytesAck =
                    recvfrom(
                        LoginPort,
                        Request->Packet1,
                        sizeof(Request->Packet1),
                        0,
                        (struct sockaddr *) &LoginAddress,
                        &AddressLength);

                    if( SOCKET_ERROR == BytesAck ){

                        DbgPrint("TftpdHandleLogin: recvfrom failed=%d\n",
                                 WSAGetLastError() );

                        goto cleanup;
                    }


                    if (CHECK_ACK(Request->Packet1, TFTPD_LOGIN, LastMessageSequence)) {
                        Acked = TRUE;
                    }
                }

                Retry ++;

            }  // end while.

            if (!Acked) {
                DbgPrint("TftpdHandleLogin: Timed out waiting for ack\n");
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "Timeout");
                goto cleanup;
            }

            if (LastMessageSequence == (USHORT)-1) {

                //
                // If we got an ack for the last sequence number, then
                // break.
                //

                break;

            } else {

                //
                // Loop back and process this message.
                //

                IncomingMessage = Request->Packet1 + 4;

            }


        } // end while 1.

    } else if (strcmp(OperationType, "logoff") == 0) {

        PackageName = OperationType + strlen(OperationType) + 1;

        //
        // Don't bother checking the package name.
        //

        SecurityString = PackageName + strlen(PackageName) + 1;

        for (CharPtr = SecurityString; *CharPtr; CharPtr ++) {
            *CharPtr = (char)tolower(*CharPtr);
        }

        if (strcmp(SecurityString, "security") != 0) {

            DbgPrint("TftpdHandleLogin: invalid logoff handle %s\n", SecurityString );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_ILLEGAL_OPERATION,
                "invalid security handle");
            goto cleanup;

        }

        //
        // Open a new socket for this request
        //

        LoginPort =
        socket(
            AF_INET,
            SOCK_DGRAM,
            0);

        if (LoginPort == INVALID_SOCKET) {
            DbgPrint("TftpdHandleLogin: cannot open socket, Error=%d\n",
                     WSAGetLastError() );

            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        //
        // Bind to a random address
        //

        LoginAddress.sin_family = AF_INET;
        LoginAddress.sin_port = 0;

        LoginAddress.sin_addr.s_addr = INADDR_ANY;

        Status =
        bind(
            LoginPort,
            (struct sockaddr *) &LoginAddress,
            sizeof(LoginAddress)
        );

        if (Status) {
            DbgPrint("TftpdHandleLogin: cannot bind socket, error=%d.\n",
                     WSAGetLastError() );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }


        //
        // Start to prepare the response.
        //

        ((unsigned short *) Request->Packet2)[0] = htons(TFTPD_LOGIN);
        ((unsigned short *) Request->Packet2)[1] = htons((USHORT)-1);

        //
        // Now get the handle and delete the security entry if it is valid.
        //

        Options = SecurityString + strlen(SecurityString) + 1;
        SecurityHandle = atoi(Options);

        TftpdGetSecurityEntry((USHORT)(SecurityHandle >> 16), &Security);
        if (Security.Validation == ((SecurityHandle) & 0xffff)) {

            TftpdFreeSecurityEntry((USHORT)(SecurityHandle >> 16));
            sprintf(Request->Packet2+4, "status %u ", 0);

        } else {

            sprintf(Request->Packet2+4, "status %u ", STATUS_INVALID_HANDLE);

        }

        packetLength = 4 + strlen(Request->Packet2+4);

        for (CharPtr = Request->Packet2+4; *CharPtr; CharPtr ++) {
            if (*CharPtr == ' ') {
                *CharPtr = '\0';
            }
        }

        //
        // Wait for his ack, but not for too long.
        //

        Acked = FALSE;
        Retry = 0;

        while (!Acked && (Retry < 3) ){

            //
            // send the data
            //

            Status = sendto(
                LoginPort,
                Request->Packet2,
                packetLength,
                0,
                (struct sockaddr *) &Request->ForeignAddress,
                sizeof(struct sockaddr_in));


            if( SOCKET_ERROR == Status ){

                DbgPrint("TftpdHandleLogin: sendto failed=%d\n",
                         WSAGetLastError() );

                goto cleanup;
            }

            //
            // wait for the ack
            //

            FD_ZERO( &loginfds );
            FD_ZERO( &exceptfds );

            FD_SET( LoginPort, &loginfds );
            FD_SET( LoginPort, &exceptfds );

            timeval.tv_sec = 2;
            timeval.tv_usec = 0;

            Status = select(0, &loginfds, NULL, &exceptfds, &timeval);

            if( SOCKET_ERROR == Status ){
                DbgPrint("TftpdHandleLogin: select failed=%d\n",
                         WSAGetLastError() );
                goto cleanup;
            }

            if ((Status > 0) && (FD_ISSET(LoginPort, &loginfds))) {

                //
                // Got response, maybe
                //

                AddressLength = sizeof(LoginAddress);

                BytesAck =
                recvfrom(
                    LoginPort,
                    Request->Packet1,
                    sizeof(Request->Packet1),
                    0,
                    (struct sockaddr *) &LoginAddress,
                    &AddressLength);

                if( SOCKET_ERROR == BytesAck ){

                    DbgPrint("TftpdHandleLogin: recvfrom failed=%d\n",
                             WSAGetLastError() );

                    goto cleanup;
                }


                if (CHECK_ACK(Request->Packet1, TFTPD_LOGIN, (USHORT)-1)) {
                    Acked = TRUE;
                }
            }

            Retry ++;

        }  // end while.

        //
        // If the ack timed out, don't worry about it.
        //

    } else {

        DbgPrint("TftpdHandleLogin: invalid OperationType=%s?\n", OperationType );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;

    }


cleanup:

    if (LoginPort != INVALID_SOCKET) {
        closesocket(LoginPort);
    }
//    free(Request);

    return 0;
}
#endif  //defined(REMOTE_BOOT_SECURITY)


#if defined(REMOTE_BOOT_SECURITY)
DWORD
TftpdHandleKey(
    PVOID Argument
)

/*++

Routine Description:

    This handles an incoming key.

Arguments:

    Argument - buffer containing the read request datagram
               freed when done.

Return Value:

    Exit status
    0 == success
    1 == failure
    N >0 failure

--*/

{
    int                Status, err;
    int                packetLength;
    struct timeval     timeval;
    struct sockaddr_in LoginAddress;
    BOOL               Acked;
    int                AddressLength;
    char *             CharPtr;
    PTFTP_REQUEST      Request;
    char *             OperationType;
    char *             SpiString;
    char *             SecurityString;
    ULONG              SpiValue;
    ULONG              KeyValue;
    ULONG              SecurityHandle;
    SOCKET             LoginPort = INVALID_SOCKET;
    HANDLE             IpsecHandle = INVALID_HANDLE_VALUE;
    BOOL               IOStatus;
    char               PolicyBuffer[sizeof(IPSEC_SET_POLICY) + sizeof(IPSEC_POLICY_INFO)];
    PIPSEC_SET_POLICY  SetPolicy = (PIPSEC_SET_POLICY)PolicyBuffer;
    IPSEC_FILTER       OutboundFilter;
    IPSEC_FILTER       InboundFilter;
    IPSEC_GET_SPI      GetSpi;
    char               SaBuffer[sizeof(IPSEC_ADD_UPDATE_SA) + (6 * sizeof(ULONG))];
    PIPSEC_ADD_UPDATE_SA AddUpdateSa;
    IPSEC_DELETE_POLICY DeletePolicy;
    // char               EnumPolicyBuffer[(UINT)(FIELD_OFFSET(IPSEC_ENUM_POLICY, pInfo[0]))];
    char               EnumPolicyBuffer[2 * sizeof(DWORD)];
    PIPSEC_ENUM_POLICY EnumPolicy;
    DWORD              EnumPolicySize;
    char               MyName[80];
    PHOSTENT           Host;
    DWORD              BytesReturned;
    DWORD              i;
    LARGE_INTEGER      SystemTime;
    TFTPD_SECURITY     Security;

    //
    // Parse the request. The initial request should always be a
    // "spi".
    //

    Request = (PTFTP_REQUEST) Argument;

    OperationType = &Request->Packet1[4];

    //
    // Convert the operation to all lower case for comparison
    //

    for (CharPtr = OperationType; *CharPtr; CharPtr ++) {
        *CharPtr = (char)tolower(*CharPtr);
    }

    if (strcmp(OperationType, "spi") == 0) {

        SpiString = OperationType + sizeof("spi");

        SpiValue = atoi(SpiString);

        OperationType = SpiString + strlen(SpiString) + 1;

        //
        // See if the client request encryption of the key.
        //

        if (strcmp(OperationType, "security") == 0) {

            SecurityString = OperationType + sizeof("security");

            SecurityHandle = atoi(SecurityString);

            //
            // High 16 bits of handle is index, low 16 bits is validation.
            //

            TftpdGenerateKeyForSecurityEntry((USHORT)(SecurityHandle >> 16), &Security);
            if ((Security.Validation != ((Request->SecurityHandle) & 0xffff)) ||
                (!Security.GeneratedKey)) {
                DbgPrint("TftpdHandleRead: SecurityHandle %x is invalid.\n",
                        Request->SecurityHandle);
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "Invalid security handle");
                goto cleanup;
            }

            KeyValue = Security.Key;

            DbgPrint("TftpdHandleKey: SPI %lx, retrieved secure key %lx\n", SpiValue, KeyValue);

        } else {

            NtQuerySystemTime(&SystemTime);
            KeyValue = (ULONG)(SystemTime.QuadPart % Request->ForeignAddress.sin_addr.s_addr);
            SecurityHandle = 0;

            DbgPrint("TftpdHandleKey: SPI %lx, generated key %lx\n", SpiValue, KeyValue);

        }

        //
        // Open IPSEC so we can send down IOCTLS.
        //

        IpsecHandle = CreateFileW(
                          DD_IPSEC_DOS_NAME,             // IPSEC device name
                          GENERIC_READ | GENERIC_WRITE,  // access (read-write) mode
                          0,                             // share mode
                          NULL,                          // pointer to security attributes
                          OPEN_EXISTING,                 // how to create
                          0,                             // file attributes
                          NULL);                         // handle to file with attributes to copy

        if (IpsecHandle == INVALID_HANDLE_VALUE) {
            DbgPrint("TftpdHandleKey: Could not open <%ws>\n", DD_IPSEC_DOS_NAME);
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }


        //
        // See how many policies are defined. First send down a buffer
        // with room for no policies, to see how many there are.
        //

        EnumPolicy = (PIPSEC_ENUM_POLICY)EnumPolicyBuffer;
        memset(EnumPolicy, 0, sizeof(EnumPolicyBuffer));

        IOStatus = DeviceIoControl(
                     IpsecHandle,                     // Driver handle
                     IOCTL_IPSEC_ENUM_POLICIES,       // Control code
                     EnumPolicy,                      // Input buffer
                     sizeof(EnumPolicyBuffer),        // Input buffer size
                     EnumPolicy,                      // Output buffer
                     sizeof(EnumPolicyBuffer),        // Output buffer size
                     &BytesReturned,
                     NULL);

        if (!IOStatus) {

            if (GetLastError() != ERROR_MORE_DATA) {

                DbgPrint("TftpdHandleKey: IOCTL_IPSEC_ENUM_POLICY #1 failed %x\n", GetLastError());
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "IOCTL_IPSEC_ENUM_POLICIES");
                goto cleanup;

            }

            EnumPolicySize = FIELD_OFFSET(IPSEC_ENUM_POLICY, pInfo[0]) +
                                sizeof(IPSEC_POLICY_INFO) * EnumPolicy->NumEntriesPresent;
            EnumPolicy = malloc(EnumPolicySize);

            if (EnumPolicy == NULL) {
                DbgPrint("TftpdHandleKey: alloc ENUM_POLICIES buffer failed %x\n", GetLastError());
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "IOCTL_IPSEC_ENUM_POLICIES");
                goto cleanup;
            }

            //
            // Re-submit the IOCTL.
            //

            memset(EnumPolicy, 0, EnumPolicySize);

            IOStatus = DeviceIoControl(
                         IpsecHandle,                     // Driver handle
                         IOCTL_IPSEC_ENUM_POLICIES,       // Control code
                         EnumPolicy,                      // Input buffer
                         EnumPolicySize,                  // Input buffer size
                         EnumPolicy,                      // Output buffer
                         EnumPolicySize,                  // Output buffer size
                         &BytesReturned,
                         NULL);

            //
            // We may get MORE_DATA if someone just added a policy, but
            // that is OK since it won't be for this remote.
            //

            if (!IOStatus && (GetLastError() != ERROR_MORE_DATA)) {

                DbgPrint("TftpdHandleKey: IOCTL_IPSEC_ENUM_POLICY #2 failed %x\n", GetLastError());
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "IOCTL_IPSEC_ENUM_POLICIES");
                free(EnumPolicy);
                goto cleanup;

            }

            //
            // Display all the policies.
            //
            // Delete any policies involving the remote machine.
            //

            for (i = 0; i < EnumPolicy->NumEntriesPresent; i++) {

                if ((EnumPolicy->pInfo[i].AssociatedFilter.SrcAddr ==
                        Request->ForeignAddress.sin_addr.s_addr) ||
                    (EnumPolicy->pInfo[i].AssociatedFilter.DestAddr ==
                        Request->ForeignAddress.sin_addr.s_addr)) {

                    DeletePolicy.NumEntries = 1;
                    DeletePolicy.pInfo[0] = EnumPolicy->pInfo[i];

                    IOStatus = DeviceIoControl(
                                 IpsecHandle,                     // Driver handle
                                 IOCTL_IPSEC_DELETE_POLICY,       // Control code
                                 &DeletePolicy,                   // Input buffer
                                 sizeof(IPSEC_DELETE_POLICY),     // Input buffer size
                                 NULL,                            // Output buffer
                                 0,                               // Output buffer size
                                 &BytesReturned,
                                 NULL);

                    if (!IOStatus) {
                        DbgPrint("TftpdHandleKey: IOCTL_IPSEC_DELETE_POLICY(%lx, %lx) failed %x\n",
                            EnumPolicy->pInfo[i].AssociatedFilter.SrcAddr,
                            EnumPolicy->pInfo[i].AssociatedFilter.DestAddr,
                            GetLastError());
                    }

                }

            }

            free(EnumPolicy);

        } else {

            //
            // If the call succeeds, we don't need to do anything, since
            // there should have been 0 policies returned.
            //
        }

        //
        // Get our local IP address.
        //

        gethostname(MyName, sizeof(MyName));
        Host = gethostbyname(MyName);

        //
        // Set the policy. We need two filters, one for outbound and
        // one for inbound.
        //

        memset(&OutboundFilter, 0, sizeof(IPSEC_FILTER));
        memset(&InboundFilter, 0, sizeof(IPSEC_FILTER));

        OutboundFilter.SrcAddr = *(DWORD *)Host->h_addr;
        OutboundFilter.SrcMask = 0xffffffff;
        // OutboundFilter.SrcPort = 0x8B;  // netbios session port
        OutboundFilter.DestAddr = Request->ForeignAddress.sin_addr.s_addr;
        OutboundFilter.DestMask = 0xffffffff;
        OutboundFilter.Protocol = 0x6;   // TCP

        InboundFilter.SrcAddr = Request->ForeignAddress.sin_addr.s_addr;
        InboundFilter.SrcMask = 0xffffffff;
        InboundFilter.DestAddr = *(DWORD *)Host->h_addr;
        InboundFilter.DestMask = 0xffffffff;
        // InboundFilter.DestPort = 0x8B;  // netbios session port
        InboundFilter.Protocol = 0x6;   // TCP

        memset(SetPolicy, 0, sizeof(PolicyBuffer));

        SetPolicy->NumEntries = 2;
        SetPolicy->pInfo[0].Index = 1;
        SetPolicy->pInfo[0].AssociatedFilter = OutboundFilter;
        SetPolicy->pInfo[1].Index = 2;
        SetPolicy->pInfo[1].AssociatedFilter = InboundFilter;

        IOStatus = DeviceIoControl(
                     IpsecHandle,                     // Driver handle
                     IOCTL_IPSEC_SET_POLICY,          // Control code
                     SetPolicy,                       // Input buffer
                     sizeof(PolicyBuffer),            // Input buffer size
                     NULL,                            // Output buffer
                     0,                               // Output buffer size
                     &BytesReturned,
                     NULL);

        if (!IOStatus) {
            DbgPrint("TftpdHandleKey: IOCTL_IPSEC_SET_POLICY failed %x\n", GetLastError());
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "IOCTL_IPSEC_SET_POLICY");
            goto cleanup;
        }

        //
        // Now get an SPI to give to the remote.
        //

        GetSpi.Context = 0;
        GetSpi.InstantiatedFilter = InboundFilter;

        IOStatus = DeviceIoControl(
                     IpsecHandle,                     // Driver handle
                     IOCTL_IPSEC_GET_SPI,             // Control code
                     &GetSpi,                         // Input buffer
                     sizeof(IPSEC_GET_SPI),           // Input buffer size
                     &GetSpi,                         // Output buffer
                     sizeof(IPSEC_GET_SPI),           // Output buffer size
                     &BytesReturned,
                     NULL);

        if (!IOStatus) {
            DbgPrint("TftpdHandleKey: IOCTL_IPSEC_GET_SPI failed %x\n", GetLastError());
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "IOCTL_IPSEC_GET_SPI");
            goto cleanup;
        }

        //
        // Set up the security association for the outbound
        // connection.
        //

        AddUpdateSa = (PIPSEC_ADD_UPDATE_SA)SaBuffer;
        memset(AddUpdateSa, 0, sizeof(SaBuffer));

        AddUpdateSa->SAInfo.Context = GetSpi.Context;
        AddUpdateSa->SAInfo.NumSAs = 1;
        AddUpdateSa->SAInfo.InstantiatedFilter = OutboundFilter;
        AddUpdateSa->SAInfo.SecAssoc[0].Operation = Encrypt;
        AddUpdateSa->SAInfo.SecAssoc[0].SPI = SpiValue;
        AddUpdateSa->SAInfo.SecAssoc[0].IntegrityAlgo.algoIdentifier = IPSEC_AH_MD5;
        AddUpdateSa->SAInfo.SecAssoc[0].IntegrityAlgo.algoKeylen = 4 * sizeof(ULONG);
        AddUpdateSa->SAInfo.SecAssoc[0].ConfAlgo.algoIdentifier = IPSEC_ESP_DES;
        AddUpdateSa->SAInfo.SecAssoc[0].ConfAlgo.algoKeylen = 2 * sizeof(ULONG);

        AddUpdateSa->SAInfo.KeyLen = 6 * sizeof(ULONG);
        memcpy(AddUpdateSa->SAInfo.KeyMat, &KeyValue, sizeof(ULONG));
        memcpy(AddUpdateSa->SAInfo.KeyMat+sizeof(ULONG), &KeyValue, sizeof(ULONG));
        memcpy(AddUpdateSa->SAInfo.KeyMat+(2*sizeof(ULONG)), &KeyValue, sizeof(ULONG));
        memcpy(AddUpdateSa->SAInfo.KeyMat+(3*sizeof(ULONG)), &KeyValue, sizeof(ULONG));
        memcpy(AddUpdateSa->SAInfo.KeyMat+(4*sizeof(ULONG)), &KeyValue, sizeof(ULONG));
        memcpy(AddUpdateSa->SAInfo.KeyMat+(5*sizeof(ULONG)), &KeyValue, sizeof(ULONG));

        IOStatus = DeviceIoControl(
                     IpsecHandle,                     // Driver handle
                     IOCTL_IPSEC_ADD_SA,              // Control code
                     AddUpdateSa,                     // Input buffer
                     FIELD_OFFSET(IPSEC_ADD_UPDATE_SA, SAInfo.KeyMat[0]) +
                         AddUpdateSa->SAInfo.KeyLen,  // Input buffer size
                     NULL,                            // Output buffer
                     0,                               // Output buffer size
                     &BytesReturned,
                     NULL);

        if (!IOStatus) {
            DbgPrint("TftpdHandleKey: IOCTL_IPSEC_ADD_SA failed %x\n", GetLastError());
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "IOCTL_IPSEC_ADD_SA");
            goto cleanup;
        }

        //
        // Set up the security association for the inbound connection.
        // If our Operation is "None", then IPSEC does this for us.
        //

        if (AddUpdateSa->SAInfo.SecAssoc[0].Operation != None) {

            AddUpdateSa->SAInfo.SecAssoc[0].SPI = GetSpi.SPI;
            AddUpdateSa->SAInfo.InstantiatedFilter = InboundFilter;

            IOStatus = DeviceIoControl(
                         IpsecHandle,                     // Driver handle
                         IOCTL_IPSEC_UPDATE_SA,           // Control code
                         AddUpdateSa,                     // Input buffer
                         FIELD_OFFSET(IPSEC_ADD_UPDATE_SA, SAInfo.KeyMat[0]) +
                             AddUpdateSa->SAInfo.KeyLen,  // Input buffer size
                         NULL,                            // Output buffer
                         0,                               // Output buffer size
                         &BytesReturned,
                         NULL);

            if (!IOStatus) {
                DbgPrint("TftpdHandleKey: IOCTL_IPSEC_UPDATE_SA failed %x\n", GetLastError());
                TftpdErrorPacket(
                    (struct sockaddr *) &Request->ForeignAddress,
                    Request->Packet2,
                    Request->TftpdPort,
                    TFTPD_ERROR_UNDEFINED,
                    "IOCTL_IPSEC_UPDATE_SA");
                goto cleanup;
            }
        }

        //
        // Open a new socket for this request
        //

        LoginPort =
        socket(
            AF_INET,
            SOCK_DGRAM,
            0);

        if (LoginPort == INVALID_SOCKET) {
            DbgPrint("TftpdHandleKey: cannot open socket, Error=%d\n",
                     WSAGetLastError() );

            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        //
        // Bind to a random address
        //

        LoginAddress.sin_family = AF_INET;
        LoginAddress.sin_port = 0;

        LoginAddress.sin_addr.s_addr = INADDR_ANY;

        Status =
        bind(
            LoginPort,
            (struct sockaddr *) &LoginAddress,
            sizeof(LoginAddress)
        );

        if (Status) {
            DbgPrint("TftpdHandleKey: cannot bind socket, error=%d.\n",
                     WSAGetLastError() );
            TftpdErrorPacket(
                (struct sockaddr *) &Request->ForeignAddress,
                Request->Packet2,
                Request->TftpdPort,
                TFTPD_ERROR_UNDEFINED,
                "Insufficient resources");
            goto cleanup;
        }

        //
        // Generate the response for the client.
        //

        ((unsigned short *) Request->Packet2)[0] = htons(TFTPD_KEY);
        Request->Packet2[2] = Request->Packet1[2];   // copy sequence number
        Request->Packet2[3] = Request->Packet1[3];

        //
        // They key is sent as hex digits since it might be longer
        // than four bytes.
        //

        if (SecurityHandle == 0) {

            //
            // No security, send key in the clear.
            //

            sprintf(Request->Packet2+4, "spi %d key %2.2x%2.2x%2.2x%2.2x",
                GetSpi.SPI,
                ((PUCHAR)(&KeyValue))[0],
                ((PUCHAR)(&KeyValue))[1],
                ((PUCHAR)(&KeyValue))[2],
                ((PUCHAR)(&KeyValue))[3]);

            packetLength = 4 + strlen(Request->Packet2+4);

        } else {

            PCHAR SignLoc;
            ULONG i;

            //
            // Security requested, so send the encrypted key and the sign.
            //

            sprintf(Request->Packet2+4, "spi %d security %d sign ",
                GetSpi.SPI,
                SecurityHandle);

            packetLength = 4 + strlen(Request->Packet2+4);

            SignLoc = Request->Packet2 + packetLength;

            for (i = 0; i < NTLMSSP_MESSAGE_SIGNATURE_SIZE; i++) {
                sprintf(SignLoc, "%2.2x", Security.Sign[i]);
                SignLoc += 2;
                packetLength += 2;
            }

            sprintf(Request->Packet2+packetLength, " key %2.2x%2.2x%2.2x%2.2x",
                Security.SignedKey[0],
                Security.SignedKey[1],
                Security.SignedKey[2],
                Security.SignedKey[3]);

            packetLength += strlen(" key ") + (2 * sizeof(Security.SignedKey));

        }

        for (CharPtr = Request->Packet2+4; *CharPtr; CharPtr ++) {
            if (*CharPtr == ' ') {
                *CharPtr = '\0';
            }
        }

        //
        // Send the response back to the client.
        //

        Status = sendto(
            LoginPort,
            Request->Packet2,
            packetLength,
            0,
            (struct sockaddr *) &Request->ForeignAddress,
            sizeof(struct sockaddr_in));

        if( SOCKET_ERROR == Status ){

            DbgPrint("TftpdHandleKey: sendto failed=%d\n",
                     WSAGetLastError() );

            goto cleanup;
        }

    } else {

        DbgPrint("TftpdHandleKey: invalid OperationType=%s?\n", OperationType );
        TftpdErrorPacket(
            (struct sockaddr *) &Request->ForeignAddress,
            Request->Packet2,
            Request->TftpdPort,
            TFTPD_ERROR_ILLEGAL_OPERATION,
            NULL);
        goto cleanup;

    }

cleanup:

    if (IpsecHandle != INVALID_HANDLE_VALUE) {
        CloseHandle(IpsecHandle);
    }
    if (LoginPort != INVALID_SOCKET) {
        closesocket(LoginPort);
    }
//    free(Request);

    return 0;
}

#endif  //defined(REMOTE_BOOT_SECURITY)

// ========================================================================

int
TftpdDoRead(
    int ReadFd,
    char * Buffer,
    int BufferSize,
    int ReadMode)

/*++

Routine Description:

    This does a read with the appropriate conversions for netascii or octet
    modes.

Arguments:

    ReadFd - file to read from
    Buffer - Buffer to read into
    BufferSize - size of buffer
    ReadMode - O_TEXT or O_BINARY
               O_TEXT means the netascii conversions must be done
               O_BINARY means octet mode

Return Value:

    BytesRead

    Error?

--*/
{
    int BytesRead;
    int BytesWritten;
    int BytesUsed;
    char NextChar;
    char State;
    char LocalBuffer[MAX_TFTP_DATA];
    int  err;

    if (ReadMode == O_BINARY) {
        BytesRead = _read(ReadFd, Buffer, BufferSize);
        if( BytesRead == -1 ){
            DbgPrint("TftpdDoRead: read failed, errno=%d\n", errno );
            SetLastError( errno );
        }
        return(BytesRead);
    } else {

        //
        // Do those cr/lf conversions.  A \r not followed by a \n must
        // be followed by a \0.
        //

        BytesWritten = 0;
        BytesUsed = 0;
        State = '\0';
        BytesRead = _read(ReadFd, LocalBuffer, sizeof(LocalBuffer));

        if( BytesRead == -1 ){
            DbgPrint("TftpdDoRead: read failed, errno=%d\n", errno );
            SetLastError( errno );
            return -1;
        }


        while ((BytesUsed < BytesRead) && (BytesWritten < BufferSize)) {
            NextChar = LocalBuffer[BytesUsed++];
            if (State == '\r') {
                if (NextChar == '\n') {
                    Buffer[BytesWritten++] = NextChar;
                    State = '\0';
                } else {
                    Buffer[BytesWritten++] = '\0';
                    Buffer[BytesWritten++] = NextChar;
                    State = '\0';
                }
            } else {
                Buffer[BytesWritten++] = NextChar;
                State = '\0';
            }
            if (NextChar == '\r') {
                State = '\r';
            }
        }

        err = _lseek(ReadFd, BytesUsed - BytesRead, SEEK_CUR);

        if( err == -1 ){
            DbgPrint("TftpdDoRead: lseek failed, errno=%d\n",
                     errno );
            SetLastError( errno );
            return -1;
        }

        return(BytesWritten);
    }
}

// End function TftpdDoRead.
// ========================================================================

int
TftpdDoWrite(
    int WriteFd,
    char * Buffer,
    int BufferSize,
    int WriteMode,
    char * State)

/*++

Routine Description:

    This does a write with the appropriate conversions for netascii or octet
    modes.

Arguments:

    WriteFd - file to write to
    Buffer - Buffer to read into
    BufferSize - size of buffer
    WriteMode - O_TEXT or O_BINARY
                O_TEXT means the netascii conversions must be done
                O_BINARY means octet mode
    State - pointer to the current output state.  If the last character in the
            buffer is a '\r', that fact must be remembered.

Return Value:

    BytesWritten

    Error?

--*/
{
    int BytesWritten;
    int i;
    char OutputBuffer[MAX_TFTP_DATA*2];
    int OutputPointer;

    if (WriteMode == O_BINARY) {
        BytesWritten = _write(WriteFd, Buffer, BufferSize);
        if( BytesWritten == -1 ){
            DbgPrint("TftpdDoWrite: write failed=%d\n", errno );
            SetLastError( errno );
        }
    } else {

        //
        // Do those cr/lf conversions.  If a '\r' followed by a '\0' is
        // followed by a '\0', the '\0' is stripped.
        //

        OutputPointer = 0;
        for (i=0; i<BufferSize; i++) {
            if ((*State == '\r') && (Buffer[i] == '\0')) {
                *State = '\0';
            } else {
                OutputBuffer[OutputPointer ++] = Buffer[i];
                if (Buffer[i] == '\r') {
                    *State = '\r';
                }
            }
        }
        BytesWritten = _write(WriteFd, Buffer, OutputPointer);
        if( BytesWritten == -1 ){
            DbgPrint("TftpdDoWrite: write failed=%d\n", errno );
            SetLastError( errno );
        }
    }
    return(BytesWritten);
}

// End function TftpdDoWrite.
// ========================================================================
// EOF.