/**********************************************************************/
/**                       Microsoft Windows NT                       **/
/**                Copyright(c) Microsoft Corp., 1993                **/
/**********************************************************************/

/*
    sockutil.cxx

    This module contains utility routines for managing & manipulating
    sockets.

    Functions exported by this module:

        InitializeSockets
        TerminateSockets
        CreateDataSocket
        CreateFtpdSocket
        CloseSocket
        ResetSocket
        AcceptSocket
        SockSend
        SockRecv
        SockPrintf2
        ReplyToUser()
        SockReadLine
        SockMultilineMessage2



    FILE HISTORY:
        KeithMo     07-Mar-1993 Created.
        MuraliK     April-1995 Misc modifications (removed usage of various
                                  socket functions/modified them)

*/


#include "ftpdp.hxx"


//
//  Private constants.
//

#define DEFAULT_BUFFER_SIZE     4096    // bytes

//
//  Private globals.
//


//
//  Private prototypes.
//

SOCKERR
vSockPrintf(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    LPCSTR      pszFormat,
    va_list     args
    );

SOCKERR
vSockReply(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    UINT        ReplyCode,
    CHAR        chSeparator,
    LPCSTR      pszFormat,
    va_list     args
    );

BOOL
vTelnetEscapeIAC(
    PCHAR       pszBuffer,
    PINT        pcchBufChars,
    INT         ccbMaxLen
    );

//
//  Public functions.
//


/*******************************************************************

    NAME:       CreateDataSocket

    SYNOPSIS:   Creates a data socket for the specified address & port.

    ENTRY:      psock - Will receive the new socket ID if successful.

                addrLocal - The local Internet address for the socket
                    in network byte order.

                portLocal - The local port for the socket in network
                    byte order.

                addrRemote - The remote Internet address for the socket
                    in network byte order.

                portRemote - The remote port for the socket in network
                    byte order.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     10-Mar-1993 Created.
        KeithMo     07-Sep-1993 Enable SO_REUSEADDR.

********************************************************************/
SOCKERR
CreateDataSocket(
    SOCKET * psock,
    ULONG    addrLocal,
    PORT     portLocal,
    ULONG    addrRemote,
    PORT     portRemote
    )
{
    SOCKET      sNew = INVALID_SOCKET;
    SOCKERR     serr = 0;
    SOCKADDR_IN sockAddr;

    //
    //  Just to be paranoid...
    //

    DBG_ASSERT( psock != NULL );
    *psock = INVALID_SOCKET;

    //
    //  Create the socket.
    //

    sNew = WSASocketW(AF_INET,
                      SOCK_STREAM,
                      IPPROTO_TCP,
                      NULL,
                      0,
                      WSA_FLAG_OVERLAPPED);

    serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;

    if( serr == 0 )
    {
        BOOL fReuseAddr = TRUE;

        //
        //  Since we always bind to the same local port,
        //  allow the reuse of address/port pairs.
        //

        if( setsockopt( sNew,
                        SOL_SOCKET,
                        SO_REUSEADDR,
                        (CHAR *)&fReuseAddr,
                        sizeof(fReuseAddr) ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Bind the local internet address & port to the socket.
        //

        sockAddr.sin_family      = AF_INET;
        sockAddr.sin_addr.s_addr = addrLocal;
        sockAddr.sin_port        = portLocal;

        if( bind( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Connect to the remote internet address & port.
        //

        sockAddr.sin_family      = AF_INET;
        sockAddr.sin_addr.s_addr = addrRemote;
        sockAddr.sin_port        = portRemote;

        if( connect( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Success!  Return the socket to the caller.
        //

        DBG_ASSERT( sNew != INVALID_SOCKET );
        *psock = sNew;

        IF_DEBUG( SOCKETS )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "data socket %d connected from (%08lX,%04X) to (%08lX,%04X)\n",
                        sNew,
                        ntohl( addrLocal ),
                        ntohs( portLocal ),
                        ntohl( addrRemote ),
                        ntohs( portRemote ) ));
        }
    }
    else
    {
        //
        //  Something fatal happened.  Close the socket if
        //  managed to actually open it.
        //

        IF_DEBUG( SOCKETS )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "no data socket from (%08lX,%04X) to (%08lX, %04X), error %d\n",
                        ntohl( addrLocal ),
                        ntohs( portLocal ),
                        ntohl( addrRemote ),
                        ntohs( portRemote ),
                        serr ));

        }

        if( sNew != INVALID_SOCKET )
        {
            ResetSocket( sNew );
        }
    }

    return serr;

}   // CreateDataSocket




/*******************************************************************

    NAME:       CreateFtpdSocket

    SYNOPSIS:   Creates a new socket at the FTPD port.
                  This will be used by the passive data transfer.

    ENTRY:      psock - Will receive the new socket ID if successful.

                addrLocal - The lcoal Internet address for the socket
                    in network byte order.

                portLocal - The local port for the socket in network
                    byte order.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     08-Mar-1993 Created.

********************************************************************/
SOCKERR
CreateFtpdSocket(
    SOCKET * psock,
    ULONG    addrLocal,
    PORT     portLocal,
    FTP_SERVER_INSTANCE *pInstance
    )
{
    SOCKET  sNew = INVALID_SOCKET;
    SOCKERR serr = 0;

    //
    //  Just to be paranoid...
    //

    DBG_ASSERT( psock != NULL );
    *psock = INVALID_SOCKET;

    //
    //  Create the connection socket.
    //

    sNew = WSASocketW(AF_INET,
                      SOCK_STREAM,
                      IPPROTO_TCP,
                      NULL,
                      0,
                      WSA_FLAG_OVERLAPPED);


    serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;

    if( serr == 0 )
    {
        BOOL fReuseAddr = FALSE;

        //
        //  Muck around with the socket options a bit.
        //  Berkeley FTPD does this.
        //

        if( setsockopt( sNew,
                        SOL_SOCKET,
                        SO_REUSEADDR,
                        (CHAR *)&fReuseAddr,
                        sizeof(fReuseAddr) ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        SOCKADDR_IN sockAddr;

        //
        //  Bind an address to the socket.
        //

        sockAddr.sin_family      = AF_INET;
        sockAddr.sin_addr.s_addr = addrLocal;
        sockAddr.sin_port        = portLocal;

        if( bind( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Put the socket into listen mode.
        //

        if( listen( sNew, (INT)pInstance->NumListenBacklog()) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

    if( serr == 0 )
    {
        //
        //  Success!  Return the socket to the caller.
        //

        DBG_ASSERT( sNew != INVALID_SOCKET );
        *psock = sNew;

        IF_DEBUG( SOCKETS )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "connection socket %d created at (%08lX,%04X)\n",
                        sNew,
                        ntohl( addrLocal ),
                        ntohs( portLocal ) ));
        }
    }
    else
    {
        //
        //  Something fatal happened.  Close the socket if
        //  managed to actually open it.
        //

        IF_DEBUG( SOCKETS )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "no connection socket at (%08lX, %04X), error %d\n",
                        ntohl( addrLocal ),
                        ntohs( portLocal ),
                        serr ));

        }

        if( sNew != INVALID_SOCKET )
        {
            ResetSocket( sNew );
        }
    }

    return serr;

}   // CreateFtpdSocket



/*******************************************************************

    NAME:       CloseSocket

    SYNOPSIS:   Closes the specified socket.  This is just a thin
                wrapper around the "real" closesocket() API.

    ENTRY:      sock - The socket to close.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     26-Apr-1993 Created.

********************************************************************/
SOCKERR
CloseSocket(
    SOCKET sock
    )
{
    SOCKERR serr = 0;

    //
    //  Close the socket.
    //

    if( closesocket( sock ) != 0 )
    {
        serr = WSAGetLastError();
    }

    IF_DEBUG( SOCKETS )
    {
        if( serr == 0 )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "closed socket %d\n",
                        sock ));
        }
        else
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "cannot close socket %d, error %d\n",
                        sock,
                        serr ));
        }
    }

    return serr;

}   // CloseSocket


/*******************************************************************

    NAME:       ResetSocket

    SYNOPSIS:   Performs a "hard" close on the given socket.

    ENTRY:      sock - The socket to close.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     08-Mar-1993 Created.

********************************************************************/
SOCKERR
ResetSocket(
    SOCKET sock
    )
{
    SOCKERR serr = 0;
    LINGER  linger;

    //
    //  Enable linger with a timeout of zero.  This will
    //  force the hard close when we call closesocket().
    //
    //  We ignore the error return from setsockopt.  If it
    //  fails, we'll just try to close the socket anyway.
    //

    linger.l_onoff  = TRUE;
    linger.l_linger = 0;

    setsockopt( sock,
                SOL_SOCKET,
                SO_LINGER,
                (CHAR *)&linger,
                sizeof(linger) );

    //
    //  Close the socket.
    //

    if( closesocket( sock ) != 0 )
    {
        serr = WSAGetLastError();
    }

    IF_DEBUG( SOCKETS )
    {
        if( serr == 0 )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "reset socket %d\n",
                        sock ));
        }
        else
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "cannot reset socket %d, error %d\n",
                        sock,
                        serr ));
        }
    }

    return serr;

}   // ResetSocket



/*******************************************************************

    NAME:       AcceptSocket

    SYNOPSIS:   Waits for a connection to the specified socket.
                The socket is assumed to be "listening".

    ENTRY:      sockListen - The socket to accept on.

                psockNew - Will receive the newly "accepted" socket
                    if successful.

                paddr - Will receive the client's network address.

                fEnforceTimeout - If TRUE, this routine will enforce
                    the idle-client timeout.  If FALSE, no timeouts
                    are enforced (and this routine may block
                    indefinitely).

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     27-Apr-1993 Created.

********************************************************************/
SOCKERR
AcceptSocket(
    SOCKET          sockListen,
    SOCKET *        psockNew,
    LPSOCKADDR_IN   paddr,
    BOOL            fEnforceTimeout,
    FTP_SERVER_INSTANCE *pInstance
    )
{
    SOCKERR serr    = 0;
    SOCKET  sockNew = INVALID_SOCKET;
    BOOL    fRead = FALSE;

    DBG_ASSERT( psockNew != NULL );
    DBG_ASSERT( paddr != NULL );

    if( fEnforceTimeout ) {

        //
        //  Timeouts are to be enforced, so wait for a connection
        //  to the socket.
        //

        serr = WaitForSocketWorker(
                            sockListen,
                            INVALID_SOCKET,
                            &fRead,
                            NULL,
                            pInstance->QueryConnectionTimeout()
                            );
    }

    if( serr == 0 )
    {
        INT cbAddr = sizeof(SOCKADDR_IN);

        //
        //  Wait for the actual connection.
        //

        sockNew = accept( sockListen, (SOCKADDR *)paddr, &cbAddr );

        if( sockNew == INVALID_SOCKET )
        {
            serr = WSAGetLastError();
        }
    }

    //
    //  Return the (potentially invalid) socket to the caller.
    //

    *psockNew = sockNew;

    return serr;

}   // AcceptSocket



/*******************************************************************

    NAME:       SockSend

    SYNOPSIS:   Sends a block of bytes to a specified socket.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

                pBuffer - Contains the data to send.

                cbBuffer - The size (in bytes) of the buffer.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     13-Mar-1993 Created.

********************************************************************/
SOCKERR
SockSend(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    LPVOID      pBuffer,
    DWORD       cbBuffer
    )
{
    SOCKERR     serr = 0;
    INT         cbSent;
    DWORD       dwBytesSent = 0;

    DBG_ASSERT( pBuffer != NULL );

    //
    //  Loop until there's no more data to send.
    //

    while( cbBuffer > 0 ) {

        //
        //  Wait for the socket to become writeable.
        //
        BOOL  fWrite = FALSE;

        serr = WaitForSocketWorker(
                        INVALID_SOCKET,
                        sock,
                        NULL,
                        &fWrite,
                        (pUserData != NULL) ?
                            pUserData->QueryInstance()->QueryConnectionTimeout():
                            FTP_DEF_SEND_TIMEOUT
                        );

        if( serr == 0 )
        {
            //
            //  Write a block to the socket.
            //

            cbSent = send( sock, (CHAR *)pBuffer, (INT)cbBuffer, 0 );

            if( cbSent < 0 )
            {
                //
                //  Socket error.
                //

                serr = WSAGetLastError();
            }
            else
            {
                dwBytesSent += (DWORD)cbSent;

                IF_DEBUG( SEND )
                {
                    if( pUserData && TEST_UF( pUserData, TRANSFER ) )
                    {
                        DBGPRINTF(( DBG_CONTEXT,
                                    "send %d bytes @%p to socket %d\n",
                                    cbSent,
                                    pBuffer,
                                    sock ));
                    }
                }
            }
        }


        // added a check for special case when we are sending and thinking that we are sending
        // synchronoulsy on socket which was set to non blocking mode. In that case when buffer space
        // in winsock becomes exhausted send return WSAEWOULDBLOCK. So then we just retry.

        if ( serr != WSAEWOULDBLOCK )
        {
            if( serr != 0 )
            {
                break;
            }

            pBuffer   = (LPVOID)( (LPBYTE)pBuffer + cbSent );
            cbBuffer -= (DWORD)cbSent;
        }
    }

    if( serr != 0 )
    {
        IF_DEBUG( SEND )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "socket error %d during send on socket %d.\n",
                        serr,
                        sock));
        }
    }

    if( pUserData != NULL ) {
        pUserData->QueryInstance()->QueryStatsObj()->UpdateTotalBytesSent(
                                                                dwBytesSent );
    }

    return serr;

}   // SockSend



/*******************************************************************

    NAME:       SockRecv

    SYNOPSIS:   Receives a block of bytes from a specified socket.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

                pBuffer - Will receive the data.

                cbBuffer - The size (in bytes) of the buffer.

                pbReceived - Will receive the actual number of bytes
                    received.  This value is undefined if this function
                    fails.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     13-Mar-1993 Created.

********************************************************************/
SOCKERR
SockRecv(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    LPVOID      pBuffer,
    DWORD       cbBuffer,
    LPDWORD     pbReceived
    )
{
    SOCKERR     serr = 0;
    DWORD       cbTotal = 0;
    INT         cbReceived;
    DWORD       dwBytesRecv = 0;

    DBG_ASSERT( pBuffer != NULL );
    DBG_ASSERT( pbReceived != NULL );

    //
    //  Loop until the buffer's full.
    //

    while (cbBuffer > 0) {

        BOOL fRead = FALSE;

        //
        //  Wait for the socket to become readable.
        //

        serr = WaitForSocketWorker(
                            sock,
                            INVALID_SOCKET,
                            &fRead,
                            NULL,
                            (pUserData != NULL) ?
                                pUserData->QueryInstance()->QueryConnectionTimeout():
                                FTP_DEF_RECV_TIMEOUT
                            );

        if( serr == 0 )
        {
            //
            //  Read a block from the socket.
            //
            DBG_ASSERT( fRead);

            cbReceived = recv( sock, (CHAR *)pBuffer, (INT)cbBuffer, 0 );

            if( cbReceived < 0 )
            {
                //
                //  Socket error.
                //

                serr = WSAGetLastError();
            }
            else
            {
                dwBytesRecv += (DWORD)cbReceived;

                IF_DEBUG( RECV )
                {
                    if( pUserData && TEST_UF( pUserData, TRANSFER ) )
                    {
                        DBGPRINTF(( DBG_CONTEXT,
                                    "received %d bytes @%p from socket %d\n",
                                    cbReceived,
                                    pBuffer,
                                    sock ));
                    }
                }
            }
        }

        if( ( serr != 0 ) || ( cbReceived == 0 ) )
        {
            //
            //  End of file, socket closed, timeout, or socket error.
            //

            break;
        }

        pBuffer   = (LPVOID)( (LPBYTE)pBuffer + cbReceived );
        cbBuffer -= (DWORD)cbReceived;
        cbTotal  += (DWORD)cbReceived;
    }

    if( serr == 0 )
    {
        //
        //  Return total byte count to caller.
        //

        *pbReceived = cbTotal;
    }
    else
    {
        IF_DEBUG( RECV )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "socket error %d during recv on socket %d\n",
                        serr,
                        sock ));
        }
    }

    if( pUserData != NULL ) {
        pUserData->QueryInstance()->QueryStatsObj()->UpdateTotalBytesReceived(
                                                                dwBytesRecv );
    }

    return serr;

}   // SockRecv



/*******************************************************************

    NAME:       SockPrintf2

    SYNOPSIS:   Send a formatted string to a specific socket.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

                pszFormat - A printf-style format string.

                ... - Any other parameters needed by the format string.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     10-Mar-1993 Created.

********************************************************************/
SOCKERR
__cdecl
SockPrintf2(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    LPCSTR      pszFormat,
    ...
    )
{
    va_list ArgPtr;
    SOCKERR serr;

    //
    //  Let the worker do the dirty work.
    //

    va_start( ArgPtr, pszFormat );

    serr = vSockPrintf( pUserData,
                        sock,
                        pszFormat,
                        ArgPtr );

    va_end( ArgPtr );

    return serr;

}   // SockPrintf2



SOCKERR
__cdecl
ReplyToUser(
    IN LPUSER_DATA pUserData,
    IN UINT        ReplyCode,
    IN LPCSTR      pszFormat,
    ...
    )
/*++

  This function sends an FTP reply to the user data object. The reply
   is usually sent over the control socket.

  Arguments:
     pUserData    pointer to UserData object initiating the reply
     ReplyCode    One of the REPLY_* manifests.
     pszFormat    pointer to null-terminated string containing the format
     ...          additional paramters if any required.

  Returns:
     SOCKET error code. 0 on success and !0 on failure.

  History:
     MuraliK
--*/
{
    va_list ArgPtr;
    SOCKERR serr;

    DBG_ASSERT( pUserData != NULL);

    pUserData->SetLastReplyCode( ReplyCode );

    if ( pUserData->QueryControlSocket() != INVALID_SOCKET) {

        //
        //  Let the worker do the dirty work.
        //

        va_start( ArgPtr, pszFormat );

        serr = vSockReply( pUserData,
                          pUserData->QueryControlSocket(),
                          ReplyCode,
                          ' ',
                          pszFormat,
                          ArgPtr );

        va_end( ArgPtr );
    } else {

        serr = WSAECONNABORTED;
    }


    return serr;

}   // ReplyToUser()


// Private functions

/*******************************************************************

    NAME:       vSockPrintf

    SYNOPSIS:   Worker function for printf-to-socket functions.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

                pszFormat - The format string.

                args - Variable number of arguments.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     17-Mar-1993 Created.

********************************************************************/
SOCKERR
vSockPrintf(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    LPCSTR      pszFormat,
    va_list     args
    )
{
    INT     cchBuffer = 0;
    INT     buffMaxLen;
    SOCKERR serr = 0;
    CHAR    szBuffer[MAX_REPLY_LENGTH];

    DBG_ASSERT( pszFormat != NULL );

    //
    //  Render the format into our local buffer.
    //


    DBG_ASSERT( MAX_REPLY_LENGTH > 3);
    buffMaxLen = MAX_REPLY_LENGTH - 3;
    cchBuffer = _vsnprintf( szBuffer,
                           buffMaxLen,
                           pszFormat, args );
    //
    // The string length is long, we get back -1.
    //   so we get the string length for partial data.
    //

    if ( cchBuffer == -1 ) {

        //
        // terminate the string properly,
        //   since _vsnprintf() does not terminate properly on failure.
        //
        cchBuffer = buffMaxLen;
        szBuffer[ buffMaxLen] = '\0';
    }

    IF_DEBUG( SOCKETS ) {
        DBGPRINTF(( DBG_CONTEXT, "sending '%s'\n", szBuffer ));
    }

    //
    // Escape all telnet IAC bytes with a second IAC
    //
    vTelnetEscapeIAC( szBuffer, &cchBuffer, MAX_REPLY_LENGTH - 3 );

    strcpy( szBuffer + cchBuffer, "\r\n" );
    cchBuffer += 2;

    //
    //  Blast it out to the client.
    //

    serr = SockSend( pUserData, sock, szBuffer, cchBuffer );
    return serr;

} // vSockPrintf



/*******************************************************************

    NAME:       vSockReply

    SYNOPSIS:   Worker function for reply functions.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

                ReplyCode - A three digit reply code from RFC 959.

                chSeparator - Should be either ' ' (normal reply) or
                    '-' (first line of multi-line reply).

                pszFormat - The format string.

                args - Variable number of arguments.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.

    HISTORY:
        KeithMo     17-Mar-1993 Created.

********************************************************************/
SOCKERR
vSockReply(
    LPUSER_DATA pUserData,
    SOCKET      sock,
    UINT        ReplyCode,
    CHAR        chSeparator,
    LPCSTR      pszFormat,
    va_list     args
    )
{
    INT     cchBuffer;
    INT     cchBuffer1;
    INT     buffMaxLen;
    SOCKERR serr = 0;
    CHAR    szBuffer[MAX_REPLY_LENGTH];

    DBG_ASSERT( ( ReplyCode >= 100 ) && ( ReplyCode < 600 ) );

    //
    //  Render the format into our local buffer.
    //

    cchBuffer = wsprintfA( szBuffer,
                          "%u%c",
                          ReplyCode,
                          chSeparator );

    DBG_ASSERT( MAX_REPLY_LENGTH > cchBuffer + 3);
    buffMaxLen = MAX_REPLY_LENGTH - cchBuffer - 3;
    cchBuffer1 = _vsnprintf( szBuffer + cchBuffer,
                            buffMaxLen,
                            pszFormat, args );
    //
    // The string length is long, we get back -1.
    //   so we get the string length for partial data.
    //

    if ( cchBuffer1 == -1 ) {

        //
        // terminate the string properly,
        //   since _vsnprintf() does not terminate properly on failure.
        //
        cchBuffer = buffMaxLen;
        szBuffer[ buffMaxLen] = '\0';
    } else {

        cchBuffer += cchBuffer1;
    }

    IF_DEBUG( SOCKETS ) {
        DBGPRINTF(( DBG_CONTEXT, "sending '%s'\n",szBuffer ));
    }

    //
    // Escape all telnet IAC bytes with a second IAC
    //
    vTelnetEscapeIAC( szBuffer, &cchBuffer, MAX_REPLY_LENGTH - 3 );

    strcpy( szBuffer + cchBuffer, "\r\n" );
    cchBuffer += 2;

    //
    //  Blast it out to the client.
    //

    serr = SockSend( pUserData, sock, szBuffer, cchBuffer );

    return serr;

}   // vSockReply


DWORD
FtpFormatResponseMessage( IN UINT     uiReplyCode,
                          IN LPCTSTR  pszReplyMsg,
                          OUT LPTSTR  pszReplyBuffer,
                          IN DWORD    cchReplyBuffer)
/*++
  This function formats the message to be sent to the client,
    given the reply code and the message to be sent.

  The formatting function takes care of the reply buffer length
    and ensures the safe write of data. If the reply buffer is
    not sufficient to hold the entire message, the reply msg is trunctaed.

  Arguments:
    uiReplyCode   reply code to be used.
    pszReplyMsg   pointer to string containing the reply message
    pszReplyBuffer pointer to character buffer where the reply message
                   can be sent.
    cchReplyBuffer character count for the length of reply buffer.

  Returns:
    length of the data written to the reply buffer.

--*/
{
    DWORD len;

    DBG_ASSERT( pszReplyMsg != NULL && pszReplyBuffer != NULL);

    len = lstrlen( pszReplyMsg) + 10;   // 10 chars are required for aux info.

    if ( len >= cchReplyBuffer) {

        // truncate the message since length is too high.

        len = wsprintf( pszReplyBuffer, TEXT("%u \r\n"),
                        uiReplyCode);

        DBG_ASSERT( len >= 3);  // length greater than formatting string
        DBG_ASSERT( len < cchReplyBuffer);

        lstrcpyn( pszReplyBuffer + len, pszReplyMsg, cchReplyBuffer - len);

        len = lstrlen( pszReplyBuffer);
        DBG_ASSERT( len < cchReplyBuffer);

    } else {

        len = wsprintf( pszReplyBuffer, TEXT("%u %s\r\n"),
                        uiReplyCode,
                        pszReplyMsg);
        DBG_ASSERT( len >= 4);
        DBG_ASSERT( len <= cchReplyBuffer);
    }

    return (len);
} // FtpFormatResponseMessage()


/*******************************************************************

    NAME:       vTelnetEscapeIAC

    SYNOPSIS:   replace (in place) all 0xFF bytes in a buffer with 0xFF 0xFF.
                This is the TELNET escape sequence for an IAC value data byte.

    ENTRY:      pszBuffer - data buffer.

                pcchBufChars - on entry, current number of chars in buffer.
                               on return, number of chars in buffer.

                cchMaxLen - maximum characters in the output buffer

    RETURNS:    TRUE - success, FALSE - overflow.

    HISTORY:
        RobSol     25-April-2001 Created.

********************************************************************/
BOOL
vTelnetEscapeIAC( IN OUT PCHAR pszBuffer,
                  IN OUT PINT  pcchBufChars,
                  IN     INT   cchMaxLen)
{

#   define CHAR_IAC ((CHAR)(-1))

    PCHAR  pszFirstIAC;
    PCHAR  pSrc, pDst;
    BOOL   fReturn = TRUE;
    CHAR   szBuf[MAX_REPLY_LENGTH];
    INT    cCharsInDst;

    DBG_ASSERT( pszBuffer );
    DBG_ASSERT( pcchBufChars );
    DBG_ASSERT( cchMaxLen <= MAX_REPLY_LENGTH );

    if ((pszFirstIAC = strchr( pszBuffer, CHAR_IAC)) == NULL) {

        //
        // no IAC - return.
        //

        return TRUE;
    }

    //
    // we'll expand the string into a temp buffer, then copy back.
    //

    pSrc = pszFirstIAC;
    pDst = szBuf;
    cCharsInDst = DIFF(pszFirstIAC - pszBuffer);

    do {
       if (*pSrc == CHAR_IAC) {

          //
          // this is a char to escape
          //

          if ((cCharsInDst + 1) < cchMaxLen) {

              //
              // we have space to escape the char, so do it.
              //

              cCharsInDst++;
              *pDst++ = CHAR_IAC;
          } else {

              //
              // overflow.
              //

              fReturn = FALSE;
              break;
          }
       }

       *pDst++ = *pSrc++;

    } while ((++cCharsInDst < cchMaxLen) && (*pSrc != '\0'));

    //
    // copy the expanded data back into the input buffer and terminate the string
    //

    memcpy( pszFirstIAC, szBuf, pDst - szBuf);
    pszBuffer[ cCharsInDst ] = '\0';

    *pcchBufChars = cCharsInDst;

    return fReturn;
}

/************************ End of File ******************************/