//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File:        credmgr.cxx
//
// Contents:    Code for managing credentials list for the Kerberos package
//
//
// History:     17-April-1996   Created         MikeSw
//              26-Sep-1998   ChandanS
//                            Added more debugging support etc.
//
//------------------------------------------------------------------------

#include <kerb.hxx>
#define CREDMGR_ALLOCATE
#include <kerbp.h>
#ifdef RETAIL_LOG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__);
#endif


//+-------------------------------------------------------------------------
//
//  Function:   KerbInitCredentialList
//
//  Synopsis:   Initializes the Credentials list
//
//  Effects:    allocates a resources
//
//  Arguments:  none
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS on success, other error codes
//              on failure
//
//  Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInitCredentialList(
    VOID
    )
{
    NTSTATUS Status;


    Status = KerbInitializeList( &KerbCredentialList );
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }
    KerberosCredentialsInitialized = TRUE;

Cleanup:
    if (!NT_SUCCESS(Status))
    {
        KerbFreeList( &KerbCredentialList);

    }
    return(Status);
}


//+-------------------------------------------------------------------------
//
//  Function:   KerbFreeCredentialList
//
//  Synopsis:   Frees the credentials list
//
//  Effects:
//
//  Arguments:  none
//
//  Requires:
//
//  Returns:    none
//
//  Notes:
//
//
//--------------------------------------------------------------------------

VOID
KerbFreeCredentialList(
    VOID
    )
{
    PKERB_CREDENTIAL Credential;


    if (KerberosCredentialsInitialized)
    {
        KerbLockList(&KerbCredentialList);

        //
        // Go through the list of logon sessions and dereferences them all
        //

        while (!IsListEmpty(&KerbCredentialList.List))
        {
            Credential = CONTAINING_RECORD(
                            KerbCredentialList.List.Flink,
                            KERB_CREDENTIAL,
                            ListEntry.Next
                            );

            KerbReferenceListEntry(
                &KerbCredentialList,
                &Credential->ListEntry,
                TRUE
                );

            KerbDereferenceCredential(Credential);

        }

        KerbFreeList(&KerbCredentialList);
    }
}

//+-------------------------------------------------------------------------
//
//  Function:   KerbAllocateCredential
//
//  Synopsis:   Allocates a credential structure
//
//  Effects:    Allocates a credential, but does not add it to the
//              list of credentials
//
//  Arguments:  NewCredential - receives a new credential allocated
//                  with KerbAllocate
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS on success
//              STATUS_INSUFFICIENT_RESOURCES if the allocation fails
//
//  Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAllocateCredential(
    PKERB_CREDENTIAL * NewCredential
    )
{
    PKERB_CREDENTIAL Credential;
    SECPKG_CALL_INFO CallInfo;
    NTSTATUS Status = STATUS_SUCCESS;

    *NewCredential = NULL;

    if (!LsaFunctions->GetCallInfo(&CallInfo))
    {
        D_DebugLog((DEB_ERROR,"Failed to get call info. %ws, line %d\n",
            THIS_FILE, __LINE__));
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

    Credential = (PKERB_CREDENTIAL) KerbAllocate(
                        sizeof(KERB_CREDENTIAL) );

    if (Credential == NULL)
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto Cleanup;
    }

    Credential->ClientProcess = CallInfo.ProcessId;

    KerbInitializeListEntry(
        &Credential->ListEntry
        );
    //
    // Set the references to 1 since we are returning a pointer to the
    // logon session
    //

    Credential->HandleCount = 1;

    *NewCredential = Credential;

Cleanup:
    return(Status);
}


//+-------------------------------------------------------------------------
//
//  Function:   KerbInsertCredential
//
//  Synopsis:   Inserts a logon session into the list of logon sessions
//
//  Effects:    bumps reference count on logon session
//
//  Arguments:  Credential - Credential to insert
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS always
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS
KerbInsertCredential(
    IN PKERB_CREDENTIAL Credential
    )
{

    //
    // insert entry at tail of list.
    // reason: entries at the head are more likely to have _TGT flag set
    // and, those are the ones we want to satisfy from cache for repeat
    // high stress offenders...
    //

    Credential->CredentialTag = KERB_CREDENTIAL_TAG_ACTIVE;

    KerbInsertListEntryTail(
        &Credential->ListEntry,
        &KerbCredentialList
        );

    return(STATUS_SUCCESS);
}




//+-------------------------------------------------------------------------
//
//  Function:   KerbFreePrimaryCredentials
//
//  Synopsis:   frees a primary credentials structure
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreePrimaryCredentials(
    IN PKERB_PRIMARY_CREDENTIAL Credentials,
    IN BOOLEAN FreeBaseStructure
    )
{
    if (Credentials != NULL)
    {
        KerbFreeString(&Credentials->DomainName);
        KerbFreeString(&Credentials->OldDomainName);
        KerbFreeString(&Credentials->UserName);
        KerbFreeString(&Credentials->OldUserName);

        RtlZeroMemory( &Credentials->OldHashPassword, sizeof(Credentials->OldHashPassword) );
        if (Credentials->ClearPassword.Buffer != NULL)
        {
            RtlZeroMemory(
                Credentials->ClearPassword.Buffer,
                Credentials->ClearPassword.Length
                );
            KerbFreeString(&Credentials->ClearPassword);
            RtlZeroMemory(&Credentials->ClearPassword, sizeof(Credentials->ClearPassword));
        }
        if (Credentials->Passwords != NULL)
        {
            KerbFreeStoredCred(Credentials->Passwords);
        }
        if (Credentials->OldPasswords != NULL)
        {
            KerbFreeStoredCred(Credentials->OldPasswords);
        }
        KerbPurgeTicketCache(&Credentials->ServerTicketCache);
        KerbPurgeTicketCache(&Credentials->AuthenticationTicketCache);

        KerbFreePKCreds(Credentials->PublicKeyCreds);
        Credentials->PublicKeyCreds = NULL;

        if (FreeBaseStructure)
        {
            KerbFree(Credentials);
        }
    }
}

//+-------------------------------------------------------------------------
//
//  Function:   KerbFreeCredential
//
//  Synopsis:   Frees a credential structure and all contained data
//
//  Effects:
//
//  Arguments:  Credential - The credential to free.
//
//  Requires:   This credential must be unlinked
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


VOID
KerbFreeCredential(
    IN PKERB_CREDENTIAL Credential
    )
{

    Credential->CredentialTag = KERB_CREDENTIAL_TAG_DELETE;

    if (Credential->SuppliedCredentials != NULL)
    {
        KerbFreePrimaryCredentials(Credential->SuppliedCredentials, TRUE);
    }
    DsysAssert(Credential->ListEntry.Next.Flink == NULL);
    KerbFreeString(&Credential->CredentialName);

    KerbFree(Credential);

}

//+-------------------------------------------------------------------------
//
//  Function:   KerbGetTicketForCredential
//
//  Synopsis:   Obtains a TGT for a credential if it doesn't already
//              have one.
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS
KerbGetTicketForCredential(
    IN OPTIONAL PKERB_LOGON_SESSION LogonSession,
    IN PKERB_CREDENTIAL Credential,
    IN OPTIONAL PKERB_CREDMAN_CRED CredManCredentials,
    IN OPTIONAL PUNICODE_STRING SuppRealm
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PKERB_LOGON_SESSION LocalLogonSession = NULL;
    BOOLEAN GetRestrictedTgt = FALSE;
    PKERB_TICKET_CACHE_ENTRY Tgt = NULL;
    BOOLEAN PrimaryLogonSessionCredential = FALSE;

    // 
    // We've got to make a determination w.r.t. whether this
    // is an attempt to renew our primary credential.
    // This will affect our logon session flags.
    //


    if (!ARGUMENT_PRESENT(LogonSession))
    {
      LocalLogonSession = KerbReferenceLogonSession(
                              &Credential->LogonId,
                              FALSE                   // don't unlink
                              );
      if (LocalLogonSession == NULL)
      {
          Status = STATUS_NO_SUCH_LOGON_SESSION;
          goto Cleanup;
      }

    }     
    else
    {   
        LocalLogonSession = LogonSession;
    }

    
    //
    // Here we make the assumption that if we didn't get a credential
    // and we also got a logon session, then we're dealing w/ the 
    // logon session's primary credential
    //
    if ((ARGUMENT_PRESENT(Credential)) && 
        (Credential->SuppliedCredentials == NULL) &&
        (!ARGUMENT_PRESENT(CredManCredentials)))

    {
        PrimaryLogonSessionCredential = TRUE;
        D_DebugLog((DEB_TRACE_CRED, "Getting Credentials for primary logon session - %x\n", LogonSession));
    }
    else
    {
        D_DebugLog((DEB_TRACE_CRED, "Got a credential && a logon session\n"));
    }

    Status = KerbGetTicketGrantingTicket(
                LocalLogonSession,
                Credential,
                CredManCredentials,
                SuppRealm,
                &Tgt,
                NULL            // don't return credential key
                );  


    if (!NT_SUCCESS(Status))
    {                                                                                  
        goto Cleanup;

    }

    KerbWriteLockLogonSessions(LocalLogonSession);

    //
    // Clear the logon deferred bit for the logon session, if set
    // Note:  This will only be cleared as a result of refreshing
    // logon session's primary cred tgt
    // 
    if (PrimaryLogonSessionCredential && 
       ((LocalLogonSession->LogonSessionFlags & KERB_LOGON_DEFERRED) != 0))
    {   
        LocalLogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED;
    }                                                                  

    //
    // If we have a credential, be sure to set the TGT_avail bit for
    // those credentials.
    //
    if (ARGUMENT_PRESENT(Credential))
    {
        Credential->CredentialFlags |= KERB_CRED_TGT_AVAIL;
        if ((Credential->CredentialFlags & KERB_CRED_RESTRICTED) != 0)
        {
            GetRestrictedTgt = TRUE;
        }
    }

    if (ARGUMENT_PRESENT(CredManCredentials))
    {
        CredManCredentials->CredentialFlags |= KERB_CRED_TGT_AVAIL;
    }

    KerbUnlockLogonSessions(LocalLogonSession);

#ifdef RESTRICTED_TOKEN
    if (GetRestrictedTgt)
    {
        Status = KerbGetRestrictedTgtForCredential(
                    LocalLogonSession,
                    Credential
                    );

        if (!NT_SUCCESS(Status))
        {
            DebugLog((DEB_ERROR,"Failed to get restricted TGT for credential: 0x%x\n",Status));

            KerbRemoveTicketCacheEntry(Tgt);

            goto Cleanup;
        }
    }

#endif

Cleanup:

    //
    // Reset the bits if we failed
    //

    if (LocalLogonSession  && !NT_SUCCESS(Status))
    {
        KerbWriteLockLogonSessions(LocalLogonSession);

        //
        // Don't touch logon session flag, unless we're 
        // dealing w/ our own logon session.   This means
        // we don't have a TGT for our initial logon session, 
        // See RefreshTgt() -- only place we supply logon session
        //
        if (PrimaryLogonSessionCredential)
        {
            LocalLogonSession->LogonSessionFlags |= KERB_LOGON_DEFERRED;
            D_DebugLog((DEB_TRACE_CRED, "Toggling ON logon deferred bit for logon session %x\n", LogonSession));
        }

        //
        // Or, we expected it to be there with our (supplied) credential
        //
        if (ARGUMENT_PRESENT(Credential))
        {
            Credential->CredentialFlags &= ~KERB_CRED_TGT_AVAIL;
        }

        KerbUnlockLogonSessions(LocalLogonSession);
    }

    if (!ARGUMENT_PRESENT(LogonSession) && (LocalLogonSession != NULL))
    {
        KerbDereferenceLogonSession(LocalLogonSession);
    }
    if (Tgt != NULL)
    {
        KerbDereferenceTicketCacheEntry(
            Tgt
            );

    }
    return(Status);
}


//+-------------------------------------------------------------------------
//
//  Function:   KerbReferenceCredential
//
//  Synopsis:   Locates a logon session from the logon ID and references it
//
//  Effects:    Increments reference count and possible unlinks it from list
//
//  Arguments:  LogonId - LogonId of logon session to locate
//              RequiredFlags - Flags required
//              RemoveFromList - If TRUE, logon session will be delinked
//              Credential - Receives the referenced credential
//
//  Requires:
//
//  Returns:    NT status codes
//
//  Notes:
//
//
//--------------------------------------------------------------------------


NTSTATUS
KerbReferenceCredential(
    IN LSA_SEC_HANDLE CredentialHandle,
    IN ULONG RequiredFlags,
    IN BOOLEAN RemoveFromList,
    OUT PKERB_CREDENTIAL * Credential
    )
{
    PKERB_CREDENTIAL LocalCredential = NULL;
    BOOLEAN Found = FALSE;
    SECPKG_CALL_INFO CallInfo;
    BOOLEAN LocalRemoveFromList = FALSE;
    NTSTATUS Status = STATUS_SUCCESS;

    ULONG DereferenceCount;

    *Credential = NULL;

    if(LsaFunctions->GetCallInfo(&CallInfo))
    {
        DereferenceCount = CallInfo.CallCount;
    } else {
        ASSERT((STATUS_INTERNAL_ERROR == STATUS_SUCCESS));
        return STATUS_INTERNAL_ERROR;
    }

    if( CallInfo.Attributes & SECPKG_CALL_CLEANUP )
    {
        CallInfo.Attributes |= SECPKG_CALL_IS_TCB;
        DebugLog((DEB_TRACE, "CredHandle %p leaked by ProcessId %x Deref count: %x\n",
                    CredentialHandle, CallInfo.ProcessId, DereferenceCount));
    }

    KerbLockList(&KerbCredentialList);

    //
    // Go through the list of logon sessions looking for the correct
    // LUID
    //

    __try {
        LocalCredential = (PKERB_CREDENTIAL)CredentialHandle;

        while( LocalCredential->CredentialTag == KERB_CREDENTIAL_TAG_ACTIVE )
        {
            if (((CallInfo.Attributes & SECPKG_CALL_IS_TCB) == 0) &&
                (LocalCredential->ClientProcess != CallInfo.ProcessId) )
            {
                D_DebugLog((DEB_ERROR,"Trying to reference a credential from another process! %ws, line %d\n", THIS_FILE, __LINE__));
                // FESTER
                D_DebugLog((DEB_ERROR, "Cred - %x \nClient process - %d  Call info Pid - %d\n", LocalCredential, LocalCredential->ClientProcess, CallInfo.ProcessId));
                Found = FALSE;
                Status = STATUS_PRIVILEGE_NOT_HELD;
                break;
            }

            KerbReferenceListEntry(
                    &KerbCredentialList,
                    &LocalCredential->ListEntry,
                    FALSE                   // don't remove
                    );

            Found = TRUE;
            break;
        }

    } __except (EXCEPTION_EXECUTE_HANDLER)
    {
        D_DebugLog((DEB_ERROR,"Trying to reference invalid credential %ws, line %d\n", THIS_FILE, __LINE__));
        Found = FALSE;
    }

    if (!Found)
    {
        LocalCredential = NULL;
        Status = STATUS_INVALID_HANDLE;
    }
    else
    {
        ULONG MissingFlags = RequiredFlags - (LocalCredential->CredentialFlags & RequiredFlags);

        if (MissingFlags != 0)
        {
            D_DebugLog((DEB_TRACE,"Credential %p is missing flags: needs %x\n",
                Credential,
                MissingFlags));

            if ((MissingFlags &= KERB_CRED_TGT_AVAIL) != 0)
            {
                //
                // if this is a cred for a local account then no point in attempting to
                // get a TGT
                //
                if ((LocalCredential->CredentialFlags & KERB_CRED_LOCAL_ACCOUNT) == 0)
                {
                    DsysAssert(!RemoveFromList);

                    KerbUnlockList(&KerbCredentialList);

                    D_DebugLog((DEB_TRACE_CRED,"Getting missing TGT for credential %x\n", LocalCredential));

                    Status = KerbGetTicketForCredential(
                                NULL,
                                LocalCredential,
                                NULL,
                                NULL
                                );

                    KerbLockList(&KerbCredentialList);
                }
            }
            else
            {
                Status = SEC_E_NO_CREDENTIALS;
            }

        }


        if (NT_SUCCESS(Status))
        {


            //
            // Since there may be multiple outstanding handles using this
            // structure we don't want to really remove it from the list unless
            // the last one releases it.
            //

            if (RemoveFromList)
            {
                ASSERT( DereferenceCount != 0 );
                ASSERT ( (DereferenceCount <= LocalCredential->HandleCount) );

                if( DereferenceCount > LocalCredential->HandleCount ) {
                    LocalCredential->HandleCount = 0;
                } else {
                    LocalCredential->HandleCount -= DereferenceCount;
                }

                if (LocalCredential->HandleCount == 0)
                {
                    LocalRemoveFromList = TRUE;
                }
            }

            KerbReferenceListEntry(
                &KerbCredentialList,
                &LocalCredential->ListEntry,
                LocalRemoveFromList
                );
   
            KerbDereferenceCredential(LocalCredential);
        }
        else
        {
            //
            // Remove the earlier reference
            //

            KerbDereferenceCredential(LocalCredential);
            LocalCredential = NULL;
        }


        *Credential = LocalCredential;
    }

    KerbUnlockList(&KerbCredentialList);
    return(Status);
}


//+-------------------------------------------------------------------------
//
//  Function:   KerbDereferenceCredential
//
//  Synopsis:   Dereferences a logon session - if reference count goes
//              to zero it frees the logon session
//
//  Effects:    decrements reference count
//
//  Arguments:  Credential - Logon session to dereference
//
//  Requires:
//
//  Returns:    none
//
//  Notes:
//
//
//--------------------------------------------------------------------------


VOID
KerbDereferenceCredential(
    IN PKERB_CREDENTIAL Credential
    )
{
    if (KerbDereferenceListEntry(
            &Credential->ListEntry,
            &KerbCredentialList
            ) )
    {
        KerbFreeCredential(Credential);
    }
}



//+-------------------------------------------------------------------------
//
//  Function:   KerbPurgeCredentials
//
//  Synopsis:   Purges the list of credentials associated with a logon session
//              by dereferencing and unlinking them.
//
//  Effects:    Unlinks all credential on the list
//
//  Arguments:  CredentialList - List of credentials to purge
//
//  Requires:
//
//  Returns:    none
//
//  Notes:  No longer used, as some system processes hold cred handles long
//          after logons go away.  Leads to refcounting disasters.
//
//
//--------------------------------------------------------------------------
/*

VOID
KerbPurgeCredentials(
    IN PLIST_ENTRY CredentialList
    )
{
    PKERB_CREDENTIAL Credential;

    KerbLockList(&KerbCredentialList);
    while (!IsListEmpty(CredentialList))
    {
        Credential = CONTAINING_RECORD(
                        CredentialList->Flink,
                        KERB_CREDENTIAL,
                        NextForThisLogonSession
                        );


        //
        // Remove it from the credential list
        //

        //RemoveEntryList(&Credential->NextForThisLogonSession);
        Credential->HandleCount = 0;

        //
        // Reference it to unlink it and then dereference it
        //

        KerbReferenceListEntry(
            &KerbCredentialList,
            &Credential->ListEntry,
            TRUE
            );

        KerbDereferenceCredential(Credential);

    }
    KerbUnlockList(&KerbCredentialList);

} */


//+-------------------------------------------------------------------------
//
//  Function:   KerbLocateCredential
//
//  Synopsis:
//
//  Effects:
//
//  Arguments:
//
//  Requires:
//
//  Returns:
//
//  Notes:
//
//
//--------------------------------------------------------------------------
PKERB_CREDENTIAL
KerbLocateCredential(
    IN PLUID LogonId,
    IN ULONG CredentialUseFlags,
    IN PKERB_PRIMARY_CREDENTIAL SuppliedCredentials,
    IN ULONG CredentialFlags,
    IN PUNICODE_STRING CredentialName
    )
{
    PLIST_ENTRY ListEntry;
    PKERB_CREDENTIAL Credential = NULL;
    BOOLEAN Found = FALSE;
    SECPKG_CALL_INFO CallInfo;
    NT_OWF_PASSWORD HashPassword;

    if( SuppliedCredentials != NULL )
    {
        if( SuppliedCredentials->ClearPassword.Buffer != NULL )
        {
            RtlCalculateNtOwfPassword(
                    &SuppliedCredentials->ClearPassword,
                    &HashPassword
                    );
        } else {
            ZeroMemory( &HashPassword, sizeof(HashPassword) );
        }
    }


    //
    // Match both flags
    //

    CredentialUseFlags |= CredentialFlags;

    if(!LsaFunctions->GetCallInfo(&CallInfo))
    {

        D_DebugLog((DEB_ERROR,"Failed to get client info:. %ws, line %d\n",
            THIS_FILE, __LINE__));
        return(NULL);
    }


    KerbLockList(&KerbCredentialList);

    //
    // Go through the list of logon sessions looking for the correct
    // LUID
    //

    for (ListEntry = KerbCredentialList.List.Flink ;
         ListEntry !=  &KerbCredentialList.List ;
         ListEntry = ListEntry->Flink )
    {
        Credential = CONTAINING_RECORD(ListEntry, KERB_CREDENTIAL, ListEntry.Next);

        if( (Credential->ClientProcess != CallInfo.ProcessId) )
        {
            continue;
        }

        if ( (Credential->CredentialFlags & KERB_CRED_MATCH_FLAGS) != CredentialUseFlags)
        {
            continue;
        }

        if(!RtlEqualLuid(
                &Credential->LogonId,
                LogonId
                ))
        {
            continue;
        }

        if(!RtlEqualUnicodeString(
                CredentialName,
                &Credential->CredentialName,
                FALSE
                ))
        {
            continue;
        }

        if( SuppliedCredentials != NULL )
        {
            //
            // credentials supplied, but candidate didn't have creds.  continue search.
            //

            if( Credential->SuppliedCredentials == NULL )
            {
                continue;
            }


            if(!RtlEqualUnicodeString(
                        &SuppliedCredentials->UserName,
                        &Credential->SuppliedCredentials->UserName,
                        FALSE
                        ))
            {
                if(!RtlEqualUnicodeString(
                            &SuppliedCredentials->UserName,
                            &Credential->SuppliedCredentials->OldUserName,
                            FALSE
                            ))
                {
                    continue;
                }
            }

            //
            // note: both candidate and input SuppliedCredentials ClearPassword
            // is actually encrypted via KerbHidePassword().
            //

            if(!RtlEqualMemory(
                        &HashPassword,
                        &Credential->SuppliedCredentials->OldHashPassword,
                        sizeof(HashPassword)
                        ))
            {
                continue;
            }


            //
            // optimize for UPN case:
            // check as typed versus as stored/updated first,
            // then check as typed versus as typed in original cred build.
            //

            if(!RtlEqualUnicodeString(
                        &SuppliedCredentials->DomainName,
                        &Credential->SuppliedCredentials->DomainName,
                        FALSE
                        ))
            {
                if(!RtlEqualUnicodeString(
                            &SuppliedCredentials->DomainName,
                            &Credential->SuppliedCredentials->OldDomainName,
                            FALSE
                            ))
                {
                    continue;
                }
            }


            if ((SuppliedCredentials->PublicKeyCreds != NULL) &&
                (Credential->SuppliedCredentials->PublicKeyCreds != NULL))
            {

                if(!RtlEqualUnicodeString(
                        &SuppliedCredentials->PublicKeyCreds->Pin,
                        &Credential->SuppliedCredentials->PublicKeyCreds->Pin,
                        FALSE
                        ))
                {
                    continue;
                }


                if (!KerbComparePublicKeyCreds(
                        SuppliedCredentials->PublicKeyCreds,
                        Credential->SuppliedCredentials->PublicKeyCreds
                        ))
                {
                    continue;
                }

                
            }    


        } else {

            //
            // credentials not supplied, but candidate has creds.  continue search
            //

            if( Credential->SuppliedCredentials != NULL )
            {
                continue;
            }

        }

        KerbReferenceListEntry(
                    &KerbCredentialList,
                    &Credential->ListEntry,
                    FALSE
                    );

        Credential->HandleCount++;
        Found = TRUE;
        break;

    }

    KerbUnlockList(&KerbCredentialList);

    if (!Found)
    {
        Credential = NULL;
    }
    return(Credential);
}


//+-------------------------------------------------------------------------
//
//  Function:   KerbCreateCredential
//
//  Synopsis:   Creates a new credential and links it to the credential list
//              and the list for this logon session
//
//  Effects:
//
//  Arguments:  LogonId - LogonId for this logon session
//              LogonSession - LogonSession for the client
//              CredentialUseFlags - Flags indicating if the credential is
//                      inbound or outbound
//              SuppliedCredentials - (Optionally) supplied credentials to store
//                      in the credentials. If these are present, there need
//                      not be a password on the logon session. The field is
//                      zeroed when the primary creds are stuck in the
//                      credential structure.
//              CredentialFlags - Flags about how credentials are to be
//                      used, such as to not use a PAC or to use a null
//                      session.
//              NewCredential - Receives new credential, referenced and linked
//              ExpirationTime - Receives credential expiration time
//
//  Requires:
//
//  Returns:    STATUS_SUCCESS on success,
//              STATUS_INSUFFICIENT_RESOURCES on allocation failure
//
//  Notes:      Readers and writers of this credential must hold the
//              credential lock
//
//
//--------------------------------------------------------------------------


NTSTATUS
KerbCreateCredential(
    IN PLUID LogonId,
    IN PKERB_LOGON_SESSION LogonSession,
    IN ULONG CredentialUseFlags,
    IN PKERB_PRIMARY_CREDENTIAL * SuppliedCredentials,
    IN ULONG CredentialFlags,
    IN PUNICODE_STRING CredentialName,
    OUT PKERB_CREDENTIAL * NewCredential,
    OUT PTimeStamp ExpirationTime
    )
{
    NTSTATUS Status;
    PKERB_CREDENTIAL Credential = NULL;
    ULONG LogonSessionFlags = 0;
    UNICODE_STRING ServiceRealm = NULL_UNICODE_STRING;
    BOOLEAN FoundKdc = FALSE;
    BOOLEAN fLocalCredential = FALSE;


    //
    // Check to see if we already have a credential for this situation
    //

    Credential = KerbLocateCredential(
                    LogonId,
                    CredentialUseFlags,
                    *SuppliedCredentials,
                    CredentialFlags,
                    CredentialName
                    );
    if (Credential != NULL)
    {
        KerbReadLockLogonSessions(LogonSession);

        *ExpirationTime = LogonSession->Lifetime;

        KerbUnlockLogonSessions(LogonSession);

        *NewCredential = Credential;
        return(STATUS_SUCCESS);
    }

    Status = KerbAllocateCredential(&Credential);
    if (!NT_SUCCESS(Status))
    {
         goto Cleanup;
    }

    Credential->LogonId = *LogonId;

    //
    // Make sure the flags are valid
    //

    if ((CredentialUseFlags & ~SECPKG_CRED_BOTH) != 0)
    {
        D_DebugLog((DEB_ERROR,"Invalid credential use flags: 0x%x. %ws, line %d\n",CredentialUseFlags, THIS_FILE, __LINE__));
        Status = STATUS_INVALID_PARAMETER;
        goto Cleanup;
    }


    //
    // Make sure the logon session is valid for acquiring credentials
    //

    KerbReadLockLogonSessions(LogonSession);

    LogonSessionFlags = LogonSession->LogonSessionFlags;
    *ExpirationTime = LogonSession->Lifetime;

    KerbUnlockLogonSessions(LogonSession);


    //
    // Make sure the logon session is not local only.
    // We do not handle the local only case.
    //
    if (((LogonSessionFlags & KERB_LOGON_LOCAL_ONLY) != 0) &&
        (*SuppliedCredentials == NULL ))
    {
        D_DebugLog((DEB_WARN, "Trying to acquire cred handle for local logon session\n"));
        fLocalCredential = TRUE;
/*
        Status = SEC_E_NO_CREDENTIALS;
        goto Cleanup;
*/
    }

    Credential->SuppliedCredentials = *SuppliedCredentials;
    *SuppliedCredentials = NULL;

    Credential->CredentialName = *CredentialName;
    CredentialName->Buffer = NULL;

    Credential->CredentialFlags = CredentialUseFlags | CredentialFlags;
    if (fLocalCredential)
    {
        Credential->CredentialFlags |= KERB_CRED_LOCAL_ACCOUNT;
    }

    //
    // If we have no password, we must either not have deferred the logon
    // and we can't do inbound with no TGT.
    //

    if (((LogonSessionFlags & KERB_LOGON_NO_PASSWORD) != 0) &&
        ((LogonSessionFlags & KERB_LOGON_SMARTCARD) == 0) &&
        (Credential->SuppliedCredentials == NULL))
    {
        if (((CredentialUseFlags & SECPKG_CRED_OUTBOUND) != 0) &&
            ((LogonSessionFlags & KERB_LOGON_DEFERRED) != 0))
        {
            DebugLog((DEB_WARN,"Trying to acquire cred handle w/ no supplied creds for logon session with no pass or TGT\n"));
            Status = SEC_E_NO_CREDENTIALS;
            goto Cleanup;
        }
    }

    //
    // Check to see if this was a deferred logon - if so, try to
    // finish it now.
    //

    if (((CredentialFlags & KERB_CRED_NULL_SESSION) == 0) &&
        (((LogonSessionFlags & KERB_LOGON_DEFERRED) != 0) ||
         (Credential->SuppliedCredentials != NULL)))

    {
        //
        // If this is outbound, we need to get a TGT or supplied credentials
        //

        if ((CredentialUseFlags & SECPKG_CRED_OUTBOUND) != 0)
        {
            //
            // Get an authentication ticket
            //
#ifdef notdef
            Status = KerbGetTicketForCredential(
                        LogonSession,
                        Credential
                        );


            if (!NT_SUCCESS(Status))
            {
                DebugLog((DEB_ERROR,"Failed to get TGT for credential: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__ ));
                goto Cleanup;

            }
#endif

        }
        else
        {
            //
            // Verify that a KDC actually exists for this users's realm -
            // otherwise we don't want to bother with Kerberos. If someone
            // is explicitly supplying credentials we probably want to
            // allow them to do it.
            //

            if (((CredentialUseFlags & SECPKG_CRED_INBOUND) != 0) &&
                ((LogonSessionFlags & KERB_LOGON_DEFERRED) != 0) &&
                ((CredentialFlags & KERB_CRED_NULL_SESSION) == 0) &&
                (Credential->SuppliedCredentials == NULL))
            {

                if ((LogonSessionFlags & KERB_LOGON_NO_PASSWORD) != 0)
                {
                    D_DebugLog((DEB_WARN,"Trying to get inbound cred with no pwd or tgt\n"));
                    Status = SEC_E_NO_CREDENTIALS;
                    goto Cleanup;
                }

                KerbReadLockLogonSessions(LogonSession);

                Status = KerbDuplicateString(
                            &ServiceRealm,
                            &LogonSession->PrimaryCredentials.DomainName
                            );
                KerbUnlockLogonSessions(LogonSession);

                if (!NT_SUCCESS(Status))
                {
                    goto Cleanup;
                }

                //
                // If there was no domain name, then assume there is a KDC.
                //

                if (ServiceRealm.Length == 0)
                {
                    FoundKdc = TRUE;
                }

                if (!FoundKdc)
                {
                    //
                    // If we are a DC, or f this domain is our worksatation
                    // domain, check to see if
                    // we have a DNS name. If we do, then at one point we were
                    // part of an NT5 domain. Otherwise call DsGetDCName to see
                    // if there is a KDC around
                    //

                    if ((KerbGlobalRole == KerbRoleDomainController) ||
                        KerbIsThisOurDomain(
                            &ServiceRealm
                            ))
                    {
                        FoundKdc = TRUE;
                    }
                }

                //
                // If we haven't found one yet, try looking for a KDC in
                // this domain
                //

                if (!FoundKdc)
                {
                    PKERB_BINDING_CACHE_ENTRY BindingHandle = NULL;

                    DsysAssert(ServiceRealm.MaximumLength >= ServiceRealm.Length + sizeof(WCHAR));
                    DsysAssert(ServiceRealm.Buffer[ServiceRealm.Length/sizeof(WCHAR)] == L'\0');

                    Status = KerbGetKdcBinding(
                                &ServiceRealm,
                                NULL,           // no account name
                                0,              // no desired flags,
                                FALSE,          // don't call kadmin
                                FALSE,
                                &BindingHandle
                                );
                    if (NT_SUCCESS(Status))
                    {
                        FoundKdc = TRUE;
                        KerbDereferenceBindingCacheEntry(BindingHandle);
                    }

                }
                if (!FoundKdc)
                {
                    D_DebugLog((DEB_ERROR,"Didn't find KDC for domain %wZ. %ws, line %d\n",
                        &ServiceRealm, THIS_FILE, __LINE__ ));
                    Status = SEC_E_NO_AUTHENTICATING_AUTHORITY;
                    goto Cleanup;
                }
            }
        }
    }
    else
    {
        Credential->CredentialFlags |= KERB_CRED_TGT_AVAIL;
    }


    //
    // Insert the credential into the list of credentials
    //

    KerbInsertCredential(Credential);

    //
    // Notice: the order of acquiring these locks is important.
    //
    
    *NewCredential = Credential;

Cleanup:
    if (!NT_SUCCESS(Status))
    {
        if (Credential != NULL)
        {
            //
            // Make sure we haven't linked this one yet.
            //

            DsysAssert(Credential->ListEntry.ReferenceCount == 1);
            KerbFreeCredential(Credential);
        }

        //
        // Map the error if necessary. Normally STATUS_OBJECT_NAME_NOT_FOUND
        // gets mapped to SEC_E_UNKNOWN_TARGET, but this is an invalid
        // status to return from AcquireCredentialsHandle, so instead
        // return SEC_E_NO_CREDENTIALS.
        //

        if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
        {
            Status = SEC_E_NO_CREDENTIALS;
        }
    }
    KerbFreeString(&ServiceRealm);
    return(Status);
}