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

1753 lines
42 KiB

/**********************************************************************/
/** 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 <inetinfo.h>
#include "basereq.hxx"
#pragma warning( disable:4355 ) // 'this' used in base member initialization
//
// Private constants.
//
//
// Maximum HTTP header length we will accept
//
#define MAX_HEADER_LENGTH 255
//
// Default response buffer size
//
#define DEF_RESP_BUFF_SIZE 4096
//
// 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)
//
// Private prototypes.
//
BOOL
BuildCGIHeaderList( STR * pstr,
PARAM_LIST * pHeaderList
);
//
// Public functions.
//
//
// Private functions.
//
HTTP_REQ_BASE::HTTP_REQ_BASE(
CLIENT_CONN * pClientConn,
PVOID pvInitialBuff,
DWORD cbInitialBuff
) :
_tcpauth( TCPAUTH_SERVER | TCPAUTH_UUENCODE ),
_Filter ( this ),
_fValid ( FALSE ),
m_hVrootImpersonation ( NULL),
_bufServerResp( DEF_RESP_BUFF_SIZE ),
_dwLogHttpResponse( HT_DONT_LOG )
{
InitializeSession( pClientConn,
pvInitialBuff,
cbInitialBuff );
TCP_ASSERT( pClientConn->CheckSignature() );
if ( !_Filter.IsValid() ||
!_bufServerResp.QueryPtr() )
{
return;
}
_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;
_fLoggedOn = FALSE;
_fKeepConn = FALSE;
_fAnonymous = FALSE;
_fAuthenticating = FALSE;
_fClearTextPass = FALSE;
_cFilesSent = 0;
_cFilesReceived = 0;
_cbBytesSent = 0;
_cbBytesReceived = 0;
_cbTotalBytesSent = 0;
_cbTotalBytesReceived= 0;
_Filter.Reset();
_strAuthType.Copy ( (TCHAR *) NULL );
_strAuthorization.Copy ( (TCHAR *) NULL );
_strUserName.Copy ( (TCHAR *) NULL );
_strPassword.Copy ( (TCHAR *) NULL );
_strUnmappedUserName.Copy( (TCHAR *) NULL );
#if 0
_strUnmappedPassword.Copy( (TCHAR *) NULL );
#endif
_strHostAddr.Copy ( (TCHAR *) NULL );
_pSslCtxtHandle = NULL;
_dwRenegotiated = 0;
}
HTTP_REQ_BASE::~HTTP_REQ_BASE( VOID )
{
}
BOOL
HTTP_REQ_BASE::Reset(
VOID
)
/*++
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:
--*/
{
_msStartRequest = GetCurrentTime();
_VersionMajor = 0;
_VersionMinor = 0;
_cbGatewayData = 0;
_cbContentLength = 0;
_cbClientRequest = 0;
_cbOldData = 0;
_liModifiedSince.QuadPart = 0;
_liUnlessModifiedSince.QuadPart = 0;
_status = NO_ERROR;
_cbBytesWritten = 0;
_HeaderList.Reset();
//
// Reset our statistics for this request
//
_cbTotalBytesSent += _cbBytesSent;
_cbTotalBytesReceived += _cbBytesReceived;
_cbBytesSent = 0;
_cbBytesReceived = 0;
//
// Don't log this request unless we're explicity indicated a status
//
_dwLogHttpResponse = HT_DONT_LOG;
_dwLogWinError = NO_ERROR;
TCP_REQUIRE( _strURL.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strURLParams.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strPathInfo.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strRawURL.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strMethod.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strLanguage.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strAuthInfo.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE(_strPhysicalPath.Copy( (TCHAR *) NULL ));
TCP_REQUIRE( _strDenialHdrs.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strRespHdrs.Copy ( (TCHAR *) NULL ));
TCP_REQUIRE( _strRange.Copy ( (TCHAR *) NULL ));
_fAuthenticationRequested = FALSE;
_fProxyRequest = FALSE;
// Accept range variables
_fProcessByteRange = FALSE;
_fAcceptRange = FALSE;
_iRangeIdx = 0;
_cbMimeMultipart = 0;
_fMimeMultipart = FALSE;
SetState( HTR_READING_CLIENT_REQUEST );
return TRUE;
}
BOOL
HTTP_REQ_BASE::IsSecurePort( VOID ) const
/*++
Routine Description:
Small helper to get around circular dependency.
CODEWORK: need header for these types so they can be inline
--*/
{
return QueryClientConn()->IsSecurePort();
}
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
//
HTTP_FILTER::NotifyEndOfNetSession( &_Filter );
//
// Update the statistics
//
if ( _fLoggedOn )
{
if ( _fAnonymous )
DECREMENT_COUNTER( CurrentAnonymousUsers );
else
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
TCP_REQUIRE( _tcpauth.Reset() );
LockStatistics();
W3Stats.TotalBytesSent.QuadPart += _cbTotalBytesSent + _cbBytesSent;
W3Stats.TotalBytesReceived.QuadPart += _cbTotalBytesReceived + _cbBytesReceived;
W3Stats.TotalFilesSent += _cFilesSent;
W3Stats.TotalFilesReceived += _cFilesReceived;
UnlockStatistics();
//
// 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 )
{
TCP_REQUIRE( _bufClientRequest.Resize( 0 ));
}
}
BOOL
HTTP_REQ_BASE::OnIfModifiedSince(
CHAR * pszValue
)
/*++
Routine Description:
Extracts the modified date for later use
Arguments:
pszValue - Pointer to zero terminated string
--*/
{
BOOL fRet;
fRet = StringTimeToFileTime( pszValue,
&_liModifiedSince );
//
// If we couldn't parse the time, then just ignore this field all
// together
//
if ( !fRet )
{
TCP_PRINT(( DBG_CONTEXT,
"[OnIfModifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n",
GetLastError() ));
_liModifiedSince.QuadPart = 0;
fRet = TRUE;
}
return fRet;
}
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;
fRet = StringTimeToFileTime( pszValue,
&_liUnlessModifiedSince );
//
// If we couldn't parse the time, then just ignore this field all
// together
//
if ( !fRet )
{
TCP_PRINT(( DBG_CONTEXT,
"[OnIfModifiedSince] Error %d parsing If-Modified-Since time, ignoring field\n",
GetLastError() ));
_liUnlessModifiedSince.QuadPart = 0;
fRet = TRUE;
}
return fRet;
}
/*******************************************************************
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 )
{
_cbContentLength = atoi( pszValue );
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
PSTR pP = strchr( _strHostAddr.QueryStr(), ':' );
if ( pP != NULL )
*pP = '\0';
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(*pszValue) )
++pszValue;
if ( !_strnicmp( pszValue, "bytes", sizeof("bytes")-1 ) )
{
pszValue += sizeof("bytes")-1;
while ( *pszValue && *pszValue++ != '=' )
;
while ( *pszValue && isspace(*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
--*/
{
return (CHAR *) (_strHostAddr.IsEmpty()
? ( (g_pszDefaultHostName && g_pszDefaultHostName[0]) ? g_pszDefaultHostName : _pClientConn->QueryLocalAddr() )
: _strHostAddr.QueryStr());
}
BOOL
HTTP_REQ_BASE::OnAuthorization(
CHAR * pszValue
)
/*++
Routine Description:
Processes the HTTP "Authorization: <type> <authdata>" 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;
}
BOOL
HTTP_REQ_BASE::ParseAuthorization (
CHAR * pszValue
)
/*++
Routine Description:
Processes the HTTP "Authorization: <type> <authdata>" field
or "Proxy-Authorization: <type> <authdata>" field
Return Value:
TRUE if successful, FALSE on error
--*/
{
CHAR * pchBlob;
CHAR * pchSpace = NULL;
DWORD dwAuthFlags = g_pTsvcInfo->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() )
{
//
// 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 )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
_fLoggedOn = FALSE;
_fClearTextPass = FALSE;
_fAnonymous = FALSE;
_fAuthenticating = FALSE;
_tcpauth.Reset();
_strAuthType.Copy ( (TCHAR *) NULL );
_strAuthorization.Copy ( (TCHAR *) NULL );
_strUserName.Copy ( (TCHAR *) NULL );
_strPassword.Copy ( (TCHAR *) NULL );
_strUnmappedUserName.Copy( (TCHAR *) NULL );
#if 0
_strUnmappedPassword.Copy( (TCHAR *) NULL );
#endif
}
//
// 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;
}
//
// Save the whole line for gateways
//
if ( !_strAuthorization.Copy( pszValue ) )
return FALSE;
//
// 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 )
{
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' ))
{
return FALSE;
}
IF_DEBUG( PARSING )
{
TCP_PRINT(( DBG_CONTEXT,
"[OnAuthorization] User name = %s\n",
_strUserName.QueryStr(),
_strPassword.QueryStr() ));
}
_fClearTextPass = TRUE;
}
else
{
//
// See if it's one of the SSP packages
//
DWORD i = 0;
BUFFER buff;
BOOL fNeedMoreData;
DWORD cbOut;
while ( apszNTProviders[i] )
{
if ( !_stricmp( _strAuthType.QueryStr(), apszNTProviders[i++] ))
{
goto Found;
}
}
goto NotFound;
Found:
//
// 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,
_strAuthType.QueryStr()) ||
!_strAuthInfo.Copy( _strAuthType ) ||
!_strAuthInfo.Append( " " ) ||
!_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 );
}
return FALSE;
}
//
// CODEWORK - We currently do not support authentication
// schemes that finish on the server but still need to
// send data back to the client
//
_fAuthenticating = fNeedMoreData;
//
// 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 )
{
// Check if guest account
if ( _tcpauth.IsGuest( FALSE ) )
{
if ( !g_fW3AllowGuest ||
!(g_pTsvcInfo->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.GetFastMap()->Cancel( HM_AUT );
_fLoggedOn = FALSE;
_fClearTextPass = FALSE;
_fAnonymous = FALSE;
_fAuthenticating = FALSE;
_tcpauth.Reset();
_strAuthType.Copy ( (TCHAR *) NULL );
_strAuthorization.Copy ( (TCHAR *) NULL );
_strUserName.Copy ( (TCHAR *) NULL );
_strPassword.Copy ( (TCHAR *) NULL );
_strUnmappedUserName.Copy( (TCHAR *) NULL );
// return as if no authorization had been seen
return TRUE;
}
INCREMENT_COUNTER( TotalNonAnonymousUsers );
INCREMENT_COUNTER( CurrentNonAnonymousUsers );
if ( W3Stats.CurrentNonAnonymousUsers > W3Stats.MaxNonAnonymousUsers )
{
LockStatistics();
if ( W3Stats.CurrentNonAnonymousUsers > W3Stats.MaxNonAnonymousUsers )
W3Stats.MaxNonAnonymousUsers = W3Stats.CurrentNonAnonymousUsers;
UnlockStatistics();
}
//
// Get the user name
//
if ( !_tcpauth.QueryUserName( &_strUserName ) ||
!_strUnmappedUserName.Copy( _strUserName ))
{
TCP_PRINT(( DBG_CONTEXT,
"[OnAuthorization] Getting username failed, error %d\n",
GetLastError() ));
}
}
}
NotFound:
//
// Restore the string
//
if ( pchSpace )
{
*pchSpace = ' ';
}
return TRUE;
}
BOOL
HTTP_REQ_BASE::OnProxyAuthorization(
CHAR * pszValue
)
/*++
Routine Description:
Processes the HTTP "Proxy-Authorization: <type> <authdata>" 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:
<http version> <status code> <reason> <CrLf>
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 strStatus;
STR strError2;
LPSTR pErr2 = NULL;
CHAR * pszStatus;
CHAR ach[40];
//
// 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;
default:
if ( !g_pTsvcInfo->LoadStr( strStatus, dwHTTPError + ID_HTTP_ERROR_BASE ))
{
TCP_PRINT((DBG_CONTEXT,
"BuildErrorResponse: failed to load HTTP status code %d (res=%d), error %d\n",
dwHTTPError,
dwHTTPError + ID_HTTP_ERROR_BASE,
GetLastError() ));
}
pszStatus = strStatus.QueryStr();
break;
}
//
// If the client wants a secondary error string, get it now
//
if ( dwError2 )
{
if ( !g_pTsvcInfo->LoadStr( strError2, dwError2 ))
{
TCP_PRINT((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
//
_itoa( dwError2, ach, 10 );
if ( !strError2.Copy( ach ))
return FALSE;
}
else 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)&pErr2, dwL, (va_list*)&pszError2 ) )
pErr2 = NULL;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
pErr2 = NULL;
}
}
}
//
// Make sure there is room for the wsprintf
//
if ( !pbufResp->Resize( strlen(pszStatus) + 1 +
(dwError2 ? (pErr2 ? strlen(pErr2) : strError2.QueryCB()) : 0) +
sizeof(TEXT(HTTP_VERSION_STR)) +
20 * sizeof(TCHAR) )) // status code + space
{
if ( pErr2 )
LocalFree( pErr2 );
return FALSE;
}
::wsprintf( (CHAR *) pbufResp->QueryPtr(),
dwError2 ? TEXT("%s %d %s (%s)\r\n") : TEXT("%s %d %s\r\n"),
TEXT(HTTP_VERSION_STR),
dwHTTPError,
pszStatus,
pErr2 ? pErr2 : strError2.QueryStr() );
if ( pErr2 )
LocalFree( pErr2 );
return TRUE;
}
BOOL HTTP_REQ_BASE::BuildExtendedStatus(
BUFFER * pbufResp,
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
--*/
{
CHAR * pszResp;
CHAR * pszTail;
//
// "HTTP/<ver> <status>"
//
if ( !BuildStatusLine( pbufResp,
dwHTTPError,
dwError2, pszError2 ))
{
return FALSE;
}
pszResp = (CHAR *) pbufResp->QueryPtr();
pszTail = pszResp + strlen( pszResp );
//
// "Server: <Server>/<version>
//
APPEND_VER_STR( pszTail );
//
// "MIME-version: 1.0" and Content-Type, it's OK to assume all clients
// accept text/html
//
APPEND_STRING( pszTail, "Content-Type: text/html\r\n\r\n" );
//
// If we need to add an explanation, also include a content length
//
if ( dwExplanation )
{
STR str;
if ( !g_pTsvcInfo->LoadStr( str, dwExplanation ))
{
return FALSE;
}
strcpy( pszTail,
str.QueryStr() );
}
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;
TCHAR * pszUser;
TCHAR * pszPswd;
if ( !_strUnmappedUserName.Copy( _strUserName )
#if 0
|| !_strUnmappedPassword.Copy( _strPassword )
#endif
)
{
return FALSE;
}
if ( fAnyFilters )
{
if ( !_strUserName.Resize( SF_MAX_USERNAME ) ||
!_strPassword.Resize( SF_MAX_PASSWORD ) )
{
return FALSE;
}
if ( !HTTP_FILTER::NotifyAuthInfoFilters( &_Filter,
_strUserName.QueryStr(),
SF_MAX_USERNAME,
_strPassword.QueryStr(),
SF_MAX_PASSWORD,
pfFinished ))
{
SetDeniedFlags( SF_DENIED_LOGON );
return FALSE;
}
if ( *pfFinished )
return TRUE;
}
pszUser = *_strUserName.QueryStr() ?
_strUserName.QueryStr():
NULL;
pszPswd = *_strPassword.QueryStr() ?
_strPassword.QueryStr():
NULL;
INCREMENT_COUNTER( LogonAttempts );
log_in:
if ( !_tcpauth.ClearTextLogon( pszUser,
pszPswd,
&fAsGuest,
&fAsAnonymous,
g_pTsvcInfo ))
{
TCP_PRINT(( DBG_CONTEXT,
"[ClearTextLogon] ::LogonUser failed, error %d\n",
GetLastError()));
if ( GetLastError() == ERROR_ACCESS_DENIED ||
GetLastError() == ERROR_LOGON_FAILURE )
{
SetDeniedFlags( SF_DENIED_LOGON );
SetLastError( ERROR_ACCESS_DENIED );
}
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 &&
!(g_pTsvcInfo->QueryAuthentication() & INET_INFO_AUTH_ANONYMOUS ))
{
TCP_PRINT(( 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 &&
!(g_pTsvcInfo->QueryAuthentication() & INET_INFO_AUTH_CLEARTEXT ))
{
TCP_PRINT(( 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 ( !g_fW3AllowGuest ||
!(g_pTsvcInfo->QueryAuthentication() & INET_INFO_AUTH_ANONYMOUS) )
{
TCP_PRINT(( 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.Copy( (TCHAR*)NULL );
_strPassword.Copy( (TCHAR*)NULL );
pszUser = NULL;
pszPswd = NULL;
goto log_in;
}
}
_fLoggedOn = TRUE;
_fAnonymous = fAsAnonymous;
if ( fAsAnonymous )
{
INCREMENT_COUNTER( TotalAnonymousUsers );
INCREMENT_COUNTER( CurrentAnonymousUsers );
if ( W3Stats.CurrentAnonymousUsers > W3Stats.MaxAnonymousUsers )
{
LockStatistics();
if ( W3Stats.CurrentAnonymousUsers > W3Stats.MaxAnonymousUsers )
W3Stats.MaxAnonymousUsers = W3Stats.CurrentAnonymousUsers;
UnlockStatistics();
}
}
else
{
INCREMENT_COUNTER( TotalNonAnonymousUsers );
INCREMENT_COUNTER( CurrentNonAnonymousUsers );
if ( W3Stats.CurrentNonAnonymousUsers > W3Stats.MaxNonAnonymousUsers )
{
LockStatistics();
if ( W3Stats.CurrentNonAnonymousUsers > W3Stats.MaxNonAnonymousUsers )
W3Stats.MaxNonAnonymousUsers = W3Stats.CurrentNonAnonymousUsers;
UnlockStatistics();
}
}
return TRUE;
}
# define MAX_ERROR_MESSAGE_LEN ( 500)
BOOL
HTTP_REQ_BASE::WriteLogRecord(
VOID
)
/*++
Routine Description:
Writes a transaction log for this request
Return Value:
TRUE if successful, FALSE on error
--*/
{
INETLOG_INFORMATIONA ilRequest;
HTTP_FILTER_LOG Log;
DWORD dwLog;
CHAR pszError[MAX_ERROR_MESSAGE_LEN] = "";
DWORD cchError = MAX_ERROR_MESSAGE_LEN;
if ( dwAllNotifFlags & SF_NOTIFY_END_OF_REQUEST )
{
HTTP_FILTER::NotifyEndOfRequest( &_Filter );
}
//
// Log this request if we actually did anything
//
if ( _dwLogHttpResponse == HT_DONT_LOG || IsProxyRequest() )
{
if ( !IsProxyRequest() )
{
TCP_PRINT(( DBG_CONTEXT,
"[WriteLogRecord] not writing log record, status is HT_DONT_LOG\n" ));
}
return TRUE;
}
//
// If no logging is required, get out now
//
if ( !g_fLogErrors && QueryLogWinError() ||
!g_fLogSuccess && QueryLogWinError() == NO_ERROR )
{
return TRUE;
}
if ( fAnyFilters )
{
//
// If we have filters, use the possible filter replacement items
//
Log.pszClientHostName = QueryClientConn()->QueryRemoteAddr();
Log.pszClientUserName = _strUserName.QueryStr();
Log.pszServerName = QueryClientConn()->QueryLocalAddr();
Log.pszOperation = _strMethod.QueryStr();
Log.pszTarget = _strURL.QueryStr();
Log.pszParameters = _strURLParams.QueryStr();
Log.dwHttpStatus = QueryLogHttpResponse();
Log.dwWin32Status = QueryLogWinError();
HTTP_FILTER::NotifyLogFilters( &_Filter,
&Log );
ilRequest.pszClientHostName = Log.pszClientHostName;
ilRequest.pszClientUserName = Log.pszClientUserName;
ilRequest.pszServerIpAddress = Log.pszServerName;
ilRequest.pszOperation = Log.pszOperation;
ilRequest.pszTarget = Log.pszTarget;
ilRequest.pszParameters = Log.pszParameters;
ilRequest.dwServiceSpecificStatus = Log.dwHttpStatus;
ilRequest.dwWin32Status = Log.dwWin32Status;
}
else
{
ilRequest.pszClientHostName = QueryClientConn()->QueryRemoteAddr();
ilRequest.pszClientUserName = _strUserName.QueryStr();
ilRequest.pszServerIpAddress = QueryClientConn()->QueryLocalAddr();
ilRequest.pszOperation = _strMethod.QueryStr();
ilRequest.pszTarget = _strURL.QueryStr();
ilRequest.pszParameters = _strURLParams.QueryStr();
ilRequest.dwServiceSpecificStatus = QueryLogHttpResponse();
ilRequest.dwWin32Status = QueryLogWinError();
}
//
// Never reveal the client password
//
ilRequest.pszClientPassword = NULL;
ilRequest.msTimeForProcessing = GetCurrentTime() - _msStartRequest;
ilRequest.liBytesSent.LowPart = _cbBytesSent;
ilRequest.liBytesSent.HighPart = 0;
ilRequest.liBytesRecvd.LowPart = _cbBytesReceived;
ilRequest.liBytesRecvd.HighPart = 0;
dwLog = g_pTsvcInfo->LogInformation( &ilRequest, pszError, &cchError );
if ( dwLog != NO_ERROR )
{
TCP_PRINT((DBG_CONTEXT,
"[WriteLogRecord] - Failed, error %d\n",
GetLastError() ));
//
// We should make sure LogInformation will never fail
//
}
return TRUE;
}
/*******************************************************************
NAME: HTTP_REQ_BASE::Disconnect
SYNOPSIS: Forwards the disconnect request to the client connection
ENTRY: Same as for CLIENT_CONN::Disconnect
HISTORY:
Johnl 24-Aug-1994 Created
********************************************************************/
VOID HTTP_REQ_BASE::Disconnect( DWORD htResp,
DWORD dwError2,
BOOL fDoShutdown )
{
_pClientConn->Disconnect( this, htResp, dwError2, fDoShutdown );
}
DWORD HTTP_REQ_BASE::Reference( VOID )
{
return _pClientConn->Reference();
}
DWORD HTTP_REQ_BASE::Dereference( VOID )
{
return _pClientConn->Dereference();
}
DWORD HTTP_REQ_BASE::QueryRefCount( VOID )
{
return _pClientConn->QueryRefCount();
}
BOOL
HTTP_REQ_BASE::BuildHttpHeader( OUT BOOL * pfFinished,
IN CHAR * pchStatus OPTIONAL,
IN CHAR * pchAdditionalHeaders OPTIONAL )
/*++
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 )
{
if ( !str.Copy( HTTP_VERSION_STR " " ) ||
!str.Append( pchStatus ) ||
!str.Append( "\r\n" ))
{
return GetLastError();
}
}
if ( !BuildBaseResponseHeader( QueryRespBuf(),
pfFinished,
(pchStatus ? &str :
NULL ) ))
{
return FALSE;
}
if ( pchAdditionalHeaders )
{
// BUGBUG: Additional space availability is not checked for....
strcat( QueryRespBufPtr(),
pchAdditionalHeaders );
}
return TRUE;
} // HTTP_REQ_BASE::BuildHttpHeader()
BOOL
HTTP_REQ_BASE::SendHeader( IN BOOL fRunThroughFilter,
IN CHAR * pchStatus OPTIONAL,
IN CHAR * pchAdditionalHeaders OPTIONAL)
/*++
Routine Description:
Does a synchronous send of an HTTP header with optional status
and additional headers.
Arguments:
fRunThroughFilter - TRUE if the data should be ran through a filter
before sending
pchStatus - optional HTTP status string like "401 Access Denied"
pchAdditionalHeaders - optional additional HTTP or MIME headers
Return Value:
TRUE on success, FALSE on failure
--*/
{
DWORD BytesSent;
DWORD cbAddHeaders = 0;
CHAR * pchExtraData = NULL;
BOOL fFinished = FALSE;
//
// Since we're placing the data in a fixed size buffer, watch for
// the additional headers overflowing the buffer. If they are too
// big then send the additional headers separately.
//
if ( pchAdditionalHeaders )
{
cbAddHeaders = strlen( pchAdditionalHeaders );
}
if ( cbAddHeaders &&
cbAddHeaders > QueryRespBuf()->QuerySize() / 2)
{
pchExtraData = pchAdditionalHeaders;
pchAdditionalHeaders = NULL;
}
if ( !BuildHttpHeader( &fFinished,
pchStatus,
pchAdditionalHeaders ))
{
return FALSE;
}
if ( fFinished )
return TRUE;
TCP_ASSERT( QueryRespBufCB() <=
QueryRespBuf()->QuerySize() );
if ( !WriteFile( QueryRespBufPtr(),
QueryRespBufCB(),
&BytesSent,
IO_FLAG_SYNC |
(fRunThroughFilter ? 0 :
IO_FLAG_NO_FILTER) ))
{
return FALSE;
}
//
// Send the additional headers now if they didn't fit in the original
// response buffer
//
if ( pchExtraData )
{
if ( !WriteFile( pchExtraData,
cbAddHeaders,
&BytesSent,
IO_FLAG_SYNC |
(fRunThroughFilter ? 0 :
IO_FLAG_NO_FILTER) ))
{
return FALSE;
}
}
return TRUE;
} // HTTP_REQ_BASE::SendHeader()
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
--*/
{
STR strTemp;
SYSTEMTIME SysTime;
BOOL fSysTimeValid = FALSE;
CHAR * pszResp;
CHAR * pszTail;
CHAR achTime[64];
DWORD cb;
DWORD cbLeft;
pszResp = (CHAR *) pbufResponse->QueryPtr();
//
// Add the status line - "HTTP/1.0 nnn sss...\r\n"
//
if ( !pstrStatus ) {
pszTail = pszResp;
if ( !_fProcessByteRange ) {
APPEND_STRING( pszTail, "HTTP/1.0 200 OK\r\n" );
} else {
APPEND_STRING( pszTail, "HTTP/1.0 206 Partial content\r\n" );
}
} else {
strcpy( (CHAR *) pbufResponse->QueryPtr(),
pstrStatus->QueryStr() );
pszTail = pszResp + strlen(pszResp);
}
//
// "Server: Microsoft/xxx
//
APPEND_VER_STR( pszTail );
TCP_ASSERT( pbufResponse->QuerySize() >= 4096 );
//
// Fill in the rest of the headers
//
//
// "Date: <GMT Time>" - Time the response was sent.
//
if ( !(dwOptions & HTTPH_NO_DATE ) )
{
// build Date: uses Date/Time cache
::GetSystemTime( &SysTime );
fSysTimeValid = TRUE;
pszTail += IslFormatDateTime( &SysTime, dftGmt, pszTail );
}
//
// Add an expires header if the feature is enabled and the caller wants it
//
if ( (dwOptions & HTTPH_SEND_GLOBAL_EXPIRE)
&& csecGlobalExpire != NO_GLOBAL_EXPIRE )
{
if ( !fSysTimeValid )
{
::GetSystemTime( &SysTime );
fSysTimeValid = TRUE;
}
if ( !::SystemTimeToGMTEx( SysTime,
achTime,
sizeof(achTime),
csecGlobalExpire ))
{
return FALSE;
}
pszTail += wsprintf( pszTail,
"Expires: %s\r\n",
achTime );
}
//
// "Connection: keep-alive" - Indicate if the server accepted the
// session modifier by reflecting it back to the client
//
if ( IsKeepConnSet() )
{
APPEND_STRING( pszTail, "Connection: keep-alive\r\n" );
}
// Authentication headers -- indicate the server requests authentication
if ( IsAuthenticationRequested() )
{
if ( !AppendAuthenticationHdrs( pbufResponse,
&pszTail,
pfFinished ))
{
return FALSE;
}
if ( *pfFinished )
{
return TRUE;
}
}
//
// If we accepted this request on a particular language root, then
// add a Content-Language tag
//
if ( !_strLanguage.IsEmpty() )
{
pszTail += wsprintf( pszTail,
"Content-Language: %s\r\n",
_strLanguage.QueryStr() );
}
//
// Append any headers specified by filters
//
if ( !QueryAdditionalRespHeaders()->IsEmpty() )
{
cb = QueryAdditionalRespHeaders()->QueryCB() + sizeof(CHAR);
cbLeft = pbufResponse->QuerySize() - (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;
}