Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2111 lines
57 KiB

/**********************************************************************/
/** 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"
#include <issched.hxx>
#pragma warning( disable:4355 ) // 'this' used in base member initializer list
//
// Used for iisprobe
//
extern "C" {
VOID
DumpW3InfoToHTML(
OUT CHAR * pch,
IN OUT LPDWORD pcb
);
};
//
// Globals
//
#if CC_REF_TRACKING
//
// Ref count trace log size
//
#define C_CLIENT_CONN_REFTRACES 4000
#define C_LOCAL_CONN_REFTRACES 40
#endif
//
// Ref trace log for CLIENT_CONN objects
// NOTE we make this global so other classes can get at it
//
#if DBG
PTRACE_LOG g_pDbgCCRefTraceLog = NULL;
#endif
//
// Private constants.
//
//
// How often to run the free list scavenger (thirty minutes)
//
#define FREE_LIST_SCAVENGE_TIME (30 * 60 * 1000)
//
// Private globals.
//
CRITICAL_SECTION CLIENT_CONN::_csBuffList;
LIST_ENTRY CLIENT_CONN::_BuffListHead;
BOOL CLIENT_CONN::_fGlobalInit = FALSE;
DWORD CLIENT_CONN::_cFree = 0; // Number of items on lookaside free list
DWORD CLIENT_CONN::_FreeListScavengerCookie;
DWORD ErrorRespTable[] = {IDS_EXECUTE_ACCESS_DENIED,
IDS_READ_ACCESS_DENIED,
IDS_WRITE_ACCESS_DENIED,
IDS_SSL_REQUIRED,
IDS_SSL128_REQUIRED,
IDS_ADDR_REJECT,
IDS_CERT_REQUIRED,
IDS_SITE_ACCESS_DENIED,
IDS_TOO_MANY_USERS,
IDS_INVALID_CNFG,
IDS_PWD_CHANGE,
IDS_MAPPER_DENY_ACCESS,
#if defined(CAL_ENABLED)
IDS_CAL_EXCEEDED,
#endif
IDS_CERT_REVOKED,
IDS_DIR_LIST_DENIED,
IDS_SITE_RESOURCE_BLOCKED,
IDS_CERT_BAD,
IDS_CERT_TIME_INVALID,
IDS_SITE_NOT_FOUND
};
DWORD SubStatusTable[] = {MD_ERROR_SUB403_EXECUTE_ACCESS_DENIED,
MD_ERROR_SUB403_READ_ACCESS_DENIED ,
MD_ERROR_SUB403_WRITE_ACCESS_DENIED ,
MD_ERROR_SUB403_SSL_REQUIRED ,
MD_ERROR_SUB403_SSL128_REQUIRED ,
MD_ERROR_SUB403_ADDR_REJECT ,
MD_ERROR_SUB403_CERT_REQUIRED ,
MD_ERROR_SUB403_SITE_ACCESS_DENIED ,
MD_ERROR_SUB403_TOO_MANY_USERS ,
MD_ERROR_SUB403_INVALID_CNFG ,
MD_ERROR_SUB403_PWD_CHANGE ,
MD_ERROR_SUB403_MAPPER_DENY_ACCESS ,
#if defined(CAL_ENABLED)
MD_ERROR_SUB403_CAL_EXCEEDED ,
#endif
MD_ERROR_SUB403_CERT_REVOKED,
MD_ERROR_SUB403_DIR_LIST_DENIED,
MD_ERROR_SUB503_CPU_LIMIT,
MD_ERROR_SUB403_CERT_BAD,
MD_ERROR_SUB403_CERT_TIME_INVALID,
MD_ERROR_SUB404_SITE_NOT_FOUND
};
/*******************************************************************
Maco support for CLIENT_CONN::Reference/Dereference
HISTORY:
DaveK 10-Sep-1997 Added ref trace logging
********************************************************************/
#if CC_REF_TRACKING
#define CC_LOG_REF_COUNT( cRefs ) \
\
SHARED_LOG_REF_COUNT( \
cRefs \
, (PVOID) this \
, _phttpReq \
, (_phttpReq) \
? ((HTTP_REQUEST *) _phttpReq)->QueryWamRequest() \
: NULL \
, (_phttpReq) \
? _phttpReq->QueryState() \
: NULL \
); \
LOCAL_LOG_REF_COUNT( \
cRefs \
, (PVOID) this \
, _phttpReq \
, (_phttpReq) \
? ((HTTP_REQUEST *) _phttpReq)->QueryWamRequest() \
: NULL \
, (_phttpReq) \
? _phttpReq->QueryState() \
: NULL \
);
#else
#define CC_LOG_REF_COUNT( cRefs )
#endif
#if CC_REF_TRACKING
//
// ATQ notification trace
//
// ATQ notification : stored with context1 set magic value fefefefe
//
#define CCA_LOG_REF_COUNT( cRefs,BytesWritten,CompletionStatus, dwSig) \
\
SHARED_LOG_REF_COUNT( \
cRefs \
, (PVOID) this \
, (LPVOID)dwSig \
, (LPVOID)BytesWritten \
, CompletionStatus \
); \
LOCAL_LOG_REF_COUNT( \
cRefs \
, (PVOID) this \
, (LPVOID)dwSig \
, (LPVOID)BytesWritten \
, CompletionStatus \
);
#else
#define CCA_LOG_REF_COUNT( Ctx,BytesWritten,CompletionStatus, dwSig)
#endif
//
// Private prototypes.
//
VOID
WINAPI
ClientConnTrimScavenger(
PVOID pContext
);
inline BOOL
FastScanForTerminator(
const CHAR * pch,
UINT cbData
)
/*++
Routine Description:
Check if buffer contains a full HTTP header.
Can return false negatives.
Arguments:
pch - request buffer
cbData - # of bytes in pch, excluding trailing '\0'
Return Value:
TRUE if buffer contains a full HTTP header
FALSE if could not insure this, does not mean there is
no full HTTP header.
--*/
{
return ( (cbData > 4) &&
(!memcmp(pch+cbData - sizeof("\r\n\r\n") + 1, "\r\n\r\n",
sizeof("\r\n\r\n") - 1 ) ||
!memcmp(pch+cbData - sizeof("\n\n") + 1, "\n\n",
sizeof("\n\n")-1 )
)
);
} // FastScanForTerminator()
//
// Public functions.
//
BYTE *
ScanForTerminator(
const TCHAR * pch
)
/*++
Routine Description:
Returns the first byte of data after the header
Arguments:
pch - Zero terminated buffer
Return Value:
Pointer to first byte of data after the header or NULL if the
header isn't terminated
--*/
{
while ( *pch )
{
if ( !(pch = strchr( pch, '\n' )))
{
break;
}
//
// If we find an EOL, check if the next character is an EOL character
//
// NYI: UGLY cast is used ...
if ( *(pch = SkipWhite( (char * ) (pch + 1) )) == W3_EOL )
{
return (BYTE *) pch + 1;
}
else if ( *pch )
{
pch++;
}
}
return NULL;
}
//
// 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(
IN PCLIENT_CONN_PARAMS ClientParams
) :
_phttpReq ( NULL),
m_pInstance ( NULL),
_fIsValid ( FALSE )
{
#if CC_REF_TRACKING
_pDbgCCRefTraceLog = CreateRefTraceLog( C_LOCAL_CONN_REFTRACES, 0 );
#endif
Initialize( ClientParams );
//
// initialize statistics pointer to point to global statistics;
// it will point to local copy of the instance when instance
// pointer is set
//
m_pW3Stats = g_pW3Stats;
}
VOID
CLIENT_CONN::Initialize(
IN PCLIENT_CONN_PARAMS ClientParams
)
/*++
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;
PSOCKADDR pAddrLocal = ClientParams->pAddrLocal;
_Signature = CLIENT_CONN_SIGNATURE;
_sClient = ClientParams->sClient;
_ccState = CCS_STARTUP;
_cRef = 1;
_AtqContext = ClientParams->pAtqContext;
_fIsValid = FALSE;
_fReuseContext = TRUE;
_fAbortiveClose = FALSE;
m_atqEndpointObject = ClientParams->pEndpointObject;
//
// Get the endpoint and reference it
//
m_pW3Endpoint = ClientParams->pEndpoint;
//
// Don't actually reference the endpoint. The CLIENT_CONN reference is meaningless
//
// m_pW3Endpoint->Reference( );
_fSecurePort = m_pW3Endpoint->IsSecure( );
_pvInitial = ClientParams->pvInitialBuff;
_cbInitial = ClientParams->cbInitialBuff;
g_pW3Stats->IncrCurrentConnections();
IF_DEBUG( CONNECTION )
{
DBGPRINTF((DBG_CONTEXT,
"Initializing connection object %lx, new user count %d\n",
this,
g_pW3Stats->QueryCurrentConnections() ));
}
LockGlobals();
InsertHeadList( &listConnections, &ListEntry );
UnlockGlobals();
m_remoteIpAddress =
((PSOCKADDR_IN)ClientParams->pAddrRemote)->sin_addr.s_addr;
_sRemotePort =
ntohs( ((PSOCKADDR_IN)ClientParams->pAddrRemote)->sin_port );
DBG_ASSERT( ClientParams->pAddrRemote->sa_family == AF_INET );
InetNtoa( ((SOCKADDR_IN *)ClientParams->pAddrRemote)->sin_addr, _achRemoteAddr );
_acCheck.BindAddr( ClientParams->pAddrRemote );
//
// look at local address
//
if (pAddrLocal->sa_family == AF_INET)
{
InetNtoa( ((SOCKADDR_IN *)pAddrLocal)->sin_addr, _achLocalAddr );
_sPort = m_pW3Endpoint->QueryPort( );
m_localIpAddress =
((PSOCKADDR_IN)ClientParams->pAddrLocal)->sin_addr.s_addr;
DBG_ASSERT(_sPort == ntohs( ((SOCKADDR_IN *)pAddrLocal)->sin_port));
DBG_ASSERT(_sPort != 0);
}
else
{
//
// This should not happen
// Remote winsock bug returns all zeros
//
DBGPRINTF((DBG_CONTEXT,
"Invalid socket family %d\n",pAddrLocal->sa_family));
SetLastError( ERROR_INVALID_PARAMETER );
goto error;
}
DBG_ASSERT( (strlen( _achLocalAddr ) + 1) <= sizeof(_achLocalAddr));
if ( _phttpReq != NULL )
{
_phttpReq->InitializeSession( this,
_pvInitial,
_cbInitial );
}
else
{
_phttpReq = new HTTP_REQUEST( this,
_pvInitial,
_cbInitial );
if ( (_phttpReq == NULL) || !_phttpReq->IsValid() )
{
DBGPRINTF((DBG_CONTEXT,"Cannot allocate HTTP_REQUEST object\n"));
if (_phttpReq != NULL)
{
delete _phttpReq;
_phttpReq = NULL;
}
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
goto error;
}
}
CC_LOG_REF_COUNT( _cRef );
_fIsValid = TRUE;
return;
error:
CC_LOG_REF_COUNT( _cRef );
//
// Set instance to null so that it will not be dereferenced
// twice (one on failure, one on cleanup)
//
//
// Set AtqContext to null so that it will not be dereferenced
// twice (one on failure, one on cleanup)
// it's like deja vu all over again.
_AtqContext = NULL;
return;
} // CLIENT_CONN::Initialize()
CLIENT_CONN::~CLIENT_CONN()
{
if (_phttpReq != NULL)
{
delete _phttpReq;
}
#if CC_REF_TRACKING
DestroyRefTraceLog( _pDbgCCRefTraceLog );
#endif
_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:
--*/
{
//
// before we reset this client-conn:
// we assert that httpreq is null (we may have reached here
// on a failure path) or that our httpreq was unbound from its wamreq
//
DBG_ASSERT(
( _phttpReq == NULL )
||
( ((HTTP_REQUEST *)_phttpReq)->QueryWamRequest() == NULL )
);
IF_DEBUG( CONNECTION )
{
DBGPRINTF((DBG_CONTEXT,
"Resetting connection object %lx, AtqCont = %lx new user count %d\n",
this,
_AtqContext,
g_pW3Stats->QueryCurrentConnections() ));
}
if ( _phttpReq != NULL )
{
_phttpReq->SessionTerminated();
}
if ( QueryAtqContext() != NULL )
{
AtqFreeContext( QueryAtqContext(), _fReuseContext );
_AtqContext = NULL;
}
_acCheck.UnbindAddr();
//
// Dereference the instance
//
if ( m_pInstance != NULL ) {
m_pInstance->DecrementCurrentConnections();
m_pInstance->Dereference( );
m_pInstance = NULL;
//
// Reset statistics pointer to point to global statistics
// as the statistic object is associated with the instance.
//
m_pW3Stats = g_pW3Stats;
}
//
// Dereference the endpoint
//
if ( m_pW3Endpoint != NULL ) {
//
// Don't actually dereference. The CLIENT_CONN references are meaningless anyway.
//
// m_pW3Endpoint->Dereference( );
m_pW3Endpoint = NULL;
}
//
// Remove ourselves from the connection list and knock down our
// connected user count
//
LockGlobals();
RemoveEntryList( &ListEntry );
DBG_ASSERT( ((LONG ) g_pW3Stats->QueryCurrentConnections()) >= 0 );
UnlockGlobals();
g_pW3Stats->DecrCurrentConnections();
_Signature = CLIENT_CONN_SIGNATURE_FREE;
} // CLIENT_CONN::Reset
/*******************************************************************
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;
BOOL fDoAgain;
IF_DEBUG( CONNECTION )
{
DBGPRINTF(( DBG_CONTEXT,
"DoWork: Object %lx Last IO error %lu Bytes = %d State = %d\n"
"\tIsIO = %s Ref = %d Sock = %p\n",
this,
CompletionStatus,
BytesWritten,
QueryState(),
fIOCompletion ? "TRUE" : "FALSE",
QueryRefCount(),
(QueryAtqContext() ? QueryAtqContext()->hAsyncIO : (PVOID)-1L) ));
}
//
// If this was a completion generated by an IO request, decrement the
// ref count
//
if ( fIOCompletion )
{
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
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.
//
if (_phttpReq == NULL)
{
return FALSE;
}
_phttpReq->SetLastCompletionStatus( BytesWritten,
CompletionStatus );
if ( CompletionStatus && !IsCleaningUp() )
{
//
// We want to log this error iff we've received some bytes
// on the connection already. Otherwise this could be a
// 'natural' abort after a successful request.
//
if ( _phttpReq->QueryBytesReceived() != 0)
{
//Log status may still be set to HT_DONT_LOG; if it is, change it
//to "bad request" because we always want to log errors.
DWORD dwLogHttpResponse = _phttpReq->QueryLogHttpResponse();
_phttpReq->SetLogStatus ( ( dwLogHttpResponse == HT_DONT_LOG ?
HT_BAD_REQUEST :
dwLogHttpResponse ),
CompletionStatus );
}
#if DBG
if ( CompletionStatus != ERROR_NETNAME_DELETED &&
CompletionStatus != ERROR_OPERATION_ABORTED )
{
DBGPRINTF(( DBG_CONTEXT,
"DoWork: Aborting client connection %8lx, error %d\n",
this,
CompletionStatus ));
}
#endif
if ( HTR_GATEWAY_ASYNC_IO == _phttpReq->QueryState()) {
DBGPRINTF((
DBG_CONTEXT
, "CLIENT_CONN[%08x]::DoWork async i/o failed "
"_phttpReq[%08x] "
"pWamRequest[%08x] "
"CompletionStatus(%d) "
"\n"
, this
, _phttpReq
, ((HTTP_REQUEST * )_phttpReq)->QueryWamRequest()
, CompletionStatus
));
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
// 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();
} else {
//
// if this is not a WAM request, cleanup Exec, etc...
//
_phttpReq->WriteLogRecord();
}
Disconnect( NULL, 0, 0, FALSE );
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
goto Exit;
}
switch ( QueryState() )
{
case CCS_STARTUP:
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
//
// Do this at the beginning of every request
//
fRet = OnSessionStartup( &fDoAgain,
_pvInitial,
_cbInitial,
TRUE);
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
if ( !fRet || !_pvInitial )
{
break;
}
//
// Fall through
//
case CCS_PROCESSING_CLIENT_REQ:
//
// Set the start time on this request
//
_phttpReq->SetRequestStartTime();
do
{
fDoAgain = FALSE;
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
fRet = _phttpReq->DoWork( &fFinished );
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
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(&fDoAgain)) )
{
//
// Either we completed the disconnect so
// close the socket or an error
// occurred setting up for the next request w/ KeepConn set
//
Disconnect();
fDoAgain = FALSE;
}
fFinished = FALSE;
}
}
} while ( fDoAgain );
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
break;
case CCS_DISCONNECTING:
Disconnecting:
//
// Write the log record for this request
//
_phttpReq->WriteLogRecord();
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
Disconnect();
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
//
// Fall-through to async i/o cleanup code ...
//
case CCS_SHUTDOWN:
//
// If we are still in async i/o state when shutting down, cancel
//
// NOTE this should only happen in cases like 97842,
// where oop isapi submited async i/o and then crashed
//
if ( _phttpReq->QueryState() == HTR_GATEWAY_ASYNC_IO ) {
DBGPRINTF((
DBG_CONTEXT
, "CLIENT_CONN::DoWork: calling CancelAsyncGatewayIO "
"State = %d, this = %lx, Ref = %d\n"
, QueryState()
, this
, QueryRefCount()
));
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
((HTTP_REQUEST * )_phttpReq)->CancelAsyncGatewayIO();
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
}
break;
default:
fRet = FALSE;
DBG_ASSERT( FALSE );
}
//
// If an error occurred, disconnect without sending a response
//
if ( !fRet )
{
//
// There was a problem with SetLogStatus blowing away the
// last error, so save it here and assert that it does not
// get changed.
//
DWORD dwTempError = GetLastError();
_phttpReq->SetLogStatus( HT_SERVER_ERROR, dwTempError );
_phttpReq->WriteLogRecord();
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
_phttpReq->Disconnect( HT_SERVER_ERROR, dwTempError );
// NOTE negative ref count ==> no change to ref count
CC_LOG_REF_COUNT( -_cRef );
}
Exit:
IF_DEBUG( CONNECTION )
{
DBGPRINTF((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
//
DBG_ASSERT( QueryRefCount() > 0 );
return TRUE;
} // CLIENT_CONN::DoWork
/*******************************************************************
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(
BOOL *pfDoAgain,
PVOID pvInitial,
DWORD cbInitial,
BOOL fFirst
)
{
APIERR err = NO_ERROR;
//
// Associate our client socket with Atq if it hasn't already
//
if ( !QueryAtqContext() )
{
DBG_ASSERT( pvInitial == NULL );
if ( !AtqAddAsyncHandle( &_AtqContext,
m_atqEndpointObject,
this,
W3Completion,
W3_DEF_CONNECTION_TIMEOUT,
(HANDLE) QuerySocket()))
{
DBGPRINTF(( DBG_CONTEXT,
"OnSessionStartup: failed to add Atq handle, error %lu\n",
GetLastError() ));
return FALSE;
}
}
SetState( CCS_PROCESSING_CLIENT_REQ );
return _phttpReq->StartNewRequest( pvInitial,
cbInitial,
fFirst,
pfDoAgain);
}
DWORD
ErrorResponseToSubStatus(
DWORD dwErrorResponse
)
/*++
Routine Description:
Map an 'ErrorResponse' to a substatus for use in custom error lookup.
Arguments:
dwErrorResponse - The error response to be mapped.
Return Value:
The substatus if we can map it, or 0 otherwise.
--*/
{
int i;
DBG_ASSERT((sizeof(ErrorRespTable)/sizeof(DWORD)) == (sizeof(SubStatusTable)/sizeof(DWORD)));
for (i = 0; i < sizeof(ErrorRespTable)/sizeof(DWORD);i++)
{
if (ErrorRespTable[i] == dwErrorResponse)
{
return SubStatusTable[i];
}
}
return 0;
}
#define ERROR_HTML_PREFIX "<head><title>Error</title></head><body>"
/*******************************************************************
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,
LPBOOL pfFinished )
{
CHAR *pszResp;
BOOL fDone;
DWORD dwSubStatus = 0;
STACK_STR(strError, 80);
STACK_STR(strResp, 80);
CHAR ach[17];
//
// If Disconnect has already been called, then this is a no-op
//
if ( QueryState() == CCS_SHUTDOWN )
{
return;
}
if ( _fAbortiveClose )
{
fDoShutdown = FALSE;
}
if ( pRequest && HTResponse )
{
STACK_STR(strBody, 200);
BOOL bBodyBuilt;
DWORD dwRespLength;
DWORD dwContentLength;
if ( HTResponse == HT_NOT_FOUND )
{
QueryW3StatsObj()->IncrTotalNotFoundErrors();
}
//
// Means we have to wait for the status response before closing
// the socket
//
SetState( CCS_DISCONNECTING );
IF_DEBUG( CONNECTION )
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Going to Disconnecting for %lx, ref = %d, response = %d\n",
this,
QueryRefCount(),
HTResponse ));
}
if ( ErrorResponse != 0)
{
dwSubStatus = ErrorResponseToSubStatus(ErrorResponse);
}
if (pRequest->CheckCustomError(&strError, HTResponse, dwSubStatus, &fDone,
&dwContentLength))
{
// Had at least some custom error processing. If we're done, just return.
if (fDone)
{
return;
}
// Otherwise we've got a custom error body. Build up the basic
// status line that we need, convert the length
// to ascii for use as a Content-Length, build up what we need in
// strBody, and keep going.
if ( !HTTP_REQ_BASE::BuildStatusLine( &strResp,
HTResponse,
0, pRequest->QueryURL(),
NULL))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to send status (error %d), aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
_itoa( dwContentLength, ach, 10 );
bBodyBuilt = strBody.Copy("Content-Length: ",
sizeof("Content-Length: ") - 1) &&
strBody.Append(ach) &&
strBody.Append("\r\n", sizeof("\r\n") - 1) &&
strBody.Append((CHAR *)strError.QueryPtr());
strResp.SetLen(strlen((CHAR *)strResp.QueryPtr()));
}
else
{
//
// No custom error, build the standard status line and body.
//
// Send the requested status response and after that completes close
// the socket
//
if (ErrorResponse != 0 && ErrorResponse < STR_RES_ID_BASE)
{
if (!strError.Copy(ERROR_HTML_PREFIX,
sizeof(ERROR_HTML_PREFIX) - 1))
{
goto DisconnectNow;
}
}
if ( !HTTP_REQ_BASE::BuildStatusLine( &strResp,
HTResponse,
ErrorResponse, pRequest->QueryURL(),
&strError))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to send status (error %d), aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
//
// Now build up the body.
strResp.SetLen(strlen((CHAR *)strResp.QueryPtr()));
dwRespLength = strResp.QueryCB() - CRLF_SIZE;
if (!strBody.Copy("Content-Type: text/html\r\n",
sizeof("Content-Type: text/html\r\n") - 1))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to build error message for error %d, aborting connecting\n",
HTResponse));
goto DisconnectNow;
}
// The body we'll build depends on whether or not there's an
// error string. If there is, then that's it, we'll just copy it
// in. If not, we'll fabricate a short HTML document from the
// status line.
if (strError.IsEmpty())
{
// No error string.
dwContentLength = (dwRespLength * 2) -
(sizeof("HTTP/1.1 XXX ") - 1) +
sizeof("<head><title>") - 1 +
sizeof("<html></title></head>") - 1 +
sizeof("<body><h1>") - 1 +
sizeof("</h1></body></html>") - 1;
_itoa( dwContentLength, ach, 10 );
bBodyBuilt = strBody.Append("Content-Length: ",
sizeof("Content-Length: ") - 1) &&
strBody.Append(ach) &&
strBody.Append("\r\n\r\n", sizeof("\r\n\r\n") - 1) &&
strBody.Append("<html><head><title>",
sizeof("<html><head><title>") - 1) &&
strBody.Append(strResp.QueryStr() +
sizeof("HTTP/1.1 XXX ") - 1,
dwRespLength -
(sizeof("HTTP/1.1 XXX ") - 1)) &&
strBody.Append("</title></head>",
sizeof("</title></head>") - 1) &&
strBody.Append("<body><h1>",
sizeof("<body><h1>") - 1) &&
strBody.Append( strResp.QueryStr(), dwRespLength) &&
strBody.Append( "</h1></body></html>",
sizeof("</h1></body></html>") - 1 );
}
else
{
dwContentLength = strError.QueryCCH() + sizeof("</body>") - 1
+ sizeof("<html></html>") - 1;
_itoa( dwContentLength, ach, 10 );
bBodyBuilt = strBody.Append("Content-Length: ",
sizeof("Content-Length: ") - 1) &&
strBody.Append(ach) &&
strBody.Append("\r\n\r\n<html>", sizeof("\r\n\r\n<html>") - 1) &&
strBody.Append(strError) &&
strBody.Append("</body></html>", sizeof("</body></html>") - 1);
}
}
if (!bBodyBuilt)
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to build error message for error %d, aborting connecting\n",
HTResponse));
goto DisconnectNow;
}
pRequest->SetKeepConn(FALSE);
pRequest->SetAuthenticationRequested(FALSE);
fDone = FALSE;
if ( !pRequest->BuildBaseResponseHeader( pRequest->QueryRespBuf(),
&fDone,
&strResp,
HTTPH_NO_CUSTOM))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to send status (error %d), aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
DBG_ASSERT(!fDone);
pszResp = pRequest->QueryRespBufPtr();
dwRespLength = strlen(pszResp);
if (HTResponse == HT_SVC_UNAVAILABLE)
{
CHAR *pszRespEnd;
// Need to append a 'Retry-After' header in this case.
if ((dwRespLength + 1 + g_dwPutTimeoutStrlen) >
pRequest->QueryRespBuf()->QuerySize())
{
// Resize the buffer.
if (!pRequest->QueryRespBuf()->Resize(dwRespLength + 1 + g_dwPutTimeoutStrlen))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Unable to resize response buf for status %d, aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
pszResp = pRequest->QueryRespBufPtr();
}
pszRespEnd = pszResp + dwRespLength;
APPEND_STRING(pszRespEnd, "Retry-After: ");
memcpy(pszRespEnd, g_szPutTimeoutString, g_dwPutTimeoutStrlen);
pszRespEnd += g_dwPutTimeoutStrlen;
APPEND_STRING(pszRespEnd, "\r\n");
dwRespLength = DIFF(pszRespEnd - pszResp);
}
if (HTResponse == HT_METHOD_NOT_ALLOWED)
{
if ((dwRespLength + 1 + MAX_ALLOW_SIZE) >
pRequest->QueryRespBuf()->QuerySize())
{
// Resize the buffer.
if (!pRequest->QueryRespBuf()->Resize(dwRespLength + 1 + MAX_ALLOW_SIZE))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Unable to resize response buf for status %d, aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
pszResp = pRequest->QueryRespBufPtr();
}
dwRespLength += pRequest->BuildAllowHeader(
pRequest->QueryURL(),
pszResp + dwRespLength
);
}
if ((dwRespLength + 1 + strBody.QueryCB()) > pRequest->QueryRespBuf()->QuerySize() )
{
if (!pRequest->QueryRespBuf()->Resize(dwRespLength + 1 + strBody.QueryCB()))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Unable to resize response buf for status %d, aborting connecting\n",
::GetLastError()));
goto DisconnectNow;
}
pszResp = pRequest->QueryRespBufPtr();
}
// If this is an error response to a head request, truncate it now.
if (pRequest->QueryVerb() == HTV_HEAD)
{
CHAR *pszHeaderEnd;
DWORD dwBodySize;
pszHeaderEnd = strstr(strBody.QueryStr(), "\r\n\r\n");
if (pszHeaderEnd == NULL)
{
DBG_ASSERT(FALSE);
goto DisconnectNow;
}
dwBodySize = DIFF(pszHeaderEnd - strBody.QueryStr())
+ sizeof("\r\n\r\n") - 1;
strBody.SetLen(dwBodySize);
}
memcpy( pszResp + dwRespLength, strBody.QueryStr(),
strBody.QueryCCH() + 1 );
if ( !pRequest->SendHeader( pszResp,
dwRespLength + strBody.QueryCCH(),
IO_FLAG_ASYNC,
&fDone ))
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Failed to send status (error %d), aborting connecting\n",
::GetLastError()));
//
// It's possible a filter failed the writefile, cause the
// filter code to issue a disconnect by the time we get here,
// so recheck the state
//
if ( QueryState() != CCS_SHUTDOWN )
{
goto DisconnectNow;
}
}
if ( fDone )
{
goto DisconnectNow;
}
}
else
{
DisconnectNow:
IF_DEBUG( CONNECTION )
{
DBGPRINTF((DBG_CONTEXT,
"Disconnect: Going to Shutdown for %lx, ref count = %d\n",
this,
QueryRefCount() ));
}
if ( pfFinished )
{
*pfFinished = TRUE;
}
SetState( CCS_SHUTDOWN );
//
// Do a shutdown to avoid a nasty client reset when:
//
// The client sent more entity data then they indicated (very common)
// and we may not have received all of it in our initial receive
// buffer OR
//
// This was a CGI request. The exiting process will cause the
// socket handle to be deleted but NT will force a reset on the
// socket because the process didn't do a close/shutdown (the
// process inherited this socket handle). Atq does the right thing
// when using TransmitFile.
//
#if CC_REF_TRACKING
CC_LOG_REF_COUNT( -_cRef );
#endif
AtqCloseSocket( QueryAtqContext(),
fDoShutdown );
//
// This removes the last reference count to *this except for
// any outstanding IO requests
//
Dereference();
}
}
/*******************************************************************
NAME: CLIENT_CONN::DisconnectAllUsers
SYNOPSIS: Static method that walks the connection list and disconnects
each active connection.
ENTRY: Instance - Pointer to a server instance. If NULL, then all
users are disconnected. If !NULL, then only those users
associated with the specified instance are disconnected.
HISTORY:
Johnl 02-Sep-1994 Created
********************************************************************/
VOID CLIENT_CONN::DisconnectAllUsers( PIIS_SERVER_INSTANCE Instance )
{
LIST_ENTRY * pEntry;
CLIENT_CONN * pconn;
DBGPRINTF((DBG_CONTEXT,
"DisconnectAllUsers entered\n"));
LockGlobals();
for ( pEntry = listConnections.Flink;
pEntry != &listConnections;
pEntry = pEntry->Flink )
{
pconn = CONTAINING_RECORD( pEntry, CLIENT_CONN, ListEntry );
DBG_ASSERT( pconn->CheckSignature() );
if( Instance == NULL ||
Instance == (PIIS_SERVER_INSTANCE)pconn->QueryW3Instance() ) {
#if CC_REF_TRACKING
//
// log to our various ref logs
//
// NOTE negative indicates no change to ref count
//
//
// log to local (per-object) CLIENT_CONN log
//
LogRefCountCCLocal(
- pconn->_cRef
, pconn
, pconn->_phttpReq
, NULL
, (pconn->_phttpReq ? pconn->_phttpReq->QueryState() : 0)
);
#endif
//
// If we've already posted a TransmitFile() for the connection
// before being asked
// to shutdown, then AtqCloseSocket() by itself will not close
// the socket (because by default, we will pass TF_DISCONNECT|
// TS_REUSE_SOCKET and ATQ will assume WinSock will take care of
// it). The end result is that we will be stuck
// waiting for the completion (for 2 minutes). Let's force the
// issue
//
AtqContextSetInfo( pconn->QueryAtqContext(),
ATQ_INFO_FORCE_CLOSE,
TRUE );
AtqCloseSocket( pconn->QueryAtqContext(),
FALSE );
}
}
UnlockGlobals();
}
/*******************************************************************
NAME: CLIENT_CONN::Reference
SYNOPSIS: Increments the reference count.
HISTORY:
DaveK 10-Sep-1997 Added ref trace logging
********************************************************************/
UINT CLIENT_CONN::Reference( VOID )
{
LONG cRefs = InterlockedIncrement( &_cRef );
CC_LOG_REF_COUNT( cRefs );
return cRefs;
}
/*******************************************************************
NAME: CLIENT_CONN::Dereference
SYNOPSIS: Increments the reference count.
HISTORY:
DaveK 10-Sep-1997 Added ref trace logging
********************************************************************/
UINT CLIENT_CONN::Dereference( VOID )
{
//
// Write the trace log BEFORE the decrement operation :(
// If we write it after the decrement, we will run into potential
// race conditions in this object getting freed up accidentally
// by another thread
//
// NOTE we write (_cRef - 1) == ref count AFTER decrement happens
//
LONG cRefsAfter = (_cRef - 1);
CC_LOG_REF_COUNT( cRefsAfter );
return InterlockedDecrement( &_cRef );
}
/*******************************************************************
More CLIENT_CONN methods
SYNOPSIS: More methods.
.
HISTORY:
DaveK 10-Sep-1997 Added this comment
********************************************************************/
DWORD
CLIENT_CONN::Initialize(
VOID
)
{
DWORD msScavengeTime = ACACHE_REG_DEFAULT_CLEANUP_INTERVAL;
HKEY hkey;
INITIALIZE_CRITICAL_SECTION( &_csBuffList );
InitializeListHead( &_BuffListHead );
#if CC_REF_TRACKING
g_pDbgCCRefTraceLog = CreateRefTraceLog( C_CLIENT_CONN_REFTRACES, 0 );
#endif
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
ACACHE_REG_PARAMS_REG_KEY,
0,
KEY_READ,
&hkey ) == NO_ERROR )
{
msScavengeTime = ReadRegistryDword( hkey,
ACACHE_REG_LOOKASIDE_CLEANUP_INTERVAL,
ACACHE_REG_DEFAULT_CLEANUP_INTERVAL );
DBG_REQUIRE( !RegCloseKey( hkey ));
}
msScavengeTime *= 1000; // Convert to milliseconds
_FreeListScavengerCookie = ScheduleWorkItem( ClientConnTrimScavenger,
NULL,
msScavengeTime,
TRUE );
_fGlobalInit = TRUE;
return NO_ERROR;
}
VOID
CLIENT_CONN::Terminate(
VOID
)
{
CLIENT_CONN * pConn;
if ( !_fGlobalInit )
{
return;
}
RemoveWorkItem( _FreeListScavengerCookie );
EnterCriticalSection( &_csBuffList );
while ( !IsListEmpty( &_BuffListHead ))
{
pConn = CONTAINING_RECORD( _BuffListHead.Flink,
CLIENT_CONN,
_BuffListEntry );
DBG_ASSERT( pConn->_Signature == CLIENT_CONN_SIGNATURE_FREE );
RemoveEntryList( &pConn->_BuffListEntry );
delete pConn;
}
LeaveCriticalSection( &_csBuffList );
DeleteCriticalSection( &_csBuffList );
#if CC_REF_TRACKING
DestroyRefTraceLog( g_pDbgCCRefTraceLog );
g_pDbgCCRefTraceLog = NULL;
#endif
}
CLIENT_CONN *
CLIENT_CONN::Alloc(
IN PCLIENT_CONN_PARAMS ClientParams
)
{
CLIENT_CONN * pConn;
EnterCriticalSection( &_csBuffList );
if ( !IsListEmpty( &_BuffListHead ))
{
pConn = CONTAINING_RECORD( _BuffListHead.Flink,
CLIENT_CONN,
_BuffListEntry );
RemoveEntryList( &pConn->_BuffListEntry );
_cFree--;
LeaveCriticalSection( &_csBuffList );
DBG_ASSERT( pConn->_Signature == CLIENT_CONN_SIGNATURE_FREE );
pConn->Initialize( ClientParams );
DBG_ASSERT( pConn->CheckSignature() );
return pConn;
}
LeaveCriticalSection( &_csBuffList );
return new CLIENT_CONN( ClientParams );
}
VOID
CLIENT_CONN::Free(
CLIENT_CONN * pConn
)
{
DBG_ASSERT( pConn->CheckSignature() );
pConn->Reset();
EnterCriticalSection( &_csBuffList );
InsertHeadList( &_BuffListHead,
&pConn->_BuffListEntry );
_cFree++;
LeaveCriticalSection( &_csBuffList );
return;
}
VOID
CLIENT_CONN::TrimFreeList(
VOID
)
{
CLIENT_CONN * pConn;
LIST_ENTRY ListEntry;
// Hold the lock only to reset the list to empty. Do the actual
// traversal and deletion out side the lock.
EnterCriticalSection( &_csBuffList );
if ( IsListEmpty( &_BuffListHead ) )
{
LeaveCriticalSection( &_csBuffList );
DBG_ASSERT( _cFree == 0 );
return;
}
else
{
ListEntry = _BuffListHead;
ListEntry.Blink->Flink = &ListEntry;
ListEntry.Flink->Blink = &ListEntry;
_cFree = 0;
InitializeListHead( &_BuffListHead );
}
LeaveCriticalSection( &_csBuffList );
while ( !IsListEmpty( &ListEntry ))
{
pConn = CONTAINING_RECORD( ListEntry.Flink,
CLIENT_CONN,
_BuffListEntry );
DBG_ASSERT( pConn->_Signature == CLIENT_CONN_SIGNATURE_FREE );
RemoveEntryList( &pConn->_BuffListEntry );
delete pConn;
}
}
VOID
WINAPI
ClientConnTrimScavenger(
PVOID pContext
)
{
CLIENT_CONN::TrimFreeList();
}
/*******************************************************************
NAME: ::W3Completion
SYNOPSIS: Completion routine for W3 Atq requests
HISTORY:
Johnl 20-Aug-1994 Created
********************************************************************/
VOID W3Completion( PVOID Context,
DWORD BytesWritten,
DWORD CompletionStatus,
OVERLAPPED * lpo )
{
CLIENT_CONN * pConn = (CLIENT_CONN *) Context;
DBG_ASSERT( pConn );
DBG_ASSERT( pConn->CheckSignature() );
#if 0
DBGPRINTF((DBG_CONTEXT,
"W3Completion( %08lx ) byteswritten = %d, status = %08lx, lpo = %08lx\n",
Context, BytesWritten, CompletionStatus, lpo ));
#endif
if ( !((W3_IIS_SERVICE*)g_pInetSvc)->GetReferenceCount() )
{
return;
}
W3_IIS_SERVICE::ReferenceW3Service( g_pInetSvc );
#if CC_REF_TRACKING
//
// ATQ notification trace
//
// Check for magic signature of ATQ notification
// ATQ generates such a notification for all non-oplock completion
//
if ( ((DWORD)(LPVOID)lpo & 0xf0f0f0f0) == 0xf0f0f0f0 )
{
pConn->NotifyAtqProcessContext( BytesWritten,
CompletionStatus,
(DWORD)(LPVOID)lpo );
W3_IIS_SERVICE::DereferenceW3Service( g_pInetSvc );
return;
}
#endif
ReferenceConn( pConn );
if ( lpo != NULL )
{
lpo->Offset = 0;
}
TCP_REQUIRE( pConn->DoWork( BytesWritten,
CompletionStatus,
lpo != NULL ));
DereferenceConn( pConn );
W3_IIS_SERVICE::DereferenceW3Service( g_pInetSvc );
}
#if CC_REF_TRACKING
VOID
CLIENT_CONN::NotifyAtqProcessContext(
DWORD BytesWritten,
DWORD CompletionStatus,
DWORD dwSig
)
/*++
Routine Description:
Store ATQ notification in ref log
Arguments:
BytesWritten - count of bytes written
CompletionStatus - completion status
Return Value:
Nothing
--*/
{
//
// Store current ref count as checkpoint
//
CCA_LOG_REF_COUNT( -_cRef, BytesWritten, CompletionStatus, dwSig );
}
#endif
/*******************************************************************
NAME: ::CheckForTermination
SYNOPSIS: Looks in the passed buffer for a line followed by a blank
line. If not found, the buffer is resized.
ENTRY: pfTerminted - Set to TRUE if this block is terminated
pbuff - Pointer to buffer data
cbData - Size of pbuff
ppbExtraData - Receives a pointer to the first byte
of extra data following the header
pcbExtraData - Number of bytes in data following the header
cbReallocSize - Increase buffer by this number of bytes
if the terminate isn't found
RETURNS: TRUE if successful, FALSE otherwise
HISTORY:
Johnl 28-Sep-1994 Created
********************************************************************/
BOOL CheckForTermination( BOOL * pfTerminated,
BUFFER * pbuff,
UINT cbData,
BYTE * * ppbExtraData,
DWORD * pcbExtraData,
UINT cbReallocSize )
{
DWORD cbNewSize;
cbNewSize = cbData + 1 + cbReallocSize;
if ( !pbuff->Resize( cbNewSize ) )
{
return FALSE;
}
//
// Terminate the string but make sure it will fit in the
// buffer
//
CHAR * pchReq = (CHAR *) pbuff->QueryPtr();
*(pchReq + cbData) = '\0';
//
// Scan for double end of line marker
//
//
// if do not care for ptr info, can use fast method
//
if ( ppbExtraData == NULL )
{
if ( FastScanForTerminator( pchReq, cbData )
|| ScanForTerminator( pchReq ) )
{
*pfTerminated = TRUE;
return TRUE;
}
goto not_term;
}
*ppbExtraData = ScanForTerminator( pchReq );
if ( *ppbExtraData )
{
*pcbExtraData = cbData - DIFF(*ppbExtraData - (BYTE *) pchReq);
*pfTerminated = TRUE;
return TRUE;
}
not_term:
if ( cbNewSize > g_cbMaxClientRequestBuffer )
{
SetLastError( ERROR_INSUFFICIENT_BUFFER );
return FALSE;
}
*pfTerminated = FALSE;
return TRUE;
}
BOOL
CLIENT_CONN::RequestAbortiveClose(
)
/*++
Routine Description:
Request for abortive close on disconnect
Arguments:
None
Returns:
TRUE if successful, else FALSE
--*/
{
_fAbortiveClose = TRUE;
AtqContextSetInfo( QueryAtqContext(), ATQ_INFO_ABORTIVE_CLOSE, TRUE );
return TRUE;
}
BOOL
CLIENT_CONN::CloseConnection(
)
/*++
Routine Description:
Cancels any pending async IO by closing the socket
Arguments:
None
Returns:
TRUE if successful, else FALSE
--*/
{
return AtqCloseSocket( QueryAtqContext(), FALSE );
}
#if 0
BOOL
CLIENT_CONN::CheckIpAccess(
LPBOOL pfGranted,
LPBOOL pfNeedDns,
LPBOOL pfComplete
)
/*++
Routine Description:
Check IP access granted
Arguments:
pfGranted - updated with grant status
pfNeedDns - updated with TRUE if need DNS name for DNS check
pfComplete - updated with TRUE is check complete
Return Value:
TRUE if no error, otherwise FALSE
--*/
{
return _acCheck.CheckIpAccess( pfGranted, pfNeedDns, pfComplete );
}
BOOL
CLIENT_CONN::CheckDnsAccess(
LPBOOL pfGranted
)
/*++
Routine Description:
Check DNS access granted
Arguments:
pfGranted - updated with grant status
Return Value:
TRUE if no error, otherwise FALSE
--*/
{
return _acCheck.CheckDnsAccess( pfGranted );
}
#endif
VOID
DumpW3InfoToHTML(
OUT CHAR * pch,
IN OUT LPDWORD pcb
)
{
DWORD cb = 0;
DBG_ASSERT( *pcb > 1024 );
cb += wsprintf( pch + cb, "<TABLE BORDER>" );
cb += wsprintf( pch + cb, "<TR><TD>Current Connections: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryCurrentConnections() );
cb += wsprintf( pch + cb, "<TR><TD>Connections Attempts: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryConnectionAttempts() );
cb += wsprintf( pch + cb, "<TR><TD>Current CGIs: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryCurrentCGIRequests() );
cb += wsprintf( pch + cb, "<TR><TD>Total CGIs: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryTotalCGIRequests() );
cb += wsprintf( pch + cb, "<TR><TD>Current ISAPI Requests: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryCurrentBGIRequests() );
cb += wsprintf( pch + cb, "<TR><TD>Total ISAPI Requests: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryTotalBGIRequests() );
cb += wsprintf( pch + cb, "<TR><TD>Current Connections: </TD><TD>%d</TD></TR>", g_pW3Stats->QueryCurrentConnections() );
cb += wsprintf( pch + cb, "<TR><TD>Free CLIENT_CONN list: </TD><TD>%d</TD></TR>", CLIENT_CONN::QueryFreeListSize() );
cb += wsprintf( pch + cb, "</TABLE><p>" );
DBG_ASSERT( *pcb > cb );
*pcb = cb;
}