// Users.cpp: implementation of the CUsers class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "efsadu.h"
#include "Users.h"
#include <wincrypt.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CUsers::CUsers()
{
    m_UsersRoot = NULL;
	m_UserAddedCnt = 0;
	m_UserRemovedCnt = 0;
}

//////////////////////////////////////////////////////////////////////
// Walk through the chain to free the memory
//////////////////////////////////////////////////////////////////////

CUsers::~CUsers()
{
    Clear();
}

PUSERSONFILE
CUsers::RemoveItemFromHead(void)
{
    PUSERSONFILE PItem = m_UsersRoot;
    if (m_UsersRoot){
        m_UsersRoot = m_UsersRoot->Next;
        if ((PItem->Flag & USERADDED) && !(PItem->Flag & USERREMOVED)){
            m_UserAddedCnt--;
        }
        if ((PItem->Flag & USERINFILE) && (PItem->Flag & USERREMOVED)){
            m_UserRemovedCnt--;
        }
    }
    return PItem;
}

DWORD
CUsers::Add( CUsers &NewUsers )
{
    PUSERSONFILE NewItem;

    while (NewItem = NewUsers.RemoveItemFromHead()){
        PUSERSONFILE    TmpItem = m_UsersRoot;
        
        while ( TmpItem ){

            if ((NewItem->UserName && TmpItem->UserName && !_tcsicmp(NewItem->UserName, TmpItem->UserName)) ||
                 ((NULL == NewItem->UserName) && (TmpItem->UserName == NULL))){

                //
                // User exist
                //

                if ( TmpItem->Flag & USERREMOVED ){

                    if ( TmpItem->Flag & USERADDED ){

                        ASSERT(!(TmpItem->Flag & USERINFILE));

                        //
                        //    User added and removed
                        //
                        m_UserAddedCnt++;

                    } else if ( TmpItem->Flag & USERINFILE ){

                        //
                        //    User added and removed
                        //
                        m_UserRemovedCnt--;

                    }
                    TmpItem->Flag &= ~USERREMOVED;
                }

                //
                // The caller will count on CUsers to release the memory
                //

                if (NewItem->UserName){
                    delete [] NewItem->UserName;
                }
                if ( NewItem->Context ) {
                    CertFreeCertificateContext((PCCERT_CONTEXT)NewItem->Context);
                }
                delete [] NewItem->Cert;
                if (NewItem->UserSid){
                    delete [] NewItem->UserSid;
                }
                delete NewItem;
                NewItem = NULL;                
                break;
            }
            TmpItem = TmpItem->Next;
        }

        if (NewItem ){ 
            //
            // New item. Insert into the head.
            //

            NewItem->Next = m_UsersRoot;
            m_UsersRoot = NewItem;
            m_UserAddedCnt++;
        }

    }

    return ERROR_SUCCESS;
}

DWORD
CUsers::Add(
    LPTSTR UserName,
    PVOID UserCert, 
    PSID UserSid, /* = NULL */
    DWORD Flag, /* = USERINFILE */
    PVOID Context /* = NULL */
    )
//////////////////////////////////////////////////////////////////////
// Routine Description:
//      Create an item for a user
// Arguments:
//      UserName -- User's name
//      UserCert -- User's certificate blob or hash
//      UserSid -- User's ID. Can be NULL
//      Flag -- Indicate if the item is existing in the file, to be added or removed
//  Return Value:
//      NO_ERROR if succeed.
//      Will throw exception if memory allocation fails. ( From new.)
// 
//////////////////////////////////////////////////////////////////////
{

    PUSERSONFILE UserItem;
    PUSERSONFILE TmpUserItem = m_UsersRoot;
    PEFS_CERTIFICATE_BLOB CertBlob;
    PEFS_HASH_BLOB  CertHashBlob;
    DWORD   CertSize;
    DWORD   SidSize;

    if ( !UserCert ){
        return ERROR_INVALID_PARAMETER;
    }

    ASSERT ( (( Flag & USERADDED ) || ( Flag & USERINFILE )) &&
                       ( (Flag & (USERADDED | USERINFILE)) != (USERADDED | USERINFILE)));


    //
    // If the user already in the memory, no new item is to be created except for unknown user
    //

    while ( TmpUserItem ){
        if ( (UserName && TmpUserItem->UserName && !_tcsicmp(UserName, TmpUserItem->UserName)) ||
              ((NULL == UserName) && (TmpUserItem->UserName == NULL))){

            //
            // User exist
            //

            if ( TmpUserItem->Flag & USERREMOVED ){

                if ( TmpUserItem->Flag & USERADDED ){

                    ASSERT(!(TmpUserItem->Flag & USERINFILE));

                    //
                    //    User added and removed
                    //
                    m_UserAddedCnt++;

                } else if ( TmpUserItem->Flag & USERINFILE ){

                    //
                    //    User added and removed
                    //
                    m_UserRemovedCnt--;

                }
                TmpUserItem->Flag &= ~USERREMOVED;
            }

            //
            // The caller will count on CUsers to release 
            // the context if the call returns CRYPT_E_EXISTS. This is just for
            // performance reason.
            //
/*
            if (UserName){
                delete [] UserName;
            }
*/
            if ( Context ) {
                CertFreeCertificateContext((PCCERT_CONTEXT)Context);
                Context = NULL;
            }
            return CRYPT_E_EXISTS;
        }
        TmpUserItem = TmpUserItem->Next;
    }
    
    try {
        UserItem = new USERSONFILE;
        if ( NULL == UserItem ){
            AfxThrowMemoryException( );
        }

        UserItem->Next = NULL;

        //
        // In case exception raised, we can call delete.
        // Delete NULL is OK, but random data is not OK.
        //

        UserItem->UserSid = NULL;
        UserItem->Cert = NULL;
        UserItem->Context = NULL;

        if ( UserSid ){
            SidSize = GetLengthSid( UserSid );
            if (  SidSize > 0 ){
                UserItem->UserSid = new BYTE[SidSize];
                if ( NULL == UserItem->UserSid ){
                    AfxThrowMemoryException( );
                }
                if ( !CopySid(SidSize, UserItem->UserSid, UserSid)){
                    delete [] UserItem->UserSid;
                    delete UserItem;
                    return GetLastError();
                }
                
            } else {
                delete UserItem;
                return GetLastError();
            }
        } else {
            UserItem->UserSid = NULL;
        }
 
        if ( Flag & USERINFILE ){

            //
            // The info is from the file. Use the hash structure
            //

            CertHashBlob = ( PEFS_HASH_BLOB ) UserCert;
            CertSize = sizeof(EFS_HASH_BLOB) + CertHashBlob->cbData;
            UserItem->Cert = new BYTE[CertSize];
            if ( NULL == UserItem->Cert ){
                AfxThrowMemoryException( );
            }
            ((PEFS_HASH_BLOB)UserItem->Cert)->cbData = CertHashBlob->cbData;
            ((PEFS_HASH_BLOB)UserItem->Cert)->pbData = (PBYTE)(UserItem->Cert) + sizeof(EFS_HASH_BLOB);
            memcpy(((PEFS_HASH_BLOB)UserItem->Cert)->pbData, 
                   CertHashBlob->pbData,
                   CertHashBlob->cbData
                  );
        } else {

            //
            // The info is from the user picked cert. Use Cert Blob structure
            //

            CertBlob = ( PEFS_CERTIFICATE_BLOB ) UserCert;
            CertSize = sizeof(EFS_CERTIFICATE_BLOB) + CertBlob->cbData;
            UserItem->Cert = new BYTE[CertSize];
            if ( NULL == UserItem->Cert ){
                AfxThrowMemoryException( );
            }
            ((PEFS_CERTIFICATE_BLOB)UserItem->Cert)->cbData = CertBlob->cbData;
            ((PEFS_CERTIFICATE_BLOB)UserItem->Cert)->dwCertEncodingType = CertBlob->dwCertEncodingType;
            ((PEFS_CERTIFICATE_BLOB)UserItem->Cert)->pbData = (PBYTE)(UserItem->Cert) + sizeof(EFS_CERTIFICATE_BLOB);
            memcpy(((PEFS_CERTIFICATE_BLOB)UserItem->Cert)->pbData, 
                   CertBlob->pbData,
                   CertBlob->cbData
                  );

        }
 
        UserItem->UserName = UserName;
        UserItem->Context = Context;
        UserItem->Flag = Flag;
        if ( Flag & USERADDED ){
            m_UserAddedCnt ++;
        }
    }
    catch (...) {
        delete [] UserItem->UserSid;
        delete [] UserItem->Cert;
        delete UserItem;
        AfxThrowMemoryException( );
        return ERROR_NOT_ENOUGH_MEMORY; 
    }

    //
    // Add to the head
    //

    if ( NULL != m_UsersRoot ){
        UserItem->Next = m_UsersRoot;
    }
    m_UsersRoot = UserItem;

    return NO_ERROR;
}

DWORD
CUsers::Remove(
    LPCTSTR UserName
    )
//////////////////////////////////////////////////////////////////////
// Routine Description:
//      Remove a user from the list. Actually just mark for remove.
// Arguments:
//      UserName -- User's name
//  Return Value:
//      NO_ERROR if succeed.
//      ERROR_NOT_FOUND if the user cannot be found.
// 
//////////////////////////////////////////////////////////////////////
{
    PUSERSONFILE TmpUserItem = m_UsersRoot;

    BOOL    UserMatched =FALSE;

    while ( TmpUserItem ){
        if (((NULL==UserName) && ( NULL == TmpUserItem->UserName)) || 
            ( UserName && TmpUserItem->UserName && !_tcsicmp(UserName, TmpUserItem->UserName))){

            //
            // User exist, mark it for remove
            //

            if ( TmpUserItem->Flag & USERINFILE ){
                m_UserRemovedCnt++;
            } else if ( TmpUserItem->Flag & USERADDED ) {
                m_UserAddedCnt--;
            }
            TmpUserItem->Flag |= USERREMOVED;
            return NO_ERROR;
        }
        TmpUserItem = TmpUserItem->Next;
    }
    return ERROR_NOT_FOUND;
}

PVOID
CUsers::StartEnum()
//////////////////////////////////////////////////////////////////////
// Routine Description:
//      Prepare for GetNextUser
// Arguments:
//
//  Return Value:
//      A pointer used for GetNextUser
// 
//////////////////////////////////////////////////////////////////////
{
    return ((PVOID)m_UsersRoot);
}

PVOID
CUsers::GetNextUser(
    PVOID Token, 
    CString &UserName,
    CString &CertHash
    )
//////////////////////////////////////////////////////////////////////
// Routine Description:
//      Get next user in the list.(Not removed).
// Arguments:
//      UserName -- Next User's name
//      CertHash -- Certificate Thumbprinter
//      Token -- A pointer returned by previous GetNextUser or StartEnum. 
// Return Value:
//      A pointer for GetNextUser()
// 
//////////////////////////////////////////////////////////////////////
{

    PUSERSONFILE   TmpItem = (PUSERSONFILE) Token;
    PVOID   RetPointer = NULL;

    while ( TmpItem ){

        if ( TmpItem->Flag & USERREMOVED ){
            TmpItem = TmpItem->Next;
            continue;
        }

        try{    
            LPWSTR     HashString = NULL;

            UserName = TmpItem->UserName;

            if (TmpItem->Flag & USERINFILE){

                PEFS_HASH_BLOB UserHashBlob;

                UserHashBlob = (PEFS_HASH_BLOB)TmpItem->Cert;
                HashString = new WCHAR[((((UserHashBlob->cbData + 1)/2) * 5) + 1)];
                if (HashString) {
                    ConvertHashToStr(UserHashBlob->pbData, UserHashBlob->cbData, HashString);
                }

            } else if ( TmpItem->Context ){

                DWORD cbHash;
                PBYTE pbHash;

                if (CertGetCertificateContextProperty(
                             (PCCERT_CONTEXT)TmpItem->Context,
                             CERT_HASH_PROP_ID,
                             NULL,
                             &cbHash
                             )) {

                    pbHash = (PBYTE)new BYTE[cbHash];

                    if (pbHash != NULL) {

                        if (CertGetCertificateContextProperty(
                                     (PCCERT_CONTEXT)TmpItem->Context,
                                     CERT_HASH_PROP_ID,
                                     pbHash,
                                     &cbHash
                                     )) {

                            HashString = new WCHAR[((((cbHash + 1)/2) * 5) + 1)];
                            if (HashString) {
                                ConvertHashToStr(pbHash, cbHash, HashString);
                            }
                        }
                      
                        delete [] pbHash;

                    }
                }

            }
            
            CertHash = HashString;
            if (HashString){
                delete [] HashString;
            }
            RetPointer = TmpItem->Next;
        }
        catch (...){

            //
            // Out of memory
            //

            TmpItem = NULL;
            RetPointer = NULL;
        }
        break;
    }

    if ( NULL == TmpItem ){
        UserName.Empty();
        CertHash.Empty();
    }
    return RetPointer;

}

DWORD CUsers::GetUserAddedCnt()
{
    return m_UserAddedCnt;
}

DWORD CUsers::GetUserRemovedCnt()
{
    return m_UserRemovedCnt;
}

PVOID
CUsers::GetNextChangedUser(
    PVOID Token, 
    LPTSTR * UserName,
    PSID * UserSid, 
    PVOID * CertData, 
    DWORD * Flag
    )
//////////////////////////////////////////////////////////////////////
// Routine Description:
//      Get the info for changed users. This method is not well behaved in the
//  sense of OOP. It exposes internal pointers to the ouside world. The gain
//  is performance. At this moment, CUsers is a supporting class and used only
//  by USERLIST and CAddSheet (single thread). We can make USERLIST a 
//  friend of CUsers if such concerns are raised in the future or reimplement this. 
//  The same issue applies to the enumerate methods.
//
// Arguments:
//      Token -- A pointer to the item returned in previous GetNextChangedUser or StartEnum.
//      UserName -- User's name
//      CertData -- User's certificate blob or hash
//      UserSid -- User's ID. Can be NULL
//      Flag -- Indicate if the item is existing in the file, to be added or removed
//  Return Value:
//      Next item pointer.
// 
//////////////////////////////////////////////////////////////////////
{
    BOOL    ChangedUserFound = FALSE;

    while ( Token ){

        *Flag = ((PUSERSONFILE) Token)->Flag;

        if ( ( *Flag & USERADDED ) && !( *Flag & USERREMOVED )){

            //
            // The user is to to be added to the file
            //

            *Flag = USERADDED;
            ChangedUserFound = TRUE;

        } else if ( ( *Flag & USERREMOVED ) && ( *Flag & USERINFILE)){

            //
            // The user is to be removed from the file
            //

            *Flag = USERREMOVED;
            ChangedUserFound = TRUE;

        }

        if ( ChangedUserFound ){

            *UserName = ((PUSERSONFILE) Token)->UserName;
            *UserSid = ((PUSERSONFILE) Token)->UserSid;
            *CertData = ((PUSERSONFILE) Token)->Cert;
            return ((PUSERSONFILE) Token)->Next;

        } else {

            Token = ((PUSERSONFILE) Token)->Next;

        }

    }

    *UserName = NULL;
    *UserSid = NULL;
    *CertData = NULL;
    *Flag = 0;
    return NULL;
}

void CUsers::Clear()
{

    PUSERSONFILE TmpUserItem = m_UsersRoot;
    while (TmpUserItem){
        m_UsersRoot = TmpUserItem->Next;
        delete [] TmpUserItem->UserName;
        delete [] TmpUserItem->Cert;
        if (TmpUserItem->UserSid){
            delete [] TmpUserItem->UserSid;
        }
        if (TmpUserItem->Context){
            CertFreeCertificateContext((PCCERT_CONTEXT)TmpUserItem->Context);
        }
        delete TmpUserItem;
        TmpUserItem = m_UsersRoot;
    }

    m_UsersRoot = NULL;
	m_UserAddedCnt = 0;
	m_UserRemovedCnt = 0;

}

void CUsers::ConvertHashToStr(
    PBYTE pHashData,
    DWORD cbData,
    LPWSTR OutHashStr
    )
{

    DWORD Index = 0;
    BOOLEAN NoLastZero = FALSE;

    for (; Index < cbData; Index+=2) {

        BYTE HashByteLow = pHashData[Index] & 0x0f;
        BYTE HashByteHigh = (pHashData[Index] & 0xf0) >> 4;

        OutHashStr[Index * 5/2] = HashByteHigh > 9 ? (WCHAR)(HashByteHigh - 9 + 0x40): (WCHAR)(HashByteHigh + 0x30);
        OutHashStr[Index * 5/2 + 1] = HashByteLow > 9 ? (WCHAR)(HashByteLow - 9 + 0x40): (WCHAR)(HashByteLow + 0x30);

        if (Index + 1 < cbData) {
            HashByteLow = pHashData[Index+1] & 0x0f;
            HashByteHigh = (pHashData[Index+1] & 0xf0) >> 4;
    
            OutHashStr[Index * 5/2 + 2] = HashByteHigh > 9 ? (WCHAR)(HashByteHigh - 9 + 0x40): (WCHAR)(HashByteHigh + 0x30);
            OutHashStr[Index * 5/2 + 3] = HashByteLow > 9 ? (WCHAR)(HashByteLow - 9 + 0x40): (WCHAR)(HashByteLow + 0x30);
    
            OutHashStr[Index * 5/2 + 4] = L' ';

        } else {
            OutHashStr[Index * 5/2 + 2] = 0;
            NoLastZero = TRUE;
        }

    }

    if (!NoLastZero) {
        OutHashStr[Index*5/2] = 0;
    }

}
