/**********************************************************************/ /** 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 #include // // 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 ); }