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.
1406 lines
38 KiB
1406 lines
38 KiB
/*++
|
|
|
|
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"
|
|
#include "passportprovider.hxx"
|
|
|
|
#define IIS_SUBAUTH_NAME L"iissuba"
|
|
|
|
W3_STATE_AUTHENTICATION * W3_STATE_AUTHENTICATION::sm_pAuthState;
|
|
BOOL W3_STATE_AUTHENTICATION::sm_fSubAuthConfigured = FALSE;
|
|
BOOL W3_STATE_AUTHENTICATION::sm_fLocalSystem = FALSE;
|
|
LONG W3_STATE_AUTHENTICATION::sm_lSubAuthAnonEvent = 0;
|
|
LONG W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent = 0;
|
|
LONG W3_STATE_AUTHENTICATION::sm_lLocalSystemEvent = 0;
|
|
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:
|
|
|
|
None
|
|
|
|
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 );
|
|
|
|
if( dwLength < sizeof( _achDefaultDomainName ) / sizeof( WCHAR ) )
|
|
{
|
|
wcsncpy( _achDefaultDomainName,
|
|
(LPCWSTR)pAcctDomainInfo->DomainName.Buffer,
|
|
dwLength );
|
|
|
|
_achDefaultDomainName[ dwLength ] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
err = ERROR_INSUFFICIENT_BUFFER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// Tres freakin lame. Gotta call into GetComputerNameEx() since I
|
|
// need to fully qualified name. If it fails, oh well, we don't
|
|
// provide a default domain name for Passport calls
|
|
//
|
|
|
|
dwLength = sizeof( _achMemberDomainName ) / sizeof( WCHAR );
|
|
|
|
GetComputerNameEx( ComputerNameDnsDomain,
|
|
_achMemberDomainName,
|
|
&dwLength );
|
|
}
|
|
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;
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Don't care about the return value here since we want to add
|
|
// all possible authentication headers
|
|
//
|
|
hr = pProvider->OnAccessDenied( pMainContext );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_STATE_AUTHENTICATION::GetSubAuthConfiguration(
|
|
VOID
|
|
)
|
|
/*++
|
|
Description:
|
|
|
|
Find out if sub authenticator is configured correctly for the
|
|
current process.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Returns:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = S_OK;
|
|
PPRIVILEGE_SET pPrivilegeSet = NULL;
|
|
HANDLE hProcessToken = NULL;
|
|
BOOL fPrivilegeEnabled = FALSE;
|
|
HKEY hKey = NULL;
|
|
LUID TcbPrivilegeValue;
|
|
WCHAR pszSubAuthName[ sizeof( IIS_SUBAUTH_NAME ) ];
|
|
DWORD dwType;
|
|
DWORD cbValue;
|
|
|
|
if ( !OpenProcessToken( GetCurrentProcess(),
|
|
TOKEN_ALL_ACCESS,
|
|
&hProcessToken ) )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
goto Exit;
|
|
}
|
|
|
|
DBG_ASSERT( hProcessToken != NULL );
|
|
|
|
pPrivilegeSet = ( PPRIVILEGE_SET )LocalAlloc( LMEM_FIXED,
|
|
sizeof( PRIVILEGE_SET ) + sizeof( LUID_AND_ATTRIBUTES ) );
|
|
if( pPrivilegeSet == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
|
|
goto Exit;
|
|
}
|
|
|
|
if ( !LookupPrivilegeValue( NULL,
|
|
L"SeTcbPrivilege",
|
|
&TcbPrivilegeValue ) )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
goto Exit;
|
|
}
|
|
|
|
pPrivilegeSet->PrivilegeCount = 1;
|
|
pPrivilegeSet->Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|
pPrivilegeSet->Privilege[0].Luid = TcbPrivilegeValue;
|
|
pPrivilegeSet->Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
|
|
if( !PrivilegeCheck( hProcessToken,
|
|
pPrivilegeSet,
|
|
&fPrivilegeEnabled ) )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
goto Exit;
|
|
}
|
|
|
|
if( fPrivilegeEnabled )
|
|
{
|
|
sm_fLocalSystem = TRUE;
|
|
}
|
|
|
|
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
L"System\\CurrentControlSet\\Control\\Lsa\\MSV1_0",
|
|
0,
|
|
KEY_READ,
|
|
&hKey ) == ERROR_SUCCESS )
|
|
{
|
|
DBG_ASSERT( hKey != NULL );
|
|
|
|
cbValue = sizeof pszSubAuthName;
|
|
|
|
if ( RegQueryValueEx( hKey,
|
|
L"Auth132",
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE) pszSubAuthName,
|
|
&cbValue ) != ERROR_SUCCESS ||
|
|
dwType != REG_SZ ||
|
|
_wcsicmp( pszSubAuthName, IIS_SUBAUTH_NAME ) )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
sm_fSubAuthConfigured = TRUE;
|
|
}
|
|
|
|
RegCloseKey( hKey );
|
|
hKey = NULL;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if( pPrivilegeSet != NULL )
|
|
{
|
|
LocalFree( pPrivilegeSet );
|
|
pPrivilegeSet = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
W3_STATE_AUTHENTICATION::W3_STATE_AUTHENTICATION()
|
|
{
|
|
_pAnonymousProvider = NULL;
|
|
_pCustomProvider = NULL;
|
|
_fHasAssociatedUserBefore = FALSE;
|
|
|
|
//
|
|
// Figure out the default domain name once
|
|
//
|
|
|
|
_hr = GetDefaultDomainName();
|
|
if ( FAILED( _hr ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Figure out if we can use the IIS subauthenticator
|
|
//
|
|
|
|
_hr = GetSubAuthConfiguration();
|
|
if( FAILED( _hr ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize all the authentication providers
|
|
//
|
|
|
|
ZeroMemory( _rgAuthProviders, sizeof( _rgAuthProviders ) );
|
|
|
|
_hr = InitializeAuthenticationProviders();
|
|
if ( FAILED( _hr ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// 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();
|
|
|
|
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++;
|
|
|
|
//
|
|
// Passport provider
|
|
//
|
|
|
|
DBG_ASSERT( cProviderCount < AUTH_PROVIDER_COUNT );
|
|
_rgAuthProviders[ cProviderCount ] = new PASSPORT_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;
|
|
BOOL fSupported = FALSE;
|
|
HRESULT hr = NO_ERROR;
|
|
BOOL fApplies = FALSE;
|
|
BOOL fFilterFinished = FALSE;
|
|
W3_MAIN_CONTEXT_STATE * pContextState = NULL;
|
|
|
|
UNREFERENCED_PARAMETER( cbCompletion );
|
|
UNREFERENCED_PARAMETER( dwCompletionStatus );
|
|
|
|
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!
|
|
//
|
|
|
|
pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
|
|
//
|
|
// Optimization for path when only anonymous authentication is enabled
|
|
// (certmapping is checked as well)
|
|
// Issue jaroslad 01/08/23. This may conflict in the future with
|
|
// DoesApply of new providers.
|
|
//
|
|
|
|
if ( pMetaData->IsOnlyAnonymousAuthSupported() &&
|
|
pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization ) == NULL )
|
|
{
|
|
//
|
|
// no authorization header and only anonymous enabled
|
|
//
|
|
pProvider = QueryAnonymousProvider();
|
|
DBG_ASSERT( pProvider != NULL );
|
|
}
|
|
else
|
|
{
|
|
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 );
|
|
}
|
|
|
|
//
|
|
// Clean up the security context if there is one
|
|
//
|
|
|
|
pProvider->SetConnectionAuthContext( pMainContext, 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)
|
|
//
|
|
|
|
if ( pMetaData->QueryAuthTypeSupported( pProvider->QueryAuthType() ) )
|
|
{
|
|
fSupported = TRUE;
|
|
}
|
|
else if( pProvider->QueryAuthType() == MD_AUTH_ANONYMOUS )
|
|
{
|
|
//
|
|
// Give the anonymous provider a shot at this request.
|
|
//
|
|
// We need to do this even if MD_AUTH_ANONYMOUS is not
|
|
// supported, so that authentication filters get a
|
|
// crack at it. It's up to the anonymous provider to
|
|
// fail if it's not supported, and no filter sets
|
|
// credentials.
|
|
//
|
|
|
|
pMainContext->SetCheckAnonAuthTypeSupported( TRUE );
|
|
|
|
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 )
|
|
{
|
|
//
|
|
// Clear any context state which was set
|
|
//
|
|
|
|
pContextState = pMainContext->QueryContextState();
|
|
if ( pContextState != NULL )
|
|
{
|
|
pContextState->Cleanup( pMainContext );
|
|
pMainContext->SetContextState( NULL );
|
|
}
|
|
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
|
Http401Config );
|
|
pMainContext->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) );
|
|
pMainContext->SetFinishedResponse();
|
|
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// 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();
|
|
|
|
fFilterFinished = FALSE;
|
|
|
|
hr = pProvider->DoAuthenticate( pMainContext, &fFilterFinished );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DWORD dwError = WIN32_FROM_HRESULT( hr );
|
|
|
|
if( dwError == ERROR_PASSWORD_MUST_CHANGE ||
|
|
dwError == 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;
|
|
}
|
|
}
|
|
else if( SEC_E_NO_CREDENTIALS == hr )
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
|
Http401Config );
|
|
pMainContext->SetErrorStatus( hr );
|
|
pMainContext->SetFinishedResponse();
|
|
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
pMainContext->SetErrorStatus( hr );
|
|
|
|
if ( dwError == ERROR_ACCESS_DENIED )
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
|
Http401Filter );
|
|
}
|
|
else if ( dwError == ERROR_FILE_NOT_FOUND ||
|
|
dwError == ERROR_PATH_NOT_FOUND )
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusNotFound );
|
|
}
|
|
else
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusServerError );
|
|
}
|
|
|
|
pMainContext->SetFinishedResponse();
|
|
pMainContext->SetDisconnect( TRUE );
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
|
|
if ( fFilterFinished )
|
|
{
|
|
pMainContext->SetDone();
|
|
|
|
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->QueryIsAuthNTLM()
|
|
&& !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
|
|
//
|
|
|
|
if ( !pMainContext->NotifyFilters( SF_NOTIFY_AUTH_COMPLETE,
|
|
&AuthInfo,
|
|
&fFinished ) )
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
|
|
pMainContext->SetErrorStatus( HRESULT_FROM_WIN32( dwError ) );
|
|
|
|
if ( dwError == ERROR_ACCESS_DENIED )
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
|
Http401Filter );
|
|
}
|
|
else if ( dwError == ERROR_FILE_NOT_FOUND ||
|
|
dwError == ERROR_PATH_NOT_FOUND )
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusNotFound );
|
|
}
|
|
else
|
|
{
|
|
pMainContext->QueryResponse()->SetStatus( HttpStatusServerError );
|
|
}
|
|
|
|
pMainContext->SetFinishedResponse();
|
|
pMainContext->SetDisconnect( TRUE );
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
CONTEXT_STATUS
|
|
W3_STATE_AUTHENTICATION::OnCompletion(
|
|
W3_MAIN_CONTEXT * pMainContext,
|
|
DWORD cbCompletion,
|
|
DWORD dwCompletionStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Complete the done state
|
|
|
|
Arguments:
|
|
|
|
pMainContext - W3_MAIN_CONTEXT representing an execution of the state machine
|
|
cbCompletion - Number of bytes on completion
|
|
dwCompletionStatus - Win32 Error on completion (if any)
|
|
|
|
Return Value:
|
|
|
|
CONTEXT_STATUS_CONTINUE - if we should continue in state machine
|
|
else stop executing the machine and free up the current thread
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER( cbCompletion );
|
|
UNREFERENCED_PARAMETER( dwCompletionStatus );
|
|
|
|
DBG_ASSERT( pMainContext != NULL );
|
|
|
|
//
|
|
// During authentication state, only the routines for password notification
|
|
// could post asynchonous completion while they are doing child execution.
|
|
// The following assert is to make sure that the completion is posted by
|
|
// ExecuteExpiredURL routine.
|
|
//
|
|
DBG_ASSERT( pMainContext->QueryRequest()->GetHeader( "CFG-ENC-CAPS" )
|
|
!= NULL );
|
|
|
|
//
|
|
// Since the response has already been send asynchronously, we advance the
|
|
// state machine to CONTEXT_STATE_RESPONSE here.
|
|
//
|
|
pMainContext->SetFinishedResponse();
|
|
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|