mirror of https://github.com/tongzx/nt5src
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
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 );
|
|
}
|