/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1994 **/ /**********************************************************************/ /* basereq.cxx This module contains the http base request class implementation FILE HISTORY: Johnl 24-Aug-1994 Created MuraliK 16-May-1995 Modified LogInformation structure after adding additional fields. MuraliK 13-Oct-1995 Created basereq file from old httreq.cxx */ #include "w3p.hxx" #include #include "basereq.hxx" #include #include #include #include #include #pragma warning( disable:4355 ) // 'this' used in base member initialization // // Private constants. // // // Default response buffer size // #define DEF_RESP_BUFF_SIZE 4096 // // minimum buffer size after custom headers // #define POST_CUSTOM_HEADERS_SIZE 256 // // We cache our read and write buffers but if they get beyond this size, we // free it and start from scratch on the next request // #define MAX_CLIENT_SIZE_ALLOWED (4 * 4096) // // Handle used to indicate request to deny access to current HTTP request // #define IIS_ACCESS_DENIED_HANDLE ((HANDLE)1) // // Public functions. // // // Private functions. // #define CONST_TO_STRING(x) #x HTTP_REQ_BASE::HTTP_REQ_BASE( CLIENT_CONN * pClientConn, PVOID pvInitialBuff, DWORD cbInitialBuff ) : _tcpauth ( TCPAUTH_SERVER | TCPAUTH_UUENCODE ), _Filter ( this ), _fValid ( FALSE ), _bufServerResp ( DEF_RESP_BUFF_SIZE ), _dwLogHttpResponse ( HT_DONT_LOG ), _pURIInfo ( NULL ), _pMetaData ( NULL ) { #if defined(CAL_ENABLED) m_pCalAuthCtxt = NULL; m_pCalSslCtxt = NULL; #endif InitializeSession( pClientConn, pvInitialBuff, cbInitialBuff ); DBG_ASSERT( pClientConn->CheckSignature() ); if ( !_Filter.IsValid() || !_bufServerResp.QueryPtr() ) { return; } _acIpAccess = AC_NOT_CHECKED; _fNeedDnsCheck = FALSE; _fValid = TRUE; } VOID HTTP_REQ_BASE::InitializeSession( CLIENT_CONN * pClientConn, PVOID pvInitialBuff, DWORD cbInitialBuff ) /*++ Routine Description: This is a pseudo constructor called by the buffer list code. This routine should initialize all of the items that should be reset between TCP sessions (but may remain valid over multiple requests on the same TCP session, i.e., "Connection: keep-alive") Arguments: pClientConn - Client connection object we're speaking to --*/ { _pClientConn = pClientConn; _pW3Instance = NULL; _pW3Stats = g_pW3Stats; _fKeepConn = FALSE; _fSecurePort = pClientConn->IsSecurePort(); ResetAuth( TRUE ); _fSingleRequestAuth = FALSE; _cFilesSent = 0; _cFilesReceived = 0; _cbBytesSent = 0; _cbBytesReceived = 0; _cbTotalBytesSent = 0; _cbTotalBytesReceived= 0; _fAsyncSendPosted = FALSE; _Filter.Reset(); _pAuthFilter = NULL; _strHostAddr.Reset(); _dwRenegotiated = 0; _dwSslNegoFlags = 0; IF_DEBUG(REQUEST) { DBGPRINTF(( DBG_CONTEXT, "[InitializeSession] time=%u HTTP=%08x\n", GetTickCount(), (LPVOID)this )); } } HTTP_REQ_BASE::~HTTP_REQ_BASE( VOID ) { } BOOL HTTP_REQ_BASE::ResetAuth( BOOL fSessionReset ) /*++ Routine Description: This method is called to reset authentication status. Arguments: None --*/ { TCP_REQUIRE( _tcpauth.Reset( fSessionReset ) ); _fClearTextPass = FALSE; _fAnonymous = FALSE; _fMappedAcct = FALSE; _fAuthenticating = FALSE; _fLoggedOn = FALSE; _fInvalidAccessToken = FALSE; _fAuthTypeDigest = FALSE; _fAuthSystem = FALSE; _fAuthCert = FALSE; _cbLastAnonAcctDesc = 0; _strAuthType.Reset(); _strUserName.Reset(); _strPassword.Reset(); _strUnmappedUserName.Reset(); _strUnmappedPassword.Reset(); _fSingleRequestAuth = FALSE; #if defined(CAL_ENABLED) if ( m_pCalAuthCtxt ) { CalDisconnect( m_pCalAuthCtxt ); m_pCalAuthCtxt = NULL; } #endif return TRUE; } BOOL HTTP_REQ_BASE::Reset( BOOL fResetPipelineInfo ) /*++ Routine Description: This method is called after an individual request has been processed. If the session is being kept open, this object can be used again for the next request on this TCP session. In this case, various items such as authentication information etc. remain valid. The method is also called once when the object is first allocated. Arguments: --*/ { _fStartTimeValid = FALSE; _VersionMajor = 0; _VersionMinor = 0; _cbEntityBody = 0; _cbTotalEntityBody = 0; _cbContentLength = 0; _cbClientRequest = 0; if (fResetPipelineInfo) { _cbOldData = 0; } else if (_cbOldData > 0) { _cbOldData -= _cbBytesReceived; } _cbExtraData = 0; _pchExtraData = NULL; _liModifiedSince.QuadPart = 0; _liUnlessModifiedSince.QuadPart = 0; _liUnmodifiedSince.QuadPart = 0; _dwModifiedSinceLength = 0; _status = NO_ERROR; _cbBytesWritten = 0; _HeaderList.Reset(); _Filter.Reset(); // // Reset our statistics for this request // _cbTotalBytesSent += _cbBytesSent; _cbTotalBytesReceived += _cbBytesReceived; _cbBytesSent = 0; _cbBytesReceived = 0; _fAsyncSendPosted = FALSE; // // Don't log this request unless we're explicity indicated a status // _dwLogHttpResponse = HT_DONT_LOG; _dwLogWinError = NO_ERROR; _strURL.Reset(); _strURLPathInfo.Reset(); _strURLParams.Reset(); _strLogParams.Reset(); _strPathInfo.Reset(); _strRawURL.Reset(); _strOriginalURL.Reset(); _strMethod.Reset(); _strAuthInfo.Reset(); _strPhysicalPath.Reset(); _strUnmappedPhysicalPath.Reset(); _strDenialHdrs.Reset(); _strRespHdrs.Reset(); _strRange.Reset(); _bProcessingCustomError = FALSE; _bForceNoCache = FALSE; _bSendContentLocation = FALSE; _bSendVary = FALSE; ClearNoCache(); ClearSendCL(); ClearSendVary(); _fAuthenticationRequested = FALSE; _fProxyRequest = FALSE; _fBasicRealm = FALSE; _fIfModifier = FALSE; _fHaveContentLength = FALSE; _fLogRecordWritten = FALSE; _dwExpireInDay = 0x7fffffff; if ( _fSingleRequestAuth ) { LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj(); pStatsObj->DecrCurrentNonAnonymousUsers(); ResetAuth( FALSE ); _fSingleRequestAuth = FALSE; } // Accept range variables _fProcessByteRange = FALSE; _fUnsatisfiableByteRange = FALSE; _fAcceptRange = FALSE; _iRangeIdx = 0; _cbMimeMultipart = 0; _fMimeMultipart = FALSE; _acIpAccess = AC_NOT_CHECKED; _fNeedDnsCheck = FALSE; _fChunked = FALSE; #if 0 // Not used anywhere /SAB _fIsWrite = FALSE; #endif _fDiscNoError = FALSE; _fNoDisconnectOnError = FALSE; SetState( HTR_READING_CLIENT_REQUEST ); return TRUE; } VOID HTTP_REQ_BASE::SessionTerminated( VOID ) /*++ Routine Description: This method updates the statistics when the TCP session is closed just before this object gets destructed (or placed on the free list). Arguments: --*/ { // // Notify filters // if ( _Filter.IsNotificationNeeded( SF_NOTIFY_END_OF_NET_SESSION, IsSecurePort() )) { _Filter.NotifyEndOfNetSession(); } LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj(); W3_STATISTICS_1 * pW3Stats = pStatsObj->QueryStatsObj(); // // Update the statistics // if ( _fLoggedOn ) { if ( _fAnonymous ) { pStatsObj->DecrCurrentAnonymousUsers(); } else { pStatsObj->DecrCurrentNonAnonymousUsers(); } } pStatsObj->LockStatistics(); pW3Stats->TotalBytesSent.QuadPart += _cbTotalBytesSent + _cbBytesSent; pW3Stats->TotalBytesReceived.QuadPart += _cbTotalBytesReceived + _cbBytesReceived; pW3Stats->TotalFilesSent += _cFilesSent; pW3Stats->TotalFilesReceived += _cFilesReceived; pStatsObj->UnlockStatistics(); TCP_REQUIRE( _tcpauth.Reset() ); #if defined(CAL_ENABLED) if ( m_pCalSslCtxt ) { CalDisconnect( m_pCalSslCtxt ); m_pCalSslCtxt = NULL; } if ( m_pCalAuthCtxt ) { CalDisconnect( m_pCalAuthCtxt ); m_pCalAuthCtxt = NULL; } #endif // // Cleanup the filter // _Filter.Cleanup( ); // // Make sure our input buffer doesn't grow too large. For example if // somebody just sent 500k of entity data, we want to release the memory // that was used to store that. // if ( _bufClientRequest.QuerySize() > MAX_CLIENT_SIZE_ALLOWED ) { _bufClientRequest.FreeMemory(); } } BOOL HTTP_REQ_BASE::OnIfModifiedSince( CHAR * pszValue ) /*++ Routine Description: Extracts the modified date for later use Arguments: pszValue - Pointer to zero terminated string --*/ { if ( StringTimeToFileTime( pszValue, &_liModifiedSince )) { CHAR *pszLength; _fIfModifier = TRUE; pszLength = strchr(pszValue, ';'); if (pszLength != NULL) { pszLength++; while (isspace((UCHAR)(*pszLength))) { pszLength++; } if (!_strnicmp(pszLength, "length", sizeof("length") - 1)) { pszLength += sizeof("length") - 1; while (isspace((UCHAR)(*pszLength))) { pszLength++; } if (*pszLength == '=') { pszLength++; while (isspace((UCHAR)(*pszLength))) { pszLength++; } _dwModifiedSinceLength = atoi(pszLength); } } } return TRUE; } // // If we couldn't parse the time, then just ignore this field all // together // DBGPRINTF(( DBG_CONTEXT, "[OnIfModifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n", GetLastError() )); _liModifiedSince.QuadPart = 0; _dwModifiedSinceLength = 0; return TRUE; } BOOL HTTP_REQ_BASE::OnIfUnmodifiedSince( CHAR * pszValue ) /*++ Routine Description: Extracts the unmodified date for later use Arguments: pszValue - Pointer to zero terminated string --*/ { if ( StringTimeToFileTime( pszValue, &_liUnmodifiedSince )) { _fIfModifier = TRUE; return TRUE; } // // If we couldn't parse the time, then just ignore this field all // together // DBGPRINTF(( DBG_CONTEXT, "[OnIfUnmodifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n", GetLastError() )); _liUnmodifiedSince.QuadPart = 0; return TRUE; } BOOL HTTP_REQ_BASE::OnUnlessModifiedSince( CHAR * pszValue ) /*++ Routine Description: Extracts the modified date for later use Arguments: pszValue - Pointer to zero terminated string --*/ { BOOL fRet; if ( StringTimeToFileTime( pszValue, &_liUnlessModifiedSince )) { return TRUE; } // // If we couldn't parse the time, then just ignore this field all // together // DBGPRINTF(( DBG_CONTEXT, "[OnIfUnmodifiedSince] Error %d parsing If-Unmodified-Since time, ignoring field\n", GetLastError() )); _liUnlessModifiedSince.QuadPart = 0; return TRUE; } BOOL HTTP_REQ_BASE::OnIfMatch( CHAR * pszValue ) /*++ Routine Description: Handle the If-Match header. Arguments: pszValue - Pointer to zero terminated string --*/ { _fIfModifier = TRUE; return TRUE; } BOOL HTTP_REQ_BASE::OnIfNoneMatch( CHAR * pszValue ) /*++ Routine Description: Handle the If-None-Match header. Arguments: pszValue - Pointer to zero terminated string --*/ { _fIfModifier = TRUE; return TRUE; } BOOL HTTP_REQ_BASE::OnIfRange( CHAR * pszValue ) /*++ Routine Description: Handle the If-Range header. Arguments: pszValue - Pointer to zero terminated string --*/ { _fIfModifier = TRUE; return TRUE; } /******************************************************************* NAME: HTTP_REQ_BASE::OnContentLength SYNOPSIS: Pulls out the number of bytes we expect the client to give us ENTRY: pszValue - Pointer to a zero terminated string RETURNS: TRUE if successful, FALSE if the field wasn't found HISTORY: Johnl 21-Sep-1994 Created ********************************************************************/ BOOL HTTP_REQ_BASE::OnContentLength( CHAR * pszValue ) { CHAR *pszEnd; if (!IsChunked()) { // + and - aren't valid as part of a content length. if (*pszValue == '-' || *pszValue == '+') { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } errno = 0; // WinSE 16859 _cbContentLength = strtoul( pszValue, &pszEnd, 10 ); if (_cbContentLength == ULONG_MAX) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if (_cbContentLength == 0 || _cbContentLength == ULONG_MAX) { // Might possibly have underflow or overflow. if (errno == ERANGE || pszEnd == pszValue) { // Either had an overflow/underflow or no conversion. SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } } // See what terminated the scan. if (*pszEnd != '\0' && !isspace((UCHAR)(*pszEnd))) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } _fHaveContentLength = TRUE; } return TRUE; } BOOL HTTP_REQ_BASE::OnHost ( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Host: domain name" field Return Value: TRUE if successful, FALSE on error --*/ { if ( !_strHostAddr.Copy( (TCHAR *) pszValue ) ) { return FALSE; } // // remove erroneous port info if present // NYI: It will be very useful to get the length information // PSTR pP = (PSTR) memchr( _strHostAddr.QueryStr(), ':', _strHostAddr.QueryCB() ); if ( pP != NULL ) { *pP = '\0'; _strHostAddr.SetLen( DIFF(pP - _strHostAddr.QueryStr()) ); } return TRUE; } BOOL HTTP_REQ_BASE::OnRange ( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Range" field Return Value: TRUE if successful, FALSE on error --*/ { while ( *pszValue && isspace((UCHAR)(*pszValue)) ) ++pszValue; if ( !_strnicmp( pszValue, "bytes", sizeof("bytes")-1 ) ) { pszValue += sizeof("bytes")-1; while ( *pszValue && *pszValue++ != '=' ) ; while ( *pszValue && isspace((UCHAR)(*pszValue)) ) ++pszValue; if ( !_strRange.Copy( (TCHAR *) pszValue ) ) return FALSE; } return TRUE; } CHAR * HTTP_REQ_BASE::QueryHostAddr ( VOID ) /*++ Routine Description: Returns the local domain name if specified in the request or else the local network address Return Value: ASCII representation of the local address --*/ { if ( QueryW3Instance() == NULL ) { return _pClientConn->QueryLocalAddr(); } if ( IsProxyRequest() ) { return ( QueryW3Instance()->QueryDefaultHostName() != NULL ? QueryW3Instance()->QueryDefaultHostName() : QueryClientConn()->QueryLocalAddr() ); } else { return (CHAR *) (_strHostAddr.IsEmpty() ? ( (QueryW3Instance()->QueryDefaultHostName() != NULL) ? QueryW3Instance()->QueryDefaultHostName() : _pClientConn->QueryLocalAddr() ) : _strHostAddr.QueryStr()); } } #if 0 Authorization info now processed after processing is complete BOOL HTTP_REQ_BASE::OnAuthorization( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Authorization: " field Return Value: TRUE if successful, FALSE on error --*/ { return TRUE; } #endif BOOL HTTP_REQ_BASE::ProcessAuthorization( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Authorization: " field at logo time Return Value: TRUE if successful, FALSE on error --*/ { // // If a filter has indicated this is a proxy request, use the // authorization information // if ( !IsProxyRequest() ) { return ParseAuthorization( pszValue ); } return TRUE; } BOOL HTTP_REQ_BASE::ParseAuthorization ( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Authorization: " field or "Proxy-Authorization: " field Return Value: TRUE if successful, FALSE on error --*/ { CHAR * pchBlob; CHAR * pchSpace = NULL; DWORD dwAuthFlags = QueryAuthentication(); // // If we've already logged this user and they're specifying authentication // headers, back out the old authentication information and // re-authenticate. // if ( IsLoggedOn() ) { if ( !QueryW3Instance()->ProcessNtcrIfLoggedOn() ) { // // Ignore the the authorization information if we're logged on with // an NT provider. Otherwise we authenticate every request with a // full challenge response // if ( !_fClearTextPass && !_fAnonymous ) { goto NotFound; } } if ( _fAnonymous ) { QueryW3StatsObj()->DecrCurrentAnonymousUsers(); } else { QueryW3StatsObj()->DecrCurrentNonAnonymousUsers(); } ResetAuth( FALSE ); } // // If only anonymous is checked, ignore all authentication information // (i.e., force all users to the anonymous user). // if ( !(dwAuthFlags & (~INET_INFO_AUTH_ANONYMOUS) )) { goto NotFound; } // // Now break out the authorization type and see if it's an // authorization type we understand // pchSpace = pchBlob = strchr( pszValue, ' ' ); if ( pchBlob ) { *pchBlob = '\0'; pchBlob++; } else { pchBlob = ""; } if ( !_strAuthType.Copy( pszValue ) ) { return FALSE; } // // This processes "user name:password" // if ( !_stricmp( _strAuthType.QueryStr(), "Basic" ) || !_stricmp( _strAuthType.QueryStr(), "user" )) { // // If Basic is not enabled, force the user to anonymous if // anon is enabled or kick them out with Access denied // if ( !(dwAuthFlags & INET_INFO_AUTH_CLEARTEXT) ) { if ( dwAuthFlags & INET_INFO_AUTH_ANONYMOUS ) { _HeaderList.FastMapCancel( HM_AUT ); _strAuthType.Reset(); goto NotFound; } else { SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } } // // If the type is Basic, then the string has been uuencoded // if ( !ExtractClearNameAndPswd( pchBlob, &_strUserName, &_strPassword, *_strAuthType.QueryStr() == 'B' || *_strAuthType.QueryStr() == 'b' )) { // // If we can't extract the username/pwd from Authorization header for // Basic, we'll assume the client sent an invalid blob // SetDeniedFlags( SF_DENIED_LOGON ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } IF_DEBUG( PARSING ) { DBGPRINTF(( DBG_CONTEXT, "[OnAuthorization] User name = %s\n", _strUserName.QueryStr(), _strPassword.QueryStr() )); } _fClearTextPass = TRUE; } else if ( !_stricmp( _strAuthType.QueryStr(), "Digest" ) || !_stricmp( _strAuthType.QueryStr(), "NT-Digest" ) ) { #if 0 if ( !(QueryAuthentication() & INET_INFO_AUTH_MD5_AUTH) ) { goto non_allowed; } LPSTR aValueTable[ MD5_AUTH_LAST ]; STR strNonce; if ( !ParseForName( pchBlob, MD5_AUTH_NAMES, MD5_AUTH_LAST, aValueTable ) || aValueTable[MD5_AUTH_USERNAME] == NULL || aValueTable[MD5_AUTH_REALM] == NULL || aValueTable[MD5_AUTH_URI] == NULL || aValueTable[MD5_AUTH_NONCE] == NULL || aValueTable[MD5_AUTH_RESPONSE] == NULL ) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } if ( GenerateNonce( &strNonce, IISSUBA_MD5 ) ) { if ( _tcpauth.LogonDigestUser( aValueTable[MD5_AUTH_USERNAME], aValueTable[MD5_AUTH_REALM], aValueTable[MD5_AUTH_URI], _strMethod.QueryStr(), aValueTable[MD5_AUTH_NONCE], strNonce.QueryStr(), aValueTable[MD5_AUTH_RESPONSE], IISSUBA_MD5, g_pTsvcInfo ) ) { _fAuthenticating = FALSE; _fLoggedOn = TRUE; } else { DWORD err = GetLastError(); if ( err == ERROR_ACCESS_DENIED || err == ERROR_LOGON_FAILURE ) { _fAuthenticating = FALSE; SetDeniedFlags( SF_DENIED_LOGON ); SetKeepConn( FALSE ); } return FALSE; } } else { return FALSE; } #else _fAuthTypeDigest = TRUE; _strUserName.Reset(); _strPassword.Copy ( pchBlob ); #endif } else { // // See if it's one of the SSP packages // BUFFER buff; BOOL fNeedMoreData; DWORD cbOut; if ( !QueryMetaData()->CheckSSPPackage( _strAuthType.QueryStr() ) ) { goto NotFound; } // // If NTLM is not enabled, force the user to anonymous if // anon is enabled or kick them out with Access denied // if ( !(dwAuthFlags & INET_INFO_AUTH_NT_AUTH) ) { if ( dwAuthFlags & INET_INFO_AUTH_ANONYMOUS ) { goto NotFound; } else { SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } } // // Process the authentication blob the client sent // us and build the blob to be returned in _strAuthInfo // if ( !_tcpauth.Converse( pchBlob, 0, &buff, &cbOut, &fNeedMoreData, QueryMetaData()->QueryAuthentInfo(), _strAuthType.QueryStr(), NULL, NULL, QueryW3Instance() ) || !_strAuthInfo.Copy( _strAuthType ) || !_strAuthInfo.Append( " ", 1 ) || !_strAuthInfo.Append( cbOut ? ((CHAR *) buff.QueryPtr()) : "" )) { DWORD err = GetLastError(); // // If the authentication package gives us a denied error, then // we need to reset our authorization info to indicate the client // needs to start from scratch. We also force a disconnect. // if ( err == ERROR_ACCESS_DENIED || err == ERROR_LOGON_FAILURE ) { _fAuthenticating = FALSE; SetDeniedFlags( SF_DENIED_LOGON ); SetKeepConn( FALSE ); } if ( err == ERROR_PASSWORD_EXPIRED || err == ERROR_PASSWORD_MUST_CHANGE ) { _fAuthenticating = FALSE; SetDeniedFlags( SF_DENIED_LOGON ); SetKeepConn( FALSE ); } return FALSE; } _fAuthenticating = fNeedMoreData; if ( !fNeedMoreData && !cbOut ) { _strAuthInfo.Reset(); } // // If the last server side conversation succeeded and there isn't // any more data, then we've successfully logged the user on // if ( _fLoggedOn = !fNeedMoreData ) { if ( !CheckValidSSPILogin() ) { return FALSE; } } #if 0 else if ( !IsKeepConnSet() ) { // no point in sending data : connection won't be kept alive // assume that auth method is session oriented SetDeniedFlags( SF_DENIED_LOGON ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } #endif } NotFound: // // Restore the string // if ( pchSpace ) { *pchSpace = ' '; } return TRUE; } BOOL HTTP_REQ_BASE::OnProxyAuthorization( CHAR * pszValue ) /*++ Routine Description: Processes the HTTP "Proxy-Authorization: " field Return Value: TRUE if successful, FALSE on error --*/ { // // If a filter has indicated this is a proxy request, use the // authorization information // if ( IsProxyRequest() ) { return ParseAuthorization( pszValue ); } return TRUE; } /******************************************************************* NAME: HTTP_REQ_BASE::BuildStatusLine SYNOPSIS: Formulates the HTTP status line of the server response to the client of the form: ENTRY: pbufResp - Receives status string dwHTTPError - Response code to load dwError2 - Optional reason error NOTES: Optional acceptable authentication information will be added if the HTTP error is access denied HISTORY: Johnl 29-Aug-1994 Created ********************************************************************/ BOOL HTTP_REQ_BASE::BuildStatusLine( BUFFER * pbufResp, DWORD dwHTTPError, DWORD dwError2, LPSTR pszError2, STR *pstrErrorStr) { STACK_STR( strStatus, MAX_PATH ); STACK_STR( strError2, MAX_PATH ); LPSTR pErr2 = NULL; LPSTR pFormatStrBuff = NULL; CHAR * pszStatus; CHAR ach[64]; CHAR * pszTail; // // Get the HTTP error string // switch ( dwHTTPError ) { case HT_OK: pszStatus = "OK"; break; case HT_RANGE: pszStatus = "Partial content"; break; case HT_NOT_MODIFIED: pszStatus = "Not Modified"; break; case HT_REDIRECT: pszStatus = "Object Moved"; break; default: if ( !g_pInetSvc->LoadStr( strStatus, dwHTTPError + ID_HTTP_ERROR_BASE )) { DBGPRINTF((DBG_CONTEXT, "BuildErrorResponse: failed to load HTTP status code %d (res=%d), error %d\n", dwHTTPError, dwHTTPError + ID_HTTP_ERROR_BASE, GetLastError() )); pszStatus = "Error"; } else { pszStatus = strStatus.QueryStr(); } break; } // // If the client wants a secondary error string, get it now // if ( dwError2 ) { if ( g_pInetSvc->LoadStr( strError2, dwError2 )) { pErr2 = strError2.QueryStr(); } else { DBGPRINTF((DBG_CONTEXT, "BuildErrorResponse: failed to load 2nd status code %d (res=%d), error %d\n", dwError2, dwError2, GetLastError() )); // // Couldn't load the string, just provide the error number then // wsprintf( ach, "%d (0x%08lx)", dwError2, dwError2 ); if ( !strError2.Copy( ach )) return FALSE; pErr2 = strError2.QueryStr(); } if ( dwError2 == ERROR_BAD_EXE_FORMAT ) { // format the message to include file name DWORD dwL = 0; // handle exception that could be generated if this message // requires more than the # of param we supply and AV __try { if ( !FormatMessage( ( FORMAT_MESSAGE_ALLOCATE_BUFFER| FORMAT_MESSAGE_FROM_STRING| FORMAT_MESSAGE_ARGUMENT_ARRAY ), strError2.QueryStr(), 0, 0, (LPTSTR)&pFormatStrBuff, dwL, (va_list*)&pszError2 ) ) { pErr2 = NULL; pFormatStrBuff = NULL; } } __except ( EXCEPTION_EXECUTE_HANDLER ) { pErr2 = NULL; pFormatStrBuff = NULL; } } } // // Make sure there is room for the wsprintf // if ( !pbufResp->Resize( strlen(pszStatus) + 1 + (pErr2 ? strlen(pErr2) : 0) + LEN_PSZ_HTTP_VERSION_STR + 20 * sizeof(TCHAR) )) // status code + space { if ( pFormatStrBuff ) LocalFree( pFormatStrBuff ); return FALSE; } pszTail = (CHAR *) pbufResp->QueryPtr(); // // Build "HTTP/1.0 ### \r\n" or "HTTP/1.0 ### h()\r\n" // if (!g_ReplyWith11) { APPEND_NUMERIC_HEADER( pszTail, "HTTP/1.0 ", dwHTTPError, " " ); } else { APPEND_NUMERIC_HEADER( pszTail, "HTTP/1.1 ", dwHTTPError, " " ); } APPEND_PSZ_HEADER( pszTail, "", pszStatus, "" ); if ( pErr2 ) { if (pstrErrorStr != NULL) { if (!pstrErrorStr->Append(strError2)) { pstrErrorStr->SetLen(0); } } } APPEND_STRING( pszTail, "\r\n" ); if ( pFormatStrBuff ) { LocalFree( pFormatStrBuff ); } return TRUE; } BOOL HTTP_REQ_BASE::BuildExtendedStatus( STR * pstrResp, DWORD dwHTTPError, DWORD dwError2, DWORD dwExplanation, LPSTR pszError2 ) /*++ Routine Description: This static method build a HTTP response string with extended explanation information Arguments: pStr - Receives built response dwHTTPError - HTTP error response dwError2 - Extended error information (win/socket error) dwExplanation - String ID of the explanation text Return Value: TRUE if successful, FALSE on error --*/ { // NYI: Who does the resize for the string before calling pstrResp ?? // How long is the buffer though ?? // The code originally assumed that there will be enough space // to append strings after buildStatusLine -- we use assert to check it. // // // "HTTP/ " // // NYI: Do a downlevel cast and send the buffer pointer around :( if ( !BuildStatusLine( pstrResp, dwHTTPError, dwError2, pszError2)) { return FALSE; } // NYI: I need to setlen here because the buffer object was used earlier. DBG_REQUIRE( pstrResp->SetLen( strlen(pstrResp->QueryStr()))); // // "Server: / // Obtain this fro the global cache. // DBG_REQUIRE( pstrResp->Append( szServerVersion, (cbServerVersionString)) ); #if 0 // // If we need to add an explanation, also include a content length // if ( dwExplanation ) { STR str; if ( !g_pInetSvc->LoadStr( str, dwExplanation )) { return FALSE; } DBG_REQUIRE( pstrResp->Append( str)); } #endif return TRUE; } BOOL HTTP_REQ_BASE::LogonUser( BOOL * pfFinished ) /*++ Routine Description: This method attempts to retrieve an impersonation token based on the current request Arguments: pfFinished - Set to TRUE if no further processing is needed Return Value: TRUE if successful, FALSE on error --*/ { BOOL fAsGuest; BOOL fAsAnonymous; BOOL fAsync; TCHAR * pszUser; DWORD cbUser; TCHAR * pszPswd; DWORD cbPswd; LPSTR pDns = NULL; DWORD dwAuth; HANDLE hAccessTokenPrimary = NULL; HANDLE hAccessTokenImpersonation = NULL; LPW3_SERVER_STATISTICS pStatsObj = QueryW3StatsObj(); W3_STATISTICS_1 * pW3Stats = pStatsObj->QueryStatsObj(); STACK_STR( strRealm, MAX_PATH); // make a local copy of the realm headers. dwAuth = QueryAuthentication(); if ( _fAuthTypeDigest ) { if ( dwAuth & INET_INFO_AUTH_MD5_AUTH ) { if ( !_strUserName.Resize( SF_MAX_USERNAME ) || !_strUnmappedUserName.Resize( SF_MAX_USERNAME ) || !_strUnmappedUserName.Copy( _strUserName ) || !_strPassword.Resize( SF_MAX_PASSWORD ) || !_strUnmappedPassword.Copy( _strPassword ) || !_strAuthType.Resize(SF_MAX_AUTH_TYPE) ) { return FALSE; } if ( _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATIONEX, IsSecurePort() ) && !_Filter.NotifyAuthInfoFiltersEx( _strUnmappedUserName.QueryStr(), SF_MAX_USERNAME, _strUserName.QueryStr(), SF_MAX_USERNAME, _strPassword.QueryStr(), "", QueryMetaData()->QueryAuthentInfo()-> strDefaultLogonDomain.QueryStr(), _strAuthType.QueryStr(), SF_MAX_AUTH_TYPE, &hAccessTokenPrimary, &hAccessTokenImpersonation, pfFinished )) { SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER ); return FALSE; } // // The filter may have modified the lengths - reset the lengths. // Note _strPassword doesn't need to be reset here since // NotifyAuthInfoFiltersEx() doesn't modify it. // _strUnmappedUserName.SetLen( strlen( _strUnmappedUserName.QueryStr())); _strUserName.SetLen( strlen( _strUserName.QueryStr() )); _strAuthType.SetLen( strlen( _strAuthType.QueryStr() )); if ( *pfFinished ) { return TRUE; } if ( hAccessTokenPrimary != NULL || hAccessTokenImpersonation != NULL ) { _fMappedAcct = TRUE; _fSingleRequestAuth = TRUE; goto logged_in; } } IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT,"Access denied based on configuration\n")); } SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } if ( !_strUnmappedUserName.Copy( _strUserName ) || !_strUnmappedPassword.Copy( _strPassword )) { return FALSE; } if ( _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATION, IsSecurePort() )) { if ( !_strUserName.Resize( SF_MAX_USERNAME ) || !_strPassword.Resize( SF_MAX_PASSWORD ) ) { return FALSE; } if ( !_Filter.NotifyAuthInfoFilters( _strUserName.QueryStr(), SF_MAX_USERNAME, _strPassword.QueryStr(), SF_MAX_PASSWORD, pfFinished )) { IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT,"Access denied based on filter\n")); } SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER ); return FALSE; } _strUserName.SetLen( strlen( _strUserName.QueryStr() )); _strPassword.SetLen( strlen( _strPassword.QueryStr() )); if ( *pfFinished ) { return TRUE; } if ( strcmp( _strUserName.QueryStr(), _strUnmappedUserName.QueryStr() ) ) { _fMappedAcct = TRUE; } } if ( (dwAuth & INET_INFO_AUTH_MAPBASIC) && _Filter.IsNotificationNeeded( SF_NOTIFY_AUTHENTICATIONEX, IsSecurePort() ) ) { if ( !_strUserName.Resize( SF_MAX_USERNAME ) || !_strPassword.Resize( SF_MAX_PASSWORD ) || !_strAuthType.Resize( SF_MAX_AUTH_TYPE ) ) { return FALSE; } // // generate the realm information for this request // strRealm.Copy( QueryMetaData()->QueryRealm() ? QueryMetaData()->QueryRealm() : QueryHostAddr() ); if ( !_Filter.NotifyAuthInfoFiltersEx( _strUnmappedUserName.QueryStr(), SF_MAX_USERNAME, _strUserName.QueryStr(), SF_MAX_USERNAME, _strPassword.QueryStr(), strRealm.QueryStr(), QueryMetaData()->QueryAuthentInfo()-> strDefaultLogonDomain.QueryStr(), _strAuthType.QueryStr(), SF_MAX_AUTH_TYPE, &hAccessTokenPrimary, &hAccessTokenImpersonation, pfFinished )) { IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT,"Access denied based on filter\n")); } SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_FILTER ); return FALSE; } _strUnmappedUserName.SetLen( strlen( _strUnmappedUserName.QueryStr())); _strUserName.SetLen( strlen( _strUserName.QueryStr() )); _strAuthType.SetLen( strlen( _strAuthType.QueryStr() )); if ( *pfFinished ) { return TRUE; } } logged_in: pszUser = *_strUserName.QueryStr() ? _strUserName.QueryStr(): NULL; cbUser = _strUserName.QueryCB(); pszPswd = *_strPassword.QueryStr() ? _strPassword.QueryStr(): NULL; cbPswd = _strPassword.QueryCB(); pStatsObj->IncrLogonAttempts(); logged_in2: if ( (hAccessTokenPrimary != NULL) || (hAccessTokenImpersonation != NULL) ) { _fMappedAcct = TRUE; if ( !_tcpauth.SetAccessToken( hAccessTokenPrimary, hAccessTokenImpersonation )) { DBGPRINTF(( DBG_CONTEXT, "[ClearTextLogon] ::LogonUser failed, error %d\n", GetLastError())); return FALSE; } fAsGuest = FALSE; fAsAnonymous = FALSE; _fClearTextPass = FALSE; } else { if ( !(dwAuth & INET_INFO_AUTH_ANONYMOUS ) && (pszUser == NULL) ) { IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT, "Access denied based on configuration[%x]\n", dwAuth)); } SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } if ( QueryMetaData()->QueryAuthentInfo()->dwLogonMethod == LOGON32_LOGON_NETWORK ) { switch ( QueryW3Instance()->QueryNetLogonWks() ) { case MD_NETLOGON_WKS_DNS: pDns = QueryClientConn()->QueryResolvedDnsName(); break; case MD_NETLOGON_WKS_IP: pDns = QueryClientConn()->QueryRemoteAddr(); break; } } if ( ( cbUser > UNLEN ) || ( cbPswd > PWLEN ) ) { SetDeniedFlags( SF_DENIED_LOGON ); SetLastError( ERROR_INSUFFICIENT_BUFFER ); return FALSE; } if ( !_tcpauth.ClearTextLogon( pszUser, pszPswd, &fAsGuest, &fAsAnonymous, QueryW3Instance(), QueryMetaData()->QueryAuthentInfo(), pDns )) { DBGPRINTF(( DBG_CONTEXT, "[ClearTextLogon] ::LogonUser failed, error %d\n", GetLastError())); DWORD dwErr = GetLastError(); if ( (fAsAnonymous || fAsGuest) && (dwErr == ERROR_LOGIN_TIME_RESTRICTION || dwErr == ERROR_INVALID_LOGON_HOURS || dwErr == ERROR_ACCOUNT_DISABLED || dwErr == ERROR_ACCOUNT_LOCKED_OUT || dwErr == ERROR_ACCOUNT_EXPIRED ) ) { dwErr = ERROR_ACCESS_DENIED; } // // If we pass an invalid username/domain to LogonUser, LastError is set // to ERROR_INVALID_PARAMETER, so we'll just massage that into returning // "Access Denied" // if ( (dwErr == ERROR_ACCESS_DENIED) || (dwErr == ERROR_LOGON_FAILURE) || (dwErr == ERROR_INVALID_PARAMETER) ) { SetDeniedFlags( SF_DENIED_LOGON ); dwErr = ERROR_ACCESS_DENIED; } // // Query fully qualified name ( including domain ) // so that we can prompt user for new password // with a name suitable for NetUserChangePassword // _tcpauth.QueryFullyQualifiedUserName( pszUser, &_strUnmappedUserName, QueryW3Instance(), QueryMetaData()->QueryAuthentInfo()); // // set last error here because QueryFullyQualifiedUserName // modified last error. // SetLastError( dwErr ); return FALSE; } } // // Are anonymous or clear text (basic) logons allowed? We assume // it's an NT logon if it's neither one of these // if ( fAsAnonymous && !(dwAuth & INET_INFO_AUTH_ANONYMOUS )) { DBGPRINTF(( DBG_CONTEXT, "[ClearTextLogon] Denying anonymous logon (not enabled), user %s\n", _strUserName.QueryStr() )); SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } else if ( _fClearTextPass && !(dwAuth & INET_INFO_AUTH_CLEARTEXT )) { DBGPRINTF(( DBG_CONTEXT, "[ClearTextLogon] Denying clear text logon (not enabled), user %s\n", _strUserName.QueryStr() )); SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } if ( fAsGuest ) { if ( !(dwAuth & INET_INFO_AUTH_ANONYMOUS ) ) { DBGPRINTF(( DBG_CONTEXT, "[ClearTextLogon] Denying guest logon (not enabled), user %s\n", _strUserName.QueryStr() )); SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } if ( !fAsAnonymous ) { _tcpauth.Reset(); _strUserName.Reset(); _strPassword.Reset(); pszUser = NULL; pszPswd = NULL; cbUser = 0; cbPswd = 0; goto logged_in2; } } _fLoggedOn = TRUE; _fAnonymous = fAsAnonymous; if ( fAsAnonymous ) { pStatsObj->IncrAnonymousUsers(); _cbLastAnonAcctDesc = QueryMetaData()->QueryAuthentInfo()->cbAnonAcctDesc; if (!_bufLastAnonAcctDesc.Resize(_cbLastAnonAcctDesc) ) { // Couldn't resize the buffer properly, set the size to 0 so // we'll force a relogon each time to be safe. _cbLastAnonAcctDesc = 0; } memcpy(_bufLastAnonAcctDesc.QueryPtr(), QueryMetaData()->QueryAuthentInfo()->bAnonAcctDesc.QueryPtr(), _cbLastAnonAcctDesc); } else { pStatsObj->IncrNonAnonymousUsers(); _cbLastAnonAcctDesc = 0; } return TRUE; } # define MAX_ERROR_MESSAGE_LEN ( 500) #define EXTRA_LOGGING_BUFFER_SIZE 2048 BOOL HTTP_REQ_BASE::WriteLogRecord( VOID ) /*++ Routine Description: Writes a transaction log for this request Return Value: TRUE if successful, FALSE on error --*/ { // // HACK ALERT // // This function may crash if this request has already written // a log record - bail out now if so. // // This HACK must precede the call to EndOfRequest(), which // is the likely cause of the crash. // // NOTE we set flag true immediately (even though we still may // not log) to absolutely guarantee that we won't hit the crash, // and to keep code for this hack as localized as possible. // // CONSIDER for AFTER we ship IIS 4.0: // clean up cause of crash, and then remove this hack // if ( _fLogRecordWritten ) { return TRUE; } _fLogRecordWritten = TRUE; INETLOG_INFORMATION ilRequest; HTTP_FILTER_LOG Log; DWORD dwLog; BOOL fDontLog = FALSE; LPSTR pszClientHostName = QueryClientConn()->QueryRemoteAddr(); // // Metadata pointer can be NULL at this point // if ( QueryMetaData() ) { if ( QueryMetaData()->QueryDoReverseDns() && QueryClientConn()->IsDnsResolved() ) { pszClientHostName = QueryClientConn()->QueryResolvedDnsName(); } fDontLog = QueryMetaData()->DontLog(); } // // Notify filters and close any handles for this request // EndOfRequest(); // // Log this request if we actually did anything // if ( _dwLogHttpResponse == HT_DONT_LOG || IsProxyRequest() ) { if ( !IsProxyRequest() ) { IF_DEBUG(REQUEST) { DBGPRINTF(( DBG_CONTEXT, "[WriteLogRecord] not writing log record, status is HT_DONT_LOG\n" )); } } return TRUE; } // // If no logging is required, get out now // if ( ((HTTP_REQUEST*)this)->QueryVerb() == HTV_TRACECK || (QueryW3Instance() == NULL) || (!QueryW3Instance()->IsLoggingEnabled()) || (!QueryW3Instance()->IsLogErrors() && (QueryLogWinError() || (QueryLogHttpResponse() >= 400))) || (!QueryW3Instance()->IsLogSuccess() && (QueryLogWinError() == NO_ERROR) && (QueryLogHttpResponse() < 400)) || fDontLog ) { return TRUE; } if ( _Filter.IsNotificationNeeded( SF_NOTIFY_LOG, IsSecurePort() )) { // // If we have filters, use the possible filter replacement items // Log.pszClientHostName = pszClientHostName; Log.pszClientUserName = _strUserName.QueryStr(); Log.pszServerName = QueryClientConn()->QueryLocalAddr(); Log.pszOperation = _strMethod.QueryStr(); Log.pszTarget = _strURL.QueryStr(); Log.pszParameters = _strLogParams.QueryCCH() ? _strLogParams.QueryStr() : _strURLParams.QueryStr(); Log.dwHttpStatus = QueryLogHttpResponse(); Log.dwWin32Status = QueryLogWinError(); Log.dwBytesSent = _cbBytesSent; Log.dwBytesRecvd = _cbClientRequest + _cbTotalEntityBody; Log.msTimeForProcessing = GetCurrentTime() - _msStartRequest; _Filter.NotifyLogFilters( &Log ); ilRequest.pszClientHostName = (char *) Log.pszClientHostName; ilRequest.pszClientUserName = (char *) Log.pszClientUserName; ilRequest.pszServerAddress = (char *) Log.pszServerName; ilRequest.pszOperation = (char *) Log.pszOperation; ilRequest.pszTarget = (char *) Log.pszTarget; ilRequest.pszParameters = (char *) Log.pszParameters; ilRequest.dwProtocolStatus = Log.dwHttpStatus; ilRequest.dwWin32Status = Log.dwWin32Status; ilRequest.msTimeForProcessing = Log.msTimeForProcessing; ilRequest.dwBytesSent = Log.dwBytesSent; ilRequest.dwBytesRecvd = Log.dwBytesRecvd; ilRequest.cbOperation = strlen( Log.pszOperation ); ilRequest.cbTarget = strlen( Log.pszTarget ); ilRequest.cbClientHostName = strlen(ilRequest.pszClientHostName); } else { ilRequest.pszClientHostName = pszClientHostName; ilRequest.pszClientUserName = _strUserName.QueryStr(); ilRequest.pszServerAddress = QueryClientConn()->QueryLocalAddr(); ilRequest.pszOperation = _strMethod.QueryStr(); ilRequest.pszTarget = _strURL.QueryStr(); ilRequest.pszParameters = _strLogParams.QueryCCH() ? _strLogParams.QueryStr() : _strURLParams.QueryStr(); ilRequest.dwProtocolStatus = QueryLogHttpResponse(); ilRequest.dwWin32Status = QueryLogWinError(); ilRequest.msTimeForProcessing = GetCurrentTime() - _msStartRequest; ilRequest.dwBytesSent = _cbBytesSent; ilRequest.dwBytesRecvd = _cbClientRequest + _cbTotalEntityBody; // // Get length of some strings // ilRequest.cbOperation = _strMethod.QueryCCH(); ilRequest.cbTarget = _strURL.QueryCCH(); ilRequest.cbClientHostName = strlen(ilRequest.pszClientHostName); } // // write capacity planning trace info. // if (GetIISCapTraceFlag()) { PIIS_CAP_TRACE_INFO pHttpCapTraceInfo; pHttpCapTraceInfo = AtqGetCapTraceInfo(QueryClientConn()->QueryAtqContext()); pHttpCapTraceInfo->IISCapTraceHeader.TraceHeader.Size = sizeof (IIS_CAP_TRACE_INFO); pHttpCapTraceInfo->IISCapTraceHeader.TraceHeader.Class.Type = EVENT_TRACE_TYPE_INFO; pHttpCapTraceInfo->MofFields[0].Length = ilRequest.cbOperation+1; pHttpCapTraceInfo->MofFields[0].DataPtr = (ULONGLONG) ilRequest.pszOperation; pHttpCapTraceInfo->MofFields[1].Length = ilRequest.cbTarget+1; pHttpCapTraceInfo->MofFields[1].DataPtr = (ULONGLONG) ilRequest.pszTarget; pHttpCapTraceInfo->MofFields[2].Length = sizeof(DWORD); pHttpCapTraceInfo->MofFields[2].DataPtr = (ULONGLONG) &ilRequest.dwBytesSent; if ( ERROR_INVALID_HANDLE == TraceEvent ( GetIISCapTraceLoggerHandle(), (PEVENT_TRACE_HEADER) pHttpCapTraceInfo)) { SetIISCapTraceFlag(0); } } BYTE pchTemp[EXTRA_LOGGING_BUFFER_SIZE]; BUFFER buf( pchTemp, EXTRA_LOGGING_BUFFER_SIZE); // init w/- stack buffer ilRequest.pszHTTPHeader= NULL; ilRequest.dwPort = QueryClientConn()->QueryPort( ); ilRequest.pszVersion=(LPSTR)_HeaderList.FastMapQueryValue(HM_VER); // // Only log extra fields if the request was from a version > 0.9 [which didn't // have headers] and extra logging fields have been requested // if ( !IsPointNine() && QueryW3Instance()->m_Logging.IsRequiredExtraLoggingFields()) { // // reconstruct the buffer // PCHAR pszFieldName = QueryW3Instance()->m_Logging.QueryExtraLoggingFields(); DWORD cbValueSize=0; DWORD cbTotalSize=0; DWORD cbActualSize=0; DWORD cbCurrentBufferSize = EXTRA_LOGGING_BUFFER_SIZE; PCHAR pszValue; PCHAR pszBuff = (TCHAR *) buf.QueryPtr(); while ( *pszFieldName != '\0' ) { // // add the string into the buffer // pszValue = QueryHeaderList()->FindValue( pszFieldName, &cbValueSize ); if ( pszValue == NULL ) { cbValueSize = 0; pszValue = ""; } cbTotalSize = cbActualSize + cbValueSize + 2; if (cbTotalSize > cbCurrentBufferSize) { buf.Resize(cbTotalSize); cbCurrentBufferSize = cbTotalSize; pszBuff=(TCHAR*)buf.QueryPtr(); pszBuff += cbActualSize; } CopyMemory( pszBuff, pszValue, strlen(pszValue)+1); pszFieldName += strlen(pszFieldName) + 1; pszBuff += cbValueSize+1; cbActualSize += cbValueSize+1; *pszBuff = '\0'; } ilRequest.pszHTTPHeader = (PCHAR)buf.QueryPtr(); ilRequest.cbHTTPHeaderSize = cbActualSize; } dwLog = QueryW3Instance()->m_Logging.LogInformation(&ilRequest); if ( dwLog != NO_ERROR ) { DBGPRINTF((DBG_CONTEXT, "[WriteLogRecord] - Failed, error %d\n", GetLastError() )); // // We should make sure LogInformation will never fail // } return TRUE; } BOOL HTTP_REQ_BASE::AppendLogParameter( CHAR * pszParam ) /*++ Routine Description: Appends data for logging. Arguments: pszParam - The data to be appended Return Value: TRUE if successful, FALSE on error Notes: The way that AppendLogData works is that it just appends data to the query string, so that when the query string is logged, the new data gets a free ride. The problem with this is that anyone who looks at the query string after AppendLogParameter will see the additional log data. To prevent this, we'll create a copy of the query string data upon the first call of this function and then append the new data to the copy. When the log is written, the copy will be written if it has data, else the original query string will be written. --*/ { // // If the _strLogParams buffer is empty, copy // the query string first. // if ( _strLogParams.QueryCCH() == 0 ) { BOOL fRet = _strLogParams.Copy( _strURLParams.QueryStr() ); if ( !fRet ) { return fRet; } } // // Append the new data. // return _strLogParams.Append( pszParam ); } BOOL HTTP_REQ_BASE::BuildHttpHeader( OUT BOOL * pfFinished, IN CHAR * pchStatus OPTIONAL, IN CHAR * pchAdditionalHeaders OPTIONAL, IN DWORD dwOptions) /*++ Routine Description: Builds a full HTTP header reply with an optional status and other headers/data Arguments: pchStatus - optional HTTP status string like "401 Access Denied" pchAdditionalHeaders - optional additional HTTP or MIME headers and data. Must supply own '\r\n' terminator if this parameter is supplied pfFinished - Set to TRUE if no further processing is required Return Value: TRUE on success, FALSE on failure --*/ { STR str; BOOL fFinished = FALSE; if ( pchStatus ) { DWORD cbLen = ::strlen( pchStatus); // NYI: Can we compress the calls to string class here // Also make sure enough space is allocated in string object if ( !str.Resize( LEN_PSZ_HTTP_VERSION_STR + cbLen + 4) || !str.Copy( (!g_ReplyWith11 ? PSZ_HTTP_VERSION_STR : PSZ_HTTP_VERSION_STR11), LEN_PSZ_HTTP_VERSION_STR ) || !str.Append( pchStatus, cbLen ) ) { return FALSE; } // I am safe to assume space is there, because of resize DBG_ASSERT( str.QueryCB() < (str.QuerySize() - 2)); str.AppendCRLF(); } if ( !BuildBaseResponseHeader( QueryRespBuf(), pfFinished, (pchStatus ? &str : NULL ), dwOptions )) { return FALSE; } if ( pchAdditionalHeaders ) { DWORD dwAddlHdrLength; DWORD dwCurrentLength; dwAddlHdrLength = strlen(pchAdditionalHeaders) + 1; dwCurrentLength = QueryRespBufCB(); if (!QueryRespBuf()->Resize(dwCurrentLength + dwAddlHdrLength)) { return FALSE; } memcpy(QueryRespBufPtr() + dwCurrentLength, pchAdditionalHeaders, dwAddlHdrLength); } return TRUE; } // HTTP_REQ_BASE::BuildHttpHeader() BOOL HTTP_REQ_BASE::SendHeader( IN CHAR * pchStatus OPTIONAL, IN CHAR * pchAdditionalHeaders OPTIONAL, IN DWORD IOFlags, OUT BOOL * pfFinished, IN DWORD dwOptions, IN BOOL fWriteHeader ) /*++ Routine Description: Does a synchronous send of an HTTP header with optional status and additional headers. Arguments: pchStatus - HTTP Status code (or NULL for "200 Ok") pchAdditionalHeaders - Headers to add to the standard response set IOFlags - IO_* flags to send the headers with pfFinished - Set to TRUE if no further processing is needed for this request fWriteHeader - Defaults to TRUE; pass FALSE to suppress writing headers (designed for callers that want to send header and body together) Return Value: TRUE on success, FALSE on failure --*/ { DWORD BytesSent; DWORD cbAddHeaders; BOOL fAnyChanges = FALSE; *pfFinished = FALSE; if ( pchAdditionalHeaders ) { cbAddHeaders = strlen( pchAdditionalHeaders ); if ( cbAddHeaders && cbAddHeaders > QueryRespBuf()->QuerySize() / 2) { if ( !QueryRespBuf()->Resize( QueryRespBuf()->QuerySize() + cbAddHeaders )) { return FALSE; } } } if ( !BuildHttpHeader( pfFinished, pchStatus, pchAdditionalHeaders, dwOptions)) { return FALSE; } if ( *pfFinished ) { return TRUE; } DBG_ASSERT( QueryRespBufCB() <= QueryRespBuf()->QuerySize() ); if ( _Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE, IsSecurePort() )) { if ( !_Filter.NotifySendHeaders( QueryRespBufPtr(), pfFinished, &fAnyChanges, QueryRespBuf() )) { return FALSE; } if ( *pfFinished ) { return TRUE; } } if ( fWriteHeader ) { if ( !WriteFile( QueryRespBufPtr(), QueryRespBufCB(), &BytesSent, IOFlags )) { return FALSE; } } return TRUE; } // HTTP_REQ_BASE::SendHeader() BOOL HTTP_REQ_BASE::SendHeader( IN CHAR * pchHeaders, IN DWORD cbHeaders, IN DWORD IOFlags, OUT BOOL * pfFinished ) /*++ Routine Description: Does a send of the HTTP header response where the status is already embedded in the header set If the pfFinished comes back TRUE and IO_FLAG_ASYNC was specified, then an IO completion will be made Arguments: pchHeaders - Pointer to header set cbHeaders - Length of headers to send (or -1 if headers are '\0' terminated) IOFlags - IO_* flags to send the headers with pfFinished - Set to TRUE if no further processing is needed for this request Return Value: TRUE on success, FALSE on failure --*/ { DWORD BytesSent; DWORD cbAddHeaders; BOOL fAnyChanges = FALSE; *pfFinished = FALSE; if ( _Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE, IsSecurePort() )) { if ( !_Filter.NotifySendHeaders( pchHeaders, pfFinished, &fAnyChanges, QueryRespBuf() )) { return FALSE; } if ( *pfFinished ) { return TRUE; } if ( fAnyChanges ) { pchHeaders = QueryRespBufPtr(); cbHeaders = QueryRespBufCB(); } } if ( cbHeaders == -1 ) { cbHeaders = strlen( pchHeaders ); } if ( !WriteFile( pchHeaders, cbHeaders, &BytesSent, IOFlags )) { return FALSE; } return TRUE; } // HTTP_REQ_BASE::SendHeader() #define NO_CACHE_HEADER_SIZE (sizeof("Cache-Control: no-cache,no-transform\r\n") - 1 +\ sizeof("Expires: Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1) BOOL HTTP_REQ_BASE::BuildBaseResponseHeader( BUFFER * pbufResponse, BOOL * pfFinished, STR * pstrStatus, DWORD dwOptions ) /*++ Routine Description: Builds a set of common server response headers Arguments: pbufResponse - Receives response headers pfFinished - Set to TRUE if no further processing is needed pstrStatus - Optional HTTP response status (defaults to 200) dwOptions - bit field of options. HTTPH_SEND_GLOBAL_EXPIRE Indicates whether an "Expires: xxx" based on the global expires value is include with the headers HTTPH_NO_DATE indicates whether to generate a "Date:" header. Return Value: TRUE on success, FALSE on failure --*/ { FILETIME ftSysTime; CHAR achTime[64]; BOOL fSysTimeValid = FALSE; CHAR * pszResp; CHAR * pszTail; DWORD cb; DWORD cbExpire; DWORD cbCustom; DWORD cbLeft; DWORD dwSizeUsed; pszResp = (CHAR *) pbufResponse->QueryPtr(); // // Add the status line - "HTTP/1.0 nnn sss...\r\n" // if ( !pstrStatus ) { pszTail = pszResp; if ( !_fProcessByteRange ) { if (!g_ReplyWith11) { APPEND_STRING( pszTail, "HTTP/1.0 200 OK\r\n" ); } else { APPEND_STRING( pszTail, "HTTP/1.1 200 OK\r\n" ); } } else { if (!g_ReplyWith11) { APPEND_STRING( pszTail, "HTTP/1.0 206 Partial content\r\n" ); } else { APPEND_STRING( pszTail, "HTTP/1.1 206 Partial content\r\n" ); } } } else { // ++WinSE 27217 DWORD dwRequired = pstrStatus->QuerySize() + MIN_BUFFER_SIZE_FOR_HEADERS; if(pbufResponse->QuerySize() < dwRequired) { if(!pbufResponse->Resize(dwRequired)) return FALSE; pszResp = (CHAR *) pbufResponse->QueryPtr(); } // --WinSE 27217 strcpy( pszResp, pstrStatus->QueryStr() ); pszTail = pszResp + strlen(pszResp); } // // "Server: Microsoft/xxx // APPEND_VER_STR( pszTail ); DBG_ASSERT( pbufResponse->QuerySize() >= MIN_BUFFER_SIZE_FOR_HEADERS ); // // Fill in the rest of the headers // // // "Date: " - Time the response was sent. // if ( !(dwOptions & HTTPH_NO_DATE ) ) { // build Date: uses Date/Time cache pszTail += g_pDateTimeCache->GetFormattedCurrentDateTime( pszTail ); } // // Add an expires header and any custom headers if the feature // is enabled and the caller wants it. Since we could be adding // lots of headers here check for space. // // Note for filters that send response headers, _pMetaData may // be NULL at this point // if (!(dwOptions & HTTPH_NO_CUSTOM)) { cbCustom = _pMetaData ? _pMetaData->QueryHeaders()->QueryCB() : 0; } else { cbCustom = 0; } if ( dwOptions & HTTPH_SEND_GLOBAL_EXPIRE ) { if (!_bForceNoCache) { cbExpire = _pMetaData->QueryExpireMaxLength() + _pMetaData->QueryCacheControlHeaderLength(); } else { cbExpire = NO_CACHE_HEADER_SIZE; } } else { cbExpire = 0; } if ( (cb = cbCustom + cbExpire) != 0 ) { cb += POST_CUSTOM_HEADERS_SIZE; // Find out how many bytes are left in the response buffer. cbLeft = pbufResponse->QuerySize() - DIFF(pszTail - pszResp); if (cb > cbLeft) { // Not enough left, try to resize the buffer. if ( !pbufResponse->Resize( pbufResponse->QuerySize() - cbLeft + cb + 1)) { // Couldn't resize, fail. return FALSE; } pszTail = (CHAR *) pbufResponse->QueryPtr() + (pszTail - pszResp); // Update pszResp, in case it's used later. pszResp = (CHAR *) pbufResponse->QueryPtr(); } if ( cbCustom ) { memcpy( pszTail, _pMetaData->QueryHeaders()->QueryStr(), cbCustom + 1); pszTail += cbCustom; } if ( cbExpire ) { DWORD dwExpireHeaderLength; FILETIME ftNow; DWORD dwStaticMaxAge; DWORD dwExpireMode; DWORD dwDelta; if ( !fSysTimeValid ) { ::IISGetCurrentTimeAsFileTime(&ftSysTime); fSysTimeValid = TRUE; } dwExpireHeaderLength = _pMetaData->QueryExpireHeaderLength(); if (!_bForceNoCache) { dwExpireMode = _pMetaData->QueryExpireMode(); if (_pMetaData->QueryCacheControlHeaderLength() != 0) { memcpy( pszTail, _pMetaData->QueryCacheControlHeader(), _pMetaData->QueryCacheControlHeaderLength() + 1 ); pszTail += _pMetaData->QueryCacheControlHeaderLength(); } } else { memcpy(pszTail, "Cache-Control: no-cache,no-transform\r\n", sizeof("Cache-Control: no-cache,no-transform\r\n")); pszTail += sizeof("Cache-Control: no-cache,no-transform\r\n") - 1; dwExpireMode = EXPIRE_MODE_DYNAMIC; } switch ( dwExpireMode ) { case EXPIRE_MODE_STATIC: if (!_pMetaData->QueryConfigNoCache() && !_pMetaData->QueryHaveMaxAge()) { // // Don't have a pre-configured max-age or no-cache // header. Compute the proper one now. LONGLONG llNow; llNow = *(LONGLONG *)&ftSysTime; if (llNow < _pMetaData->QueryExpireTime().QuadPart) { llNow = _pMetaData->QueryExpireTime().QuadPart - llNow; llNow /= FILETIME_1_SECOND; // Convert to seconds. if ( llNow > (LONGLONG)0xffffffff) { dwStaticMaxAge = 0xfffffff; } else { dwStaticMaxAge = (DWORD)llNow; } } else { dwStaticMaxAge = 0; } if (dwStaticMaxAge != 0) { APPEND_NUMERIC_HEADER(pszTail, "max-age=", dwStaticMaxAge, "\r\n"); } else { APPEND_STRING(pszTail, "no-cache\r\n"); } } memcpy( pszTail, _pMetaData->QueryExpireHeader(), dwExpireHeaderLength + 1 ); pszTail += dwExpireHeaderLength; break; case EXPIRE_MODE_DYNAMIC: if (!_bForceNoCache) { dwDelta = _pMetaData->QueryExpireDelta(); } else { dwDelta = 0; } if ( !::FileTimeToGMTEx( ftSysTime, achTime, sizeof(achTime), dwDelta )) { return FALSE; } APPEND_STRING( pszTail, "Expires: " ); cb = strlen( achTime ); memcpy( pszTail, achTime, cb ); pszTail += cb; APPEND_STRING( pszTail, "\r\n" ); break; default: break; } } } // // "Connection: keep-alive" - Indicate if the server accepted the // session modifier by reflecting it back to the client // if ( IsKeepConnSet() ) { if (!IsOneOne() && !(dwOptions & HTTPH_NO_CONNECTION)) { APPEND_STRING( pszTail, "Connection: keep-alive\r\n" ); } } else { if (IsOneOne() && !(dwOptions & HTTPH_NO_CONNECTION)) { APPEND_STRING( pszTail, "Connection: close\r\n" ); } } if (dwOptions & HTTPH_SEND_GLOBAL_EXPIRE) { // // Check to see if we need to send a Content-Location: or // Vary: header. if (_bSendContentLocation) { CHAR *pszHostName; DWORD cbHostNameLength; pszHostName = QueryHostAddr(); cbHostNameLength = strlen(pszHostName); dwSizeUsed = DIFF(pszTail - pszResp); if ( !pbufResponse->Resize(dwSizeUsed + POST_CUSTOM_HEADERS_SIZE + sizeof("Content-Location: https:// \r\n") + sizeof(":65536") + cbHostNameLength + _strRawURL.QueryCB())) { // Couldn't resize, fail. return FALSE; } pszResp = (CHAR *) pbufResponse->QueryPtr(); pszTail = pszResp + dwSizeUsed; // WinSE 5600 SHORT sPort = QueryClientConn()->QueryPort(); CHAR szPort[sizeof(":65536")]; DWORD szPortLen=0; if (QueryClientConn()->IsSecurePort()) { if (sPort != 443 ) { szPortLen=wsprintf( szPort, ":%d", (USHORT) sPort ); } } else { if ( sPort != QueryW3Instance()->QueryDefaultPort() ) { szPortLen=wsprintf( szPort, ":%d", (USHORT) sPort ); } } if ( cbHostNameLength != 0 ) { if (QueryClientConn()->IsSecurePort()) { APPEND_STRING(pszTail, "Content-Location: https://"); } else { APPEND_STRING(pszTail, "Content-Location: http://"); } memcpy(pszTail, pszHostName, cbHostNameLength + 1); if ( szPortLen ) { memcpy(pszTail+cbHostNameLength,szPort,szPortLen+1); } pszTail += cbHostNameLength + szPortLen; } else { APPEND_STRING(pszTail, "Content-Location: "); } APPEND_STR_HEADER(pszTail, "", _strRawURL, "\r\n"); } if (_bSendVary) { dwSizeUsed = DIFF(pszTail - pszResp); if ( !pbufResponse->Resize(dwSizeUsed + POST_CUSTOM_HEADERS_SIZE + sizeof("Vary: *\r\n")) ) { // Couldn't resize, fail. return FALSE; } pszResp = (CHAR *) pbufResponse->QueryPtr(); pszTail = pszResp + dwSizeUsed; APPEND_STRING(pszTail, "Vary: *\r\n"); } } // Authentication headers -- indicate the server requests authentication if ( IsAuthenticationRequested() ) { STR strAuthHdrs; if ( !AppendAuthenticationHdrs( &strAuthHdrs, pfFinished )) { return FALSE; } if ( *pfFinished ) { return TRUE; } if ( !QueryDenialHeaders()->IsEmpty() ) { if ( !strAuthHdrs.Append( QueryDenialHeaders()->QueryStr() ) ) { return FALSE; } } dwSizeUsed = DIFF(pszTail - pszResp); if ( !pbufResponse->Resize(dwSizeUsed + strAuthHdrs.QueryCB() + 1 ) ) { // Couldn't resize, fail. return FALSE; } pszResp = (CHAR *) pbufResponse->QueryPtr(); pszTail = pszResp + dwSizeUsed; memcpy( pszTail, strAuthHdrs.QueryStr(), strAuthHdrs.QueryCB() + 1 ); pszTail += strAuthHdrs.QueryCB(); } else if ( !_strAuthInfo.IsEmpty() ) { dwSizeUsed = DIFF(pszTail - pszResp); if ( !pbufResponse->Resize(dwSizeUsed + sizeof("Proxy-Authenticate: \r\n") + _strAuthInfo.QueryCB()) ) { // Couldn't resize, fail. return FALSE; } pszResp = (CHAR *) pbufResponse->QueryPtr(); pszTail = pszResp + dwSizeUsed; pszTail += wsprintf( pszTail, "%s: %s\r\n", (IsProxyRequest() ? "Proxy-Authenticate" : "WWW-Authenticate"), _strAuthInfo.QueryStr() ); } // // Append headers specified by filters // if ( !QueryAdditionalRespHeaders()->IsEmpty() ) { cb = QueryAdditionalRespHeaders()->QueryCB() + 1; cbLeft = pbufResponse->QuerySize() - DIFF(pszTail - pszResp); if ( cb > cbLeft ) { if ( !pbufResponse->Resize( pbufResponse->QuerySize() + cb )) { return FALSE; } pszTail = (CHAR *) pbufResponse->QueryPtr() + (pszTail - pszResp); } memcpy( pszTail, QueryAdditionalRespHeaders()->QueryStr(), cb ); } return TRUE; } BOOL WINAPI DeleteFunc( CtxtHandle* pH, PVOID pF ) /*++ Routine Description: Notification of SSPI security context destruction Arguments: pH - SSPI security context pF - HTTP_REQ_BASE to notify Return Value: TRUE on success, FALSE on failure --*/ { return ((HTTP_REQ_BASE*)pF)->NotifyRequestSecurityContextClose( pH ); } BOOL HTTP_REQ_BASE::SetCertificateInfo( IN PHTTP_FILTER_CERTIFICATE_INFO pData, IN CtxtHandle *pCtxt, IN HANDLE hPrimaryToken, IN HTTP_FILTER_DLL* pFilter ) /*++ Routine Description: Set SSL/PCT SSPI security context & access token Arguments: pData - ptr to certificate info pCtxt - SSPI security context hPrimaryToken - access token bound to SSPI security context pFilter - calling filter Return Value: TRUE on success, FALSE on failure --*/ { if ( pCtxt != NULL ) { if ( hPrimaryToken == IIS_ACCESS_DENIED_HANDLE ) { _fInvalidAccessToken = TRUE; return TRUE; } _pAuthFilter = NULL; W3_SERVER_INSTANCE *pInstance = QueryW3InstanceAggressively(); if ( hPrimaryToken != NULL ) { ResetAuth( TRUE ); _pAuthFilter = pFilter; if ( !_tcpauth.SetSecurityContextToken( pCtxt, hPrimaryToken, DeleteFunc, (PVOID)this, ( pInstance ? pInstance->GetAndReferenceSSLInfoObj() : NULL ) ) ) { _pAuthFilter = NULL; return FALSE; } _strAuthType.Copy( "SSL/PCT", (sizeof( "SSL/PCT") - 1) ); _fLoggedOn = TRUE; _fAuthCert = TRUE; _dwSslNegoFlags |= SSLNEGO_MAP; _strPassword.Reset(); QueryW3StatsObj()->IncrNonAnonymousUsers(); // // Get the user name // if ( !_tcpauth.QueryUserName( &_strUserName ) || (_strUserName.SetLen( strlen(_strUserName.QueryStr()) ), !_strUnmappedUserName.Copy( _strUserName )) ) { DBGPRINTF(( DBG_CONTEXT, "[SetCertificateInfo] Getting username failed, error %d\n", GetLastError() )); return FALSE; } } else { if ( !_tcpauth.SetSecurityContextToken( pCtxt, hPrimaryToken, DeleteFunc, (PVOID)this, ( pInstance ? pInstance->GetAndReferenceSSLInfoObj() : NULL ) ) ) { return FALSE; } } return TRUE; } SetLastError( ERROR_INVALID_PARAMETER ); DBG_ASSERT( FALSE ); return FALSE; } BOOL HTTP_REQ_BASE::CheckValidSSPILogin( VOID ) { // Check if guest account if ( _tcpauth.IsGuest( FALSE ) ) { if ( !(QueryAuthentication() & INET_INFO_AUTH_ANONYMOUS) ) { SetLastError( ERROR_LOGON_FAILURE ); SetDeniedFlags( SF_DENIED_LOGON | SF_DENIED_BY_CONFIG ); _fAuthenticating = FALSE; _fLoggedOn = FALSE; SetKeepConn( FALSE ); return FALSE; } // // cancel current authorization & authentication // _HeaderList.FastMapCancel( HM_AUT ); _fLoggedOn = FALSE; _fInvalidAccessToken = FALSE; _fClearTextPass = FALSE; _fAnonymous = FALSE; _fAuthenticating = FALSE; _fAuthTypeDigest = FALSE; _fAuthSystem = FALSE; _fAuthCert = FALSE; _tcpauth.Reset(); _strAuthType.Reset(); _strUserName.Reset(); _strPassword.Reset(); _strUnmappedUserName.Reset(); // return as if no authorization had been seen return TRUE; } QueryW3StatsObj()->IncrNonAnonymousUsers(); // // Get the user name // if ( !_tcpauth.QueryUserName( &_strUserName ) || !_strUnmappedUserName.Copy( _strUserName ) ) { DBGPRINTF(( DBG_CONTEXT, "[OnAuthorization] Getting username failed, error %d\n", GetLastError() )); return FALSE; } return TRUE; } BOOL HTTP_REQ_BASE::CheckForBasicAuthenticationHeader( LPSTR pszHeaders ) /*++ Routine Description: Parse header for realm info in Basic authentication scheme Arguments: pszHeaders - headers to parse Return Value: TRUE on success, FALSE on failure --*/ { UINT cHeaders = strlen( pszHeaders ); UINT cLine; UINT cToNext; LPSTR pEOL; int ch; while ( cHeaders ) { if ( (pEOL = (LPSTR)memchr( pszHeaders, '\n', cHeaders)) == NULL ) { cLine = cHeaders; cToNext = cHeaders; } else { cLine = DIFF(pEOL - pszHeaders); cToNext = cLine + 1; if ( pEOL != pszHeaders && pEOL[-1] == '\r' ) { --cLine; } } if ( !_strnicmp( pszHeaders, "WWW-Authenticate", sizeof("WWW-Authenticate")-1 ) ) { LPSTR pS = pszHeaders + sizeof("WWW-Authenticate:") - 1; UINT cS = cLine - (sizeof("WWW-Authenticate:") - 1); while ( cS && isspace( (UCHAR)(*pS) ) ) { ++pS; --cS; } if ( !_strnicmp( pS, "basic", sizeof("basic")-1 ) ) { while ( cS && isalpha( (UCHAR)(*pS) ) ) { ++pS; --cS; } while ( cS && !isalpha( (UCHAR)(*pS) ) ) { ++pS; --cS; } if ( !_strnicmp( pS, "realm", sizeof("realm")-1 ) ) { // check if realm value specified while ( cS && *pS != '=' ) { ++pS; --cS; } #if 0 if ( cS ) { ++pS; --cS; // locate start of realm value while ( cS ) { --cS; if ( *pS++ == '"' ) { break; } } LPSTR pR = pS; // locate end of realm value while ( cS && *pS != '"' ) { ++pS; --cS; } ch = *pS; *pS = '\0'; // pR contains zero-delimited realm *pS = ch; } #endif _fBasicRealm = TRUE; } return TRUE; } } cHeaders -= cToNext; pszHeaders += cToNext; } return FALSE; } BOOL HTTP_REQ_BASE::LogonAsSystem( VOID ) /*++ Routine Description: Associate the system account with the current request AUTH_TYPE and LOGON_USER are set to "SYSTEM" if success Arguments: None Return Value: TRUE on success, FALSE on failure --*/ { HANDLE hTok; if ( _fLoggedOn ) { if ( _fAnonymous ) { QueryW3StatsObj()->DecrCurrentAnonymousUsers(); } else { QueryW3StatsObj()->DecrCurrentNonAnonymousUsers(); } } ResetAuth( FALSE ); if ( !IISDuplicateTokenEx( g_hSysAccToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hTok )) { return FALSE; } if ( _tcpauth.SetAccessToken( hTok, NULL ) ) { _fLoggedOn = TRUE; _fMappedAcct = TRUE; _fAuthSystem = TRUE; _HeaderList.FastMapCancel( HM_AUT ); _HeaderList.FastMapCancel( HM_CON ); SetKeepConn( FALSE ); _strAuthType.Copy( PSZ_KWD_SYSTEM, LEN_PSZ_KWD_SYSTEM); _strUserName.Copy( PSZ_KWD_SYSTEM, LEN_PSZ_KWD_SYSTEM); _strPassword.Reset(); QueryW3StatsObj()->IncrNonAnonymousUsers(); //_strUnmappedUserName will contains real user name _fSingleRequestAuth = TRUE; return TRUE; } else { CloseHandle( hTok ); } return FALSE; } GENERIC_MAPPING VrootFileGenericMapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; VOID HTTP_REQ_BASE::ReleaseCacheInfo( VOID ) /*++ Description: Release ptr to URI & metadata cache Arguments: None Returns: Nothing --*/ { if ( g_fGetBackTraces ) { ULONG ulHash; RtlWalkFrameChain( m_ppvFrames, MAX_BACKTRACE_FRAMES, 0 ); } if ( _pURIInfo != NULL) { if (_pURIInfo->bIsCached) { TsCheckInCachedBlob( _pURIInfo ); } else { TsFree(QueryW3Instance()->GetTsvcCache(), _pURIInfo ); } } else { if (_pMetaData != NULL) { TsFreeMetaData(_pMetaData->QueryCacheInfo() ); } } _pMetaData = NULL; _pURIInfo = NULL; } BOOL HTTP_REQ_BASE::VrootAccessCheck( PW3_METADATA pMetaData, DWORD dwDesiredAccess ) /*++ Description: Check that the current access token have access to the ACL of the current virtual root Arguments: pMetaData - points to metadata object dwDesiredAccess - access right, e.g. FILE_GENERIC_READ Returns: TRUE if access granted, FALSE if access denied --*/ { DWORD dwGrantedAccess; BYTE PrivSet[400]; DWORD cbPrivilegeSet = sizeof(PrivSet); BOOL fAccessGranted; if ( pMetaData && pMetaData->QueryAcl() && (!::AccessCheck( pMetaData->QueryAcl(), QueryImpersonationHandle(), dwDesiredAccess, &VrootFileGenericMapping, (PRIVILEGE_SET *) &PrivSet, &cbPrivilegeSet, &dwGrantedAccess, &fAccessGranted ) || !fAccessGranted) ) { SetLastError( ERROR_ACCESS_DENIED ); return FALSE; } return TRUE; } BOOL ReadEntireFile( CHAR *pszFileName, TSVC_CACHE &Cache, HANDLE User, BUFFER *pBuf, DWORD *pdwBytesRead ) /*++ Description: Read an entire file into the specified buffer. Arguments: pszFileName - File name of file to be read. Cache - Tsunami cache info for file read. User - User token for opening the file. pBuf - Pointer to buffer to be read into. Returns: TRUE if we read it, FALSE otherwise. --*/ { HANDLE hFile; SECURITY_ATTRIBUTES sa; DWORD dwFileSize, dwFileSizeHigh; DWORD dwCurrentBufSize; BOOL bFileReadSuccess; DWORD dwBytesRead; const CHAR *pszFile[2]; DWORD dwError; CHAR szError[17]; dwError = NO_ERROR; if( !g_fIsWindows95 && !::ImpersonateLoggedOnUser( User ) ) { dwError = GetLastError(); hFile = INVALID_HANDLE_VALUE; } else { sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = FALSE; hFile = CreateFile( pszFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if( hFile == INVALID_HANDLE_VALUE ) { dwError = GetLastError(); } if ( !g_fIsWindows95 ) { ::RevertToSelf(); } } if( hFile == INVALID_HANDLE_VALUE ) { DWORD dwEventType; WORD dwParamCount; ASSERT( dwError != NO_ERROR ); pszFile[0] = pszFileName; dwParamCount = 1; // Couldn't read the error file for some reason, so just bail out // now. if (dwError == ERROR_ACCESS_DENIED) { // Couldn't read the file due to lack of access. Log an event, // and map the error to invalid configuration to prevent // us from initiating an HTTP authentication sequence. dwEventType = W3_EVENT_CANNOT_READ_FILE_SECURITY; dwError = ERROR_INVALID_PARAMETER; } else { if ( dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_PATH_NOT_FOUND ) { dwEventType = W3_EVENT_CANNOT_READ_FILE_EXIST; } else { dwEventType = W3_EVENT_CANNOT_READ_FILE; _itoa( dwError, szError, 10 ); pszFile[1] = szError; dwParamCount = 2; } } g_pInetSvc->LogEvent( dwEventType, dwParamCount, pszFile, 0 ); SetLastError(dwError); return FALSE; } // Query the size. If it's too large, fail. dwFileSize = ::GetFileSize( hFile, &dwFileSizeHigh ); if (dwFileSizeHigh != 0 || dwFileSize > MAX_CUSTOM_ERROR_FILE_SIZE) { CloseHandle( hFile ); pszFile[0] = pszFileName; pszFile[1] = CONST_TO_STRING(MAX_CUSTOM_ERROR_FILE_SIZE); g_pInetSvc->LogEvent( W3_EVENT_CANNOT_READ_FILE_SIZE, 2, pszFile, 0 ); SetLastError( ERROR_INVALID_DATA ); return FALSE; } dwCurrentBufSize = strlen( (CHAR *)pBuf->QueryPtr() ); if ( !pBuf->Resize(dwCurrentBufSize + dwFileSize + 1) ) { // Couldn't resize the buffer, so fail. CloseHandle( hFile ); pszFile[0] = pszFileName; g_pInetSvc->LogEvent( W3_EVENT_CANNOT_READ_FILE_MEMORY, 1, pszFile, 0 ); SetLastError( ERROR_INSUFFICIENT_BUFFER ); return FALSE; } // Now read the file. bFileReadSuccess = ::ReadFile( hFile, (CHAR *)pBuf->QueryPtr() + dwCurrentBufSize, dwFileSize, &dwBytesRead, NULL ); dwError = GetLastError(); // We're done with the file, so close the handle now. CloseHandle( hFile ); if (!bFileReadSuccess || (dwBytesRead != dwFileSize)) { // There was some sort of error in the file read, so bail out. _itoa( dwError, szError, 10 ); pszFile[0] = pszFileName; pszFile[1] = szError; g_pInetSvc->LogEvent( W3_EVENT_CANNOT_READ_FILE, 2, pszFile, 0 ); return FALSE; } CHAR *Temp = (CHAR *)pBuf->QueryPtr(); *(Temp + dwCurrentBufSize + dwBytesRead) = '\0'; *pdwBytesRead = dwBytesRead; return TRUE; } BOOL HTTP_REQ_BASE::CheckCustomError( BUFFER *pBuf, DWORD dwErr, DWORD dwSubError, BOOL *pfFinished, DWORD *pdwMsgSize, BOOL bCheckURL ) /*++ Description: Check for a custom error on message or URL for a specific error. If we find an custom error message, fill in the buffer with the message. If we find a URL, we'll reprocess the URL to handle the error. Arguments: pBuf - Pointer to buffer to fill in with error message. dwErr - Error code to be checked. pfFinished - Pointer to boolean. Set to TRUE if no further processing is required, FALSE otherwise. If it's set to FALSE then the caller still needs to send the buffer. Valid iff this function returns TRUE. bCheckURL - TRUE if we are to check for a URL. Returns: TRUE if there was a custom error for this error, FALSE otherwise. --*/ { PCUSTOM_ERROR_ENTRY pErrEntry; DWORD dwLogHttpResponse; DWORD dwLogWinError; // First make sure we're not already processing a custom error. if (_bProcessingCustomError) { return FALSE; } // Make sure we've got MetaData. // // CODEWORK - Fix this so we try and find the metadata for the the root VR // in this case. We'll need to fix FindAndReferenceInstance etc. We'll // still have to fail if we don't have an instance. if (QueryMetaData() == NULL) { return FALSE; } // Now lookup the error code to see if we have a custom error. pErrEntry = QueryMetaData()->LookupCustomError(dwErr, dwSubError); if (pErrEntry == NULL) { // No custom error, return FALSE. return FALSE; } if (pErrEntry->IsFileError()) { STR strMimeType; DWORD dwCurrentSize; DWORD dwSizeNeeded; CHAR *pszHeader; if (!SelectMimeMappingForFileExt(g_pInetSvc, (const CHAR *)pErrEntry->QueryErrorFileName(), &strMimeType )) { DBGPRINTF(( DBG_CONTEXT, "CheckCustomError: Unable to get MIME type for %s\n", pErrEntry->QueryErrorFileName())); return FALSE; } dwCurrentSize = strlen((CHAR *)pBuf->QueryPtr()); dwSizeNeeded = dwCurrentSize + 1 + sizeof("Content-Type: ") - 1 + strMimeType.QueryCB() + sizeof("\r\n\r\n") - 1; if ((pBuf->QuerySize() < dwSizeNeeded) && !pBuf->Resize(dwSizeNeeded)) { DBGPRINTF(( DBG_CONTEXT, "CheckCustomError: Buffer too small and unable to resize\n")); return FALSE; } pszHeader = (CHAR *)pBuf->QueryPtr() + dwCurrentSize; APPEND_PSZ_HEADER(pszHeader, "Content-Type: ", strMimeType.QueryStr(), "\r\n\r\n"); if (!ReadEntireFile(pErrEntry->QueryErrorFileName(), QueryW3Instance()->GetTsvcCache(), g_hSysAccToken, pBuf, pdwMsgSize)) { // Have to undo the copy in we did. pszHeader = (CHAR *)pBuf->QueryPtr() + dwCurrentSize; *pszHeader = '\0'; return FALSE; } // Otherwise it worked. *pfFinished = FALSE; return TRUE; } else { // This must be a custom URL. Create a new URL from the custom error // that includes the error code as a parameter, and reprocess the URL. HTTP_REQUEST *pReq; CHAR *Temp = (CHAR *)pBuf->QueryPtr(); STR strNewURL; CHAR szError[20]; enum CLIENT_CONN_STATE OldConnState; CHAR *pszHostName; DWORD cbHostNameLength; if (!bCheckURL) { return FALSE; } if (!strNewURL.Copy( (const CHAR *)pErrEntry->QueryErrorURL() ) ) { return FALSE; } // // Remote URLs are not allowed as custom errors. // if ( !_strnicmp(strNewURL.QueryStr(),"http://",sizeof("http://")-1) || !_strnicmp(strNewURL.QueryStr(),"https://",sizeof("https://") - 1) ) { DBGPRINTF(( DBG_CONTEXT, "CheckCustomError: URL is not Local. Returnig Failure\n")); return FALSE; } if (!strNewURL.Append("?", sizeof("?") - 1)) { return FALSE; } _itoa(dwErr, szError, 10); if (!strNewURL.Append( (const CHAR *)szError) ) { return FALSE; } if (!strNewURL.Append(";", sizeof(";") - 1)) { return FALSE; } pszHostName = QueryHostAddr(); cbHostNameLength = strlen(pszHostName); if (cbHostNameLength != 0) { if (!strNewURL.Append("http://", sizeof("http://") -1) || !strNewURL.Append(pszHostName, cbHostNameLength)) { return FALSE; } } if (!strNewURL.Append(_strRawURL)) { return FALSE; } *Temp = '\0'; _bProcessingCustomError = TRUE; SetNoCache(); SetSendVary(); pReq = (HTTP_REQUEST *)this; pReq->CloseGetFile(); // // Need to set the connection state to processing, because we might be // disconnecting right now. Save the old state in case the reprocess // fails. OldConnState = QueryClientConn()->QueryState(); QueryClientConn()->SetState(CCS_PROCESSING_CLIENT_REQ); dwLogHttpResponse = QueryLogHttpResponse(); dwLogWinError = QueryLogWinError(); if ( !pReq->ReprocessURL( strNewURL.QueryStr(), HTV_GET )) { // // if we failed & state is DOVERB restore to DONE to prevent // response from being generated twice (once here and once after doverb // processing in DoWork() ) // if ( QueryState() == HTR_DOVERB ) { SetState( HTR_DONE, dwLogHttpResponse, dwLogWinError ); } QueryClientConn()->SetState(OldConnState); return FALSE; } *pfFinished = TRUE; return TRUE; } } BOOL HTTP_REQ_BASE::IsClientProxy( VOID ) /*++ Description: Check if request was issued by a proxy, as determined by following rules : - "Via:" header is present (HTTP/1.1) - "Forwarded:" header is present (some HTTP/1.0 implementations) - "User-Agent:" contains "via ..." (CERN proxy) Arguments: None Returns: TRUE if client request was issued by proxy --*/ { LPSTR pUA; UINT cUA; LPSTR pEnd; if ( _HeaderList.FastMapQueryValue( HM_VIA ) != NULL || _HeaderList.FastMapQueryValue( HM_FWD ) != NULL ) { return TRUE; } if ( !IsAtLeastOneOne() && (pUA = (LPSTR)_HeaderList.FastMapQueryValue( HM_UAT )) != NULL ) { cUA = strlen( pUA ); pEnd = pUA + cUA - (sizeof("ia ")-1); // // scan for "[Vv]ia[ :]" in User-Agent: header // while ( pUA < pEnd ) { if ( *pUA == 'V' || *pUA == 'v' ) { if ( pUA[1] == 'i' && pUA[2] == 'a' && (pUA[3] == ' ' || pUA[3] == ':') ) { return TRUE; } } ++pUA; } } return FALSE; } W3_SERVER_INSTANCE* HTTP_REQ_BASE::QueryW3InstanceAggressively( VOID ) const /*++ Description: This tries "aggressively" to find the instance associated with this request. If the instance is already set, it returns that; otherwise, it tries to find an instance matching the IP/Port # for the connection associated with this request. This is useful for requests that won't use Host headers eg SSL requests, where it may be necessary to determine the instance for a request before it's been set by the URL-parsing code. Note that the function -DOES NOT- update the _pW3Instance member. Arguments: None Returns: Pointer to associated instance if found, NULL if no instance was found. --*/ { W3_SERVER_INSTANCE *pInstance = NULL; if ( _pW3Instance ) { pInstance = _pW3Instance; } else { BOOL fExceeded ; // // Try specific (IP, port) first, then try to wildcard the IP address. // Port # can't be wildcarded. // IIS_ENDPOINT *pEndPoint = g_pInetSvc->FindAndReferenceEndpoint( _pClientConn->QueryPort(), _pClientConn->QueryLocalIPAddress(), FALSE, FALSE ); if ( !pEndPoint ) { pEndPoint = g_pInetSvc->FindAndReferenceEndpoint( _pClientConn->QueryPort(), 0, FALSE, FALSE ); } if ( pEndPoint ) { pInstance = (W3_SERVER_INSTANCE *) pEndPoint->FindAndReferenceInstance( NULL, _pClientConn->QueryLocalIPAddress(), &fExceeded ); // // FindAndReferenceInstance increments # of connections, and we're not // using up a connection // if ( pInstance ) { pInstance->DecrementCurrentConnections(); pInstance->Dereference(); } pEndPoint->Dereference(); } } return ( pInstance ); }