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

/*
    sockutil.c

    This module contains utility routines for managing & manipulating
    sockets.


    FILE HISTORY:
        KeithMo     07-Mar-1993 Created.

*/


#include "ftpdp.h"
#pragma hdrstop


//
//  Private constants.
//

#define DEFAULT_BUFFER_SIZE     4096    // bytes
#define CLEANUP_POLL_INTERVAL   2000    // milliseconds
#define CLEANUP_RETRY_COUNT     4       // iterations
#define FIRST_TELNET_COMMAND    240


//
//  Private globals.
//

#if DBG
//
//  Force socket API statistics in DEBUG builds.
//
#define KEEP_SOCK_STATS
#endif  // DBG

#ifdef KEEP_SOCK_STATS

typedef struct _SOCK_STATS
{
    LONG socket_Ok;
    LONG socket_Fail;
    LONG accept_Ok;
    LONG accept_Fail;
    LONG closesocket_Ok;
    LONG closesocket_Fail;

    LONG ActiveSockets;

} SOCK_STATS;

SOCK_STATS SockStats;

#define SOCKET_OK()     InterlockedIncrement( &SockStats.socket_Ok ); \
                        InterlockedIncrement( &SockStats.ActiveSockets )

#define SOCKET_FAIL()   InterlockedIncrement( &SockStats.socket_Fail )

#define ACCEPT_OK()     InterlockedIncrement( &SockStats.accept_Ok ); \
                        InterlockedIncrement( &SockStats.ActiveSockets )

#define ACCEPT_FAIL()   InterlockedIncrement( &SockStats.accept_Fail )

#define CLOSE_OK()      InterlockedIncrement( &SockStats.closesocket_Ok ); \
                        InterlockedDecrement( &SockStats.ActiveSockets )

#define CLOSE_FAIL()    InterlockedIncrement( &SockStats.closesocket_Fail )

#else   // !KEEP_SOCK_STATS

#define SOCKET_OK()
#define SOCKET_FAIL()
#define ACCEPT_OK()
#define ACCEPT_FAIL()
#define CLOSE_OK()
#define CLOSE_FAIL()

#endif  // KEEP_SOCK_STATS


//
//  Private prototypes.
//

SOCKERR
vSockPrintf(
    SOCKET    sock,
    CHAR    * pszFormat,
    va_list   args
    );

SOCKERR
vSockReply(
    SOCKET    sock,
    UINT      ReplyCode,
    CHAR      chSeparator,
    CHAR    * pszFormat,
    va_list   args
    );

SOCKERR
WaitForSocketRead(
    SOCKET   sockRead,
    SOCKET   sockExcept,
    BOOL   * pfExcept
    );

SOCKERR
WaitForSocketWrite(
    SOCKET   sockWrite,
    SOCKET   sockExcept,
    BOOL   * pfExcept
    );

SOCKERR
WaitForSocketWorker(
    SOCKET   sockRead,
    SOCKET   sockWrite,
    SOCKET   sockExcept,
    BOOL   * pfRead,
    BOOL   * pfWrite,
    BOOL   * pfExcept
    );

SOCKERR
DiscardOutOfBandData(
    USER_DATA * pUserData,
    SOCKET      sock
    );


//
//  Public functions.
//

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

    NAME:       InitializeSockets

    SYNOPSIS:   Initializes socket access.  Among other things, this
                routine is responsible for connecting to WinSock,
                and creating the connection thread.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    NOTES:      This routine may only be called by a single thread
                of execution; it is not necessarily multi-thread safe.

    HISTORY:
        KeithMo     07-Mar-1993 Created.
        KeithMo     07-Sep-1993 Get FTP data port via getservbyname().

********************************************************************/
APIERR
InitializeSockets(
    VOID
    )
{
    WSADATA   wsadata;
    SOCKERR   serr;
    SERVENT * pserv;
    INT       cbOpt;
    HANDLE    hConnectThread;
    DWORD     idConnectThread;
    CHAR      szHost[MAXGETHOSTSTRUCT];

    IF_DEBUG( SOCKETS )
    {
        FTPD_PRINT(( "initializing sockets\n" ));
    }

    //
    //  Connect to WinSock.
    //

    serr = WSAStartup( MAKEWORD( 1, 1 ), &wsadata );

    if( serr != 0 )
    {
        FtpdLogEvent( FTPD_EVENT_CANNOT_INITIALIZE_WINSOCK,
                      0,
                      NULL,
                      serr );

        FTPD_PRINT(( "cannot initialize WinSock, socket error %d\n",
                     serr ));

        svcStatus.dwServiceSpecificExitCode = (DWORD)serr;

        return ERROR_SERVICE_SPECIFIC_ERROR;
    }

    //
    //  Determine the local host name.
    //

    pszHostName = NULL;

    if( gethostname( szHost, sizeof(szHost) ) >= 0 )
    {
        pszHostName = (CHAR *)FTPD_ALLOC( strlen( szHost ) + 1 );
    }

    if( pszHostName == NULL )
    {
        APIERR err = GetLastError();

        FtpdLogEvent( FTPD_EVENT_OUT_OF_MEMORY,
                      0,
                      NULL,
                      err );

        FTPD_PRINT(( "cannot allocate memory for host name, error %lu\n",
                     err ));

        return err;
    }

    strcpy( pszHostName, szHost );

    //
    //  Determine the port number for FTP Connections.
    //

    pserv = getservbyname( "ftp", "tcp" );

    if( pserv == NULL )
    {
        portFtpConnect = (PORT)htons( IPPORT_FTP );

        FTPD_PRINT(( "cannot locate ftp connect port, assuming port %u\n",
                     ntohs( portFtpConnect ) ));
    }
    else
    {
        portFtpConnect = (PORT)pserv->s_port;
    }

    //
    //  Determine the port number for FTP Data.
    //

    pserv = getservbyname( "ftp-data", "tcp" );

    if( pserv == NULL )
    {
        portFtpData = (PORT)htons( (u_short)( ntohs( portFtpConnect ) - 1 ) );

        FTPD_PRINT(( "cannot locate ftp data port, assuming port %u\n",
                     ntohs( portFtpData ) ));
    }
    else
    {
        portFtpData = (PORT)pserv->s_port;
    }

    //
    //  Create connection socket.  We do this now so we can
    //  abort this installation if we fail to create the socket.
    //

    serr = CreateFtpdSocket( &sConnect,
                             htonl( INADDR_ANY ),
                             portFtpConnect );

    if( serr != 0 )
    {
        FtpdLogEvent( FTPD_EVENT_CANNOT_CREATE_CONNECTION_SOCKET,
                      0,
                      NULL,
                      serr );

        FTPD_PRINT(( "cannot create connection socket, socket error %d\n",
                     serr ));

        svcStatus.dwServiceSpecificExitCode = (DWORD)serr;

        return ERROR_SERVICE_SPECIFIC_ERROR;
    }

    //
    //  Determine the sizes of the send & receive buffers.
    //
    //  BUGBUG:  This assumes that ALL sockets use the same
    //           size send & receive buffers.  Verify this
    //           assumption with DavidTr.
    //

    cbOpt = sizeof(cbReceiveBuffer);

    if( getsockopt( sConnect,
                    SOL_SOCKET,
                    SO_RCVBUF,
                    (CHAR *)&cbReceiveBuffer,
                    &cbOpt ) != 0 )
    {
        FTPD_PRINT(( "cannot get receive buffer size, using %u\n",
                     DEFAULT_BUFFER_SIZE ));

        cbReceiveBuffer = DEFAULT_BUFFER_SIZE;
    }

    cbOpt = sizeof(cbSendBuffer);

    if( getsockopt( sConnect,
                    SOL_SOCKET,
                    SO_SNDBUF,
                    (CHAR *)&cbSendBuffer,
                    &cbOpt ) != 0 )
    {
        FTPD_PRINT(( "cannot get send buffer size, using %u\n",
                     DEFAULT_BUFFER_SIZE ));

        cbSendBuffer = DEFAULT_BUFFER_SIZE;
    }

    //
    //  Create the connection thread.
    //

    hConnectThread = CreateThread( NULL,
                                   0,
                                   &ConnectionThread,
                                   NULL,
                                   0,
                                   &idConnectThread );

    if( hConnectThread == NULL )
    {
        APIERR err = GetLastError();

        FtpdLogEvent( FTPD_EVENT_CANNOT_CREATE_CONNECTION_THREAD,
                      0,
                      NULL,
                      err );

        FTPD_PRINT(( "cannot create connection thread, error %d\n",
                     err ));

        return err;
    }
    else
    {
        CloseHandle( hConnectThread );
    }

    //
    //  Success!
    //

    IF_DEBUG( SOCKETS )
    {
        FTPD_PRINT(( "sockets initialized\n" ));
    }

    return NO_ERROR;

}   // InitializeSockets

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

    NAME:       TerminateSockets

    SYNOPSIS:   Terminate socket access.  This routine is responsible
                for closing the connection socket(s) and detaching
                from WinSock.

    NOTES:      This routine may only be called by a single thread
                of execution; it is not necessarily multi-thread safe.

    HISTORY:
        KeithMo     07-Mar-1993 Created.

********************************************************************/
VOID
TerminateSockets(
    VOID
    )
{
    SOCKERR serr;
    DWORD   i;

    IF_DEBUG( SOCKETS )
    {
        FTPD_PRINT(( "terminating sockets\n" ));
    }

    //
    //  Close the connection socket.
    //

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

    //
    //  Blow away any connected users.
    //

    DisconnectAllUsers();

    //
    //  Wait for the users to die.
    //

    for( i = 0 ; ( i < CLEANUP_RETRY_COUNT ) && ( cConnectedUsers > 0 ) ; i++ )
    {
        Sleep( CLEANUP_POLL_INTERVAL );
    }

    //
    //  Free the local host name buffer.
    //

    if( pszHostName != NULL )
    {
        FTPD_FREE( pszHostName );
        pszHostName = NULL;
    }

    //
    //  Disconnect from WinSock.
    //

    serr = WSACleanup();

    if( serr != 0 )
    {
        FTPD_PRINT(( "cannot terminate WinSock, error %d\n",
                     serr ));
    }

    IF_DEBUG( SOCKETS )
    {
        FTPD_PRINT(( "sockets terminated\n" ));
    }

}   // TerminateSockets

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

    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...
    //

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

    //
    //  Create the socket.
    //

    sNew = socket( PF_INET, SOCK_STREAM, 0 );
    serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;

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

        SOCKET_OK();

        //
        //  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();
        }
    }
    else
    {
        SOCKET_FAIL();
    }

    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.
        //

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

        IF_DEBUG( SOCKETS )
        {
            FTPD_PRINT(( "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.
        //

        FTPD_PRINT(( "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.

    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
    )
{
    SOCKET  sNew = INVALID_SOCKET;
    SOCKERR serr = 0;

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

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

    //
    //  Create the connection socket.
    //

    sNew = socket( PF_INET, SOCK_STREAM, 0 );
    serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;

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

        SOCKET_OK();

        //
        //  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();
        }
    }
    else
    {
        SOCKET_FAIL();
    }

    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, nListenBacklog ) != 0 )
        {
            serr = WSAGetLastError();
        }
    }

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

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

        IF_DEBUG( SOCKETS )
        {
            FTPD_PRINT(( "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.
        //

        FTPD_PRINT(( "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( serr == 0 )
    {
        CLOSE_OK();

        IF_DEBUG( SOCKETS )
        {
            FTPD_PRINT(( "closed socket %d\n",
                         sock ));
        }
    }
    else
    {
        CLOSE_FAIL();

        FTPD_PRINT(( "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( serr == 0 )
    {
        CLOSE_OK();

        IF_DEBUG( SOCKETS )
        {
            FTPD_PRINT(( "reset socket %d\n",
                         sock ));
        }
    }
    else
    {
        CLOSE_FAIL();

        FTPD_PRINT(( "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
    )
{
    SOCKERR serr    = 0;
    SOCKET  sockNew = INVALID_SOCKET;

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

    if( fEnforceTimeout )
    {
        //
        //  Timeouts are to be enforced, so wait for a connection
        //  to the socket.
        //

        serr = WaitForSocketRead( sockListen, INVALID_SOCKET, NULL );
    }

    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();

            ACCEPT_FAIL();
        }
        else
        {
            ACCEPT_OK();
        }
    }

    //
    //  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:      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(
    SOCKET   sock,
    CHAR   * pBuffer,
    DWORD    cbBuffer
    )
{
    SOCKERR     serr = 0;
    INT         cbSent;
    SOCKET      sControl;
    BOOL        fExcept     = FALSE;
    DWORD       dwBytesSent = 0;
    USER_DATA * pUserData;

    FTPD_ASSERT( pBuffer != NULL );

    pUserData = UserDataPtr;
    sControl  = pUserData ? pUserData->sControl : INVALID_SOCKET;

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

    while( cbBuffer > 0 )
    {
        //
        //  Wait for the socket to become writeable.
        //

        serr = WaitForSocketWrite( sock,
                                   sControl,
                                   &fExcept );

        if( fExcept && ( serr == 0 ) )
        {
            //
            //  Out of band data has arrived.  Discard it & abort.
            //

            serr = DiscardOutOfBandData( pUserData, sControl );

            if( pUserData && TEST_UF( pUserData, TRANSFER ) )
            {
                SET_UF( pUserData, OOB_DATA );
                break;
            }
        }

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

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

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

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

                IF_DEBUG( SEND )
                {
                    if( pUserData && TEST_UF( pUserData, TRANSFER ) )
                    {
                        FTPD_PRINT(( "send %d bytes @%08lX to socket %d\n",
                                     cbSent,
                                     (ULONG)pBuffer,
                                     sock ));
                    }
                }
            }
        }

        if( serr != 0 )
        {
            break;
        }

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

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

    UPDATE_LARGE_COUNTER( TotalBytesSent, 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(
    USER_DATA * pUserData,
    SOCKET      sock,
    CHAR      * pBuffer,
    DWORD       cbBuffer,
    DWORD     * pbReceived
    )
{
    SOCKERR     serr = 0;
    DWORD       cbTotal = 0;
    INT         cbReceived;
    SOCKET      sControl;
    BOOL        fExcept     = FALSE;
    DWORD       dwBytesRecv = 0;

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

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

    sControl = pUserData ? pUserData->sControl : INVALID_SOCKET;

    while( cbBuffer > 0 )
    {
        //
        //  Wait for the socket to become readable.
        //

        serr = WaitForSocketRead( sock,
                                  sControl,
                                  &fExcept );

        if( fExcept && ( serr == 0 ) )
        {
            //
            //  Out of band data has arrived.  Discard it & abort.
            //

            serr = DiscardOutOfBandData( pUserData, sControl );

            if( pUserData && TEST_UF( pUserData, TRANSFER ) )
            {
                SET_UF( pUserData, OOB_DATA );
                break;
            }
        }

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

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

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

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

                IF_DEBUG( RECV )
                {
                    if( pUserData && TEST_UF( pUserData, TRANSFER ) )
                    {
                        FTPD_PRINT(( "received %d bytes @%08lX from socket %d\n",
                                     cbReceived,
                                     (ULONG)pBuffer,
                                     sock ));
                    }
                }
            }
        }

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

            break;
        }

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

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

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

    UPDATE_LARGE_COUNTER( TotalBytesReceived, dwBytesRecv );

    return serr;

}   // SockRecv

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

    NAME:       SockPrintf2

    SYNOPSIS:   Send a formatted string to a specific socket.

    ENTRY:      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
_CRTAPI2
SockPrintf2(
    SOCKET   sock,
    CHAR   * pszFormat,
    ...
    )
{
    va_list ArgPtr;
    SOCKERR serr;

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

    va_start( ArgPtr, pszFormat );

    serr = vSockPrintf( sock,
                        pszFormat,
                        ArgPtr );

    va_end( ArgPtr );

    return serr;

}   // SockPrintf2

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

    NAME:       SockReply2

    SYNOPSIS:   Send an FTP reply to a specific socket.

    ENTRY:      sock - The target socket.

                ReplyCode - One of the REPLY_* manifests.

                pszFormat - A printf-style format string.

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

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

    HISTORY:
        KeithMo     09-Mar-1993 Created.

********************************************************************/
SOCKERR
_CRTAPI2
SockReply2(
    SOCKET   sock,
    UINT     ReplyCode,
    CHAR   * pszFormat,
    ...
    )
{
    va_list ArgPtr;
    SOCKERR serr;

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

    va_start( ArgPtr, pszFormat );

    serr = vSockReply( sock,
                       ReplyCode,
                       ' ',
                       pszFormat,
                       ArgPtr );

    va_end( ArgPtr );

    return serr;

}   // SockReply2

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

    NAME:       SockReplyFirst2

    SYNOPSIS:   Send the first reply of a multi-line FTP replay
                to a specific socket.

    ENTRY:      sock - The target socket.

                ReplyCode - One of the REPLY_* manifests.

                pszFormat - A printf-style format string.

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

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

    HISTORY:
        KeithMo     03-Jun-1993 Created.

********************************************************************/
SOCKERR
_CRTAPI2
SockReplyFirst2(
    SOCKET   sock,
    UINT     ReplyCode,
    CHAR   * pszFormat,
    ...
    )
{
    va_list ArgPtr;
    SOCKERR serr;

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

    va_start( ArgPtr, pszFormat );

    serr = vSockReply( sock,
                       ReplyCode,
                       '-',
                       pszFormat,
                       ArgPtr );

    va_end( ArgPtr );

    return serr;

}   // SockReplyFirst2

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

    NAME:       SockReadLine

    SYNOPSIS:   Reads a '\n' terminate line from the specified user's
                control socket.

    ENTRY:      pUserData - The user initiating the request.

                pszBuffer - Buffer to store line into.

                cchBuffer - Maximum size of buffer.

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

    HISTORY:
        KeithMo     10-Mar-1993 Created.

********************************************************************/
SOCKERR
SockReadLine(
    USER_DATA * pUserData,
    CHAR      * pszBuffer,
    INT         cchBuffer
    )
{
    SOCKERR   serr = 0;
    SOCKET    sControl;
#if DBG
    CHAR    * pszTmp = pszBuffer;
#endif  // DBG

    FTPD_ASSERT( pUserData != NULL );
    sControl = pUserData->sControl;

    while( cchBuffer > 0 )
    {
        CHAR  ch;
        DWORD cbRead;

        //
        //  Read a byte from the socket.  This will return
        //  WSAETIMEDOUT if the connection is idle too long.
        //

        serr = SockRecv( pUserData, sControl, &ch, sizeof(ch), &cbRead );

        if( ( cbRead == 0 ) && ( serr == 0 ) )
        {
            //
            //  End of file or socket closed.
            //

            serr = WSAENOTSOCK;
        }

        if( serr != 0 )
        {
            //
            //  Socket error.
            //

            break;
        }

        //
        //  Skip TELNET commands.
        //

        if( (UINT)ch >= (UINT)FIRST_TELNET_COMMAND )
        {
            continue;
        }

        //
        //  Filter out CR & LF.
        //

        if( ( ch != '\r' ) && ( ch != '\n' ) )
        {
            *pszBuffer++ = ch;
            cchBuffer--;
        }

        //
        //  Terminate line at LF.
        //

        if( ch == '\n' )
        {
            break;
        }
    }

    //
    //  Ensure line is properly terminated.
    //

    *pszBuffer = '\0';

#if DBG
    IF_DEBUG( SOCKETS )
    {
        if( serr == 0 )
        {
            if( !_strnicmp( pszTmp, "pass", 4 ) )
            {
                pszTmp = "PASS {secret...}";
            }

            FTPD_PRINT(( "received '%s'\n",
                         pszTmp ));
        }
    }
#endif  // DBG

    return serr;

}   // SockReadLine

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

    NAME:       SendMultilineMessage2

    SYNOPSIS:   Send a multiline message to a specific socket.

    ENTRY:      sock - The target socket.

                nReply - The reply code to use for the first line
                    of the multi-line message.

                pszzMessage - The message to send.  Each line should be
                    terminated by a NULL, and the entire message should
                    be terminated by a double NULL.

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

    HISTORY:
        KeithMo     03-Jun-1993 Created.

********************************************************************/
SOCKERR
SendMultilineMessage2(
    SOCKET   sock,
    UINT     nReply,
    CHAR   * pszzMessage
    )
{
    SOCKERR serr = 0;

    if( pszzMessage != NULL )
    {
        CHAR * pszNext = pszzMessage;
        BOOL   fFirst  = TRUE;

        while( ( serr == 0 ) && ( *pszNext != '\0' ) )
        {
            if( fFirst )
            {
                serr = SockReplyFirst2( sock,
                                        nReply,
                                        "%s",
                                        pszNext );

                fFirst = FALSE;
            }
            else
            {
                serr = SockPrintf2( sock,
                                    " %s",
                                    pszNext );
            }

            pszNext += strlen(pszNext) + 1;
        }
    }

    return serr;

}   // SendMultilineMessage2


//
//  Private functions.
//

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

    NAME:       vSockPrintf

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

    ENTRY:      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(
    SOCKET    sock,
    CHAR    * pszFormat,
    va_list   args
    )
{
    INT     cchBuffer;
    INT     bufLength;
    SOCKERR serr = 0;
    CHAR    szBuffer[MAX_REPLY_LENGTH];

    FTPD_ASSERT( pszFormat != NULL );

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

    bufLength = sizeof(szBuffer) - 3;   // -3 for "\r\n\0"

    cchBuffer = _vsnprintf( szBuffer,
                            bufLength,
                            pszFormat,
                            args );

    if( cchBuffer == -1 )
    {
        cchBuffer = bufLength;
        szBuffer[cchBuffer] = '\0';
    }

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

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

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

    serr = SockSend( sock, szBuffer, cchBuffer );

    return serr;

}   // vSockPrintf

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

    NAME:       vSockReply

    SYNOPSIS:   Worker function for reply functions.

    ENTRY:      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(
    SOCKET    sock,
    UINT      ReplyCode,
    CHAR      chSeparator,
    CHAR    * pszFormat,
    va_list   args
    )
{
    INT     cchBuffer;
    INT     cch2;
    INT     bufLength;
    SOCKERR serr = 0;
    CHAR    szBuffer[MAX_REPLY_LENGTH];

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

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

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

    bufLength = sizeof(szBuffer) - cchBuffer - 3;   // -3 for "\r\n\0"

    cch2 = _vsnprintf( szBuffer + cchBuffer,
                       bufLength,
                       pszFormat,
                       args );

    if( cch2 == -1 )
    {
        cchBuffer += bufLength;
        szBuffer[cchBuffer] = '\0';
    }
    else
    {
        cchBuffer += cch2;
    }

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

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

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

    serr = SockSend( sock, szBuffer, cchBuffer );

    return serr;

}   // vSockReply

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

    NAME:       WaitForSocketRead

    SYNOPSIS:   Blocks until either a) there is data to read from
                    the specified socket, b) there is an error on
                    the specified socket, or c) timeout.

    ENTRY:      sockRead - The socket to check for readability.

                sockExcept - The socket to check for exceptions.
                    May be INVALID_SOCKET if not interested in
                    exceptions.

                pfExcept - Will receive TRUE if an exception occurred.
                    May be NULL if sockExcept == INVALID_SOCKET.


    RETURNS:    SOCKERR - 0 if successful, !0 if not.  Will return
                    WSAETIMEDOUT if the timeout period expired.

    HISTORY:
        KeithMo     17-Mar-1993 Created.

********************************************************************/
SOCKERR
WaitForSocketRead(
    SOCKET   sockRead,
    SOCKET   sockExcept,
    BOOL   * pfExcept
    )
{
    SOCKERR serr;
    BOOL    fRead;

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

    serr = WaitForSocketWorker( sockRead,
                                INVALID_SOCKET,
                                sockExcept,
                                &fRead,
                                NULL,
                                pfExcept );

    if( serr == 0 )
    {
        FTPD_ASSERT( fRead || *pfExcept );
    }

    return serr;

}   // WaitForSocketRead

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

    NAME:       WaitForSocketWrite

    SYNOPSIS:   Blocks until either a) data can be written to the
                    specified socket, b) there is an error on the
                    specified socket, or c) timeout.

    ENTRY:      sockWrite - The socket to check for writeability.

                sockExcept - The socket to check for exceptions.

                pfExcept - Will receive TRUE if an exception occurred.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.  Will return
                    WSAETIMEDOUT if the timeout period expired.

    HISTORY:
        KeithMo     17-Mar-1993 Created.

********************************************************************/
SOCKERR
WaitForSocketWrite(
    SOCKET   sockWrite,
    SOCKET   sockExcept,
    BOOL   * pfExcept
    )
{
    SOCKERR serr;
    BOOL    fWrite;

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

    serr = WaitForSocketWorker( INVALID_SOCKET,
                                sockWrite,
                                sockExcept,
                                NULL,
                                &fWrite,
                                pfExcept );

    if( serr == 0 )
    {
        FTPD_ASSERT( fWrite || *pfExcept );
    }

    return serr;

}   // WaitForSocketWrite

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

    NAME:       WaitForSocketWorker

    SYNOPSIS:   Worker for WaitForSocketRead and WaitForSocketWrite
                functions.  Will block until any of the specified
                sockets acheive the specified states, a timeout, or
                an error occurs.

    ENTRY:      sockRead - The socket to check for readability.

                sockWrite - The socket to check for writeability.

                sockExcept - The socket to check for exceptions.

                pfRead - Will receive TRUE if sockRead is readable.

                pfWrite - Will receive TRUE if sockWrite is writeable.

                pfExcept - Will receive TRUE if an exceptional condition
                    occurred on sockExcept.

    RETURNS:    SOCKERR - 0 if successful, !0 if not.  Will return
                    WSAETIMEDOUT if the timeout period expired.

    NOTES:      Any (but not all) sockets may be INVALID_SOCKET.  For
                each socket that is INVALID_SOCKET, the corresponding
                pf* parameter may be NULL.

    HISTORY:
        KeithMo     06-May-1993 Created.

********************************************************************/
SOCKERR
WaitForSocketWorker(
    SOCKET   sockRead,
    SOCKET   sockWrite,
    SOCKET   sockExcept,
    BOOL   * pfRead,
    BOOL   * pfWrite,
    BOOL   * pfExcept
    )
{
    SOCKERR   serr = 0;
    TIMEVAL   timeout;
    TIMEVAL * ptimeout;
    fd_set    fdsRead;
    fd_set    fdsWrite;
    fd_set    fdsExcept;
    INT       res;

    //
    //  Ensure we got valid parameters.
    //

    if( ( sockRead   == INVALID_SOCKET ) &&
        ( sockWrite  == INVALID_SOCKET ) &&
        ( sockExcept == INVALID_SOCKET ) )
    {
        return WSAENOTSOCK;
    }

    if( nConnectionTimeout == 0 )
    {
        //
        //  If nConnectionTimeout == 0, then we have no timeout.
        //  So, we block and wait for the specified conditions.
        //

        ptimeout = NULL;
    }
    else
    {
        //
        //  nConnectionTimeout > 0, so setup the timeout structure.
        //

        timeout.tv_sec  = (LONG)nConnectionTimeout;
        timeout.tv_usec = 0;

        ptimeout = &timeout;
    }

    for( ; ; )
    {
        //
        //  Setup our socket sets.
        //

        FD_ZERO( &fdsRead );
        FD_ZERO( &fdsWrite );
        FD_ZERO( &fdsExcept );

        if( sockRead != INVALID_SOCKET )
        {
            FD_SET( sockRead, &fdsRead );
            FTPD_ASSERT( pfRead != NULL );
            *pfRead = FALSE;
        }

        if( sockWrite != INVALID_SOCKET )
        {
            FD_SET( sockWrite, &fdsWrite );
            FTPD_ASSERT( pfWrite != NULL );
            *pfWrite = FALSE;
        }

        if( sockExcept != INVALID_SOCKET )
        {
            FD_SET( sockExcept, &fdsExcept );
            FTPD_ASSERT( pfExcept != NULL );
            *pfExcept = FALSE;
        }

        //
        //  Wait for one of the conditions to be met.
        //

        res = select( 0, &fdsRead, &fdsWrite, &fdsExcept, ptimeout );

        if( res == 0 )
        {
            //
            //  Timeout.
            //

            serr = WSAETIMEDOUT;
            break;
        }
        else
        if( res == SOCKET_ERROR )
        {
            //
            //  Bad news.
            //

            serr = WSAGetLastError();
            break;
        }
        else
        {
            BOOL fSomethingWasSet = FALSE;

            if( pfRead != NULL )
            {
                *pfRead   = FD_ISSET( sockRead,   &fdsRead   );
                fSomethingWasSet = TRUE;
            }

            if( pfWrite != NULL )
            {
                *pfWrite  = FD_ISSET( sockWrite,  &fdsWrite  );
                fSomethingWasSet = TRUE;
            }

            if( pfExcept != NULL )
            {
                *pfExcept = FD_ISSET( sockExcept, &fdsExcept );
                fSomethingWasSet = TRUE;
            }

            if( fSomethingWasSet )
            {
                //
                //  Success.
                //

                serr = 0;
                break;
            }
            else
            {
                //
                //  select() returned with neither a timeout, nor
                //  an error, nor any bits set.  This feels bad...
                //

                FTPD_ASSERT( FALSE );
                continue;
            }
        }
    }

    return serr;

}   // WaitForSocketWorker

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

    NAME:       DiscardOutOfBandData

    SYNOPSIS:   Reads & discards any out of band data pending on
                the specified socket.

    ENTRY:      pUserData - The user initiating the request.

                sock - The target socket.

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

    HISTORY:
        KeithMo     06-May-1993 Created.

********************************************************************/
SOCKERR
DiscardOutOfBandData(
    USER_DATA * pUserData,
    SOCKET      sock
    )
{
    SOCKERR     serr = 0;
    CHAR      * pszAbort = "ABOR\r\n";
    CHAR      * pszNext  = pszAbort;
#if DBG
    DWORD       cbDiscarded = 0;
#endif  // DBG

    for( ; ; )
    {
        TIMEVAL tv;
        FD_SET  fds;
        INT     res;
        CHAR    chTrash;

        //
        //  Setup for select() call.  Setting the timeout value
        //  to zero will "poll" the socket for any outstanding
        //  conditions.
        //

        FD_ZERO( &fds );
        FD_SET( sock, &fds );

        tv.tv_sec  = 0;
        tv.tv_usec = 0;

        //
        //  Check for exceptional conditions.
        //

        res = select( 0, NULL, NULL, &fds, &tv );

        if( res == 0 )
        {
            //
            //  No exceptions, ergo no OOB data pending.
            //

            serr = 0;
            break;
        }

        if( res == SOCKET_ERROR )
        {
            //
            //  Bad news.
            //

            serr = WSAGetLastError();
            break;
        }

        //
        //  Read the out of band data.
        //

        res = recv( sock, &chTrash, sizeof(chTrash), MSG_OOB );

        if( res == 0 )
        {
            //
            //  Connection closed.
            //

            serr = 0;
            break;
        }

        if( res == SOCKET_ERROR )
        {
            //
            //  Bad news.
            //

            serr = WSAGetLastError();
            break;
        }

        if( pUserData && ( res > 0 ) )
        {
            if( chTrash != *pszNext )
            {
                pszNext = pszAbort;
            }

            if( chTrash == *pszNext )
            {
                pszNext++;

                if( *pszNext == '\0' )
                {
                    SET_UF( pUserData, OOB_ABORT) ;
                    pszNext = pszAbort;
                }
            }
        }

#if DBG
        cbDiscarded += (DWORD)res;
#endif  // DBG
    }

    IF_DEBUG( SOCKETS )
    {
        if( serr == 0 )
        {
            FTPD_PRINT(( "discarded %lu bytes of out of band data\n",
                         cbDiscarded ));
        }
        else
        {
            FTPD_PRINT(( "cannot discard out of band data, socket error %d\n",
                         serr ));
        }
    }

    return serr;

}   // DiscardOutOfBandData