/*++
   Copyright    (c)    1999    Microsoft Corporation

   Module Name :
     sspiprovider.cxx

   Abstract:
     SSPI authentication provider
 
   Author:
     Bilal Alam (balam)             10-Jan-2000

   Environment:
     Win32 - User Mode

   Project:
     ULW3.DLL
--*/

#include "precomp.hxx"
#include "sspiprovider.hxx"
#include "uuencode.hxx"

ALLOC_CACHE_HANDLER * SSPI_SECURITY_CONTEXT::sm_pachSSPISecContext = NULL;

CRITICAL_SECTION     SSPI_CREDENTIAL::sm_csCredentials;
LIST_ENTRY           SSPI_CREDENTIAL::sm_CredentialListHead;

//static
HRESULT
SSPI_CREDENTIAL::Initialize(
    VOID
)
/*++

  Description:

    Credential cache initialization

  Arguments:

    None

  Returns:

    HRESULT

--*/
{
    InitializeListHead( &sm_CredentialListHead );
    INITIALIZE_CRITICAL_SECTION( &sm_csCredentials );
    return NO_ERROR;
}

//static
VOID
SSPI_CREDENTIAL::Terminate(
    VOID
)
/*++

  Description:

    Credential cache cleanup

  Arguments:

    None

  Returns:

    None

--*/
{
    SSPI_CREDENTIAL *           pCred = NULL;
    
    EnterCriticalSection( &sm_csCredentials );

    while ( !IsListEmpty( &sm_CredentialListHead ))
    {
        pCred = CONTAINING_RECORD( sm_CredentialListHead.Flink,
                                   SSPI_CREDENTIAL,
                                   m_ListEntry );

        RemoveEntryList( &pCred->m_ListEntry );
        
        pCred->m_ListEntry.Flink = NULL;

        delete pCred;
    }

    LeaveCriticalSection( &sm_csCredentials );

    DeleteCriticalSection( &sm_csCredentials );
}    

//static
HRESULT
SSPI_CREDENTIAL::GetCredential(
    CHAR *              pszPackage, 
    SSPI_CREDENTIAL **  ppCredential
)
/*++

  Description:

    Get SSPI credential handle from cache. If it does not exist 
    for the SSPI package, generates a new cache entry and adds 
    it to the credential cache

  Arguments:

    pszPackage      - SSPI package name, e.g NTLM
    ppCredential    - Set to cached credential if found

  Returns:

    HRESULT

--*/
{
    LIST_ENTRY *                pEntry;
    SSPI_CREDENTIAL *           pCred;
    SecPkgInfoA *               pSecPkg;
    TimeStamp                   LifeTime;
    SECURITY_STATUS             ss;
    HRESULT                     hr = S_OK;

    if ( pszPackage == NULL ||
         ppCredential == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    *ppCredential = NULL;

    EnterCriticalSection( &sm_csCredentials );

    for ( pEntry  = sm_CredentialListHead.Flink;
          pEntry != &sm_CredentialListHead;
          pEntry  = pEntry->Flink )
    {
        pCred = CONTAINING_RECORD( pEntry, 
                                   SSPI_CREDENTIAL, 
                                   m_ListEntry );

        if ( !strcmp( pszPackage, pCred->m_strPackageName.QueryStr() ) )
        {
            // 
            // Since we only need to read the credential info at this
            // point, leave the critical section first.
            // 
            
            LeaveCriticalSection( &sm_csCredentials );

            *ppCredential = pCred;
            return NO_ERROR;
        }
    }

    if ( ( pCred = new SSPI_CREDENTIAL ) == NULL )
    {
        LeaveCriticalSection( &sm_csCredentials );
        
        hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); 

        return hr;
    }

    hr = pCred->m_strPackageName.Copy( pszPackage );
    if ( FAILED( hr ) )
    {
        LeaveCriticalSection( &sm_csCredentials );

        delete pCred;
        pCred = NULL;

        return hr;
    }

    ss = AcquireCredentialsHandleA( NULL,             
                                    pszPackage,       
                                    SECPKG_CRED_INBOUND,
                                    NULL,             
                                    NULL,    
                                    NULL,             
                                    NULL,             
                                    &pCred->m_hCredHandle,
                                    &LifeTime );
    if ( ss != STATUS_SUCCESS )
    {
        LeaveCriticalSection( &sm_csCredentials );

        hr = HRESULT_FROM_WIN32( ss );

        DBGPRINTF(( DBG_CONTEXT,
                   "Error acquiring credential handle, hr = %x\n",
                    hr ));

        delete pCred;
        pCred = NULL;

        return hr;
    }

    //
    //  Need to determine the max token size for this package
    //
    ss = QuerySecurityPackageInfoA( pszPackage,
                                    &pSecPkg );
    if ( ss != STATUS_SUCCESS )
    {
        LeaveCriticalSection( &sm_csCredentials );

        hr = HRESULT_FROM_WIN32( ss );

        DBGPRINTF(( DBG_CONTEXT,
                   "Error querying security package info, hr = %x\n",
                    hr ));

        delete pCred;
        pCred = NULL;

        return hr;
    }

    pCred->m_cbMaxTokenLen = pSecPkg->cbMaxToken;
    pCred->m_fSupportsEncoding = !(pSecPkg->fCapabilities & SECPKG_FLAG_ASCII_BUFFERS); 

    //
    // Insert the credential handle to the list for future use
    //
    
    InsertHeadList( &sm_CredentialListHead, &pCred->m_ListEntry );

    LeaveCriticalSection( &sm_csCredentials );

    *ppCredential = pCred;

    FreeContextBuffer( pSecPkg );

    return hr;
}

HRESULT
SSPI_AUTH_PROVIDER::Initialize(
    DWORD dwInternalId
)
/*++

Routine Description:

    Initialize SSPI provider 

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr;

    SetInternalId( dwInternalId );
    hr = SSPI_SECURITY_CONTEXT::Initialize();
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    hr = SSPI_CREDENTIAL::Initialize();
    if ( FAILED( hr ) )
    {
        SSPI_SECURITY_CONTEXT::Terminate();
        return hr;
    }
    
    return NO_ERROR;
}

VOID
SSPI_AUTH_PROVIDER::Terminate(
    VOID
)
/*++

Routine Description:

    Terminate SSPI provider

Arguments:

    None
    
Return Value:

    None

--*/
{
    SSPI_CREDENTIAL::Terminate();
    SSPI_SECURITY_CONTEXT::Terminate();
}

HRESULT
SSPI_AUTH_PROVIDER::DoesApply(
    W3_MAIN_CONTEXT *           pMainContext,
    BOOL *                      pfApplies
)
/*++

Routine Description:

    Does the given request have credentials applicable to the SSPI 
    provider

Arguments:

    pMainContext - Main context representing request
    pfApplies - Set to true if SSPI is applicable
    
    
Return Value:

    HRESULT

--*/
{
    HRESULT                 hr;
    W3_METADATA *           pMetaData;
    SSPI_CONTEXT_STATE *    pContextState;
    STACK_STRA(             strPackage, 64 );
    CHAR *                  pszAuthHeader;
    
    if ( pMainContext == NULL ||
         pfApplies == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    *pfApplies = FALSE;

    //
    // Get the package name
    //
    
    hr = pMainContext->QueryRequest()->GetAuthType( &strPackage );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // No package, then this doesn't apply
    //
    
    if ( strPackage.IsEmpty() )
    {
        return NO_ERROR;
    }
    
    //
    // Check metabase for whether SSPI package is supported
    //

    pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
    DBG_ASSERT( pMetaData != NULL );

    if ( pMetaData->CheckAuthProvider( strPackage.QueryStr() ) )
    {
        pszAuthHeader = pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization );
        DBG_ASSERT( pszAuthHeader != NULL );
        
        //
        // Save away the package so we don't have to calc again
        //
        
        DBG_ASSERT( !strPackage.IsEmpty() );
    
        pContextState = new (pMainContext) SSPI_CONTEXT_STATE( 
                                pszAuthHeader + strPackage.QueryCCH() + 1 );
        if ( pContextState == NULL )
        {
            return HRESULT_FROM_WIN32( GetLastError() );
        }

        hr = pContextState->SetPackage( strPackage.QueryStr() );
        if ( FAILED( hr ) )
        {
            delete pContextState;
            return hr;
        }

        pMainContext->SetContextState( pContextState );

        *pfApplies = TRUE;
    }

    return NO_ERROR;
}

HRESULT
SSPI_AUTH_PROVIDER::DoAuthenticate(
    W3_MAIN_CONTEXT *       pMainContext
)
/*++

Description:

    Do authentication work (we will be called if we apply)

Arguments:

    pMainContext - Main context
    
Return Value:

    HRESULT

--*/
{
    SSPI_CONTEXT_STATE *        pContextState = NULL;
    W3_METADATA *               pMetaData = NULL;
    SSPI_SECURITY_CONTEXT *     pSecurityContext = NULL;
    SECURITY_STATUS             ss;
    TimeStamp                   Lifetime;
    SecBufferDesc               OutBuffDesc;
    SecBuffer                   OutSecBuff;
    SecBufferDesc               InBuffDesc;
    SecBuffer                   InSecBuff;
    ULONG                       ContextAttributes;
    SSPI_CREDENTIAL *           pCredentials = NULL;
    HRESULT                     hr;
    STACK_BUFFER              ( buffDecoded, 256 );
    CHAR *                      pszFinalBlob = NULL;
    DWORD                       cbFinalBlob;
    CtxtHandle                  hCtxtHandle;
    BOOL                        fNeedContinue = FALSE;
    SSPI_USER_CONTEXT *         pUserContext;
    BUFFER                      buffResponse;
    BOOL                        fNewConversation = TRUE;
    DWORD                       err;
    
    if ( pMainContext == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    pContextState = (SSPI_CONTEXT_STATE*) pMainContext->QueryContextState();
    DBG_ASSERT( pContextState != NULL );
    
    pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
    DBG_ASSERT( pMetaData != NULL );
    
    //
    // If we got to here, then the package better be supported!
    //
    
    DBG_ASSERT( pMetaData->CheckAuthProvider( pContextState->QueryPackage() ) );
    
    //
    // Are we in the middle of a handshake?
    //
    
    pSecurityContext = 
         ( SSPI_SECURITY_CONTEXT * )  QueryConnectionAuthContext( pMainContext );

    //
    // If the security context indicates we are complete already, then
    // cleanup that context before proceeding to create a new one.  
    //
    
    if ( pSecurityContext != NULL &&
         pSecurityContext->QueryIsComplete() )
    {
        SetConnectionAuthContext( pMainContext,
                                  NULL );
        pSecurityContext = NULL;
    }

    if ( pSecurityContext != NULL )
    {
        DBG_ASSERT( pSecurityContext->CheckSignature() );

        pCredentials = pSecurityContext->QueryCredentials();

        fNewConversation = FALSE;
    }    
    else
    {
        //
        // Nope.  Need to create a new SSPI_SECURITY_CONTEXT and find 
        // credentials for this package
        //
        
        hr = SSPI_CREDENTIAL::GetCredential( pContextState->QueryPackage(),
                                             &pCredentials );
        
        if ( FAILED( hr ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "Error get credential handle. hr = 0x%x \n",
                      hr ));
            
            goto Failure;
        }
        
        pSecurityContext = new SSPI_SECURITY_CONTEXT( pCredentials );
        if ( pSecurityContext == NULL )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            goto Failure;
        }
        
        hr = SetConnectionAuthContext( pMainContext,
                                       pSecurityContext );
        if ( FAILED( hr ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "Failed to set Connection Auth Context. hr = 0x%x \n",
                      hr ));
            
            goto Failure;
        }

    }
    
    DBG_ASSERT( pCredentials != NULL );
    DBG_ASSERT( pSecurityContext != NULL );

    //
    // Process credential blob.
    //
    
    //
    // Should we uudecode this buffer?
    //
    
    if ( pCredentials->QuerySupportsEncoding() )
    {
        if ( !uudecode( pContextState->QueryCredentials(),
                        &buffDecoded,
                        &cbFinalBlob ) )
        {
            pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
                                                      Http401BadLogon );
            
            return NO_ERROR;
        }
        
        pszFinalBlob = (CHAR*) buffDecoded.QueryPtr();
    }
    else
    {
        pszFinalBlob = pContextState->QueryCredentials();
        cbFinalBlob = strlen(pContextState->QueryCredentials()) + 1;
    }

    //
    // Setup the response blob buffer 
    // 
    
    if ( !buffResponse.Resize( pCredentials->QueryMaxTokenSize() ) )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Failure;
    }                                  

    //
    // Setup the call to AcceptSecurityContext()
    //
    
    OutBuffDesc.ulVersion = 0;
    OutBuffDesc.cBuffers  = 1;
    OutBuffDesc.pBuffers  = &OutSecBuff;

    OutSecBuff.cbBuffer   = pCredentials->QueryMaxTokenSize();
    OutSecBuff.BufferType = SECBUFFER_TOKEN;
    OutSecBuff.pvBuffer   = buffResponse.QueryPtr();

    InBuffDesc.ulVersion = 0;
    InBuffDesc.cBuffers  = 1;
    InBuffDesc.pBuffers  = &InSecBuff;

    InSecBuff.cbBuffer   = cbFinalBlob;
    InSecBuff.BufferType = SECBUFFER_TOKEN;
    InSecBuff.pvBuffer   = pszFinalBlob;

    //
    // Let'r rip!
    //

    //
    // Set required context attributes ASC_REQ_EXTENDED_ERROR, this 
    // allows Negotiate/Kerberos to support time-skew recovery.  
    //

    ss = AcceptSecurityContext( pCredentials->QueryCredHandle(),
                                fNewConversation ? 
                                NULL :
                                pSecurityContext->QueryContextHandle(),
                                &InBuffDesc,
                                ASC_REQ_EXTENDED_ERROR,
                                SECURITY_NATIVE_DREP,
                                &hCtxtHandle,
                                &OutBuffDesc,
                                &ContextAttributes,
                                &Lifetime );

    if ( !NT_SUCCESS( ss ) )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "AcceptSecurityContext failed, error %x\n",
                    ss ));

        if ( ss == SEC_E_LOGON_DENIED || 
             ss == SEC_E_INVALID_TOKEN )
        {
            err = GetLastError();
            if( err == ERROR_PASSWORD_MUST_CHANGE ||
                err == ERROR_PASSWORD_EXPIRED )
            {
                return HRESULT_FROM_WIN32( err );
            }

            //
            // Could not logon the user because of wrong credentials
            //
            
            pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
                                                      Http401BadLogon );
                                                      
            pMainContext->SetErrorStatus( ss );
            hr = NO_ERROR;
        }
        else
        {
            hr = ss;
        }
        
        goto Failure;
    }

    pSecurityContext->SetContextHandle( hCtxtHandle );
    pSecurityContext->SetContextAttributes( ContextAttributes );

    if ( ss == SEC_I_CONTINUE_NEEDED ||
         ss == SEC_I_COMPLETE_AND_CONTINUE )
    {
        fNeedContinue = TRUE;
    }
    else if ( ( ss == SEC_I_COMPLETE_NEEDED ) ||
              ( ss == SEC_I_COMPLETE_AND_CONTINUE ) )
    {
        //
        // Now we just need to complete the token (if requested) and 
        // prepare it for shipping to the other side if needed
        //
        
        ss = CompleteAuthToken( &hCtxtHandle,
                                &OutBuffDesc );

        if ( !NT_SUCCESS( ss ))
        {
            hr = HRESULT_FROM_WIN32( ss );

            DBGPRINTF(( DBG_CONTEXT,
                       "Error on CompleteAuthToken, hr = 0x%x\n",
                        hr ));

            goto Failure;
        }
    }

    //
    // Format or copy to the output buffer if we need to reply
    //
    
    if ( OutSecBuff.cbBuffer != 0 && fNeedContinue )
    {
        STACK_BUFFER( buffAuthData, 256 );
        
        hr = pContextState->QueryResponseHeader()->Copy( 
                        pContextState->QueryPackage() );
        if( FAILED( hr ) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "Error copying auth type, hr = 0x%x.\n",
                       hr ));
            
            goto Failure;
        }

        hr = pContextState->QueryResponseHeader()->Append( " ", 1 );
        if( FAILED( hr ) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                       "Error copying auth header, hr = 0x%x.\n",
                       hr ));

            goto Failure;
        }

        DBG_ASSERT( pCredentials != NULL );

        if ( pCredentials->QuerySupportsEncoding() )
        {
            if ( !uuencode( (BYTE *) OutSecBuff.pvBuffer,
                            (DWORD) OutSecBuff.cbBuffer,
                            &buffAuthData ) )
            {
                DBGPRINTF(( DBG_CONTEXT,
                           "Error uuencoding the output buffer.\n"
                         ));

                hr = HRESULT_FROM_WIN32( GetLastError() );
                goto Failure;
            }

            pszFinalBlob = (CHAR *)buffAuthData.QueryPtr();
        }
        else
        {
            pszFinalBlob = (CHAR *)OutSecBuff.pvBuffer;
        }
        
        hr = pContextState->QueryResponseHeader()->Append( pszFinalBlob );
        if( FAILED( hr ) )
        {
            DBGPRINTF(( DBG_CONTEXT,
                        "Error appending resp header, hr = 0x%x.\n",
                        hr ));

            goto Failure;
        }
        
        //
        // Add the WWW-Authenticate header
        //
        
        hr = pMainContext->QueryResponse()->SetHeader(
                          "WWW-Authenticate",
                          16, // number of chars in above string
                          pContextState->QueryResponseHeader()->QueryStr(),
                          pContextState->QueryResponseHeader()->QueryCCH() );
       
        if ( FAILED( hr ) )
        {
            goto Failure;
        }
        
        //
        // Don't let anyone else send back authentication headers when
        // the 401 is sent
        //
        
        pMainContext->SetProviderHandled( TRUE );
    }   
        
    if ( !fNeedContinue )
    {
        //
        // Create a user context and setup it up
        //
        
        pUserContext = new SSPI_USER_CONTEXT( this );
        if ( pUserContext == NULL )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            goto Failure;
        } 
        
        hr = pUserContext->Create( pSecurityContext,
                                   pMainContext );
        if ( FAILED( hr ) )
        {
            pUserContext->DereferenceUserContext();
            pUserContext = NULL;
            goto Failure;
        }
        
        pMainContext->SetUserContext( pUserContext );
        
        //
        // Mark the security context is complete, so we can detect 
        // reauthentication on the same connection
        //
        // CODEWORK:  Can probably get away will just un-associating/deleting
        //            the SSPI_SECURITY_CONTEXT now!
        //
        
        pSecurityContext->SetIsComplete( TRUE );
    }
    else
    {
        //
        // 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();
    }
    
    return NO_ERROR;

Failure:
    if ( pSecurityContext != NULL )
    {
         SetConnectionAuthContext( pMainContext,
                                   NULL );
         pSecurityContext = NULL;
    }

    return hr;
}

HRESULT
SSPI_AUTH_PROVIDER::OnAccessDenied(
    W3_MAIN_CONTEXT *       pMainContext
)
/*++

  Description:
    
    Add WWW-Authenticate headers

Arguments:

    pMainContext - main context
    
Return Value:

    HRESULT

--*/
{
    MULTISZA *              pProviders;
    W3_METADATA *           pMetaData;
    const CHAR *            pszProvider;
    HRESULT                 hr;
    
    if ( pMainContext == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
    DBG_ASSERT( pMetaData != NULL );
    
    pProviders = pMetaData->QueryAuthProviders();
    if ( pProviders != NULL )
    {
        pszProvider = pProviders->First();
        while ( pszProvider != NULL )
        {
            hr = pMainContext->QueryResponse()->SetHeader(
                                            "WWW-Authenticate",
                                            16,
                                            (CHAR *)pszProvider,
                                            strlen(pszProvider) );
            if ( FAILED( hr ) )
            {
                return hr;
            }

            pszProvider = pProviders->Next( pszProvider );
        }
    }
    
    return NO_ERROR;
}

//static
HRESULT
SSPI_SECURITY_CONTEXT::Initialize(
    VOID
)
/*++

  Description:
    
    Global SSPI_SECURITY_CONTEXT initialization

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    ALLOC_CACHE_CONFIGURATION       acConfig;

    //
    // Initialize allocation lookaside
    //
    
    acConfig.nConcurrency = 1;
    acConfig.nThreshold = 100;
    acConfig.cbSize = sizeof( SSPI_SECURITY_CONTEXT );

    DBG_ASSERT( sm_pachSSPISecContext == NULL );
    
    sm_pachSSPISecContext = new ALLOC_CACHE_HANDLER( 
                                     "SSPI_SECURITY_CONTEXT",  
                                     &acConfig );

    if ( sm_pachSSPISecContext == NULL )
    {
        HRESULT hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );

        DBGPRINTF(( DBG_CONTEXT,
               "Error initializing sm_pachSSPISecContext. hr = 0x%x\n",
               hr ));

        return hr;
    }
    
    return S_OK;

} // SSPI_SECURITY_CONTEXT::Initialize

//static
VOID
SSPI_SECURITY_CONTEXT::Terminate(
    VOID
)
/*++

Routine Description:

    Destroy SSPI_SECURITY_CONTEXT globals

Arguments:

    None
    
Return Value:

    None

--*/
{
    DBG_ASSERT( sm_pachSSPISecContext != NULL );

    delete sm_pachSSPISecContext;
    sm_pachSSPISecContext = NULL;

}

HRESULT
SSPI_USER_CONTEXT::Create(
    SSPI_SECURITY_CONTEXT *         pSecurityContext,
    W3_MAIN_CONTEXT *               pMainContext
)
/*++

Routine Description:

    Create an SSPI user context

Arguments:

    pSecurityContext - container of important SSPI handles
    
Return Value:

    HRESULT

--*/
{
    SECURITY_STATUS             ss;
    HANDLE                      hImpersonationToken;
    HRESULT                     hr;
    SecPkgContext_Names         CredNames;


    if ( pSecurityContext == NULL || 
         pMainContext == NULL )    
    {
        DBG_ASSERT( pSecurityContext != NULL );
        DBG_ASSERT( pMainContext != NULL );    

        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    //
    // Get the token
    // 
    
    ss = QuerySecurityContextToken( pSecurityContext->QueryContextHandle(), 
                                    &_hImpersonationToken );
    if ( ss == SEC_E_INVALID_HANDLE )
    {
        hr = ss;
            
        DBGPRINTF(( DBG_CONTEXT,
                   "Error QuerySecurityContextToken, hr = 0x%x.\n",
                   ss ));

        return hr;
    }

    //
    // Disable SeBackupPrivilege for impersonation token to get rid of the 
    // problem introduced by using FILE_FLAG_BACKUP_SEMANTICS in CreateFileW 
    // call in W3_FILE_INFO::OpenFile.
    //
    if ( W3_STATE_AUTHENTICATION::sm_pTokenPrivilege != NULL )
    {
        AdjustTokenPrivileges( 
                   _hImpersonationToken,
                   FALSE,
                   W3_STATE_AUTHENTICATION::sm_pTokenPrivilege,
                   NULL,
                   NULL,
                   NULL );
    }
        
    //
    // Next, the user name
    //
        
    ss = QueryContextAttributes( pSecurityContext->QueryContextHandle(),
                                 SECPKG_ATTR_NAMES,
                                 &CredNames );
    if ( !NT_SUCCESS( ss ) )
    {
        hr = ss;
          
        DBGPRINTF(( DBG_CONTEXT,
                    "QueryContextAttributes() failed with ss = 0x%x.\n",
                    ss ));

        return hr;
    }         
    else
    {
        //
        // Digest SSP may have a bug in it since the user name returned
        // is NULL, workaround here
        //

        if( CredNames.sUserName )
        {
            hr = _strUserName.Copy( CredNames.sUserName );
            FreeContextBuffer( CredNames.sUserName );
            if ( FAILED( hr ) )
            {
                return hr;
            }
        }
    }
    
    //
    // Get the package name
    //

    hr = _strPackageName.Copy( *(pSecurityContext->QueryCredentials()->QueryPackageName()));
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    //
    // Is this token delegatable?
    //
    
    _fDelegatable = !!(pSecurityContext->QueryContextAttributes() & ASC_RET_DELEGATE);

    //
    // If password expiration notification is enabled
    // and Url is configured properly
    // then save expiration info
    //
    
    if( pMainContext->QuerySite()->IsAuthPwdChangeNotificationEnabled() && 
        pMainContext->QuerySite()->QueryAdvNotPwdExpUrl() != NULL )
    {
        SecPkgContext_PasswordExpiry   speExpiry;
        ss = QueryContextAttributes( 
                         pSecurityContext->QueryContextHandle(),
                         SECPKG_ATTR_PASSWORD_EXPIRY,
                         &speExpiry );

        if ( ss == STATUS_SUCCESS )
        {
            memcpy( &_AccountPwdExpiry,
                &speExpiry.tsPasswordExpires,
                sizeof(speExpiry.tsPasswordExpires) );
            _fSetAccountPwdExpiry = TRUE;
        }
    }
    
    //
    // Save a pointer to the security context
    //
    
    _pSecurityContext = pSecurityContext;
    
    return NO_ERROR;
}

HANDLE
SSPI_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,
                               TOKEN_ALL_ACCESS,
                               NULL,
                               SecurityImpersonation,
                               TokenPrimary,
                               &_hPrimaryToken ) )
        {
            DBG_ASSERT( _hPrimaryToken != NULL );
        }
    }
    
    return _hPrimaryToken;
}

LARGE_INTEGER *
SSPI_USER_CONTEXT::QueryExpiry(
    VOID
) 
/*++

Routine Description:

    User account expiry information
    
Arguments:

    None

Return Value:

    LARGE_INTEGER
    
--*/
{

    if ( _fSetAccountPwdExpiry )
    {
        return &_AccountPwdExpiry;
    }
    return NULL;
}