1129 lines
28 KiB
C++
1129 lines
28 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 2000 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
authstate.cxx
|
||
|
|
||
|
Abstract:
|
||
|
Authenticate state implementation (and authentication utilities)
|
||
|
|
||
|
Author:
|
||
|
Ming Lu ( MingLu ) 2-Feb-2000
|
||
|
|
||
|
Environment:
|
||
|
Win32 User Mode
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.hxx"
|
||
|
#include "sspiprovider.hxx"
|
||
|
#include "digestprovider.hxx"
|
||
|
#include "iisdigestprovider.hxx"
|
||
|
#include "basicprovider.hxx"
|
||
|
#include "anonymousprovider.hxx"
|
||
|
#include "certmapprovider.hxx"
|
||
|
#include "iiscertmapprovider.hxx"
|
||
|
#include "customprovider.hxx"
|
||
|
|
||
|
W3_STATE_AUTHENTICATION * W3_STATE_AUTHENTICATION::sm_pAuthState;
|
||
|
LUID W3_STATE_AUTHENTICATION::sm_BackupPrivilegeTcbValue;
|
||
|
PTOKEN_PRIVILEGES W3_STATE_AUTHENTICATION::sm_pTokenPrivilege = NULL;
|
||
|
PTRACE_LOG W3_USER_CONTEXT::sm_pTraceLog;
|
||
|
PTRACE_LOG CONNECTION_AUTH_CONTEXT::sm_pTraceLog;
|
||
|
|
||
|
HRESULT
|
||
|
W3_STATE_AUTHENTICATION::GetDefaultDomainName(
|
||
|
VOID
|
||
|
)
|
||
|
/*++
|
||
|
Description:
|
||
|
|
||
|
Fills in the member variable with the name of the default domain
|
||
|
to use for logon validation
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
szDefaultDomainName - Buffer to hold the default domain name
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
HRESULT
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
|
NTSTATUS NtStatus;
|
||
|
DWORD dwLength;
|
||
|
DWORD err = 0;
|
||
|
LSA_HANDLE LsaPolicyHandle = NULL;
|
||
|
PPOLICY_ACCOUNT_DOMAIN_INFO pAcctDomainInfo = NULL;
|
||
|
PPOLICY_PRIMARY_DOMAIN_INFO pPrimaryDomainInfo = NULL;
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
//
|
||
|
// Open a handle to the local machine's LSA policy object.
|
||
|
//
|
||
|
|
||
|
InitializeObjectAttributes( &ObjectAttributes,
|
||
|
NULL,
|
||
|
0L,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
NtStatus = LsaOpenPolicy( NULL,
|
||
|
&ObjectAttributes,
|
||
|
POLICY_EXECUTE,
|
||
|
&LsaPolicyHandle );
|
||
|
|
||
|
if( !NT_SUCCESS( NtStatus ) )
|
||
|
{
|
||
|
DBGPRINTF(( DBG_CONTEXT,
|
||
|
"cannot open lsa policy, error %08lX\n",
|
||
|
NtStatus ));
|
||
|
|
||
|
err = LsaNtStatusToWinError( NtStatus );
|
||
|
|
||
|
//
|
||
|
// Failure LsaOpenPolicy() does not guarantee that
|
||
|
// LsaPolicyHandle was not touched.
|
||
|
//
|
||
|
LsaPolicyHandle = NULL;
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Query the account domain information from the policy object.
|
||
|
//
|
||
|
|
||
|
NtStatus = LsaQueryInformationPolicy( LsaPolicyHandle,
|
||
|
PolicyAccountDomainInformation,
|
||
|
(PVOID *)&pAcctDomainInfo );
|
||
|
|
||
|
if( !NT_SUCCESS( NtStatus ) )
|
||
|
{
|
||
|
|
||
|
DBGPRINTF(( DBG_CONTEXT,
|
||
|
"cannot query lsa policy info, error %08lX\n",
|
||
|
NtStatus ));
|
||
|
|
||
|
err = LsaNtStatusToWinError( NtStatus );
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
DBG_ASSERT( pAcctDomainInfo != NULL );
|
||
|
|
||
|
dwLength = pAcctDomainInfo->DomainName.Length / sizeof( WCHAR );
|
||
|
|
||
|
wcsncpy( _achDefaultDomainName,
|
||
|
(LPCWSTR)pAcctDomainInfo->DomainName.Buffer,
|
||
|
sizeof( _achDefaultDomainName ) / sizeof( WCHAR ) );
|
||
|
|
||
|
_achDefaultDomainName[ dwLength ] = L'\0';
|
||
|
|
||
|
//
|
||
|
// Query the primary domain information from the policy object.
|
||
|
//
|
||
|
|
||
|
NtStatus = LsaQueryInformationPolicy( LsaPolicyHandle,
|
||
|
PolicyPrimaryDomainInformation,
|
||
|
(PVOID *)&pPrimaryDomainInfo );
|
||
|
|
||
|
if( !NT_SUCCESS( NtStatus ) )
|
||
|
{
|
||
|
|
||
|
DBGPRINTF(( DBG_CONTEXT,
|
||
|
"cannot query lsa policy info, error %08lX\n",
|
||
|
NtStatus ));
|
||
|
|
||
|
err = LsaNtStatusToWinError( NtStatus );
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
DBG_ASSERT( pPrimaryDomainInfo != NULL );
|
||
|
|
||
|
if( pPrimaryDomainInfo->Sid )
|
||
|
{
|
||
|
//
|
||
|
// We are a domain member
|
||
|
//
|
||
|
_fIsDomainMember = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_fIsDomainMember = FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Success!
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( err == 0 );
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
if( pAcctDomainInfo != NULL )
|
||
|
{
|
||
|
LsaFreeMemory( (PVOID)pAcctDomainInfo );
|
||
|
pAcctDomainInfo = NULL;
|
||
|
}
|
||
|
|
||
|
if( pPrimaryDomainInfo != NULL )
|
||
|
{
|
||
|
LsaFreeMemory( (PVOID)pPrimaryDomainInfo );
|
||
|
pPrimaryDomainInfo = NULL;
|
||
|
}
|
||
|
|
||
|
if( LsaPolicyHandle != NULL )
|
||
|
{
|
||
|
LsaClose( LsaPolicyHandle );
|
||
|
}
|
||
|
|
||
|
if ( err )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( err );
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
};
|
||
|
|
||
|
//static
|
||
|
HRESULT
|
||
|
W3_STATE_AUTHENTICATION::SplitUserDomain(
|
||
|
STRU & strUserDomain,
|
||
|
STRU * pstrUserName,
|
||
|
STRU * pstrDomainName,
|
||
|
WCHAR * pszDefaultDomain,
|
||
|
BOOL * pfPossibleUPNLogon
|
||
|
)
|
||
|
/*++
|
||
|
Description:
|
||
|
|
||
|
Split the input user name into user/domain.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
strUserDomain - Combined domain\username (not altered)
|
||
|
pstrUserName - Filled with user name only
|
||
|
pstrDomainName - Filled with domain name (either embedded in
|
||
|
*pstrUserName,or from metabase/computer domain name)
|
||
|
pszDefaultDomain - Default domain specified in metabase
|
||
|
pfPossibleUPNLogon - TRUE if we may need to do UNP logon,
|
||
|
otherwise FALSE
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
HRESULT
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
WCHAR * pszUserName;
|
||
|
W3_METADATA * pMetaData = NULL;
|
||
|
WCHAR * pszDomain;
|
||
|
DWORD cbDomain;
|
||
|
HRESULT hr;
|
||
|
|
||
|
if ( pstrUserName == NULL ||
|
||
|
pstrDomainName == NULL ||
|
||
|
pfPossibleUPNLogon == NULL )
|
||
|
{
|
||
|
DBG_ASSERT( FALSE );
|
||
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
||
|
}
|
||
|
|
||
|
pszUserName = wcspbrk( strUserDomain.QueryStr(), L"/\\" );
|
||
|
if ( pszUserName == NULL )
|
||
|
{
|
||
|
//
|
||
|
// No domain in the user name. First try the metabase domain
|
||
|
// name
|
||
|
//
|
||
|
|
||
|
pszDomain = pszDefaultDomain;
|
||
|
if ( pszDomain == NULL || *pszDomain == L'\0' )
|
||
|
{
|
||
|
//
|
||
|
// No metabase domain, use default domain name
|
||
|
//
|
||
|
|
||
|
pszDomain = QueryDefaultDomainName();
|
||
|
DBG_ASSERT( pszDomain != NULL );
|
||
|
}
|
||
|
|
||
|
pszUserName = strUserDomain.QueryStr();
|
||
|
|
||
|
hr = pstrDomainName->Copy( pszDomain );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
*pfPossibleUPNLogon = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cbDomain = DIFF( pszUserName - strUserDomain.QueryStr() );
|
||
|
if( cbDomain == 0 )
|
||
|
{
|
||
|
hr = pstrDomainName->Copy( L"." );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = pstrDomainName->Copy( strUserDomain.QueryStr(), cbDomain );
|
||
|
}
|
||
|
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
pszUserName = pszUserName + 1;
|
||
|
|
||
|
*pfPossibleUPNLogon = FALSE;
|
||
|
}
|
||
|
|
||
|
hr = pstrUserName->Copy( pszUserName );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
W3_STATE_AUTHENTICATION::OnAccessDenied(
|
||
|
W3_MAIN_CONTEXT * pMainContext
|
||
|
)
|
||
|
/*++
|
||
|
Description:
|
||
|
|
||
|
Called when a resource is access denied. This routines will call
|
||
|
all authentication providers so that they may add authentication
|
||
|
headers, etc.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pMainContext - main context
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
HRESULT
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
AUTH_PROVIDER * pProvider;
|
||
|
DWORD cProviderCount = 0;
|
||
|
W3_METADATA * pMetaData;
|
||
|
HRESULT hr = NO_ERROR;
|
||
|
|
||
|
if ( pMainContext == NULL )
|
||
|
{
|
||
|
DBG_ASSERT( FALSE );
|
||
|
return HRESULT_FROM_WIN32( GetLastError() );
|
||
|
}
|
||
|
|
||
|
pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
|
||
|
DBG_ASSERT( pMetaData != NULL );
|
||
|
|
||
|
//
|
||
|
// Loop thru all authentication providers
|
||
|
//
|
||
|
|
||
|
for ( cProviderCount = 0; ; cProviderCount++ )
|
||
|
{
|
||
|
pProvider = _rgAuthProviders[ cProviderCount ];
|
||
|
if ( pProvider == NULL )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Only call OnAccessDenied() if the authentication provider is
|
||
|
// supported for the given metadata of the denied request
|
||
|
//
|
||
|
|
||
|
if ( !pMetaData->QueryAuthTypeSupported(
|
||
|
pProvider->QueryAuthType() ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
hr = pProvider->OnAccessDenied( pMainContext );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
W3_STATE_AUTHENTICATION::GetSSPTokenPrivilege(
|
||
|
VOID
|
||
|
)
|
||
|
/*++
|
||
|
Description:
|
||
|
|
||
|
Prepare an appropriate token privilege used to adjust the
|
||
|
SSP impersonation token privilege in order to work around
|
||
|
the problem introduced by using FILE_FLAG_BACKUP_SEMANTICS
|
||
|
in CreateFileW call in W3_FILE_INFO::OpenFile.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
sm_pTokenPrivilege = ( PTOKEN_PRIVILEGES )LocalAlloc( LMEM_FIXED,
|
||
|
sizeof( TOKEN_PRIVILEGES ) + sizeof( LUID_AND_ATTRIBUTES ));
|
||
|
if ( sm_pTokenPrivilege != NULL )
|
||
|
{
|
||
|
if ( !LookupPrivilegeValue( NULL,
|
||
|
L"SeBackupPrivilege",
|
||
|
&sm_BackupPrivilegeTcbValue ) )
|
||
|
{
|
||
|
sm_pTokenPrivilege->PrivilegeCount = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// Set attributes to disable SeBackupPrivilege for SSP
|
||
|
// impersonation token
|
||
|
//
|
||
|
|
||
|
sm_pTokenPrivilege->PrivilegeCount = 1;
|
||
|
|
||
|
sm_pTokenPrivilege->Privileges[0].Luid =
|
||
|
sm_BackupPrivilegeTcbValue;
|
||
|
|
||
|
sm_pTokenPrivilege->Privileges[0].Attributes = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
W3_STATE_AUTHENTICATION::W3_STATE_AUTHENTICATION()
|
||
|
{
|
||
|
_pAnonymousProvider = NULL;
|
||
|
_pCustomProvider = NULL;
|
||
|
_fHasAssociatedUserBefore = FALSE;
|
||
|
|
||
|
//
|
||
|
// Initialize token privilege for SSP impersionation token
|
||
|
//
|
||
|
|
||
|
GetSSPTokenPrivilege();
|
||
|
|
||
|
//
|
||
|
// Figure out the default domain name once
|
||
|
//
|
||
|
|
||
|
_hr = GetDefaultDomainName();
|
||
|
if ( FAILED( _hr ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize all the authentication providers
|
||
|
//
|
||
|
|
||
|
ZeroMemory( _rgAuthProviders, sizeof( _rgAuthProviders ) );
|
||
|
|
||
|
_hr = InitializeAuthenticationProviders();
|
||
|
if ( FAILED( _hr ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
_cbContextSize = sizeof( SSPI_CONTEXT_STATE ) +
|
||
|
sizeof( ANONYMOUS_USER_CONTEXT );
|
||
|
|
||
|
//
|
||
|
// Initialize reverse DNS service
|
||
|
//
|
||
|
|
||
|
if (!InitRDns())
|
||
|
{
|
||
|
_hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
|
||
|
DBGPRINTF(( DBG_CONTEXT,
|
||
|
"Error initializing RDns service. hr = 0x%x\n",
|
||
|
_hr ));
|
||
|
|
||
|
TerminateAuthenticationProviders();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize the W3_USER_CONTEXT reftrace log
|
||
|
//
|
||
|
#if DBG
|
||
|
W3_USER_CONTEXT::sm_pTraceLog = CreateRefTraceLog( 2000, 0 );
|
||
|
#else
|
||
|
W3_USER_CONTEXT::sm_pTraceLog = NULL;
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Store a pointer to the singleton (no C++ goo used in creating
|
||
|
// this singleton)
|
||
|
//
|
||
|
|
||
|
if ( sm_pAuthState != NULL )
|
||
|
{
|
||
|
DBG_ASSERT( sm_pAuthState != NULL );
|
||
|
_hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sm_pAuthState = this;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
W3_STATE_AUTHENTICATION::~W3_STATE_AUTHENTICATION()
|
||
|
{
|
||
|
if ( W3_USER_CONTEXT::sm_pTraceLog != NULL )
|
||
|
{
|
||
|
DestroyRefTraceLog( W3_USER_CONTEXT::sm_pTraceLog );
|
||
|
W3_USER_CONTEXT::sm_pTraceLog = NULL;
|
||
|
}
|
||
|
|
||
|
TerminateRDns();
|
||
|
|
||
|
TerminateAuthenticationProviders();
|
||
|
|
||
|
if (sm_pTokenPrivilege != NULL)
|
||
|
{
|
||
|
LocalFree(sm_pTokenPrivilege);
|
||
|
sm_pTokenPrivilege = NULL;
|
||
|
}
|
||
|
|
||
|
sm_pAuthState = NULL;
|
||
|
}
|
||
|
|
||
|
HRESULT
|
||
|
W3_STATE_AUTHENTICATION::InitializeAuthenticationProviders(
|
||
|
VOID
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Initialize all authentication providers
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
HRESULT
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
HRESULT hr = NO_ERROR;
|
||
|
DWORD cProviderCount = 0;
|
||
|
|
||
|
//
|
||
|
// Initialize trace for connection contexts
|
||
|
//
|
||
|
|
||
|
hr = CONNECTION_AUTH_CONTEXT::Initialize();
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
goto Failure;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Certificate map provider. This must be the first !!!!!!
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] = new CERTMAP_AUTH_PROVIDER;
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] = new IISCERTMAP_AUTH_PROVIDER;
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// SSPI provider
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] =
|
||
|
new SSPI_AUTH_PROVIDER( MD_AUTH_NT );
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// Digest provider
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] =
|
||
|
new DIGEST_AUTH_PROVIDER( MD_AUTH_MD5 );
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// IIS Digest provider (for backward compatibility)
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] =
|
||
|
new IIS_DIGEST_AUTH_PROVIDER();
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// Basic provider
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] = new BASIC_AUTH_PROVIDER;
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// Anonymous provider.
|
||
|
//
|
||
|
// Note: This one should always be the last one
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
||
|
_rgAuthProviders[ cProviderCount ] = new ANONYMOUS_AUTH_PROVIDER;
|
||
|
if ( _rgAuthProviders[ cProviderCount ] == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
hr = _rgAuthProviders[ cProviderCount ]->Initialize( cProviderCount );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
delete _rgAuthProviders[ cProviderCount ];
|
||
|
_rgAuthProviders[ cProviderCount ] = NULL;
|
||
|
goto Failure;
|
||
|
}
|
||
|
_pAnonymousProvider = _rgAuthProviders[ cProviderCount ];
|
||
|
|
||
|
cProviderCount++;
|
||
|
|
||
|
//
|
||
|
// Custom provider. Not really a provider in the sense that it does not
|
||
|
// participate in authenticating a request. Instead, it is just used
|
||
|
// as a stub provider for custom authentication done with
|
||
|
// HSE_REQ_EXEC_URL
|
||
|
//
|
||
|
|
||
|
_pCustomProvider = new CUSTOM_AUTH_PROVIDER;
|
||
|
if ( _pCustomProvider == NULL )
|
||
|
{
|
||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
||
|
goto Failure;
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
|
||
|
Failure:
|
||
|
|
||
|
for ( DWORD i = 0; i < AUTH_PROVIDER_COUNT; i++ )
|
||
|
{
|
||
|
if ( _rgAuthProviders[ i ] != NULL )
|
||
|
{
|
||
|
_rgAuthProviders[ i ]->Terminate();
|
||
|
delete _rgAuthProviders[ i ];
|
||
|
_rgAuthProviders[ i ] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CONNECTION_AUTH_CONTEXT::Terminate();
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
W3_STATE_AUTHENTICATION::TerminateAuthenticationProviders(
|
||
|
VOID
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Terminate all authentication providers
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
for ( DWORD i = 0; i < AUTH_PROVIDER_COUNT; i++ )
|
||
|
{
|
||
|
if ( _rgAuthProviders[ i ] != NULL )
|
||
|
{
|
||
|
_rgAuthProviders[ i ]->Terminate();
|
||
|
delete _rgAuthProviders[ i ];
|
||
|
_rgAuthProviders[ i ] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( _pCustomProvider != NULL )
|
||
|
{
|
||
|
delete _pCustomProvider;
|
||
|
_pCustomProvider = NULL;
|
||
|
}
|
||
|
|
||
|
CONNECTION_AUTH_CONTEXT::Terminate();
|
||
|
}
|
||
|
|
||
|
CONTEXT_STATUS
|
||
|
W3_STATE_AUTHENTICATION::DoWork(
|
||
|
W3_MAIN_CONTEXT * pMainContext,
|
||
|
DWORD cbCompletion,
|
||
|
DWORD dwCompletionStatus
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Handle authentication for this request
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pMainContext - W3_MAIN_CONTEXT representing execution of state
|
||
|
machine
|
||
|
cbCompletion - Number of bytes in an async completion
|
||
|
dwCompletionStatus - Error status of a completion
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
CONTEXT_STATUS_CONTINUE - if we should continue in state machine
|
||
|
else stop executing the machine and free up the current thread
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
DWORD cProviderCount = 0;
|
||
|
AUTH_PROVIDER * pProvider = NULL;
|
||
|
W3_METADATA * pMetaData = NULL;
|
||
|
W3_USER_CONTEXT * pUserContext = NULL;
|
||
|
URL_CONTEXT * pUrlContext = NULL;
|
||
|
BOOL fSupported = FALSE;
|
||
|
HRESULT hr = NO_ERROR;
|
||
|
BOOL fApplies = FALSE;
|
||
|
|
||
|
|
||
|
DBG_ASSERT( pMainContext != NULL );
|
||
|
|
||
|
//
|
||
|
// If we already have a user context, then we must have had an
|
||
|
// AUTH_COMPLETE notification which caused the state machine to back up
|
||
|
// and resume from URLINFO state. In that case, just bail
|
||
|
//
|
||
|
|
||
|
if ( pMainContext->QueryUserContext() != NULL )
|
||
|
{
|
||
|
DBG_ASSERT( pMainContext->IsNotificationNeeded( SF_NOTIFY_AUTH_COMPLETE ) );
|
||
|
|
||
|
return CONTEXT_STATUS_CONTINUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// First, find the authentication provider which applies. We
|
||
|
// should always find a matching provider (since anonymous
|
||
|
// provider) should always match!
|
||
|
//
|
||
|
|
||
|
for ( ; ; )
|
||
|
{
|
||
|
pProvider = _rgAuthProviders[ cProviderCount ];
|
||
|
if ( pProvider == NULL )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
DBG_ASSERT( pProvider != NULL );
|
||
|
|
||
|
hr = pProvider->DoesApply( pMainContext,
|
||
|
&fApplies );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
goto Finished;
|
||
|
}
|
||
|
|
||
|
if ( fApplies )
|
||
|
{
|
||
|
//
|
||
|
// Cool. We have a match!
|
||
|
//
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
cProviderCount++;
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If only the anonymous provider matched, then check whether we
|
||
|
// have credentials associated with the connection (since IE won't
|
||
|
// send Authorization: header for subsequent SSPI authenticated
|
||
|
// requests on a connection)
|
||
|
//
|
||
|
|
||
|
if ( pProvider->QueryAuthType() == MD_AUTH_ANONYMOUS )
|
||
|
{
|
||
|
//
|
||
|
// Another slimy optimization. If we haven't associated a user
|
||
|
// with the connection, then we don't have to bother looking up
|
||
|
// connection
|
||
|
//
|
||
|
|
||
|
if ( _fHasAssociatedUserBefore )
|
||
|
{
|
||
|
pUserContext = pMainContext->QueryConnectionUserContext();
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
pProvider = pUserContext->QueryProvider();
|
||
|
DBG_ASSERT( pProvider != NULL );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// If a provider applies, then ignore/remove any
|
||
|
// cached user associated with the request
|
||
|
//
|
||
|
|
||
|
pUserContext = pMainContext->QueryConnectionUserContext();
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
pMainContext->SetConnectionUserContext( NULL );
|
||
|
pUserContext->DereferenceUserContext();
|
||
|
pUserContext = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Is the given provider supported (by metadata)
|
||
|
//
|
||
|
|
||
|
pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
|
||
|
DBG_ASSERT( pMetaData != NULL );
|
||
|
|
||
|
if ( pMetaData->QueryAuthTypeSupported( pProvider->QueryAuthType() ) )
|
||
|
{
|
||
|
fSupported = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// If anonymous authentication is supported, then we can
|
||
|
// still let it thru
|
||
|
//
|
||
|
|
||
|
if ( pMetaData->QueryAuthTypeSupported( MD_AUTH_ANONYMOUS ) )
|
||
|
{
|
||
|
pProvider = QueryAnonymousProvider();
|
||
|
DBG_ASSERT( pProvider != NULL );
|
||
|
|
||
|
//
|
||
|
// Anonymous provider applies, remove the previous cached
|
||
|
// user associated with the request
|
||
|
//
|
||
|
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
pMainContext->SetConnectionUserContext( NULL );
|
||
|
pUserContext->DereferenceUserContext();
|
||
|
pUserContext = NULL;
|
||
|
}
|
||
|
|
||
|
fSupported = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Not supported, you're outta here!
|
||
|
//
|
||
|
|
||
|
if ( !fSupported )
|
||
|
{
|
||
|
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
||
|
Http401Config );
|
||
|
pMainContext->SetFinishedResponse();
|
||
|
|
||
|
hr = pMainContext->OnAccessDenied();
|
||
|
goto Finished;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now we can authenticate
|
||
|
//
|
||
|
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
//
|
||
|
// We already have a context associated with connection. Use it!
|
||
|
//
|
||
|
|
||
|
pUserContext->ReferenceUserContext();
|
||
|
pMainContext->SetUserContext( pUserContext );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DBG_ASSERT( pProvider != NULL );
|
||
|
|
||
|
// perf ctr
|
||
|
pMainContext->QuerySite()->IncLogonAttempts();
|
||
|
|
||
|
hr = pProvider->DoAuthenticate( pMainContext );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
if( WIN32_FROM_HRESULT( hr ) == ERROR_PASSWORD_MUST_CHANGE ||
|
||
|
WIN32_FROM_HRESULT( hr ) == ERROR_PASSWORD_EXPIRED )
|
||
|
{
|
||
|
hr = pMainContext->PasswdChangeExecute();
|
||
|
if( S_OK == hr )
|
||
|
{
|
||
|
return CONTEXT_STATUS_PENDING;
|
||
|
}
|
||
|
else if( S_FALSE == hr )
|
||
|
{
|
||
|
//
|
||
|
// S_FALSE means password change disabled
|
||
|
//
|
||
|
pMainContext->QueryResponse()->SetStatus(
|
||
|
HttpStatusUnauthorized,
|
||
|
Http401BadLogon );
|
||
|
pMainContext->SetErrorStatus( hr );
|
||
|
pMainContext->SetFinishedResponse();
|
||
|
|
||
|
return CONTEXT_STATUS_CONTINUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
goto Finished;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Do we have a valid user now
|
||
|
//
|
||
|
|
||
|
pUserContext = pMainContext->QueryUserContext();
|
||
|
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
if ( pUserContext->QueryAuthType() != MD_AUTH_ANONYMOUS )
|
||
|
{
|
||
|
hr = pMainContext->PasswdExpireNotify();
|
||
|
if( FAILED( hr ) )
|
||
|
{
|
||
|
//
|
||
|
// Internal error
|
||
|
//
|
||
|
goto Finished;
|
||
|
}
|
||
|
else if( hr == S_OK )
|
||
|
{
|
||
|
//
|
||
|
// We've successfully handled password expire
|
||
|
// notification
|
||
|
//
|
||
|
|
||
|
return CONTEXT_STATUS_PENDING;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Advanced password expire notification is disabled,
|
||
|
// we should allow the user to get access, fall through
|
||
|
//
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Should we cache the user on the connection? Do so, only if
|
||
|
//
|
||
|
|
||
|
DBG_ASSERT( pMetaData != NULL );
|
||
|
|
||
|
if ( pMetaData->QueryAuthPersistence() != MD_AUTH_SINGLEREQUEST
|
||
|
&& pUserContext->QueryProvider()->QueryAuthType() == MD_AUTH_NT
|
||
|
&& !pMainContext->QueryRequest()->IsProxyRequest()
|
||
|
&& pUserContext != pMainContext->QueryConnectionUserContext() )
|
||
|
{
|
||
|
pUserContext->ReferenceUserContext();
|
||
|
pMainContext->SetConnectionUserContext( pUserContext );
|
||
|
_fHasAssociatedUserBefore = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// If we don't have a user, then we must not allow handle request
|
||
|
// state to happen!
|
||
|
//
|
||
|
|
||
|
pMainContext->SetFinishedResponse();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// OK. If we got to here and we have a user context, then authentication
|
||
|
// is complete! So lets notify AUTH_COMPLETE filters
|
||
|
//
|
||
|
|
||
|
if ( pUserContext != NULL )
|
||
|
{
|
||
|
if ( pMainContext->IsNotificationNeeded( SF_NOTIFY_AUTH_COMPLETE ) )
|
||
|
{
|
||
|
HTTP_FILTER_AUTH_COMPLETE_INFO AuthInfo;
|
||
|
STACK_STRU( strOriginal, MAX_PATH );
|
||
|
STACK_STRU( strNewUrl, MAX_PATH );
|
||
|
BOOL fFinished = FALSE;
|
||
|
|
||
|
//
|
||
|
// Store away the original URL
|
||
|
//
|
||
|
|
||
|
hr = pMainContext->QueryRequest()->GetUrl( &strOriginal );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
goto Finished;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Call the filter
|
||
|
//
|
||
|
|
||
|
pMainContext->NotifyFilters( SF_NOTIFY_AUTH_COMPLETE,
|
||
|
&AuthInfo,
|
||
|
&fFinished );
|
||
|
|
||
|
if ( fFinished )
|
||
|
{
|
||
|
pMainContext->SetDone();
|
||
|
return CONTEXT_STATUS_CONTINUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If the URL has changed, we'll need to backup the state machine
|
||
|
//
|
||
|
|
||
|
hr = pMainContext->QueryRequest()->GetUrl( &strNewUrl );
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
goto Finished;
|
||
|
}
|
||
|
|
||
|
if ( wcscmp( strNewUrl.QueryStr(),
|
||
|
strOriginal.QueryStr() ) != 0 )
|
||
|
{
|
||
|
//
|
||
|
// URL is different!
|
||
|
//
|
||
|
|
||
|
pMainContext->BackupStateMachine();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// URL is the same. Do nothing and continue
|
||
|
//
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Finished:
|
||
|
if ( FAILED( hr ) )
|
||
|
{
|
||
|
pMainContext->QueryResponse()->
|
||
|
SetStatus( HttpStatusServerError );
|
||
|
pMainContext->SetFinishedResponse();
|
||
|
pMainContext->SetErrorStatus( hr );
|
||
|
}
|
||
|
|
||
|
return CONTEXT_STATUS_CONTINUE;
|
||
|
}
|