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.
 
 
 
 
 
 

2861 lines
83 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1994 **/
/**********************************************************************/
/*
httpio.cxx
This module contains the IO related http class methods
FILE HISTORY:
Johnl 09-Feb-1995 Created
*/
#include <w3p.hxx>
#include <issperr.h>
//
// Size of read during cert renegotiation phase
//
#define CERT_RENEGO_READ_SIZE (1024*4)
//
// size of buffer for calls to GetTokenInformation
//
#define MAX_TOKEN_USER_INFO (300)
BOOL
HTTP_REQ_BASE::StartNewRequest(
PVOID pvInitialBuff,
DWORD cbInitialBuff,
BOOL fFirst,
BOOL *pfDoAgain
)
/*++
Routine Description:
Sets up this request object for reading a new request and issues the async
read to kick things off.
Arguments:
--*/
{
//
// Set our initial state and variables for a new request, after
// checking to see if we might have a pipelined request.
//
if (!fFirst && (_cbBytesReceived > _cbClientRequest))
{
CHAR *pchRequestPtr;
DWORD dwNextRequestSize = 0;
// Might possibly have a pipelined request. We do if there is
// no entity body or there is but we didn't consume all of
// the entity body on the previous request.
//
if (!IsChunked() && (QueryTotalEntityBodyCB() == 0))
{
//
// Have a pipelined request and the last request wasn't chunked,
// since we read more data than just the request header but there was
// no entity body with the request.
//
dwNextRequestSize = _cbBytesReceived - _cbClientRequest;
pchRequestPtr = (CHAR *)_bufClientRequest.QueryPtr() +
_cbClientRequest;
}
else
{
if (_cbExtraData != 0)
{
// Have extra data in the buffer, so have a pipelined
// request.
pchRequestPtr = _pchExtraData;
dwNextRequestSize = _cbExtraData;
}
}
// Update bytes received to reflect what we would have seen
// in the non-pipelined case.
_cbBytesReceived -= dwNextRequestSize;
//
// If we have a pipelined request, copy the request forward and
// update the counts.
//
if (dwNextRequestSize != 0)
{
Reset(FALSE);
memcpy((CHAR *)_bufClientRequest.QueryPtr(),
pchRequestPtr,
dwNextRequestSize);
_cbBytesWritten = dwNextRequestSize;
// Return, forcing the reprocess.
*pfDoAgain = TRUE;
return TRUE;
}
}
Reset(TRUE);
*pfDoAgain = FALSE;
//
// Prepare a buffer to receive the client's request
//
if ( !_bufClientRequest.Resize( max( W3_DEFAULT_BUFFSIZE, cbInitialBuff )))
{
DBGPRINTF(( DBG_CONTEXT,
"[StartNewRequest] failed to allocate buffer, error %lu\n",
GetLastError()));
return FALSE;
}
//
// Make the IO request if an inital buffer wasn't supplied
//
if ( pvInitialBuff != NULL )
{
CopyMemory(
_bufClientRequest.QueryPtr(),
pvInitialBuff,
cbInitialBuff );
_cbBytesWritten = cbInitialBuff;
}
else
{
SetState( HTR_READING_CLIENT_REQUEST );
IF_DEBUG( CONNECTION )
{
DBGPRINTF(( DBG_CONTEXT,
"[StartNewRequest] Issuing initial read, Conn = %lx, AtqCont = %lx\n",
QueryClientConn(),
QueryClientConn()->QueryAtqContext() ));
}
//
// Do the initial read. We don't go through any filters at this
// point. They'll get notified on the read completion as part of
// the raw data notification.
//
if ( !ReadFile( _bufClientRequest.QueryPtr(),
_bufClientRequest.QuerySize(),
NULL,
IO_FLAG_ASYNC | IO_FLAG_NO_FILTER))
{
DBGPRINTF(( DBG_CONTEXT,
"[StartNewRequest] ReadFile failed, error %lu\n",
GetLastError() ));
return FALSE;
}
}
return TRUE;
}
/*******************************************************************
NAME: HTTP_REQ_BASE::OnFillClientReq
SYNOPSIS: Waits for the full client request packet then decides
the course of action
ENTRY: pfCompleteRequest - Set to TRUE if we've received a full
client request and we can start processing the request
pfFinished - Set to TRUE if no further processing is requred
pfContinueProcessingRequest - Set to FALSE if we must stop processing
request
RETURNS: TRUE if processing should continue, FALSE to abort the
this connection
HISTORY:
Johnl 22-Aug-1994 Created
********************************************************************/
BOOL
HTTP_REQ_BASE::OnFillClientReq(
BOOL * pfCompleteRequest,
BOOL * pfFinished,
BOOL * pfContinueProcessingRequest
)
{
BYTE * pbData = NULL;
*pfCompleteRequest = FALSE;
*pfContinueProcessingRequest = TRUE;
_cbClientRequest += QueryBytesWritten();
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( QueryBytesWritten() == 0 )
{
DBGPRINTF(( DBG_CONTEXT,
"[OnFillClientReq] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetKeepConn( FALSE );
*pfFinished = TRUE;
return TRUE;
}
//NTBUG 264445 QFE and NTBUG 266474
_msStartRequest = GetCurrentTime();
if ( !UnWrapRequest( pfCompleteRequest,
pfFinished,
pfContinueProcessingRequest))
{
return FALSE;
}
if ( *pfCompleteRequest || *pfFinished || !*pfContinueProcessingRequest)
{
return TRUE;
}
//
// We still don't have a complete header, so keep reading
//
DBG_ASSERT(!*pfCompleteRequest);
if ( !ReadFile( (BYTE *)_bufClientRequest.QueryPtr() + _cbClientRequest,
_bufClientRequest.QuerySize() - _cbClientRequest,
NULL,
IO_FLAG_ASYNC | IO_FLAG_NO_FILTER ))
{
DBGPRINTF(( DBG_CONTEXT,
"[OnFillClientReq] ReadFile failed, error %lu\n",
GetLastError() ));
return FALSE;
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::HandleCertRenegotiation(
BOOL * pfFinished,
BOOL * pfContinueProcessingRequest,
DWORD cbData
)
/*++
Routine Description:
Calls the installed read filters
Arguments:
pfFinished - No further processing is required for this request
pfContinueProcessingRequest - Set to FALSE if we have must stop processing request
Return Value:
TRUE on success, FALSE on failure
--*/
{
BYTE * pbData = NULL;
TCHAR * pchOutRequest;
DWORD cbOutRequest;
DWORD cbProcessed;
BOOL fTerminated;
BOOL fReadAgain = FALSE;
TCHAR * pchNewData;
DWORD cbOutRequestSave;
*pfContinueProcessingRequest = TRUE;
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( QueryBytesWritten() == 0 )
{
TCP_PRINT(( DBG_CONTEXT,
"[HandleCertRenegotiation] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
//
// Notify any opaque filters of the incoming data
//
cbProcessed = _cbClientRequest + _cbEntityBody;
pchNewData = (LPSTR)_bufClientRequest.QueryPtr() + cbProcessed;
if ( !_Filter.NotifyRawReadDataFilters(
pchNewData,
(cbOutRequestSave = _cbOldData + cbData - cbProcessed),
_bufClientRequest.QuerySize() -
cbProcessed, // Usable buffer size
(PVOID *) &pchOutRequest,
&cbOutRequest,
pfFinished,
&fReadAgain ))
{
return FALSE;
}
if ( *pfFinished )
{
return TRUE;
}
//
// If the output buffer is different, then we need to copy
// the data to our output buffer
//
// CODEWORK: Get rid of this buffer copy - there are assumptions the
// incoming data is contained in _bufClientRequest
//
if ( pchOutRequest != NULL &&
pchOutRequest != pchNewData )
{
if ( !_bufClientRequest.Resize( cbOutRequest + cbProcessed + 1 ))
return FALSE;
pchNewData = (LPSTR)_bufClientRequest.QueryPtr() + cbProcessed;
memcpy( pchNewData,
pchOutRequest,
cbOutRequest );
}
if ( fReadAgain )
{
_cbOldData = cbProcessed + cbOutRequest;
goto nextread;
}
//
// A filter may have changed the size of our effective input buffer
//
_cbEntityBody += cbOutRequest;
_cbOldData = _cbClientRequest + _cbEntityBody;
if ( cbOutRequestSave > cbOutRequest )
{
DBG_ASSERT( _cbBytesReceived >= cbOutRequestSave - cbOutRequest );
_cbBytesReceived -= cbOutRequestSave - cbOutRequest;
}
else
{
_cbBytesReceived += cbOutRequest - cbOutRequestSave;
}
if ( _dwRenegotiated )
{
cbData = _cbEntityBody;
_cbRestartBytesWritten = cbData;
_cbEntityBody = 0;
return OnRestartRequest( (LPSTR)_bufClientRequest.QueryPtr(),
cbData,
pfFinished,
pfContinueProcessingRequest );
}
nextread:
DWORD cbNextRead = CERT_RENEGO_READ_SIZE;
if ( !_bufClientRequest.Resize( _cbOldData + cbNextRead ))
{
return FALSE;
}
*pfContinueProcessingRequest = FALSE;
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbOldData,
cbNextRead,
NULL,
IO_FLAG_ASYNC|IO_FLAG_NO_FILTER ))
{
return FALSE;
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::UnWrapRequest(
BOOL * pfCompleteRequest,
BOOL * pfFinished,
BOOL * pfContinueProcessingRequest
)
/*++
Routine Description:
Calls the installed filters to unwrap the client request
Arguments:
pfCompleteRequest - Set to TRUE if we've received a full
client request and we can start processing the request
pfFinished - No further processing is required for this request
pfContinueProcessingRequest - Set to FALSE if we're done for now
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fHandled = FALSE;
TCHAR * pchOutRequest;
DWORD cbOutRequest;
BOOL fTerminated = FALSE;
BOOL fReadAgain;
//
// Notify any opaque filters of the incoming data
//
if ( _Filter.IsNotificationNeeded( SF_NOTIFY_READ_RAW_DATA,
IsSecurePort() ))
{
DWORD cbOutRequestSave;
//
// Make sure filters don't see the same data twice unless they
// returned "read again" the last time
//
CHAR * pchNewData = (CHAR *) _bufClientRequest.QueryPtr() +
_cbOldData;
pchOutRequest = pchNewData;
cbOutRequestSave = cbOutRequest = _cbClientRequest - _cbOldData;
fReadAgain = FALSE;
if ( !_Filter.NotifyRawReadDataFilters(
pchNewData,
cbOutRequest,
_bufClientRequest.QuerySize() -
_cbOldData, // Usable buffer size
(PVOID *) &pchOutRequest,
&cbOutRequest,
&fHandled,
&fReadAgain ))
{
return FALSE;
}
if ( fHandled )
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
//
// If the output buffer is different, then we need to copy
// the data to our output buffer
//
// CODEWORK: Get rid of this buffer copy - there are assumptions the
// incoming data is contained in _bufClientRequest
//
if ( pchOutRequest != NULL &&
pchOutRequest != pchNewData )
{
if ( !_bufClientRequest.Resize( cbOutRequest + _cbOldData + 1 ))
return FALSE;
pchNewData = (CHAR *) _bufClientRequest.QueryPtr() +
_cbOldData;
memcpy( pchNewData,
pchOutRequest,
cbOutRequest );
}
//
// A filter may have changed the size of our effective input buffer
//
if ( cbOutRequestSave > cbOutRequest )
{
DBG_ASSERT(_cbBytesReceived >= cbOutRequestSave - cbOutRequest);
_cbBytesReceived -= cbOutRequestSave - cbOutRequest;
}
else
{
_cbBytesReceived += cbOutRequest - cbOutRequestSave;
}
//
// Variable names are not consistent with what is used
// in HandleCertRenegotiation : here _cbClientRequest
// indicates where to continue reading data, and _cbOldData
// is # of decoded bytes in client request
//
_cbClientRequest = cbOutRequest + _cbOldData;
//
// Can we continue processing this request? The message just received
// may have been a session negotiation message and we have yet to
// receive the real HTTP request.
//
if ( fReadAgain )
{
//
// Resize the read buffer and issue an async read to get the next
// chunk for the filter. UnwrapRequest uses the size of
// this buffer as the size of data to read
//
if ( !_bufClientRequest.Resize( _cbClientRequest +
_Filter.QueryNextReadSize() ))
{
return FALSE;
}
return TRUE;
}
}
//
// Remember how much data the filter has already seen so we don't
// renotify them with the same data in case we don't have a full
// set of headers
//
_cbOldData = _cbClientRequest;
if ( !CheckForTermination( &fTerminated,
&_bufClientRequest,
_cbClientRequest,
NULL,
NULL,
W3_DEFAULT_BUFFSIZE ) )
{
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
{
SetState( HTR_DONE, HT_BAD_REQUEST, ERROR_INSUFFICIENT_BUFFER );
Disconnect( HT_BAD_REQUEST, NO_ERROR, TRUE, pfFinished );
*pfCompleteRequest = TRUE;
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
return FALSE;
}
if ( !fTerminated && !::IsPointNine( (CHAR *) _bufClientRequest.QueryPtr() ) )
{
//
// We don't have a complete request, read more data
//
return TRUE;
}
*pfCompleteRequest = TRUE;
return OnCompleteRequest( (CHAR *) _bufClientRequest.QueryPtr(),
_cbClientRequest,
pfFinished,
pfContinueProcessingRequest );
}
/*******************************************************************
NAME: HTTP_REQ_BASE::ReadMoreEntityBody
SYNOPSIS: Attempts to read more of the entity body, resizing
the buffer if necessary
ENTRY: cbOffset - Offset in the buffer to read at.
cbSize - Size to read.
********************************************************************/
BOOL
HTTP_REQ_BASE::ReadMoreEntityBody(
DWORD cbOffset,
DWORD cbSize
)
{
if ( cbOffset + cbSize < cbSize )
{
//
// The counter wrapped. Entity Body is greater than the address space !!
//
return FALSE;
}
if ( _bufClientRequest.QuerySize() < (cbOffset + cbSize) )
{
if ( !_bufClientRequest.Resize( cbOffset + cbSize ) )
{
return FALSE;
}
}
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + cbOffset,
cbSize,
NULL,
IO_FLAG_ASYNC ))
{
return FALSE;
}
return TRUE;
}
/*******************************************************************
NAME: HTTP_REQ_BASE::ReadEntityBody
SYNOPSIS: Attempts to retrieve an entity body from the remote
client
ENTRY: pfDone - Set to TRUE when _cbContentLength bytes have
been read
fFirstRead - TRUE if this is the first read, FALSE on
subsequent reads.
dwMaxAmmountToRead - Finish when this amount is read
pfDisconnected - Set to TRUE if we disconnected (due to error)
HISTORY:
Johnl 03-Oct-1994 Created
********************************************************************/
BOOL
HTTP_REQ_BASE::ReadEntityBody(
BOOL *pfDone,
BOOL fFirstRead,
DWORD dwMaxAmountToRead,
BOOL *pfDisconnected
)
{
DWORD cbNextRead;
if ( pfDisconnected )
{
*pfDisconnected = FALSE;
}
if (dwMaxAmountToRead == 0)
{
dwMaxAmountToRead = QueryMetaData()->QueryUploadReadAhead();
}
if (!IsChunked())
{
_cbEntityBody += QueryBytesWritten();
_cbTotalEntityBody += QueryBytesWritten();
if ( _cbTotalEntityBody >= _cbContentLength)
{
//
// Ugh - disgusting, but if we have no content length then
// we take whatever we have in the buffer currently and return
// it. This can lead to random behavior, depending on what
// actually makes it in the first read. I hate to do this, but
// we're doing it this way because of bug-for-bug compatibility
// with IIS 3.0. Probably the right thing to do if we have no
// content length is to keep reading until we get 0 bytes read,
// and return whatever we read as the content length. JohnL
// argues that the right thing is to fail the request in the
// absence of a content length.
if (_fHaveContentLength && (_cbTotalEntityBody > _cbContentLength))
{
_cbExtraData = _cbTotalEntityBody - _cbContentLength;
DBG_ASSERT(_cbExtraData <= _cbEntityBody);
_cbEntityBody -= _cbExtraData;
_pchExtraData = (CHAR *)_bufClientRequest.QueryPtr() +
_cbClientRequest +
_cbEntityBody;
_cbTotalEntityBody = _cbContentLength;
}
*pfDone = TRUE;
return TRUE;
}
if (_cbEntityBody >= dwMaxAmountToRead)
{
*pfDone = TRUE;
return TRUE;
}
//
// If no bytes were read on the last request, then the socket has been
// closed, so abort everything and get out
//
if ( !fFirstRead && QueryBytesWritten() == 0 )
{
DBGPRINTF(( DBG_CONTEXT,
"[ReadEntityBody] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
*pfDone = FALSE;
cbNextRead = min( (dwMaxAmountToRead - _cbEntityBody),
(_cbContentLength - _cbTotalEntityBody ));
if ( _bufClientRequest.QuerySize() <
(_cbClientRequest + _cbEntityBody + cbNextRead))
{
if ( !_bufClientRequest.Resize( _cbClientRequest + _cbEntityBody + cbNextRead ))
{
return FALSE;
}
}
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbEntityBody +
_cbClientRequest,
cbNextRead,
NULL,
IO_FLAG_ASYNC ))
{
return FALSE;
}
return TRUE;
} else
{
DWORD cbBytesInBuffer;
BYTE *ChunkHeader;
*pfDone = FALSE;
// We'll just return here if the app says they want to
// read it all. This might not be the right thing to do unless
// they also call this routine to read the rest of the data,
// because there could be a partial chunk in here. So we'll
// make sure that there isn't, or force the client to send a
// C-L.
if (dwMaxAmountToRead == 0)
{
if (QueryBytesWritten() != 0)
{
SetState( HTR_DONE, HT_LENGTH_REQUIRED, ERROR_NOT_SUPPORTED );
Disconnect( HT_LENGTH_REQUIRED, IDS_LENGTH_REQUIRED, FALSE, pfDone );
if ( pfDisconnected )
{
*pfDisconnected = TRUE;
}
}
else
{
*pfDone = TRUE;
}
return TRUE;
}
if (_ChunkState == READ_CHUNK_DONE)
{
*pfDone = TRUE;
return TRUE;
}
if ( fFirstRead )
{
// Initialize our state variables, as this is the start of a chunk.
_ChunkState = READ_CHUNK_SIZE;
_dwChunkSize = -1;
_cbChunkHeader = 0;
_cbChunkBytesRead = 0;
_cbEntityBody = 0;
} else
{
if ( QueryBytesWritten() == 0 )
{
DBGPRINTF(( DBG_CONTEXT,
"[ReadEntityBody] Client socket closed while reading request (Conn = %lx)\n",
QueryClientConn() ));
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
}
*pfDone = FALSE;
cbBytesInBuffer = QueryBytesWritten();
ChunkHeader = (BYTE *)_bufClientRequest.QueryPtr() + _cbChunkHeader +
+ _cbEntityBody + _cbClientRequest;
if(!DecodeChunkedBytes(ChunkHeader, &cbBytesInBuffer)) {
// error in chunked data
DBGPRINTF(( DBG_CONTEXT, "Error in chunked data at %d\r\n",
_cbEntityBody ));
SetState( HTR_DONE, HT_BAD_REQUEST, ERROR_NOT_SUPPORTED );
Disconnect( HT_BAD_REQUEST, IDS_HTRESP_BAD_REQUEST, FALSE, pfDone );
if ( pfDisconnected )
{
*pfDisconnected = TRUE;
}
return TRUE;
}
_cbEntityBody += cbBytesInBuffer;
_cbTotalEntityBody += cbBytesInBuffer;
if( _cbEntityBody < dwMaxAmountToRead &&
_ChunkState != READ_CHUNK_DONE )
{
cbNextRead = dwMaxAmountToRead - _cbEntityBody;
if(cbNextRead < CHUNK_READ_SIZE) {
cbNextRead = CHUNK_READ_SIZE;
}
return ReadMoreEntityBody(
_cbClientRequest + _cbChunkHeader + _cbEntityBody,
cbNextRead);
} else {
// We've read enough data
*pfDone = TRUE;
return TRUE;
}
}
return TRUE;
}
/*******************************************************************
NAME: HTTP_REQ_BASE::DecodeChunkedBytes
SYNOPSIS: Decodes specified number of chunked bytes in-place,
using member variables to keep track of the state
ENTRY: lpBuffer - points to first encoded byte
lpnBytes - points to count of encoded bytes
EXIT: lpnBytes - points to count of decoded bytes
********************************************************************/
BOOL
HTTP_REQ_BASE::DecodeChunkedBytes(
LPBYTE lpBuffer,
LPDWORD pnBytes
)
{
DWORD cbBytesToDecode;
DWORD cbBytesDecoded, cb;
LPBYTE lpCurrent, lpData;
BYTE Current;
BOOL fSuccess = TRUE;
// Cache the count of bytes to decode
cbBytesToDecode = *pnBytes;
// Setup our running buffer pointer and data pointer
lpCurrent = lpBuffer;
lpData = lpBuffer;
// No data decoded
cbBytesDecoded = 0;
// while we have unprocessed data
while( cbBytesToDecode ) {
switch( _ChunkState ) {
case READ_CHUNK_SIZE:
while( cbBytesToDecode ) {
Current = *lpCurrent;
if( isxdigit( (UCHAR)Current ) ) {
if( _dwChunkSize == -1 ) {
_dwChunkSize = 0;
}
// Adjust chunk size, count off consumed byte
_dwChunkSize = (_dwChunkSize * 16) +
( isdigit( (UCHAR)Current ) ? Current - '0' :
( Current | 0x20 ) - 'a' + 10 );
lpCurrent++;
cbBytesToDecode--;
} else {
if( _dwChunkSize == -1 ) {
DBGPRINTF(( DBG_CONTEXT,
"[DecodeChunkedBytes] bad chunk size\n" ));
SetLastError( ERROR_INVALID_PARAMETER );
fSuccess = FALSE;
goto done;
}
// Not a hex digit, eat the rest of the line until LF
_CRCount = _LFCount = 0;
_ChunkState = READ_CHUNK_PARAMS;
// Now is a good time to clear the counter
// of chunk data bytes that we've read
_cbChunkBytesRead = 0;
break;
}
}
break;
case READ_CHUNK_PARAMS:
// Eat anything which follows chunk size until LF
while( cbBytesToDecode ) {
Current = *(lpCurrent++);
cbBytesToDecode--;
if( Current == '\r' ) {
_CRCount = 1;
} else {
if( Current == '\n' && _CRCount != 0 ) {
// We got LF, proceed reading chunk data
_ChunkState = READ_CHUNK;
_LFCount = 1;
break;
} else {
//
// No LF -- ignore CR(s)
//
_CRCount = 0;
}
}
}
//
// Shift the data to remove any chunk header
//
if( cbBytesToDecode ) {
memmove( lpData, lpCurrent, cbBytesToDecode );
lpCurrent = lpData;
}
//
// If we have a 0 chunk size, we've read all of the data
// but we may still have some footers to read
//
if( _dwChunkSize == 0 ) {
_ChunkState = READ_CHUNK_FOOTER;
}
break;
case READ_CHUNK:
cb = _dwChunkSize - _cbChunkBytesRead;
if( cb <= cbBytesToDecode ) {
//
// We have the whole chunk
//
//
// Count off chunk worth of bytes to decode
//
cbBytesToDecode -= cb;
//
// Advance current pointer
//
lpCurrent += cb;
//
// Count in decoded bytes
//
cbBytesDecoded += cb;
//
// Notice the position right after the last data byte
//
lpData = lpCurrent;
//
// Prepare CR and LF counters to handle trailing CRLF
//
_CRCount = _LFCount = 0;
//
// Shift to a new state
//
_ChunkState = READ_CHUNK_CRLF;
} else {
//
// All cbBytesToDecode are data bytes
//
//
// Count in decoded bytes
//
cbBytesDecoded += cbBytesToDecode;
//
// Remember number of bytes read by this call - we'll need it
// on next entry to this function
//
_cbChunkBytesRead += cbBytesToDecode;
//
// Nothing left to decode
//
cbBytesToDecode = 0;
}
break;
case READ_CHUNK_FOOTER:
while( cbBytesToDecode ) {
Current = *(lpCurrent++);
cbBytesToDecode--;
if( Current == '\r' ) {
_CRCount++;
} else {
if( Current == '\n' && _CRCount != 0 ) {
_LFCount++;
if( _CRCount == 2 && _LFCount == 2 ) {
//
// CODEWORK
// We may have other footers here...
//
if(_dwChunkSize == 0) {
_ChunkState = READ_CHUNK_DONE;
goto done;
}
_ChunkState = READ_CHUNK_SIZE;
_CRCount = _LFCount = 0;
_dwChunkSize = 0;
break;
}
} else {
_CRCount = _LFCount = 0;
}
}
}
break;
case READ_CHUNK_CRLF:
if( _CRCount == 0 ) {
if( *lpCurrent == '\r' ) {
cbBytesToDecode--;
lpCurrent++;
_CRCount = 1;
} else {
SetLastError( ERROR_INVALID_PARAMETER );
fSuccess = FALSE;
goto done;
}
}
if( cbBytesToDecode != 0 ) {
if( *lpCurrent == '\n' ) {
if( cbBytesToDecode == 1 ) {
_ChunkState = READ_CHUNK_SIZE;
_CRCount = _LFCount = 0;
_dwChunkSize = 0;
goto done;
}
cbBytesToDecode--;
lpCurrent++;
_dwChunkSize = 0;
_CRCount = _LFCount = 0;
_ChunkState = READ_CHUNK_SIZE;
break;
} else {
SetLastError( ERROR_INVALID_PARAMETER );
fSuccess = FALSE;
goto done;
}
}
break;
default:
DBG_ASSERT( 0 );
}
}
done:
*pnBytes = cbBytesDecoded;
return fSuccess;
}
VOID
HttpReqResolveCallback(
ADDRCHECKARG pArg,
BOOL fSt,
LPSTR pName
)
{
// ignore fSt : DNS name is simply unavailable
((CLIENT_CONN*)pArg)->PostCompletionStatus( 0 );
//((CLIENT_CONN*)pArg)->DoWork( 0, 0, FALSE );
}
BOOL
HTTP_REQ_BASE::OnCompleteRequest(
TCHAR * pchRequest,
DWORD cbData,
BOOL * pfFinished,
BOOL * pfContinueProcessingRequest
)
/*++
Routine Description:
This method takes a complete HTTP 1.0 request and handles the results
of the parsing method
Arguments:
pchRequest - Pointer to first byte of request header
cbData - Number of data bytes in pchRequest
pfFinished - Set to TRUE if no further processing is needed
pfContinueProcessingRequest - Set to FALSE is we must stop processing request
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fRet;
DWORD cbExtraData;
BOOL fNoCert;
BOOL fHandled = FALSE;
LPBYTE pbCaList;
DWORD dwCaList;
LPBYTE pbCa;
DWORD dwCa;
BOOL fDenyOnDnsFail = TRUE;
BOOL fDenyComplete = FALSE;
BOOL fDisconnected = FALSE;
//
// Parse the request
//
fRet = Parse( pchRequest,
cbData,
&cbExtraData,
&fHandled,
pfFinished );
//
// We can process authorization now that virtual root mapping is done
//
if ( fRet && _HeaderList.FastMapQueryValue( HM_AUT ) != NULL &&
!( *pfFinished || fHandled ) )
{
fRet = ProcessAuthorization( (CHAR *)
_HeaderList.FastMapQueryValue( HM_AUT ) );
}
if ( !fRet )
{
DWORD hterr;
DWORD winerr = GetLastError();
DWORD errorResponse = NO_ERROR;
IF_DEBUG(ERROR) {
DBGPRINTF(( DBG_CONTEXT,
"[OnFillClientReq] httpReq.Parse or httpLogonUser failed, error %lu\n",
GetLastError() ));
}
switch ( winerr )
{
case ERROR_INVALID_PARAMETER:
//
// If the request is bad, then indicate that to the client
//
hterr = HT_BAD_REQUEST;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
hterr = HT_NOT_FOUND;
break;
case ERROR_BAD_NET_NAME:
hterr = HT_NOT_FOUND;
errorResponse = IDS_SITE_NOT_FOUND;
break;
case ERROR_NOT_SUPPORTED:
hterr = HT_NOT_SUPPORTED;
break;
case ERROR_PASSWORD_EXPIRED:
case ERROR_PASSWORD_MUST_CHANGE:
if ( !_fAnonymous && !_fMappedAcct )
{
if ( !DoChange( &fHandled ) )
{
return FALSE;
}
if ( fHandled )
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
if ( !DenyAccess( &fDenyComplete, &fDisconnected ) || fDenyComplete )
{
hterr = HT_DENIED;
winerr = ERROR_ACCESS_DENIED;
SetLastError( winerr );
errorResponse = IDS_PWD_CHANGE;
return FALSE;
}
else
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
case ERROR_ACCESS_DENIED:
case ERROR_LOGON_FAILURE:
SetLastError( ERROR_ACCESS_DENIED );
if ( !DenyAccess( &fDenyComplete, &fDisconnected ) || fDenyComplete )
{
return FALSE;
}
else
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
case ERROR_LOGIN_WKSTA_RESTRICTION:
hterr = HT_FORBIDDEN;
winerr = ERROR_ACCESS_DENIED;
errorResponse = IDS_SITE_ACCESS_DENIED;
break;
case ERROR_TOO_MANY_SESS:
hterr = HT_FORBIDDEN;
winerr = ERROR_ACCESS_DENIED;
errorResponse = IDS_TOO_MANY_USERS;
break;
case ERROR_INVALID_DATA:
hterr = HT_SERVER_ERROR;
errorResponse = IDS_INVALID_CNFG;
break;
default:
//
// Some other fatal error occurred
//
hterr = HT_SERVER_ERROR;
break;
}
if ( errorResponse == NO_ERROR ) {
errorResponse = winerr;
}
if (!_fNoDisconnectOnError)
{
SetState( HTR_DONE, hterr, winerr );
if (!_fDiscNoError)
{
Disconnect( hterr, errorResponse, FALSE, pfFinished );
}
else
{
Disconnect( 0, NO_ERROR, FALSE, pfFinished );
}
}
//
// Since we handled the error ourselves (by issuing a disconnect),
// we will return success (otherwise another disconnect will
// occur)
//
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
if ( fHandled )
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
if ( *pfFinished )
{
return TRUE;
}
//
// Check to see if encryption is required before we do any processing
//
if ( ( ((HTTP_REQUEST*)this)->GetFilePerms() & VROOT_MASK_SSL )
&& !IsSecurePort() )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_SSL_REQUIRED, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
//
// Check if encryption key size should be at least 128 bits
//
if ( ( ((HTTP_REQUEST*)this)->GetFilePerms() & VROOT_MASK_SSL128 ) )
{
DWORD dwKeySize;
if ( !_tcpauth.QueryEncryptionKeySize(&dwKeySize, &fNoCert) || (dwKeySize < 128) )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_SSL128_REQUIRED, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
#if 0
if ( IsSslCa( &pbCaList, &dwCaList ) )
{
if( !_tcpauth.QueryCa( &pbCa, &dwCa, &fNoCert ) )
{
if ( !fNoCert )
{
goto rjca;
}
}
else if ( IsInCaList( pbCaList, dwCaList, pbCa, dwCa ) )
{
rjca:
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_CA_NOT_ALLOWED, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
#endif
#if defined(CAL_ENABLED)
//
// Check if CAL granted for SSL access
//
if ( IsSecurePort() && !m_pCalSslCtxt )
{
if ( !CalConnect( QueryClientConn()->QueryRemoteAddr(),
strlen( QueryClientConn()->QueryRemoteAddr() ),
TRUE,
"",
0,
NULL,
&m_pCalSslCtxt ) )
{
g_pInetSvc->LogEvent( W3_EVENT_CAL_SSL_EXCEEDED,
0,
NULL,
0 );
BOOL bOverTheLimit;
switch ( ((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalMode() )
{
case MD_CAL_MODE_LOGCOUNT:
IncrErrorCount( (IMDCOM*)QueryW3Instance()->m_Service->QueryMDObject(),
MD_CAL_SSL_ERRORS,
QueryW3Instance()->m_Service->QueryMDPath(),
&bOverTheLimit );
if ( !bOverTheLimit )
{
break;
}
// fall-through
case MD_CAL_MODE_HTTPERR:
SetState( HTR_DONE,
((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalW3Error(),
ERROR_ACCESS_DENIED );
Disconnect( ((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalW3Error(),
IDS_CAL_EXCEEDED,
FALSE,
pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
}
#endif
//
// Check IP access granted
//
BOOL fNeedDns = FALSE;
//
// do access check once per authenticated request
//
if ( !IsIpDnsAccessCheckPresent() )
{
_acIpAccess = AC_IN_GRANT_LIST;
}
else if ( _acIpAccess == AC_NOT_CHECKED )
{
_acIpAccess = QueryClientConn()->CheckIpAccess( &_fNeedDnsCheck );
fNeedDns = _fNeedDnsCheck;
if ( (_acIpAccess == AC_IN_DENY_LIST) ||
((_acIpAccess == AC_NOT_IN_GRANT_LIST) && !_fNeedDnsCheck) )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
//
// If DNS name required for further processing and is not already present,
// request it now.
//
if ( !fNeedDns )
{
if ( !IsLoggedOn() &&
(QueryW3Instance()->QueryNetLogonWks() == MD_NETLOGON_WKS_DNS) &&
(QueryMetaData()->QueryAuthentInfo()->dwLogonMethod == LOGON32_LOGON_NETWORK) )
{
fNeedDns = TRUE;
}
else if ( QueryMetaData()->QueryDoReverseDns() )
{
//
// We would like to get the host name of the client. But if we
// can't we shouldn't deny access on the request.
//
fNeedDns = TRUE;
fDenyOnDnsFail = FALSE;
}
}
if ( fNeedDns && !QueryClientConn()->IsDnsResolved() )
{
BOOL fSync;
LPSTR pDns;
if ( !QueryClientConn()->QueryDnsName( &fSync,
(ADDRCHECKFUNCEX)HttpReqResolveCallback,
(ADDRCHECKARG)QueryClientConn(),
&pDns ) )
{
if ( fDenyOnDnsFail )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
else
{
//
// Just fall thru and handle the request.
//
fSync = TRUE;
}
}
if ( !fSync )
{
SetState( HTR_RESTART_REQUEST );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
return OnRestartRequest( pchRequest, cbExtraData, pfFinished, pfContinueProcessingRequest );
}
BOOL
HTTP_REQ_BASE::DoChange(
LPBOOL pfHandled
)
/*++
Routine Description:
This method handles password expiration notification
Arguments:
pfHandled - updated with TRUE if change pwd request handled
Return Value:
TRUE on success, FALSE on failure
--*/
{
STR strExpUrl;
BOOL fSt = TRUE;
STR strUrlArgs;
QueryW3Instance()->LockThisForRead();
fSt = strExpUrl.Copy((TCHAR*)QueryW3Instance()->QueryAuthExpiredUrl() );
QueryW3Instance()->UnlockThis();
if ( !fSt || !strExpUrl.QueryCCH() )
{
// can't change pwd
*pfHandled = FALSE;
return TRUE;
}
if ( LogonAsSystem() )
{
//
// Add the arg to be passed to the password-change URL - argument is the URL the
// user is pointed to after all the password-change processing is done
//
if ( fSt = strExpUrl.Append( (TCHAR*)"?" ) )
{
fSt = TRUE;
//
// If we're changing the password on the proxy, we use the original non-proxy-munged
// URL
//
if ( IsProxyRequest() )
{
fSt = strUrlArgs.Append( (TCHAR*) _strOriginalURL.QueryStr() );
}
//
// Can't just use QueryHostAddr() concatentated with
// _HeaderList.FastMapQueryValue( HM_URL ) because for HTTP 1.1 we might have a fully
// qualified request as an URL, so we have to build it up piece-meal.
//
else
{
if ( !strUrlArgs.Append( IsSecurePort() ? (TCHAR*)"https://" :
(TCHAR*)"http://" ) ||
!strUrlArgs.Append( (TCHAR*)QueryHostAddr() ) ||
!strUrlArgs.Append( (TCHAR*) QueryURL() ) ||
!strUrlArgs.Append( _strURLParams.IsEmpty() ? (TCHAR *) "" : (TCHAR*) "?" ) ||
!strUrlArgs.Append( _strURLParams.IsEmpty() ? (TCHAR*) "" :
(TCHAR*) QueryURLParams() ))
{
fSt = FALSE;
}
}
if ( fSt )
{
fSt = strExpUrl.Append( (TCHAR*) strUrlArgs.QueryStr() );
}
}
if ( !fSt )
{
*pfHandled = FALSE;
return FALSE;
}
//
// We used to call ReprocessURL() here to send back the form that allows users
// to change their password, but there was a problem with the compression filter
// [see Bug 120119 in the NT DB for full description] (and potentially other filters
// as well) that make it better to do a 302 Redirect to the password change URL
//
BOOL fFinished = FALSE;
//
// Put ourselves in a known state after the redirect - some browsers may close the
// connection, others may keep it if we don't explicitly close it
//
SetKeepConn( FALSE );
if ( BuildURLMovedResponse( QueryRespBuf(),
&strExpUrl,
HT_REDIRECT,
FALSE ) &&
( (HTTP_REQUEST*)this )->SendHeader( QueryRespBufPtr(),
QueryRespBufCB(),
IO_FLAG_SYNC,
&fFinished ) )
{
*pfHandled = TRUE;
return TRUE;
}
else
{
*pfHandled = FALSE;
return FALSE;
}
}
SetDeniedFlags( SF_DENIED_LOGON );
SetLastError( ERROR_ACCESS_DENIED );
*pfHandled = FALSE;
return FALSE;
}
BOOL
HTTP_REQ_BASE::DenyAccess(
BOOL * pfDenyComplete,
BOOL * pfDisconnected
)
/*++
Routine Description:
This method prepare the connection for a deny access return status by
eating any entity body in the denied request
Arguments:
pfDenyComplete - Set to true when all entity body is read
pfDisconnected - Set to true if we disconnected
Return Value:
TRUE if successful, else FALSE
--*/
{
HTR_STATE OldState = QueryState();
BOOL fDisconnected = FALSE;
SetState( HTR_ACCESS_DENIED );
if ( !ReadEntityBody( pfDenyComplete,
TRUE,
QueryClientContentLength(),
pfDisconnected ) )
{
return FALSE;
}
if ( *pfDenyComplete )
{
SetState( OldState );
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::OnRestartRequest(
TCHAR * pchRequest,
DWORD cbData,
BOOL * pfFinished,
BOOL * pfContinueProcessingRequest
)
/*++
Routine Description:
This method takes a complete HTTP 1.0 request and handles the results
of the parsing method
Arguments:
pchRequest - Pointer to first byte of request header
cbData - Number of read data bytes in message body
pfFinished - Set to TRUE if no further processing is needed
pfContinueProcessingRequest - Set to FALSE is we must stop processing request
Return Value:
TRUE on success, FALSE on failure
--*/
{
BOOL fGranted;
LARGE_INTEGER cExpire;
SYSTEMTIME stExpire;
FILETIME ftNow;
BYTE rgbInfo[MAX_TOKEN_USER_INFO];
DWORD cbTotalRequired;
STR strExpUrl;
BOOL fAccepted = FALSE;
DWORD cbNextRead;
AC_RESULT acDnsAccess;
BOOL fHandled;
DWORD dwCertFlags = 0;
BOOL fNoCert;
LPBYTE pbCa;
DWORD dwCa;
*pfContinueProcessingRequest = TRUE; // Assume we'll handle this w/o I/O.
//
// restore BytesWritten as set by 1st phase of request
// ( may have been overwritten by reverse DNS lookup phase )
//
_cbBytesWritten = _cbRestartBytesWritten;
//
// Check if cert renegotiation to be requested
//
if ( QueryState() != HTR_CERT_RENEGOTIATE )
{
if ( !((HTTP_REQUEST*)this)->RequestRenegotiate( &fAccepted ) )
{
if ( GetLastError() == SEC_E_INCOMPLETE_MESSAGE )
{
fAccepted = FALSE;
}
else
{
return FALSE;
}
}
}
//
// If requested, begin reading data. Notification will be handled
// by HandleCertRenegotiation()
//
if ( fAccepted )
{
_cbEntityBody = cbData;
_cbOldData = _cbClientRequest + cbData;
cbNextRead = CERT_RENEGO_READ_SIZE;
if ( !_bufClientRequest.Resize( _cbOldData + cbNextRead ))
{
return FALSE;
}
*pfContinueProcessingRequest = FALSE;
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbOldData,
cbNextRead,
NULL,
IO_FLAG_ASYNC|IO_FLAG_NO_FILTER ))
{
return FALSE;
}
return TRUE;
}
if ( _dwRenegotiated == CERT_NEGO_SUCCESS )
{
QueryW3Instance()->IsSslCa( &pbCa, &dwCa );
if ( !_tcpauth.UpdateClientCertFlags( QueryW3Instance()->QueryCertCheckMode(),
&fNoCert,
pbCa,
dwCa ) )
{
return FALSE;
}
if ( !_tcpauth.QueryCertificateFlags( &dwCertFlags, &fNoCert ) ||
( dwCertFlags & ( RCRED_STATUS_UNKNOWN_ISSUER |
CRED_STATUS_INVALID_TIME |
CRED_STATUS_REVOKED ) ) )
{
goto cert_req;
}
}
else if ( ((HTTP_REQUEST*)this)->GetFilePerms() & VROOT_MASK_NEGO_MANDATORY )
{
cert_req:
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
//
// Several things could go wrong, so order our processing from most to least severe
// (the order is a little arbitrary, but oh well ...)
//
DWORD dwSubStatus = 0;
if ( dwCertFlags & RCRED_STATUS_UNKNOWN_ISSUER )
{
dwSubStatus = IDS_CERT_BAD;
}
else if ( dwCertFlags & CRED_STATUS_INVALID_TIME )
{
dwSubStatus = IDS_CERT_TIME_INVALID;
}
else if ( dwCertFlags & CRED_STATUS_REVOKED )
{
dwSubStatus = IDS_CERT_REVOKED;
}
else
{
dwSubStatus = IDS_CERT_REQUIRED;
}
Disconnect( HT_FORBIDDEN, dwSubStatus, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
if ( _fInvalidAccessToken )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_MAPPER_DENY_ACCESS, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
//
// If we're having an authentication conversation, then we send an access denied
// response with the next authentication blob. The client returns the next blob
// to us in an HTTP request.
//
if ( IsAuthenticating() )
{
//
// If no blob to send to client then handle this as
// a 401 notification with disconnect
//
if ( _strAuthInfo.IsEmpty() )
{
SetKeepConn( FALSE );
SetDeniedFlags( SF_DENIED_LOGON );
_fAuthenticating = FALSE;
}
DoAuthentication:
//
// An access denied error automatically sends the next part
// of the authentication conversation
//
SetLastError( ERROR_ACCESS_DENIED );
BOOL fDenyComplete = FALSE;
BOOL fDisconnected = FALSE;
if ( !DenyAccess( &fDenyComplete, &fDisconnected ) || fDenyComplete )
{
return FALSE;
}
else
{
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
if ( _fNeedDnsCheck )
{
acDnsAccess = QueryClientConn()->CheckDnsAccess();
_fNeedDnsCheck = FALSE;
// not checked name should be denied
if ( acDnsAccess == AC_NOT_CHECKED ||
acDnsAccess == AC_IN_DENY_LIST ||
acDnsAccess == AC_NOT_IN_GRANT_LIST ||
(_acIpAccess == AC_NOT_IN_GRANT_LIST && acDnsAccess != AC_IN_GRANT_LIST) )
{
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
((HTTP_REQUEST*)this)->CheckValidAuth();
//
// If we have all the authentication information we need and we're
// not already logged on, try to log the user on
//
if ( !IsLoggedOn() && !LogonUser( pfFinished ) )
{
LogonErr:
if ( (GetLastError() == ERROR_ACCESS_DENIED) ||
(GetLastError() == ERROR_LOGON_FAILURE))
{
goto DoAuthentication;
}
if ( (GetLastError() == ERROR_PASSWORD_EXPIRED ||
GetLastError() == ERROR_PASSWORD_MUST_CHANGE) )
{
BOOL fDenyComplete = FALSE;
SetLastError( ERROR_ACCESS_DENIED );
if ( !DoChange( &fHandled ) )
{
return FALSE;
}
if ( !fHandled )
{
#if 0
SetState( HTR_DONE, HT_DENIED, ERROR_ACCESS_DENIED );
Disconnect( HT_DENIED, IDS_PWD_CHANGE, FALSE, pfFinished );
#else
SetDeniedFlags( SF_DENIED_LOGON );
goto DoAuthentication;
#endif
}
else
{
*pfFinished = TRUE;
}
return TRUE;
}
return FALSE;
}
else if ( (QueryNotifyExAuth() & MD_NOTIFEXAUTH_NTLMSSL ) &&
_Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATIONEX,
IsSecurePort() ) )
{
HANDLE hTok;
if ( !_Filter.NotifyAuthInfoFiltersEx( _strUserName.QueryStr(),
_strUserName.QueryCCH(),
_strUserName.QueryStr(),
_strUserName.QueryCCH(),
"",
"",
QueryMetaData()->QueryAuthentInfo()->
strDefaultLogonDomain.QueryStr(),
_strAuthType.QueryStr(),
_strAuthType.QueryCCH(),
&hTok,
&hTok,
pfFinished ))
{
SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER );
goto LogonErr;
}
}
if ( *pfFinished )
{
return TRUE;
}
//
// Call SF_NOTIFY_AUTH_COMPLETE filters if we're logged on now
//
if ( IsLoggedOn() &&
_Filter.IsNotificationNeeded( SF_NOTIFY_AUTH_COMPLETE,
IsSecurePort() ) )
{
HTTP_FILTER_AUTH_COMPLETE_INFO AuthInfo;
STACK_STR( strOriginal, MAX_PATH );
//
// Store away the original URL
//
if ( !strOriginal.Copy( _HeaderList.FastMapQueryValue( HM_URL ) ) )
{
return FALSE;
}
if ( !_Filter.NotifyAuthComplete( pfFinished,
&AuthInfo ) )
{
return FALSE;
}
if ( *pfFinished )
{
return TRUE;
}
if ( _stricmp( strOriginal.QueryStr(),
_HeaderList.FastMapQueryValue( HM_URL ) ) )
{
BOOL fRet;
//
// Filter changed the URL. Reprocess the URL
//
if ( AuthInfo.fResetAuth )
{
ResetAuth( FALSE );
}
fRet = ((HTTP_REQUEST*)this)->ReprocessURL(
(char*) _HeaderList.FastMapQueryValue( HM_URL ),
HTV_UNKNOWN );
*pfContinueProcessingRequest = FALSE;
return fRet;
}
}
#if defined(CAL_ENABLED)
//
// Check if CAL granted for authenticated access
//
if ( !_fAnonymous && !m_pCalAuthCtxt )
{
if ( !CalConnect( QueryClientConn()->QueryRemoteAddr(),
strlen( QueryClientConn()->QueryRemoteAddr() ),
FALSE,
_strUserName.QueryStr(),
_strUserName.QueryCCH(),
_tcpauth.QueryImpersonationToken(),
&m_pCalAuthCtxt ) )
{
BOOL bOverTheLimit;
switch ( ((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalMode() )
{
case MD_CAL_MODE_LOGCOUNT:
IncrErrorCount( (IMDCOM*)QueryW3Instance()->m_Service->QueryMDObject(),
MD_CAL_AUTH_ERRORS,
QueryW3Instance()->m_Service->QueryMDPath(),
&bOverTheLimit );
if ( !bOverTheLimit )
{
break;
}
// fall-through
case MD_CAL_MODE_HTTPERR:
SetState( HTR_DONE,
((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalW3Error(),
ERROR_ACCESS_DENIED );
Disconnect( ((W3_IIS_SERVICE*)(QueryW3Instance()->m_Service))->QueryCalW3Error(),
IDS_CAL_EXCEEDED,
FALSE,
pfFinished );
*pfContinueProcessingRequest = FALSE;
return TRUE;
}
}
}
#endif
//
// Query pwd expiration time.
// if available, check if in notification range as defined by configuration
// if in range, call configured URL
//
if ( !_fMappedAcct &&
!_fAnonymous &&
_tcpauth.QueryExpiry( (PTimeStamp)&cExpire ) )
{
if ( cExpire.HighPart == 0x7fffffff )
{
IF_DEBUG(REQUEST) {
DBGPRINTF( ( DBG_CONTEXT, "No expiration time\r\n" ) );
}
}
else
{
::IISGetCurrentTimeAsFileTime( &ftNow );
{
if ( *(__int64*)&cExpire > *(__int64*)&ftNow )
{
_dwExpireInDay = (DWORD)((*(__int64*)&cExpire
- *(__int64*)&ftNow)
/ ((__int64)10000000*86400));
if ( QueryW3Instance()->QueryAdvNotPwdExpInDays()
&& _dwExpireInDay
<= QueryW3Instance()->
QueryAdvNotPwdExpInDays()
&& QueryW3Instance()->QueryAdvNotPwdExpUrl() )
{
//
// Check this SID has not already been notified
// of pwd expiration
//
if ( GetTokenInformation( _tcpauth.QueryPrimaryToken(),
TokenUser,
(LPVOID ) rgbInfo,
sizeof(rgbInfo),
&cbTotalRequired) )
{
TOKEN_USER * pTokenUser = (TOKEN_USER *) rgbInfo;
PSID pSid = pTokenUser->User.Sid;
if( !PenCheckPresentAndResetTtl( pSid,
QueryW3Instance()
->QueryAdvCacheTTL() ) )
{
PenAddToCache( pSid,
QueryW3Instance()
->QueryAdvCacheTTL() );
//
// flush cache when connection close
// so that account change will not be masked
// by cached information
//
_tcpauth.DeleteCachedTokenOnReset();
QueryW3Instance()->LockThisForRead();
BOOL fSt = strExpUrl.Copy( (TCHAR*)QueryW3Instance()
->QueryAdvNotPwdExpUrl() );
QueryW3Instance()->UnlockThis();
if ( !fSt )
{
return FALSE;
}
if ( strExpUrl.QueryStr()[0] )
{
//
// Add the arg to be passed to the password-change URL -
// argument is the URL the user is pointed to after all the
// password-change processing is done
//
if ( fSt = strExpUrl.Append( (TCHAR*)"?" ) )
{
fSt = TRUE;
STR strUrlArgs;
//
// If we're changing the password on the proxy, we use
// the original non-proxy-munged URL
//
if ( IsProxyRequest() )
{
fSt = strUrlArgs.Append( (TCHAR*)
_strOriginalURL.QueryStr() );
}
//
// Can't just use QueryHostAddr() concatentated with
// _HeaderList.FastMapQueryValue( HM_URL ) because for
// HTTP 1.1 we might have a fully qualified request as an
// URL, so we have to build it up piece-meal.
//
else
{
if ( !strUrlArgs.Append( IsSecurePort() ?
(TCHAR*)"https://" :
(TCHAR*)"http://" ) ||
!strUrlArgs.Append( (TCHAR*)QueryHostAddr() ) ||
!strUrlArgs.Append( (TCHAR*) QueryURL() ) ||
!strUrlArgs.Append( _strURLParams.IsEmpty() ? (TCHAR *) "" : (TCHAR*) "?" ) ||
!strUrlArgs.Append( _strURLParams.IsEmpty() ? (TCHAR*) "" :
(TCHAR*) QueryURLParams() ))
{
fSt = FALSE;
}
}
if ( fSt )
{
fSt = strExpUrl.Append( (TCHAR*)
strUrlArgs.QueryStr() );
}
}
if ( !fSt )
{
return FALSE;
}
_tcpauth.QueryFullyQualifiedUserName(
_strUnmappedUserName.QueryStr(),
&_strUnmappedUserName,
QueryW3Instance(),
QueryMetaData()->QueryAuthentInfo());
//
// process new URL
//
SetKeepConn( FALSE ); // to resync input flow
//
// We used to call ReprocessURL() here to send back the form
// that allows users to change their password, but there was
// a problem with the compression filter
// [see Bug 120119 in the NT DB for full description]
// (and potentially other filters as well) that make it
// better to do a 302 Redirect to the password change URL
//
// We're guaranteed not to get into an infinite loop with the
// redirect because we check whether or not the given SID
// has already been notified about the password expiration
// [ see call to PenCheckPresentAndResetTtl() call above]
//
#if 1
BOOL fFinished = FALSE;
if ( BuildURLMovedResponse( QueryRespBuf(),
&strExpUrl,
HT_REDIRECT,
FALSE ) &&
( (HTTP_REQUEST*)this )->SendHeader( QueryRespBufPtr(),
QueryRespBufCB(),
IO_FLAG_SYNC,
&fFinished ) )
{
*pfFinished = TRUE;
return TRUE;
}
else
{
return FALSE;
}
#else
if ( !((HTTP_REQUEST*)this)->CancelPreconditions() )
{
return FALSE;
}
if ( ((HTTP_REQUEST*)this)->ReprocessURL(
strExpUrl.QueryStr(),
HTV_GET ) )
{
*pfFinished = TRUE;
return TRUE;
}
else
{
return FALSE;
}
#endif
}
}
}
}
}
else
{
_dwExpireInDay = 0;
}
}
#if DBG
IF_DEBUG(REQUEST) {
FileTimeToSystemTime( (FILETIME*)&cExpire, &stExpire );
DBGPRINTF( ( DBG_CONTEXT,
"Expiration date: %2d-%2d-%4d, %02d:%02d\r\n",
stExpire.wMonth, stExpire.wDay, stExpire.wYear,
stExpire.wHour, stExpire.wMinute ) );
}
#endif
}
}
//
// Check to see if the client specified any additional data
// that we need to pickup. We want to do this if there is an entity
// body, so _fHaveContentLength should be TRUE,
// and this is either a request destined for an ISAPI app or a
// non-PUT request that the server will handle. We do it this way
// to handle weird error cases, like GETs with entity bodies. We
// don't do this for unknown verbs that we're not going to handle
// anyways, since those will generate an error and we don't need
// to bother reading the entity body.
//
// Note also that this approach won't handle those cases of an
// entity body without a content-length or transfer-encoding. It's
// hard to distinguish those cases from pipelined requests. If we
// need to handle this we can check for cbData being non-zero and
// the request being for a verb that could have an entity body, i.e.
// HTV_UNKNOWN or HTV_POST. This checked would be or'ed with the check
// for _fHaveContentLength.
//
// If the server is changed such that requests with entity bodies
// other than PUT are handled then the 'if' statement below will need
// to be modified, most likely to check for verbs other than PUT.
//
if ( _fHaveContentLength &&
(IsProbablyGatewayRequest() ||
(QueryVerb() != HTV_PUT && QueryVerb() != HTV_UNKNOWN)
)
)
{
//
// If we've got a 1.1 client, and we're reading some data for the app,
// and this is a PUT or a POST, we've got to send the 100 Continue
// response here. Note: it's possible that the 100 response should
// be sent for any request that has an entity body. If we decide
// to do that then just remove the last part of the following 'if'
// statement.
if (IsAtLeastOneOne() &&
(QueryMetaData()->QueryUploadReadAhead() != 0) &&
((QueryVerb() == HTV_PUT) || (QueryVerb() == HTV_POST)))
{
if ( !SendHeader( "100 Continue", "\r\n", IO_FLAG_SYNC, pfFinished,
HTTPH_NO_CONNECTION) )
{
// An error on the header send. Abort this request.
return FALSE;
}
if ( *pfFinished )
{
return TRUE;
}
}
//
// Now let's pickup the rest of the data.
//
SetState( HTR_READING_GATEWAY_DATA );
//
// We're all set, read the entity body now.
//
if ( !ReadEntityBody( pfContinueProcessingRequest, TRUE ))
{
return FALSE;
}
if ( !*pfContinueProcessingRequest )
return TRUE;
//
// else Fall through as we have all of the gateway data
//
}
SetState( HTR_DOVERB );
return TRUE;
}
BOOL
HTTP_REQ_BASE::ReadFile(
LPVOID lpBuffer,
DWORD nBytesToRead,
DWORD * pnBytesRead,
DWORD dwFlags )
{
//
// If no filters are installed, do the normal thing
//
if ( (dwFlags & IO_FLAG_NO_FILTER) ||
!_Filter.IsNotificationNeeded( SF_NOTIFY_READ_RAW_DATA,
IsSecurePort() ))
{
if ( dwFlags & IO_FLAG_ASYNC )
{
return _pClientConn->ReadFile( lpBuffer,
nBytesToRead );
}
else
{
DWORD nBytes = 0;
BOOL fRet;
DWORD err;
//
// Bogus hack - server relies on GetLastError() too much
// select() and recv() both reset the last error which hoses
// us on some error cleanup paths
//
err = GetLastError();
fRet = TcpSockRecv( _pClientConn->QuerySocket(),
(char *) lpBuffer,
nBytesToRead,
&nBytes,
60 // 60s timeout
);
if ( pnBytesRead != NULL ) {
*pnBytesRead = nBytes;
}
if ( fRet ) {
SetLastError( err );
}
return fRet;
}
}
else
{
//
// We don't need to up the ref-count because the filter
// will eventually post an async-completion with the connection
// object
//
if ( _Filter.ReadData( lpBuffer,
nBytesToRead,
pnBytesRead,
dwFlags ))
{
return TRUE;
}
return FALSE;
}
} // HTTP_REQ_BASE::ReadFile
BOOL
HTTP_REQ_BASE::WriteFile(
LPVOID lpBuffer,
DWORD nBytesToWrite,
DWORD * pnBytesWritten,
DWORD dwFlags )
{
//
// Don't use WriteFileAndRecv unless we're told to
//
if (! g_fUseAndRecv ) {
dwFlags &= ~IO_FLAG_AND_RECV;
}
AtqSetSocketOption(_pClientConn->QueryAtqContext(),
TCP_NODELAY,
(dwFlags & IO_FLAG_NO_DELAY) ? 1 : 0
);
if ( (dwFlags & IO_FLAG_NO_FILTER ) ||
!_Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA,
IsSecurePort() ))
{
if ( dwFlags & IO_FLAG_ASYNC )
{
_fAsyncSendPosted = TRUE;
if ( dwFlags & IO_FLAG_AND_RECV ) {
return _pClientConn->WriteFileAndRecv( lpBuffer,
nBytesToWrite,
_bufClientRequest.QueryPtr(),
_bufClientRequest.QuerySize() );
} else {
return _pClientConn->WriteFile( lpBuffer,
nBytesToWrite );
}
}
else
{
DWORD nBytes = 0;
BOOL fRet;
DWORD err;
err = GetLastError();
fRet = TcpSockSend( _pClientConn->QuerySocket(),
lpBuffer,
nBytesToWrite,
&nBytes,
60 // 60s timeout
);
_cbBytesSent += nBytes;
if ( pnBytesWritten != NULL ) {
*pnBytesWritten = nBytes;
}
if ( fRet ) {
SetLastError( err );
}
return fRet;
}
}
else
{
//
// We don't need to up the ref-count because the filter
// will eventually post an async-completion with the connection
// object
//
if ( _Filter.SendData( lpBuffer,
nBytesToWrite,
pnBytesWritten,
dwFlags ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::TestConnection( VOID )
{
return TcpSockTest( _pClientConn->QuerySocket() );
}
BOOL
HTTP_REQ_BASE::TransmitFile(
TS_OPEN_FILE_INFO * pOpenFile,
HANDLE hFile,
DWORD Offset,
DWORD BytesToWrite,
DWORD dwFlags,
PVOID pHead,
DWORD HeadLength,
PVOID pTail,
DWORD TailLength
)
{
//
// Either a file handle or a TS_OPEN_FILE_INFO* must be passed in
//
// We want to support Transmit file with out a file handle.
// DBG_ASSERT( hFile || pOpenFile );
DBG_CODE(
if( hFile == NULL && !pOpenFile )
{
// This is the no file handle case
DBG_ASSERT( Offset == 0 );
DBG_ASSERT( BytesToWrite == 0 );
}
);
//
// File sends must always be async
//
DBG_ASSERT( !(dwFlags & IO_FLAG_SYNC));
//
// Don't use TransmitFileAndRecv unless we're told to
//
if (! g_fUseAndRecv ) {
dwFlags |= IO_FLAG_NO_RECV;
}
//
// Don't count filter bytes
//
if ( !(dwFlags & IO_FLAG_NO_FILTER ))
{
_cFilesSent++;
}
if ( (dwFlags & IO_FLAG_NO_FILTER) ||
!_Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA,
IsSecurePort() ))
{
_fAsyncSendPosted = TRUE;
if ( dwFlags & (TF_DISCONNECT|IO_FLAG_NO_RECV) ) {
if ( pOpenFile )
{
return TransmitFileTs( pOpenFile,
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength );
}
else
{
return _pClientConn->TransmitFile( hFile,
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength );
}
} else {
return _pClientConn->TransmitFileAndRecv( hFile ? hFile :
GetFileHandle( pOpenFile ),
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength,
_bufClientRequest.QueryPtr(),
_bufClientRequest.QuerySize() );
}
}
else
{
if ( _Filter.SendFile( pOpenFile,
hFile,
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength ))
{
return TRUE;
}
return FALSE;
}
}
BOOL
HTTP_REQ_BASE::TransmitFileTs(
TS_OPEN_FILE_INFO * pOpenFile,
DWORD Offset,
DWORD BytesToWrite,
DWORD dwFlags,
PVOID pHead,
DWORD HeadLength,
PVOID pTail,
DWORD TailLength
)
{
BOOL fRet;
PBYTE pFileBuf = pOpenFile->QueryFileBuffer();
if (pFileBuf &&
!(HeadLength && TailLength) ) {
//
// Do the fast path by sending file through
// head or tail buffer
//
if (TailLength) {
fRet = _pClientConn->TransmitFile(
NULL,
0,
0,
dwFlags,
pFileBuf + Offset,
BytesToWrite,
pTail,
TailLength
);
} else {
fRet = _pClientConn->TransmitFile(
NULL,
0,
0,
dwFlags,
pHead,
HeadLength,
pFileBuf + Offset,
BytesToWrite
);
}
} else {
//
// Do the slow path
//
fRet = _pClientConn->TransmitFile(
GetFileHandle( pOpenFile ),
Offset,
BytesToWrite,
dwFlags,
pHead,
HeadLength,
pTail,
TailLength
);
}
return fRet;
}
BOOL
HTTP_REQ_BASE::SyncWsaSend(
WSABUF * rgWsaBuffers,
DWORD cWsaBuffers,
LPDWORD pcbWritten
)
{
BOOL fRet;
DBG_ASSERT( pcbWritten );
fRet = _pClientConn->SyncWsaSend( rgWsaBuffers,
cWsaBuffers,
pcbWritten );
if( pcbWritten )
{
_cbBytesSent += *pcbWritten;
}
return fRet;
}
BOOL
HTTP_REQ_BASE::PostCompletionStatus(
DWORD cbBytesTransferred
)
{
return _pClientConn->PostCompletionStatus( cbBytesTransferred );
}