/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1994 **/ /**********************************************************************/ /* conn.cxx This module contains the connection class FILE HISTORY: Johnl 15-Aug-1994 Created */ #include "w3p.hxx" #pragma warning( disable:4355 ) // 'this' used in base member initializer list // // Private constants. // // // Private globals. // CRITICAL_SECTION CLIENT_CONN::_csBuffList; LIST_ENTRY CLIENT_CONN::_BuffListHead; BOOL CLIENT_CONN::_fGlobalInit = FALSE; // // Private prototypes. // BYTE * ScanForTerminator( TCHAR * pch ); // // Public functions. // // // Private functions. // /******************************************************************* NAME: CLIENT_CONN SYNOPSIS: Constructor for client connection ENTRY: sClient - Client socket psockaddrLocal - Optional Addressing info of server socket psockaddrRemote - Addressing info for client patqContext - Optional ATQ context HISTORY: Johnl 15-Aug-1994 Created ********************************************************************/ CLIENT_CONN::CLIENT_CONN( SOCKET sClient, SOCKADDR_IN * psockaddrLocal, SOCKADDR_IN * psockaddrRemote, PATQ_CONTEXT patqContext, PVOID pvInitialBuff, DWORD cbInitialBuff ) : _phttpReq( NULL ) { Initialize( sClient, psockaddrLocal, psockaddrRemote, patqContext, pvInitialBuff, cbInitialBuff ); } VOID CLIENT_CONN::Initialize( SOCKET sClient, SOCKADDR_IN * psockaddrLocal, SOCKADDR_IN * psockaddrRemote, PATQ_CONTEXT patqContext, PVOID pvInitialBuff, DWORD cbInitialBuff ) /*++ Routine Description: This is a pseudo constructor, called just before this object is given to somebody who allocated a new client connection Arguments: sClient - Same as for constructor psockaddr - Same as for constructor --*/ { CHAR * pchAddr; struct sockaddr_in sockaddr; int cbAddr = sizeof( sockaddr ); _Signature = CLIENT_CONN_SIGNATURE; _sClient = sClient; _ccState = CCS_STARTUP; _cRef = 1; _AtqContext = patqContext; _fIsValid = FALSE; _fReuseContext = TRUE; _pvInitial = pvInitialBuff; _cbInitial = cbInitialBuff; IF_DEBUG( CONNECTION ) { TCP_PRINT((DBG_CONTEXT, "Initializing connection object %lx, new user count %d\n", this, cConnectedUsers + 1 )); } // // Put the item on the connection list even if we fail below. When the // is freed, it will be removed from the connection list // LockGlobals(); cConnectedUsers++; InsertHeadList( &listConnections, &ListEntry ); UnlockGlobals(); INCREMENT_COUNTER( CurrentConnections ); if ( W3Stats.CurrentConnections > W3Stats.MaxConnections ) { LockStatistics(); if ( W3Stats.CurrentConnections > W3Stats.MaxConnections ) W3Stats.MaxConnections = W3Stats.CurrentConnections; UnlockStatistics(); } InetNtoa( psockaddrRemote->sin_addr, _achRemoteAddr ); if ( psockaddrLocal ) { InetNtoa( psockaddrLocal->sin_addr, _achLocalAddr ); _sPort = ntohs( psockaddrLocal->sin_port ); } else { if ( getsockname( sClient, (struct sockaddr *) &sockaddr, &cbAddr )) { TCP_PRINT((DBG_CONTEXT, "[CLIENT_CONN] inet_ntoa failed\n")); return; } InetNtoa( sockaddr.sin_addr, _achLocalAddr ); _sPort = ntohs( sockaddr.sin_port ); } TCP_ASSERT( (_tcslen( _achLocalAddr ) + 1) <= sizeof(_achLocalAddr)); if ( _phttpReq ) { _phttpReq->InitializeSession( this, pvInitialBuff, cbInitialBuff ); } else { _phttpReq = HTTP_REQUEST::Alloc(this, pvInitialBuff, cbInitialBuff ); if ( !_phttpReq ) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return; } } _fIsValid = TRUE; } CLIENT_CONN::~CLIENT_CONN() { delete _phttpReq; _Signature = CLIENT_CONN_SIGNATURE_FREE; } VOID CLIENT_CONN::Reset( VOID ) /*++ Routine Description: This is a pseudo destructor, called just before this object is put back onto the free list. Arguments: --*/ { IF_DEBUG( CONNECTION ) { TCP_PRINT((DBG_CONTEXT, "Resetting connection object %lx, AtqCont = %lx new user count %d\n", this, _AtqContext, cConnectedUsers - 1 )); } if ( _phttpReq ) { _phttpReq->SessionTerminated(); } if ( QueryAtqContext() ) { AtqFreeContext( QueryAtqContext(), _fReuseContext ); _AtqContext = NULL; } // // Remove ourselves from the connection list and knock down our // connected user count // LockGlobals(); RemoveEntryList( &ListEntry ); cConnectedUsers--; TCP_ASSERT( ((LONG) cConnectedUsers) >= 0 ); UnlockGlobals(); DECREMENT_COUNTER( CurrentConnections ); _Signature = CLIENT_CONN_SIGNATURE_FREE; } /******************************************************************* NAME: CLIENT_CONN::DoWork SYNOPSIS: Worker method driven by thread pool queue RETURNS: TRUE while processing should continue on this object If FALSE is returned, then the object should be deleted (status codes will already have been communicated to the client). NOTES: If an IO request completes with an error, the connection is immediately closed and everything is cleaned up. The worker functions will not be called when an error occurs. HISTORY: Johnl 15-Aug-1994 Created ********************************************************************/ BOOL CLIENT_CONN::DoWork( DWORD BytesWritten, DWORD CompletionStatus, BOOL fIOCompletion ) { BOOL fRet = TRUE; BOOL fFinished = FALSE; BOOL fAvailableData; IF_DEBUG( CONNECTION ) { TCP_PRINT(( DBG_CONTEXT, "DoWork: Object %lx Last IO error %lu Bytes = %d State = %d\n" "\tIsIO = %s Ref = %d Sock = %x\n", this, CompletionStatus, BytesWritten, QueryState(), fIOCompletion ? "TRUE" : "FALSE", QueryRefCount(), (QueryAtqContext() ? (int) QueryAtqContext()->hAsyncIO : 0xffff) )); } // // If this was a completion generated by an IO request, decrement the // ref count // if ( fIOCompletion ) { Dereference(); } // // If an IO Request completed with an error and we're not already in the // process of cleaning up, then abort the connection and cleanup. We // do not send a status code to the client in this instance. // _phttpReq->SetLastCompletionStatus( BytesWritten, CompletionStatus ); if ( CompletionStatus && !IsCleaningUp() ) { // // If an error occurred on an async operation, set the win32 log // status code to reflect the error but don't reset the HTTP status // code. // if ( CompletionStatus != ERROR_OPERATION_ABORTED ) { _phttpReq->SetLogStatus( _phttpReq->QueryLogHttpResponse(), CompletionStatus ); } TCP_PRINT(( DBG_CONTEXT, "DoWork: Aborting client connection, error %d\n", CompletionStatus )); if ( HTR_GATEWAY_ASYNC_IO == _phttpReq->QueryState()) { // Notify the external gateway application // NYI: This is a hack for IIS 2.0 We need to fix state machines // of CLIENT_CONN & HTTP_REQUEST for future extensions ((HTTP_REQUEST * )_phttpReq)->ProcessAsyncGatewayIO(); } Disconnect( NULL, 0, 0, FALSE ); goto Exit; } switch ( QueryState() ) { case CCS_STARTUP: // // Do this at the beginning of every request // fRet = OnSessionStartup( _pvInitial, _cbInitial ); if ( !fRet || !_pvInitial ) { break; } // // Fall through // case CCS_PROCESSING_CLIENT_REQ: fRet = _phttpReq->DoWork( &fFinished ); if ( !fRet ) { // // If we were denied access to the resource, then ask the user // for better authorization. Unless the user is in the process // of authenticating, we force a disconnect. This prevents the // case of logging on as anonymous successfully and then failing // to access the resource. // if ( GetLastError() == ERROR_ACCESS_DENIED ) { if ( !_phttpReq->IsAuthenticating() ) { _phttpReq->SetKeepConn( FALSE ); } if ( _phttpReq->IsKeepConnSet() ) { // // Zero out the inital buffer so we don't try and reuse // it next time around // _pvInitial = NULL; _cbInitial = 0; SetState( CCS_STARTUP ); } else { _phttpReq->SetLogStatus( HT_DENIED, ERROR_ACCESS_DENIED ); SetState( CCS_DISCONNECTING ); } fRet = _phttpReq->SendAuthNeededResp( &fFinished ); if ( fRet && fFinished ) { goto Disconnecting; } } } else if ( fFinished ) { _phttpReq->WriteLogRecord(); if ( !IsCleaningUp() ) { if ( !_phttpReq->IsKeepConnSet() || !(fRet = OnSessionStartup()) ) { // // Either we completed the disconnect so // close the socket or an error // occurred setting up for the next request w/ KeepConn set // Disconnect(); } } } break; case CCS_DISCONNECTING: Disconnecting: // // Write the log record for this request // _phttpReq->WriteLogRecord(); Disconnect(); break; case CCS_SHUTDOWN: // // Nothing to do but wait for the async IOs to complete // break; default: fRet = FALSE; TCP_ASSERT( FALSE ); } // // If an error occurred, disconnect without sending a response // if ( !fRet ) { _phttpReq->SetLogStatus( HT_SERVER_ERROR, GetLastError() ); _phttpReq->Disconnect( HT_SERVER_ERROR, GetLastError() ); } Exit: IF_DEBUG( CONNECTION ) { TCP_PRINT((DBG_CONTEXT, "DoWork: Leaving, State = %d, this = %lx, Ref = %d\n", QueryState(), this, QueryRefCount() )); } // // This connection's reference count should always be at least one // at this point // TCP_ASSERT( QueryRefCount() > 0 ); return TRUE; } /******************************************************************* NAME: CLIENT_CONN::OnSessionStartup SYNOPSIS: Initiates the first read to get the client request PARAMETERS: RETURNS: TRUE if processing should continue, FALSE to abort the this connection HISTORY: Johnl 15-Aug-1994 Created ********************************************************************/ BOOL CLIENT_CONN::OnSessionStartup( PVOID pvInitial, DWORD cbInitial ) { APIERR err = NO_ERROR; // // Associate our client socket with Atq if it hasn't already // if ( !QueryAtqContext() ) { TCP_ASSERT( pvInitial == NULL ); if ( !AtqAddAsyncHandle( &_AtqContext, this, W3Completion, g_pTsvcInfo->QueryConnectionTimeout(), (HANDLE) QuerySocket())) { TCP_PRINT(( DBG_CONTEXT, "OnSessionStartup: failed to add Atq handle, error %lu\n", GetLastError() )); return FALSE; } } SetState( CCS_PROCESSING_CLIENT_REQ ); return _phttpReq->StartNewRequest( pvInitial, cbInitial ); } /******************************************************************* NAME: CLIENT_CONN::Disconnect SYNOPSIS: Initiates a disconnect from the client ENTRY: pRequest - If not NULL and HTResponse is non-zero, send a response status before disconnecting HTResponse - HTTP status code to send ErrorResponse - Optional information string (system error or string resource ID). NOTES: If a response is sent, then the socket won't be disconnected till the send completes (state goes to HISTORY: Johnl 22-Aug-1994 Created ********************************************************************/ VOID CLIENT_CONN::Disconnect( HTTP_REQ_BASE * pRequest, DWORD HTResponse, DWORD ErrorResponse, BOOL fDoShutdown ) { CHAR * pszResp; // // If Disconnect has already been called, then this is a no-op // if ( QueryState() == CCS_SHUTDOWN ) { return; } if ( pRequest && HTResponse ) { STR strBody; if ( HTResponse == HT_NOT_FOUND ) { INCREMENT_COUNTER( TotalNotFoundErrors ); } // // Means we have to wait for the status response before closing // the socket // SetState( CCS_DISCONNECTING ); IF_DEBUG( CONNECTION ) { TCP_PRINT((DBG_CONTEXT, "Disconnect: Going to Disconnecting for %lx, ref = %d, response = %d\n", this, QueryRefCount(), HTResponse )); } // // Send the requested status response and after that completes close // the socket // if ( !HTTP_REQ_BASE::BuildStatusLine( pRequest->QueryRespBuf(), HTResponse, ErrorResponse, pRequest->QueryURL() ) || !strBody.Copy( "Content-Type: text/html\r\n\r\n" "