/*++

   Copyright    (c)    1994    Microsoft Corporation

   Module  Name :

        conn.cxx

   Abstract:

        This module defines the functions for base class of connections
        for Internet Services  ( class CLIENT_CONNECTION)

   Author:

           Rohan Phillips    ( Rohanp )    11-Dec-1995

   Project:

           Gibraltar Services Common Code

   Functions Exported:

          CLIENT_CONNECTION::Initialize()
          CLIENT_CONNECTION::Cleanup()
          CLIENT_CONNECTION::~CLIENT_CONNECTION()
          BOOL CLIENT_CONNECTION::ProcessClient( IN DWORD cbWritten,
                                                  IN DWORD dwCompletionStatus,
                                                  IN BOOL  fIOCompletion)
          VOID CLIENT_CONNECTION::DisconnectClient( IN DWORD ErrorReponse)

          BOOL CLIENT_CONNECTION::StartupSession( VOID)
          BOOL CLIENT_CONNECTION::ReceiveRequest(
                                               OUT LPBOOL pfFullRequestRecd)

          BOOL CLIENT_CONNECTION::ReadFile( OUT LPVOID pvBuffer,
                                            IN  DWORD  dwSize)
          BOOL CLIENT_CONNECTION::WriteFile( IN LPVOID pvBuffer,
                                             IN DWORD  dwSize)
          BOOL CLIENT_CONNECTION::TransmitFile( IN HANDLE hFile,
                                                IN DWORD cbToSend)
          BOOL CLIENT_CONNECTION::PostCompletionStatus( IN DWORD dwBytes )

   Revision History:
   Revision History:
           Richard Kamicar   ( rkamicar )  31-Dec-1995
                Moved to common directory, filled in more

--*/


/************************************************************
 *     Include Headers
 ************************************************************/

#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include "conn.hxx"



/************************************************************
 *    Functions
 ************************************************************/



/*++

   ICLIENT_CONNECTION::Initialize()

      Constructor for ICLIENT_CONNECTION object.
      Initializes the fields of the client connection.

   Arguments:

      sClient       socket for communicating with client

      psockAddrRemote pointer to address of the remote client
                ( the value should be copied).
      psockAddrLocal  pointer to address for the local card through
                  which the client came in.
      pAtqContext      pointer to ATQ Context used for AcceptEx'ed conn.
      pvInitialRequest pointer to void buffer containing the initial request
      cbInitialData    count of bytes of data read initially.

   Note:
      TO keep the number of connected users <= Max connections specified.
      Make sure to add this object to global list of connections,
       after creating it.
      If there is a failure to add to global list, delete this object.

--*/

CLIENT_CONNECTION::Initialize(
    IN SOCKET              sClient,
    IN const SOCKADDR_IN * psockAddrRemote,
    IN const SOCKADDR_IN * psockAddrLocal  /* Default  = NULL */,
    IN PATQ_CONTEXT        pAtqContext     /* Default  = NULL */,
    IN PVOID               pvInitialRequest/* Default  = NULL */,
    IN DWORD               cbInitialData   /* Default  = 0    */
    )
{
    DWORD dwError = NO_ERROR;


    m_sClient        = ( sClient);
    m_pAtqContext    = pAtqContext;
    m_cbReceived = 0;
    m_pvInitial    = pvInitialRequest;
    m_cbInitial    = cbInitialData;
    m_Destroy = FALSE;

    _ASSERT( psockAddrRemote != NULL);

    m_saClient = *psockAddrRemote;

    //  Obtain the socket addresses for the socket
    m_pchRemoteHostName[0] =
    m_pchLocalHostName[0] =
    m_pchLocalPortName[0] = '\0';

    // InetNtoa() wants just 16 byte buffer.
    _ASSERT( 16 <= MAX_HOST_NAME_LEN);
    dwError = InetNtoa( psockAddrRemote->sin_addr, m_pchRemoteHostName);

    _ASSERT( dwError == NO_ERROR);  // since we had given sufficient buffer

    if ( psockAddrLocal != NULL)
    {
      dwError = InetNtoa( psockAddrLocal->sin_addr, m_pchLocalHostName);
      _itoa( ntohs(psockAddrLocal->sin_port), m_pchLocalPortName, 10);
    } else
    {
        SOCKADDR_IN  sockAddr;
        int cbAddr = sizeof( sockAddr);

        if ( getsockname( sClient,
                         (struct sockaddr *) &sockAddr,
                         &cbAddr ))
        {

            dwError = InetNtoa( sockAddr.sin_addr, m_pchLocalHostName );
            _itoa( ntohs(sockAddr.sin_port), m_pchLocalPortName, 10);

        }
    }

   // DBG_ASSERT( dwError == NO_ERROR);  // since we had given sufficient buffer

#if 0
    DBG_CODE(
             if ( dwError != NO_ERROR) {

                 DBGPRINTF( ( DBG_CONTEXT,
                             "Obtaining Local Host Name Failed. Error = %u\n",
                             dwError)
                           );
                 SetLastError( dwError);
             }
             );

    DEBUG_IF( CLIENT, {

     DBGPRINTF( ( DBG_CONTEXT,
                    " Constructed ICLIENT_CONNECTION object ( %08x)."
                    " Socket (%d), Host (%s).\n",
                    this,
                    sClient,
                    QueryClientHostName()
                    ));
    });

#endif

    return ( TRUE);

}


/*++
     ICLIENT_CONNECTION::Cleanup()

       Destructor function for client connection object.
       Checks and frees the AtqContext.

    Note:
       If enlisted in the global list of connections,
        ensure that this object is freed from that list before deletion.

--*/
VOID CLIENT_CONNECTION::Cleanup( VOID)
{
  PATQ_CONTEXT pAtqContext;

  if(m_DoCleanup)
  {
    //release the context from Atq
    pAtqContext = (PATQ_CONTEXT)InterlockedExchangePointer( (PVOID *)&m_pAtqContext, NULL);
    if ( pAtqContext != NULL )
    {
       AtqFreeContext( pAtqContext, TRUE );
    }
  }

} // ICLIENT_CONNECTION::Cleanup()


/*++

    Description:

        Checks to see if we have received the complete request from the client.
        ( A complete request is a line of text terminated by <cr><lf> )
        if a CRLF is found, it returns a pointer into the buffer were the CR
        starts, else it returns NULL.  If this routine finds a CR without a
        LF it will return NULL

    Arguments:

        InputBuffer       pointer to character buffer containing received data.

        cbRecvd         count of bytes of data received

    Returns:

       a pointer to the CR if CRLF is found
       NULL if CRLF is not found.

--*/
//  VIRTUAL
char * CLIENT_CONNECTION::IsLineComplete(IN OUT const char * InputBuffer, IN  DWORD cbRecvd)
{
    register DWORD Loop = 0;

    _ASSERT(InputBuffer != NULL);

    //we need at least 2 bytes to find a
    //CRLF pair
    if( cbRecvd < 2)
     return NULL;

    //we are going to start at the 2nd byte
    //looking for the LF, then look backwards
    //for the CR
    Loop = 1;

    while (Loop < cbRecvd)
    {
        if(InputBuffer[Loop] == LF)
        {
            if(InputBuffer[Loop - 1] == CR)
                return (char *) &InputBuffer[Loop - 1];
            else
            {
                //skip 2 bytes since we saw a LF
                //without a CR.
                Loop += 2;
            }
        }
        else if(InputBuffer[Loop] == CR)
        {
            //we saw a CR, so increment out
            //loop variable by one so that
            //we can catch the LF on the next
            //go around
            Loop += 1;
        }
        else
        {
            //This character is neither a CR
            //or a LF, so we can increment by
            //2
            Loop += 2;
        }
    }

    //didn't find a CRLF pair
    return NULL;
}

/*++

    Description:

        VIRTUAL Method that MUST be defined by the derived class

       Main function for this class. Processes the connection based
        on current state of the connection.
       It may invoke or be invoked by ATQ functions.

    Arguments:

       cbWritten          count of bytes written

       dwCompletionStatus Error Code for last IO operation

       fIOCompletion      TRUE if this was an IO completion


    Returns:

       TRUE when processing is incomplete.
       FALSE when the connection is completely processed and this
        object may be deleted.

--*/
//  VIRTUAL
BOOL CLIENT_CONNECTION::ProcessClient( IN DWORD cbWritten, IN DWORD dwCompletionStatus, IN OUT OVERLAPPED * lpo)
{
    return ( TRUE);
} // CLIENT_CONNECTION::ProcessClient()


/*++

    Reads contents using ATQ into the given buffer.
     ( thin wrapper for ATQ call and managing references)

    Arguments:

      pvBuffer      pointer to buffer where to read in the contents

      cbSize        size of the buffer

    Returns:

      TRUE on success and FALSE on a failure.

--*/
//  VIRTUAL
BOOL CLIENT_CONNECTION::ReadFile(
            IN LPVOID pBuffer,
            IN DWORD  cbSize /* = MAX_READ_BUFF_SIZE */
            )
{
    _ASSERT(pBuffer != NULL);
    _ASSERT(cbSize > 0);

    ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));

    return  AtqReadFile(m_pAtqContext,      // Atq context
                        pBuffer,            // Buffer
                        cbSize,             // BytesToRead
                        &m_Overlapped) ;
}


/*++

    Writes contents from given buffer using ATQ.
     ( thin wrapper for ATQ call and managing references)

    Arguments:

      pvBuffer      pointer to buffer containing contents for write

      cbSize        size of the buffer

    Returns:

      TRUE on success and FALSE on a failure.

--*/
//  VIRTUAL
BOOL CLIENT_CONNECTION::WriteFile( IN LPVOID pBuffer, IN DWORD cbSize )
{
    BOOL    fReturn = TRUE;
    int     BytesAlreadySent = 0;
    DWORD   BytesSent;
    DWORD   dwError = NO_ERROR;
    DWORD   cTimesBlocked = 0;
    DWORD   dwSleepTime = 1000;

    _ASSERT(pBuffer != NULL);

    for (BytesSent = 0; BytesSent < cbSize; BytesSent += BytesAlreadySent)
    {
        BytesAlreadySent = send(m_sClient,
                                (const char FAR *) pBuffer + BytesSent,
                                (int) (cbSize - BytesSent),
                                0);
        if (BytesAlreadySent == SOCKET_ERROR)
        {
            //The above send will fail with WSAEWOULDBLOCK when
            //the TCP buffer is full...  this can easily happen for blob
            //protocol sinks.  The correct thing to do is rely on memory 
            //instead of TCP buffers to store pending sends, but the 
            //low impact work-around is to sleep after we would block
            dwError = GetLastError();
            if ((WSAEWOULDBLOCK == dwError) && (cTimesBlocked < 500))
            {
                SetLastError(NO_ERROR);
                cTimesBlocked++;
                BytesAlreadySent = 0;
                Sleep(dwSleepTime);
                dwSleepTime += dwSleepTime;
                continue;
            }
            fReturn = FALSE;
            break;
        }
    }
    return fReturn;
}

BOOL CLIENT_CONNECTION::WriteSocket( IN SOCKET Sock, IN LPVOID pBuffer, IN DWORD cbSize )
{
    BOOL    fReturn = TRUE;
    int     BytesAlreadySent = 0;
    DWORD   BytesSent;

    _ASSERT(pBuffer != NULL);

    for (BytesSent = 0; BytesSent < cbSize; BytesSent += BytesAlreadySent)
    {
        BytesAlreadySent = send(Sock,
                                (const char FAR *) pBuffer + BytesSent,
                                (int) (cbSize - BytesSent),
                                0);
        if (BytesAlreadySent == SOCKET_ERROR)
        {
            fReturn = FALSE;
            break;
        }
    }
    return fReturn;
}



/*++

    Writes contents from given buffer using ATQ.
     (thin wrapper for ATQ call and managing references)

    Arguments:

      Pov      pointer to OVERLAPPED structure describing the write

    Returns:

      TRUE on success and FALSE on a failure.

--*/
//  VIRTUAL
BOOL CLIENT_CONNECTION::WriteFile(
    IN  LPVOID      lpvBuffer,
    IN  DWORD       cbSize,
    IN  OVERLAPPED  *lpo)
{
    BOOL  fReturn = TRUE;

    _ASSERT(lpo != NULL);
    _ASSERT(lpvBuffer != NULL);
    _ASSERT(cbSize != 0);

    fReturn = AtqWriteFile(m_pAtqContext, lpvBuffer, cbSize, lpo);
    return fReturn;
}


/*++

    Transmits contents of the file ( of specified size)
     using the ATQ and client socket.
     ( thin wrapper for ATQ call and managing references)

    Arguments:

      hFile         handle for file to be transmitted

      liSize        large integer containing the size of file

      lpTransmitBuffers
        buffers containing the head and tail buffers that
            need to be transmitted along with the file.

    Returns:

      TRUE on success and FALSE on a failure.

--*/

BOOL CLIENT_CONNECTION::TransmitFile(
    IN  HANDLE                  hFile,
    IN  LARGE_INTEGER           &liSize,
    IN  LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers
    )
{
    _ASSERT(hFile != INVALID_HANDLE_VALUE);
    _ASSERT(liSize.QuadPart > 0);

    return  AtqTransmitFile(
                        m_pAtqContext,              // Atq context
                        hFile,                      // file data comes from
                        (DWORD) liSize.LowPart,                     // Bytes To Send
                        lpTransmitBuffers,          // header/tail buffers
                        0                           // Flags
                        );
}


//+------------------------------------------------------------
//
// Function: CLIENT_CONNECTION::PostCompletionStatus
//
// Synopsis: Wrapper around atq for posting completion status
//
// Arguments:
//  dwBytes: The number of bytes to indicate in the completion status
//
// Returns:
//  TRUE: Success
//  FALSE: Failure
//
// History:
// jstamerj 1998/11/03 20:16:17: Created.
//
//-------------------------------------------------------------
BOOL CLIENT_CONNECTION::PostCompletionStatus(
    IN  DWORD   dwBytes)
{
    return AtqPostCompletionStatus(
        m_pAtqContext,
        dwBytes);
}


/*++

    Starts up a session for new client.
    Adds the client socket to the ATQ completion port and gets an ATQ context.
    Then prepares  receive buffer and starts off a receive request from client.
      ( Also moves the client connection to CcsGettingRequest state)

    Parameters:
      pvInitial   pointer to void buffer containing the initial data
      cbWritten   count of bytes in the buffer

    Returns:

        TRUE on success and FALSE if there is any error.
--*/

//  VIRTUAL
BOOL CLIENT_CONNECTION::StartSession( void)
{
  return TRUE;
}


/*++

    Receive full Request from the client.
    If the entire request is received,
     *pfFullRequestRecvd will be set to TRUE and
     the request will be parsed.

    Arguments:

        cbWritten              count of bytes written in last IO operation.
        pfFullRequestRecvd     pointer to boolean, which on successful return
                                indicates if the full request was received.

    Returns:

        TRUE on success and
        FALSE if there is any error ( to abort this connection).

--*/

BOOL CLIENT_CONNECTION::ReceiveRequest(IN DWORD cbWritten, OUT LPBOOL pfFullRequestRecvd)
{
   return ( TRUE);
}

/*++

   Description:

       Initiates a disconnect operation for current connection.
       If already shutdown, this function returns doing nothing.
       Optionally if there is any error message to be sent, they may be sent

   Arguments:

      dwErrorCode
         error code for server errors if any ( Win 32 error code)
        If dwErrorCode != NO_ERROR, then there is a system level error code.
         by the REQUEST object. But the disconnection occurs immediately; Hence
         the REQUEST object should send synchronous error messages.

   Returns:
       None

--*/
VOID CLIENT_CONNECTION::DisconnectClient(IN DWORD dwErrorCode)
{
   SOCKET  hSocket;

   hSocket = (SOCKET)InterlockedExchangePointer( (PVOID *)&m_sClient, (PVOID) INVALID_SOCKET );
   if ( hSocket != INVALID_SOCKET )
   {
       if ( QueryAtqContext() != NULL )
       {
            AtqCloseSocket(QueryAtqContext() , (dwErrorCode == NO_ERROR));
       }
       else
       {
            ShutAndCloseSocket( m_sClient );
       }
   }
}



/*++

   Description:

       returns the client user name
       VIRTUAL function so apps can override its return value

   Arguments:

       void

   Returns:
       returns ptr to user name

--*/
LPCTSTR CLIENT_CONNECTION::QueryClientUserName( VOID )
{
    return   m_pchLocalHostName;
}



//
// Private Functions
//


# if DBG

//  VIRTUAL
VOID CLIENT_CONNECTION::Print( VOID) const
{


    return;

} // ICLIENT_CONNECTION::Print()


# endif // DBG


/*++

    Description:

       Performs a hard close on the socket using shutdown before close.

    Arguments:

       sock    socket to be closed

    Returns:

      0  if no errors  or
      socket specific error code

--*/

INT ShutAndCloseSocket( IN SOCKET sock)
{

    INT  serr = 0;

    //
    // Shut the socket. ( Assumes this to be a TCP socket.)
    //  Prevent future sends from occuring. hence 2nd param is "1"
    //

    if( sock != INVALID_SOCKET )
    {
      if ( shutdown( sock, 1) == SOCKET_ERROR)
        serr = WSAGetLastError();

      closesocket( sock);
    }

    return ( serr);

} // ShutAndCloseSocket()



/*++
    This function canonicalizes the path, taking into account the current
    user's current directory value.

    Arguments:
        pszDest   string that will on return contain the complete
                    canonicalized path. This buffer will be of size
                    specified in *lpdwSize.

        lpdwSize  Contains the size of the buffer pszDest on entry.
                On return contains the number of bytes written
                into the buffer or number of bytes required.

        pszSearchPath  pointer to string containing the path to be converted.
                    IF NULL, use the current directory only

    Returns:

        Win32 Error Code - NO_ERROR on success

    MuraliK   24-Apr-1995   Created.

--*/
BOOL
ResolveVirtualRoot(
        OUT CHAR *      pszDest,
    IN  OUT LPDWORD     lpdwSize,
    IN  OUT CHAR *      pszSearchPath,
        OUT HANDLE *    phToken /* = NULL */
    )
{
    TraceFunctEnter("ResolveVirtualRoot");

    _ASSERT(pszDest != NULL);
    _ASSERT(lpdwSize != NULL);
    _ASSERT(pszSearchPath != NULL);
    //
    // Now we have the complete symbolic path to the target file.
    //  Translate it into the absolute path
    //

#if 0
    if (!TsLookupVirtualRoot(g_pTsvcInfo->GetTsvcCache(),   // TSvcCache
                                pszSearchPath,              // pszRoot
                                pszDest,                    // pszDirectory
                                lpdwSize,                   // lpcbSize
                                NULL,                       // lpdwAccessMask
                                NULL,                       // pcchDirRoot
                                NULL,                       // pcchVroot
                                phToken,                    // phImpersonationToken
                                NULL,                       // pszAddress
                                NULL                        // lpdwFileSystem
                                ))
    {
        ErrorTrace(NULL, "TsLookupVirtualRoot failed looking for %s: %d", pszSearchPath, GetLastError());
        TraceFunctLeave();
        return FALSE;
    }
#endif

    TraceFunctLeave();
    return TRUE;
}



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