Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1984 lines
49 KiB

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name :
iisdigestprovider.cxx
Abstract:
IIS Digest authentication provider
- version of Digest auth as implemented by IIS5 and IIS5.1
Author:
Jaroslad - based on code from md5filt 10-Nov-2000
Environment:
Win32 - User Mode
Project:
ULW3.DLL
--*/
#include "precomp.hxx"
#include "iisdigestprovider.hxx"
#include "uuencode.hxx"
# include <mbstring.h>
#include <lm.h>
#include <lmcons.h>
#include <lmjoin.h>
#include <time.h>
//
// lonsint.dll related header files
//
#include <lonsi.hxx>
#include <tslogon.hxx>
//
// value names used by MD5 authentication.
// must be in sync with MD5_AUTH_NAMES
//
enum MD5_AUTH_NAME
{
MD5_AUTH_USERNAME,
MD5_AUTH_URI,
MD5_AUTH_REALM,
MD5_AUTH_NONCE,
MD5_AUTH_RESPONSE,
MD5_AUTH_ALGORITHM,
MD5_AUTH_DIGEST,
MD5_AUTH_OPAQUE,
MD5_AUTH_QOP,
MD5_AUTH_CNONCE,
MD5_AUTH_NC,
MD5_AUTH_LAST,
};
//
// Value names used by MD5 authentication.
// must be in sync with MD5_AUTH_NAME
//
PSTR MD5_AUTH_NAMES[] = {
"username",
"uri",
"realm",
"nonce",
"response",
"algorithm",
"digest",
"opaque",
"qop",
"cnonce",
"nc"
};
//
// Local function implementation
//
static
LPSTR
SkipWhite(
IN OUT LPSTR p
)
/*++
Routine Description:
Skip white space and ','
Arguments:
p - ptr to string
Return Value:
updated ptr after skiping white space
--*/
{
while ( SAFEIsSpace((UCHAR)(*p) ) || *p == ',' )
{
++p;
}
return p;
}
//
// class IIS_DIGEST_AUTH_PROVIDER implementation
//
//static
STRA * IIS_DIGEST_AUTH_PROVIDER::sm_pstraComputerDomain = NULL;
//static
HRESULT
IIS_DIGEST_AUTH_PROVIDER::Initialize(
DWORD dwInternalId
)
/*++
Routine Description:
Initialize IIS Digest SSPI provider
Arguments:
None
Return Value:
HRESULT
--*/
{
HRESULT hr = NO_ERROR;
SetInternalId( dwInternalId );
sm_pstraComputerDomain = new STRA;
if( sm_pstraComputerDomain == NULL )
{
return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY);
}
hr = GetLanGroupDomainName( *sm_pstraComputerDomain );
if ( FAILED( hr ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Warning: Error calling GetLanGroupDomainName(). hr = %x\n",
hr ));
//
// Ignore errors that may occur while retrieving domain name
// it is important but not critical information
// client can always explicitly specify domain
//
hr = NO_ERROR;
}
hr = IIS_DIGEST_CONN_CONTEXT::Initialize();
if ( FAILED( hr ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Error initializing Digest Auth Prov. hr = %x\n",
hr ));
return hr;
}
return NO_ERROR;
}
//static
VOID
IIS_DIGEST_AUTH_PROVIDER::Terminate(
VOID
)
/*++
Routine Description:
Terminate IIS SSPI Digest provider
Arguments:
None
Return Value:
None
--*/
{
if( sm_pstraComputerDomain != NULL )
{
delete sm_pstraComputerDomain;
sm_pstraComputerDomain = NULL;
}
IIS_DIGEST_CONN_CONTEXT::Terminate();
}
HRESULT
IIS_DIGEST_AUTH_PROVIDER::DoesApply(
IN W3_MAIN_CONTEXT * pMainContext,
OUT BOOL * pfApplies
)
/*++
Routine Description:
Does the given request have credentials applicable to the Digest
provider
Arguments:
pMainContext - Main context representing request
pfApplies - Set to true if Digest is applicable
Return Value:
HRESULT
--*/
{
LPCSTR pszAuthHeader;
if ( pMainContext == NULL ||
pfApplies == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
*pfApplies = FALSE;
//
// Is using of Digest SSP enabled?
//
if ( g_pW3Server->QueryUseDigestSSP() )
{
//
// Digest SSP is enabled => IIS Digest cannot be used
//
return NO_ERROR;
}
if( !W3_STATE_AUTHENTICATION::sm_fLocalSystem )
{
if( W3_STATE_AUTHENTICATION::sm_lLocalSystemEvent == 0 )
{
if( !InterlockedExchange( &W3_STATE_AUTHENTICATION::sm_lLocalSystemEvent, 1 ) )
{
//
// The process token does not have SeTcbPrivilege
//
g_pW3Server->LogEvent( W3_EVENT_SUBAUTH_LOCAL_SYSTEM,
0,
NULL );
}
}
return NO_ERROR;
}
if( W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent == 1 )
{
return NO_ERROR;
}
//
// Get auth type
//
pszAuthHeader = pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization );
//
// No package, no auth
//
if ( pszAuthHeader == NULL )
{
return NO_ERROR;
}
//
// Is it Digest?
//
if ( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 )
{
*pfApplies = TRUE;
}
return NO_ERROR;
}
HRESULT
IIS_DIGEST_AUTH_PROVIDER::DoAuthenticate(
IN W3_MAIN_CONTEXT * pMainContext,
OUT BOOL * // unused
)
/*++
Description:
Do authentication work (we will be called if we apply)
Arguments:
pMainContext - Main context
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL;
LPCSTR pszAuthHeader = NULL;
STACK_STRA( straAuthHeader, 128 );
BOOL fQOPAuth = FALSE;
BOOL fSt = FALSE;
HANDLE hAccessTokenImpersonation = NULL;
IIS_DIGEST_USER_CONTEXT * pUserContext = NULL;
W3_REQUEST * pW3Request = NULL;
BOOL fSendAccessDenied = FALSE;
SECURITY_STATUS secStatus = SEC_E_OK;
STACK_STRA( straVerb, 10 );
STACK_STRU( strDigestUri, MAX_PATH + 1 );
STACK_STRU( strUrl, MAX_PATH + 1 );
STACK_STRA( straCurrentNonce, NONCE_SIZE + 1 );
LPSTR aValueTable[ MD5_AUTH_LAST ];
DIGEST_LOGON_INFO DigestLogonInfo;
STACK_STRA( straUserName, UNLEN + 1 );
STACK_STRA( straDomainName, IIS_DNLEN + 1 );
ULONG cbBytesCopied;
DBG_ASSERT( pMainContext != NULL );
//
// make copy of Authorization Header
// (this function will be modifying the string)
//
hr = straAuthHeader.Copy( pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization ) );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
pszAuthHeader = straAuthHeader.QueryStr();
DBG_ASSERT( pszAuthHeader != NULL );
//
// If DoAuthenticate() for Digest is called then DIGEST_AUTH string must
// have been at the beggining of the pszAuthHeader
//
DBG_ASSERT( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 );
//
// Skip the name of Authentication scheme
//
pszAuthHeader = pszAuthHeader + sizeof(DIGEST_AUTH) - 1;
if ( !IIS_DIGEST_CONN_CONTEXT::ParseForName( (PSTR) pszAuthHeader,
MD5_AUTH_NAMES,
MD5_AUTH_LAST,
aValueTable ) )
{
DBGPRINTF(( DBG_CONTEXT,
"Failed parsing of Authorization header for Digest Auth\n"
));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
//
// Simple validation of received arguments
//
if ( aValueTable[ MD5_AUTH_USERNAME ] == NULL ||
aValueTable[ MD5_AUTH_REALM ] == NULL ||
aValueTable[ MD5_AUTH_URI ] == NULL ||
aValueTable[ MD5_AUTH_NONCE ] == NULL ||
aValueTable[ MD5_AUTH_RESPONSE ] == NULL )
{
DBGPRINTF(( DBG_CONTEXT,
"Invalid Digest Authorization Header (Username, realm, URI, nonce or response is missing).\n"
));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
//
// Verify quality of protection (qop) required by client
// We only support "auth" type. If anything else is sent by client it will be ignored
//
if ( aValueTable[ MD5_AUTH_QOP ] != NULL )
{
if ( _stricmp( aValueTable[ MD5_AUTH_QOP ], QOP_AUTH ) )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
DBGPRINTF(( DBG_CONTEXT,
"Unknown qop=%s value in Digest Authorization header. hr = %x\n",
aValueTable[ MD5_AUTH_QOP ],
hr ));
goto ExitPoint;
}
//
// If qop="auth" is used in header then CNONCE and NC are mandatory
//
if ( aValueTable[ MD5_AUTH_CNONCE ] == NULL ||
aValueTable[ MD5_AUTH_NC ] == NULL )
{
DBGPRINTF(( DBG_CONTEXT,
"Invalid Digest Authorization Header (cnonce or nc is missing).\n"
));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
fQOPAuth = TRUE;
}
else
{
aValueTable[ MD5_AUTH_QOP ] = VALUE_NONE;
aValueTable[ MD5_AUTH_CNONCE ] = VALUE_NONE;
aValueTable[ MD5_AUTH_NC ] = VALUE_NONE;
}
if ( FAILED( hr = straCurrentNonce.Copy( aValueTable[ MD5_AUTH_NONCE ] ) ) )
{
goto ExitPoint;
}
//
// Verify that the nonce is well-formed
//
if ( !IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce( straCurrentNonce ) )
{
fSendAccessDenied = TRUE;
DBGPRINTF(( DBG_CONTEXT,
"Invalid Digest Authorization Header (nonce is not well formed).\n"
));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
//
// What is the request verb?
//
if ( FAILED( hr = pMainContext->QueryRequest()->GetVerbString( &straVerb ) ) )
{
goto ExitPoint;
}
//
// Check URI field match URL
//
if ( ! strDigestUri.QueryBuffer()->Resize( (strlen(aValueTable[MD5_AUTH_URI]) + 1) * sizeof(WCHAR) ) )
{
goto ExitPoint;
}
//
// Normalize DigestUri
//
hr = UlCleanAndCopyUrl( (PUCHAR)aValueTable[MD5_AUTH_URI],
strlen( aValueTable[MD5_AUTH_URI] ),
&cbBytesCopied,
strDigestUri.QueryStr(),
NULL );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
//
// after modyfing string data in internal buffer call SyncWithBuffer
// to synchronize string length
//
strDigestUri.SyncWithBuffer();
if ( FAILED( hr = pMainContext->QueryRequest()->GetUrl( &strUrl ) ) )
{
goto ExitPoint;
}
if ( !strUrl.Equals( strDigestUri ) )
{
//
// Note: RFC says that BAD REQUEST should be returned
// but for now to be backward compatible with IIS5.1
// we will return ACCESS_DENIED
//
fSendAccessDenied = TRUE;
DBGPRINTF(( DBG_CONTEXT,
"URI in Digest Authorization header doesn't match the requested URI.\n"
));
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
goto ExitPoint;
}
pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *)
QueryConnectionAuthContext( pMainContext );
if ( pDigestConnContext == NULL )
{
//
// Create new Authentication context
//
pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT();
if ( pDigestConnContext == NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
goto ExitPoint;
}
hr = SetConnectionAuthContext( pMainContext,
pDigestConnContext );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
}
DBG_ASSERT( pDigestConnContext != NULL );
if ( FAILED( hr = pDigestConnContext->GenerateNonce( ) ) )
{
goto ExitPoint;
}
if ( FAILED( hr = BreakUserAndDomain( aValueTable[ MD5_AUTH_USERNAME ],
straDomainName,
straUserName ) ) )
{
goto ExitPoint;
}
DigestLogonInfo.pszNtUser = straUserName.QueryStr();
DigestLogonInfo.pszDomain = straDomainName.QueryStr();
DigestLogonInfo.pszUser = aValueTable[ MD5_AUTH_USERNAME ];
DigestLogonInfo.pszRealm = aValueTable[ MD5_AUTH_REALM ];
DigestLogonInfo.pszURI = aValueTable[ MD5_AUTH_URI ];
DigestLogonInfo.pszMethod = straVerb.QueryStr();
DigestLogonInfo.pszNonce = straCurrentNonce.QueryStr();
DigestLogonInfo.pszCurrentNonce = pDigestConnContext->QueryNonce().QueryStr();
DigestLogonInfo.pszCNonce = aValueTable[ MD5_AUTH_CNONCE ];
DigestLogonInfo.pszQOP = aValueTable[ MD5_AUTH_QOP ];
DigestLogonInfo.pszNC = aValueTable[ MD5_AUTH_NC ];
DigestLogonInfo.pszResponse = aValueTable[ MD5_AUTH_RESPONSE ];
pW3Request = pMainContext->QueryRequest();
DBG_ASSERT( pW3Request != NULL );
//
// Register the remote IP address with LSA so that it can be logged
//
if( pW3Request->QueryRemoteAddressType() == AF_INET )
{
secStatus = SecpSetIPAddress(
( PUCHAR )pW3Request->QueryRemoteSockAddress(),
sizeof( SOCKADDR_IN ) );
}
else if( pW3Request->QueryRemoteAddressType() == AF_INET6 )
{
secStatus = SecpSetIPAddress(
( PUCHAR )pW3Request->QueryRemoteSockAddress(),
sizeof( SOCKADDR_IN6 ) );
}
else
{
DBG_ASSERT( FALSE );
}
if( FAILED( secStatus ) )
{
hr = secStatus;
goto ExitPoint;
}
fSt = IISLogonDigestUserA( &DigestLogonInfo,
IISSUBA_DIGEST ,
&hAccessTokenImpersonation );
if ( fSt == FALSE )
{
DWORD dwRet = GetLastError();
if ( dwRet == ERROR_PASSWORD_MUST_CHANGE ||
dwRet == ERROR_PASSWORD_EXPIRED )
{
hr = HRESULT_FROM_WIN32( dwRet );
goto ExitPoint;
}
if( dwRet == ERROR_PROC_NOT_FOUND )
{
if( W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent == 0 )
{
if( !InterlockedExchange( &W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent, 1 ) )
{
//
// The registry key for iissuba is not configured correctly
//
g_pW3Server->LogEvent( W3_EVENT_SUBAUTH_REGISTRY_CONFIGURATION_DC,
0,
NULL );
}
}
hr = HRESULT_FROM_WIN32( dwRet );
goto ExitPoint;
}
fSendAccessDenied = TRUE;
hr = HRESULT_FROM_WIN32( dwRet );
goto ExitPoint;
}
//
// Response from the client was correct but the nonce has expired,
//
if ( pDigestConnContext->IsExpiredNonce( straCurrentNonce,
pDigestConnContext->QueryNonce() ) )
{
//
// User knows password but nonce that was used for
// response calculation already expired
// Respond to client with stale=TRUE
// Only Digest header will be sent to client
// ( it will prevent state information needed to be passed
// from DoAuthenticate() to OnAccessDenied() )
//
pDigestConnContext->SetStale( TRUE );
hr = SetDigestHeader( pMainContext, pDigestConnContext );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
//
// Don't let anyone else send back authentication headers when
// the 401 is sent
//
pMainContext->SetProviderHandled( TRUE );
//
// We need to send a 401 response to continue the handshake.
// We have already setup the WWW-Authenticate header
//
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
Http401BadLogon );
pMainContext->SetFinishedResponse();
pMainContext->SetErrorStatus( SEC_E_CONTEXT_EXPIRED );
hr = NO_ERROR;
goto ExitPoint;
}
//
// We successfully authenticated.
// Create a user context and setup it up
//
DBG_ASSERT( hAccessTokenImpersonation != NULL );
pUserContext = new IIS_DIGEST_USER_CONTEXT( this );
if ( pUserContext == NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
goto ExitPoint;
}
hr = pUserContext->Create( hAccessTokenImpersonation,
aValueTable[MD5_AUTH_USERNAME] );
if ( FAILED( hr ) )
{
goto ExitPoint;
}
else
{
//
// hAccessTokenImpestonation is now owned by pUserContext
//
hAccessTokenImpersonation = NULL;
}
pMainContext->SetUserContext( pUserContext );
hr = NO_ERROR;
ExitPoint:
if ( FAILED( hr ) )
{
if ( fSendAccessDenied )
{
//
// if ACCESS_DENIED then inform server to send 401 response
// if SetStatus is not called then server will respond
// with 500 Server Error
//
pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
Http401BadLogon );
//
// SetErrorStatus() and reset value of hr
//
pMainContext->SetErrorStatus( hr );
hr = NO_ERROR;
}
if ( hAccessTokenImpersonation != NULL )
{
CloseHandle( hAccessTokenImpersonation );
hAccessTokenImpersonation = NULL;
}
if ( pUserContext != NULL )
{
pUserContext->DereferenceUserContext();
pUserContext = NULL;
}
}
return hr;
}
HRESULT
IIS_DIGEST_AUTH_PROVIDER::OnAccessDenied(
IN W3_MAIN_CONTEXT * pMainContext
)
/*++
Description:
Add WWW-Authenticate Digest headers
Arguments:
pMainContext - main context
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL;
DBG_ASSERT( pMainContext != NULL );
//
// 2 providers implement Digest but they are mutually exclusive
// If DigestSSP is enabled then IIS-DIGEST cannot be used
//
if ( g_pW3Server->QueryUseDigestSSP() )
{
//
// Digest SSP is enabled => IIS Digest cannot be used
//
return NO_ERROR;
}
if( W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent == 1 )
{
//
// IIS subauth is not configured correctly on DC
//
return NO_ERROR;
}
if( !W3_STATE_AUTHENTICATION::QueryIsDomainMember() )
{
//
// We are not a domain member, so do nothing
//
return NO_ERROR;
}
pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *)
QueryConnectionAuthContext( pMainContext );
if ( pDigestConnContext == NULL )
{
//
// Create new Authentication context
// it may get reused for next request
// if connection is reused
//
pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT();
if ( pDigestConnContext == NULL )
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
hr = SetConnectionAuthContext( pMainContext,
pDigestConnContext );
if ( FAILED( hr ) )
{
return hr;
}
}
return SetDigestHeader( pMainContext, pDigestConnContext );
}
HRESULT
IIS_DIGEST_AUTH_PROVIDER::SetDigestHeader(
IN W3_MAIN_CONTEXT * pMainContext,
IN IIS_DIGEST_CONN_CONTEXT * pDigestConnContext
)
/*++
Description:
Add WWW-Authenticate Digest headers
Arguments:
pMainContext - main context
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
BOOL fStale = FALSE;
W3_METADATA * pMetaData = NULL;
STACK_STRA( strOutputHeader, MAX_PATH + 1);
STACK_STRA( strNonce, NONCE_SIZE + 1 );
DBG_ASSERT( pMainContext != NULL );
pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
fStale = pDigestConnContext->QueryStale( );
//
// Reset Stale so that it will not be used for next request
//
pDigestConnContext->SetStale( FALSE );
if ( FAILED( hr = pDigestConnContext->GenerateNonce() ) )
{
return hr;
}
//
// If a realm is configured, use it. Otherwise use host address of
// request
//
STACK_STRA( straRealm, IIS_DNLEN + 1 );
STACK_STRU( strHostAddr, 256 );
if ( pMetaData->QueryRealm() != NULL )
{
hr = straRealm.CopyW( pMetaData->QueryRealm() );
}
else
{
hr = pMainContext->QueryRequest()->GetHostAddr( &strHostAddr );
if ( FAILED( hr ) )
{
return hr;
}
hr = straRealm.CopyW( strHostAddr.QueryStr() );
}
if ( FAILED( hr ) )
{
return hr;
}
//
// build WWW-Authenticate header
//
if ( FAILED( hr = strOutputHeader.Copy( "Digest qop=\"auth\", realm=\"" ) ) )
{
return hr;
}
if ( FAILED( hr = strOutputHeader.Append( straRealm ) ) )
{
return hr;
}
if ( FAILED( hr = strOutputHeader.Append( "\", nonce=\"" ) ) )
{
return hr;
}
if ( FAILED( hr = strOutputHeader.Append( pDigestConnContext->QueryNonce() ) ) )
{
return hr;
}
if ( FAILED( hr = strOutputHeader.Append( fStale ? "\", stale=true" : "\"" ) ) )
{
return hr;
}
//
// Add the header WWW-Authenticate to the response
//
hr = pMainContext->QueryResponse()->SetHeader(
"WWW-Authenticate",
16,
strOutputHeader.QueryStr(),
(USHORT)strOutputHeader.QueryCCH()
);
return hr;
}
//static
HRESULT
IIS_DIGEST_AUTH_PROVIDER::GetLanGroupDomainName(
OUT STRA& straDomain
)
/*++
Routine Description:
Tries to retrieve the "LAN group"/domain this machine is a member of.
Arguments:
straDomain - receives current domain name
Returns:
HRESULT
--*/
{
//
// NET_API_STATUS is equivalent to WIN32 errors
//
NET_API_STATUS dwStatus = 0;
NETSETUP_JOIN_STATUS JoinStatus;
LPWSTR pwszDomainInfo = NULL;
HRESULT hr = E_FAIL;
dwStatus = NetGetJoinInformation( NULL,
&pwszDomainInfo,
&JoinStatus );
if( dwStatus == NERR_Success)
{
if ( JoinStatus == NetSetupDomainName )
{
//
// we got a domain
//
DBG_ASSERT( pwszDomainInfo != NULL );
if ( FAILED( hr = straDomain.CopyW( pwszDomainInfo ) ) )
{
goto ExitPoint;
}
}
else
{
//
// Domain information is not available
// (maybe server is member of workgroup)
//
straDomain.Reset();
}
}
else
{
hr = HRESULT_FROM_WIN32( dwStatus );
goto ExitPoint;
}
hr = NO_ERROR;
ExitPoint:
if ( pwszDomainInfo != NULL )
{
NetApiBufferFree( (LPVOID) pwszDomainInfo );
}
return hr;
}
//static
HRESULT
IIS_DIGEST_AUTH_PROVIDER::BreakUserAndDomain(
IN PCHAR pszFullName,
OUT STRA& straDomainName,
OUT STRA& straUserName
)
/*++
Routine Description:
Breaks up the supplied account into a domain and username; if no domain
is specified
in the account, tries to use either domain configured in metabase or
domain the computer
is a part of.
Arguments:
straFullName - account, of the form domain\username or just username
straDomainName - filled in with domain to use for authentication
straUserName - filled in with username on success
Return Value:
HRESULT
--*/
{
PCHAR pszSeparator = NULL;
HRESULT hr = E_FAIL;
if( pszFullName == NULL && pszFullName[0] == '\0' )
{
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
pszSeparator = (PCHAR) _mbschr( (PUCHAR) pszFullName, '\\' );
if ( pszSeparator != NULL )
{
if ( FAILED( hr = straDomainName.Copy ( pszFullName,
DIFF( pszSeparator - pszFullName ) ) ) )
{
return hr;
}
pszFullName = pszSeparator + 1;
}
else
{
straDomainName.Reset();
}
if ( FAILED( hr = straUserName.Copy ( pszFullName ) ) )
{
return hr;
}
//
// If no domain name was specified, try getting the name of the domain
// the computer is a part of
//
if ( straDomainName.IsEmpty() )
{
if ( FAILED( hr = straDomainName.Copy ( QueryComputerDomain() ) ) )
{
return hr;
}
}
return NO_ERROR;
}
//
// class IIS_DIGEST_USER_CONTEXT implementation
//
HANDLE
IIS_DIGEST_USER_CONTEXT::QueryPrimaryToken(
VOID
)
/*++
Routine Description:
Get primary token for this user
Arguments:
None
Return Value:
Token handle
--*/
{
DBG_ASSERT( _hImpersonationToken != NULL );
if ( _hPrimaryToken == NULL )
{
if ( DuplicateTokenEx( _hImpersonationToken,
0,
NULL,
SecurityImpersonation,
TokenPrimary,
&_hPrimaryToken ) )
{
DBG_ASSERT( _hPrimaryToken != NULL );
}
}
return _hPrimaryToken;
}
HRESULT
IIS_DIGEST_USER_CONTEXT::Create(
IN HANDLE hImpersonationToken,
IN PSTR pszUserName
)
/*++
Routine Description:
Create an user context
Arguments:
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
DBG_ASSERT( pszUserName != NULL );
DBG_ASSERT( hImpersonationToken != NULL );
if ( hImpersonationToken == NULL ||
pszUserName == NULL )
{
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
if ( FAILED( hr = _strUserName.CopyA(pszUserName) ) )
{
return hr;
}
//
// IIS_DIGEST_USER_CONTEXT is taking over ownership of
// hImpersonationToken
//
_hImpersonationToken = hImpersonationToken;
return NO_ERROR;
}
//
// Class IIS_DIGEST_CONN_CONTEXT implementation
//
// Initialize static variables
//static
ALLOC_CACHE_HANDLER * IIS_DIGEST_CONN_CONTEXT::sm_pachIISDIGESTConnContext = NULL;
//static
const PCHAR IIS_DIGEST_CONN_CONTEXT::_pszSecret = "IISMD5";
//static
const DWORD IIS_DIGEST_CONN_CONTEXT::_cchSecret = 6;
//static
HCRYPTPROV IIS_DIGEST_CONN_CONTEXT::sm_hCryptProv = NULL;
//static
HRESULT
IIS_DIGEST_CONN_CONTEXT::Initialize(
VOID
)
/*++
Description:
Global IIS_DIGEST_CONN_CONTEXT initialization
Arguments:
None
Return Value:
HRESULT
--*/
{
ALLOC_CACHE_CONFIGURATION acConfig;
HRESULT hr = E_FAIL;
//
// Initialize allocation lookaside
//
acConfig.nConcurrency = 1;
acConfig.nThreshold = 100;
acConfig.cbSize = sizeof( IIS_DIGEST_CONN_CONTEXT );
DBG_ASSERT( sm_pachIISDIGESTConnContext == NULL );
sm_pachIISDIGESTConnContext = new ALLOC_CACHE_HANDLER(
"IIS_DIGEST_CONTEXT",
&acConfig );
if ( sm_pachIISDIGESTConnContext == NULL ||
!sm_pachIISDIGESTConnContext->IsValid() )
{
if( sm_pachIISDIGESTConnContext != NULL )
{
delete sm_pachIISDIGESTConnContext;
sm_pachIISDIGESTConnContext = NULL;
}
hr = HRESULT_FROM_WIN32( GetLastError() );
DBGPRINTF(( DBG_CONTEXT,
"Error initializing sm_pachIISDIGESTSecContext. hr = 0x%x\n",
hr ));
goto Failed;
}
//
// Get a handle to the CSP we'll use for all our hash functions etc
//
if ( !CryptAcquireContext( &sm_hCryptProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
DBGPRINTF((DBG_CONTEXT,
"CryptAcquireContext() failed : 0x%x\n", GetLastError()));
goto Failed;
}
return S_OK;
Failed:
Terminate();
return hr;
}
//static
VOID
IIS_DIGEST_CONN_CONTEXT::Terminate(
VOID
)
/*++
Routine Description:
Destroy globals
Arguments:
None
Return Value:
None
--*/
{
if ( sm_pachIISDIGESTConnContext != NULL )
{
delete sm_pachIISDIGESTConnContext;
sm_pachIISDIGESTConnContext = NULL;
}
if ( sm_hCryptProv != NULL )
{
CryptReleaseContext( sm_hCryptProv,
0 );
sm_hCryptProv = NULL;
}
}
//static
HRESULT
IIS_DIGEST_CONN_CONTEXT::HashData(
IN BUFFER& buffData,
OUT BUFFER& buffHash )
/*++
Routine Description:
Creates MD5 hash of input buffer
Arguments:
buffData - data to hash
buffHash - buffer that receives hash; is assumed to be big enough to
contain MD5 hash
Return Value:
HRESULT
--*/
{
HCRYPTHASH hHash = NULL;
HRESULT hr = E_FAIL;
DWORD cbHash = 0;
DBG_ASSERT( buffHash.QuerySize() >= MD5_HASH_SIZE );
if ( !CryptCreateHash( sm_hCryptProv,
CALG_MD5,
0,
0,
&hHash ) )
{
//DBGPRINTF((DBG_CONTEXT,
// "CryptCreateHash() failed : 0x%x\n", GetLastError()));
hr = HRESULT_FROM_WIN32( GetLastError() );
goto ExitPoint;
}
if ( !CryptHashData( hHash,
(PBYTE) buffData.QueryPtr(),
buffData.QuerySize(),
0 ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto ExitPoint;
}
cbHash = buffHash.QuerySize();
if ( !CryptGetHashParam( hHash,
HP_HASHVAL,
(PBYTE) buffHash.QueryPtr(),
&cbHash,
0 ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto ExitPoint;
}
hr = NO_ERROR;
ExitPoint:
if ( hHash != NULL )
{
CryptDestroyHash( hHash );
}
return hr;
}
//static
BOOL
IIS_DIGEST_CONN_CONTEXT::IsExpiredNonce(
IN STRA& strRequestNonce,
IN STRA& strPresentNonce
)
/*++
Routine Description:
Checks whether nonce is expired or not by looking at the timestamp on the
nonce
that came in with the request and comparing it with the timestamp on the
latest nonce
Arguments:
strRequestNonce - nonce that came in with request
strPresentNonce - latest nonce
Return Value:
TRUE if expired, FALSE if not
--*/
{
//
// Timestamp is after first 2*RANDOM_SIZE bytes of nonce; also, note that
// timestamp is time() mod NONCE_GRANULARITY, so all we have to do is simply
// compare for equality to check that the request nonce hasn't expired
//
DBG_ASSERT( strRequestNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE );
DBG_ASSERT( strPresentNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE );
if ( memcmp( strRequestNonce.QueryStr() + 2*RANDOM_SIZE,
strPresentNonce.QueryStr() + 2*RANDOM_SIZE,
TIMESTAMP_SIZE ) != 0 )
{
return TRUE;
}
return FALSE;
}
//static
BOOL
IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce(
IN STRA& strNonce
)
/*++
Routine Description:
Checks whether a nonce is "well-formed" by checking hash value, length etc
Arguments:
pszNonce - nonce to be checked
Return Value:
TRUE if nonce is well-formed, FALSE if not
--*/
{
if ( strNonce.QueryCCH()!= NONCE_SIZE )
{
return FALSE;
}
//
// Format of nonce : <random bytes><time stamp><hash of (secret,random bytes,time stamp)>
//
STACK_BUFFER( buffBuffer, 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
STACK_BUFFER( buffHash, MD5_HASH_SIZE );
STACK_STRA( strAsciiHash, 2*MD5_HASH_SIZE + 1 );
memcpy( buffBuffer.QueryPtr(),
_pszSecret,
_cchSecret );
memcpy( (PBYTE) buffBuffer.QueryPtr() + _cchSecret,
strNonce.QueryStr(),
2*RANDOM_SIZE + TIMESTAMP_SIZE );
if ( FAILED( HashData( buffBuffer,
buffHash ) ) )
{
return FALSE;
}
ToHex( buffHash,
strAsciiHash );
if ( memcmp( strAsciiHash.QueryStr(),
strNonce.QueryStr() + 2*RANDOM_SIZE + TIMESTAMP_SIZE,
2*MD5_HASH_SIZE ) != 0)
{
return FALSE;
}
return TRUE;
}
HRESULT
IIS_DIGEST_CONN_CONTEXT::GenerateNonce(
VOID
)
/*++
Routine Description:
Generate nonce to be stored in user filter context. Nonce is
<ASCII rep of Random><Time><ASCII of MD5(Secret:Random:Time)>
Random = <8 random bytes>
Time = <16 bytes, reverse string rep of result of time() call>
Secret = 'IISMD5'
Arguments:
none
Return Value:
HRESULT
--*/
{
HRESULT hr = E_FAIL;
DWORD tNow = (DWORD) ( time( NULL ) / NONCE_GRANULARITY );
//
// If nonce has timed out, generate a new one
//
if ( _tLastNonce < tNow )
{
STACK_BUFFER( buffTempBuffer, 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
STACK_BUFFER( buffDigest, MD5_HASH_SIZE );
STACK_BUFFER( buffRandom, RANDOM_SIZE );
STACK_STRA( strTimeStamp, TIMESTAMP_SIZE + 1 );
STACK_STRA( strAsciiDigest, 2*MD5_HASH_SIZE + 1 );
STACK_STRA( strAsciiRandom, 2*RANDOM_SIZE + 1);
DWORD cbTimeStamp = 0;
PSTR pszTimeStamp = NULL;
_tLastNonce = tNow;
//
// First, random bytes
//
if ( !CryptGenRandom( sm_hCryptProv,
RANDOM_SIZE,
(PBYTE) buffRandom.QueryPtr() ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto ExitPoint;
}
//
// Convert to ASCII, doubling the length, and add to nonce
//
ToHex( buffRandom,
strAsciiRandom );
if ( FAILED( hr = _straNonce.Copy( strAsciiRandom ) ) )
{
goto ExitPoint;
}
//
// Next, reverse string representation of current time; pad with zeros if necessary
//
pszTimeStamp = strTimeStamp.QueryStr();
DBG_ASSERT( pszTimeStamp != NULL );
while ( tNow != 0 )
{
*(pszTimeStamp++) = (BYTE)( '0' + tNow % 10 );
cbTimeStamp++;
tNow /= 10;
}
DBG_ASSERT( cbTimeStamp <= TIMESTAMP_SIZE );
//
// pad with zeros if necessary
//
while ( cbTimeStamp < TIMESTAMP_SIZE )
{
*(pszTimeStamp++) = '0';
cbTimeStamp++;
}
//
// terminate the timestamp
//
*(pszTimeStamp) = '\0';
DBG_REQUIRE( strTimeStamp.SetLen( cbTimeStamp ) );
//
// Append TimeStamp to Nonce
//
if ( FAILED( hr = _straNonce.Append( strTimeStamp ) ) )
{
goto ExitPoint;
}
//
// Now hash everything, together with a private key ( IISMD5 )
//
memcpy( buffTempBuffer.QueryPtr(),
_pszSecret,
_cchSecret );
memcpy( (PBYTE) buffTempBuffer.QueryPtr() + _cchSecret,
_straNonce.QueryStr(),
2*RANDOM_SIZE + TIMESTAMP_SIZE );
DBG_ASSERT( buffTempBuffer.QuerySize() == 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
if ( FAILED( hr = HashData( buffTempBuffer,
buffDigest ) ) )
{
goto ExitPoint;
}
//
// Convert to ASCII, doubling the length
//
DBG_ASSERT( buffDigest.QuerySize() == MD5_HASH_SIZE );
ToHex( buffDigest,
strAsciiDigest );
//
// Add hash to nonce
//
if ( FAILED( hr = _straNonce.Append( strAsciiDigest ) ) )
{
goto ExitPoint;
}
}
hr = NO_ERROR;
ExitPoint:
return hr;
}
//static
BOOL
IIS_DIGEST_CONN_CONTEXT::ParseForName(
IN OUT PSTR pszStr,
IN PSTR * pNameTable,
IN UINT cNameTable,
OUT PSTR * pValueTable
)
/*++
Routine Description:
Parse list of name=value pairs for known names
Note: pszStr is modified upon return. This function doesn't
copy names and values but instead adds string terminators into
the pszStr string after each name and value found
Arguments:
pszStr - line to parse ( '\0' delimited )
pNameTable - table of known names
cNameTable - number of known names
pValueTable - updated with ptr to parsed value for corresponding name
Return Value:
TRUE if success, FALSE if error
--*/
{
BOOL fStatus = TRUE;
PSTR pszBeginName = NULL;
PSTR pszEndName = NULL;
PSTR pszBeginVal = NULL;
PSTR pszEndVal = NULL;
UINT i;
DBG_ASSERT( pszStr!= NULL );
for ( i = 0 ; i < cNameTable ; ++i )
{
pValueTable[i] = NULL;
}
while ( *pszStr != '\0' && fStatus )
{
pszStr = SkipWhite( pszStr );
//
// Got to the beggining of the value name
//
pszBeginName = pszStr;
//
// Seek for the end of the value name
//
for ( pszEndName = pszStr;
*pszEndName != '\0' &&
*pszEndName != '=' &&
*pszEndName != ' ';
pszEndName++ )
{
}
//
// The end of the name was found
// time to process the value associated with name
//
if ( *pszEndName != '\0' )
{
//
// terminate the name string
//
//
*pszEndName = '\0';
pszEndVal = NULL;
//
// process values that require special handling
//
//
// Process the value for NC
//
if ( _stricmp( pszBeginName, MD5_AUTH_NAMES[ MD5_AUTH_NC ] ) == 0 )
{
for ( pszBeginVal = ++pszEndName ;
(*pszBeginVal != '\0') && !SAFEIsXDigit( (UCHAR)*pszBeginVal );
++pszBeginVal )
{
}
if ( *pszBeginVal != '\0' )
{
//
// Find the end of the value
//
for ( pszEndVal = pszBeginVal;
*pszEndVal != '\0' ;
++pszEndVal )
{
if ( *pszEndVal == ' ' ||
*pszEndVal == ',' )
{
break;
}
}
if ( pszEndVal - pszBeginVal != SIZE_OF_NC )
{
//
// value in error is ignored
//
pszEndVal = NULL;
}
}
}
else
{
if ( _stricmp( pszBeginName, MD5_AUTH_NAMES[ MD5_AUTH_QOP ] ) == 0 )
{
BOOL fQuotedQop = FALSE;
//
// move to the begining of the qop value
//
for( pszBeginVal = ++pszEndName;
( *pszBeginVal != '\0' ) &&
( *pszBeginVal == '=' || *pszBeginVal == ' ' );
++pszBeginVal )
{
}
//
// Check if value starts with qoutes
//
if ( *pszBeginVal == '"' )
{
++pszBeginVal;
fQuotedQop = TRUE;
}
//
// Find the end of the value
//
for ( pszEndVal = pszBeginVal;
*pszEndVal != '\0' ;
++pszEndVal )
{
if ( *pszEndVal == '"' || *pszEndVal == ' ' ||
*pszEndVal == ',' )
{
break;
}
}
//
// If value started with quotes then it has
// to end with qoutes
//
if ( *pszEndVal != '"' && fQuotedQop )
{
pszEndVal = NULL;
}
}
else if( _stricmp( pszBeginName, MD5_AUTH_NAMES[ MD5_AUTH_ALGORITHM ] ) == 0 )
{
BOOL fQuotedAlgorithm = FALSE;
//
// move to the begining of the algorithm value
//
for( pszBeginVal = ++pszEndName;
( *pszBeginVal != '\0' ) &&
( *pszBeginVal == '=' || *pszBeginVal == ' ' );
++pszBeginVal )
{
}
//
// Check if value starts with qoutes
//
if ( *pszBeginVal == '"' )
{
++pszBeginVal;
fQuotedAlgorithm = TRUE;
}
//
// Find the end of the value
//
for ( pszEndVal = pszBeginVal;
*pszEndVal != '\0' ;
++pszEndVal )
{
if ( *pszEndVal == '"' || *pszEndVal == ' ' ||
*pszEndVal == ',' )
{
break;
}
}
//
// If value started with quotes then it has
// to end with qoutes
//
if ( *pszEndVal != '"' && fQuotedAlgorithm )
{
pszEndVal = NULL;
}
}
else
{
//
// handle the rest of the values
// (these are expected to be always
// enclosed in double quotes)
//
//
// Search for the opening double quote
//
for ( pszBeginVal = ++pszEndName ;
*pszBeginVal != '\0' && *pszBeginVal != '"' ;
++pszBeginVal )
{
}
//
// Found opening double quote
//
if ( *pszBeginVal == '"' )
{
for ( pszEndVal = ++pszBeginVal ;
*pszEndVal != '\0' ;
++pszEndVal )
{
if ( *pszEndVal == '"' )
{
//
// Found the closing double quote
//
break;
}
}
if ( *pszEndVal != '"' )
{
pszEndVal = NULL;
}
}
}
}
//
// The end of the value was reached
// check if correct value was found
//
if ( pszEndVal != NULL )
{
//
// Find the value name in the name table
//
for ( i = 0 ; i < cNameTable ; ++i )
{
if ( _stricmp( pNameTable[ i ], pszBeginName ) == 0 )
{
break;
}
}
//
// If the name was found in the table than make proper assignment
// to value table, otherwise ignore the value
//
if ( i < cNameTable )
{
//
// assign value to the appropriate index
//
pValueTable[ i ] = pszBeginVal;
}
//
// terminate the value with '\0'
//
if ( *pszEndVal != '\0' )
{
*pszEndVal = '\0';
pszEndVal++;
}
// move the pszStr to point past the value that was just parsed
pszStr = pszEndVal;
continue;
}
}
fStatus = FALSE;
}
return fStatus;
}