/*******************************************************
    MultiUsr.cpp

    Code for handling multiple user functionality in IE
    and friends

    Initially by Christopher Evans (cevans) 4/28/98
********************************************************/

#define DONT_WANT_SHELLDEBUG
#include "private.h"
#include "resource.h"
#include "multiusr.h"
#include <assert.h>
#include "multiutl.h"
#include "strconst.h"
#include "Shlwapi.h"
#include "multiui.h"
#include <shlobj.h>
#include "mluisup.h"
#include <lmwksta.h>

TCHAR g_szRegRoot[MAX_PATH] = "";
extern HINSTANCE g_hInst;
static void _CreateIdentitiesFolder();


// add a backslash to a qualified path
//
// in:
//  lpszPath    path (A:, C:\foo, etc)
//
// out:
//  lpszPath    A:\, C:\foo\    ;
//
// returns:
//  pointer to the NULL that terminates the path 

// this is here to avoid a dependancy on shlwapi.dll
#define CH_WHACK TEXT('\\')

STDAPI_(LPTSTR)
_PathAddBackslash(
    LPTSTR lpszPath)
{
    LPTSTR lpszEnd;

    // perf: avoid lstrlen call for guys who pass in ptr to end
    // of buffer (or rather, EOB - 1).
    // note that such callers need to check for overflow themselves.
    int ichPath = (*lpszPath && !*(lpszPath + 1)) ? 1 : lstrlen(lpszPath);

    // try to keep us from tromping over MAX_PATH in size.
    // if we find these cases, return NULL.  Note: We need to
    // check those places that call us to handle their GP fault
    // if they try to use the NULL!
    if (ichPath >= (MAX_PATH - 1))
    {
        Assert(FALSE);      // Let the caller know!        
        return(NULL);
    }

    lpszEnd = lpszPath + ichPath;

    // this is really an error, caller shouldn't pass
    // an empty string
    if (!*lpszPath)
        return lpszEnd;

    /* Get the end of the source directory
    */
    switch(*CharPrev(lpszPath, lpszEnd)) {
    case CH_WHACK:
        break;

    default:
        *lpszEnd++ = CH_WHACK;
        *lpszEnd = TEXT('\0');
    }
    return lpszEnd;
}


STDAPI_(DWORD)
_SHGetValueA(
    IN  HKEY    hkey,
    IN  LPCSTR  pszSubKey,          OPTIONAL
    IN  LPCSTR  pszValue,           OPTIONAL
    OUT LPDWORD pdwType,            OPTIONAL
    OUT LPVOID  pvData,             OPTIONAL
    OUT LPDWORD pcbData)            OPTIONAL
{
    DWORD dwRet;
    HKEY hkeyNew;

    dwRet = RegOpenKeyExA(hkey, pszSubKey, 0, KEY_QUERY_VALUE, &hkeyNew);
    if (NO_ERROR == dwRet)
    {
        dwRet = RegQueryValueEx(hkeyNew, pszValue, NULL, pdwType, (LPBYTE)pvData, pcbData);
        RegCloseKey(hkeyNew);
    }
    else if (pcbData)
        *pcbData = 0;

    return dwRet;
}

/*----------------------------------------------------------
Purpose: Recursively delete the key, including all child values
         and keys.  Mimics what RegDeleteKey does in Win95.

Returns: 
Cond:    --
*/
DWORD
_DeleteKeyRecursively(
    IN HKEY   hkey, 
    IN LPCSTR pszSubKey)
{
    DWORD dwRet;
    HKEY hkSubKey;

    // Open the subkey so we can enumerate any children
    dwRet = RegOpenKeyExA(hkey, pszSubKey, 0, MAXIMUM_ALLOWED, &hkSubKey);
    if (ERROR_SUCCESS == dwRet)
    {
        DWORD   dwIndex;
        CHAR    szSubKeyName[MAX_PATH + 1];
        DWORD   cchSubKeyName = ARRAYSIZE(szSubKeyName);
        CHAR    szClass[MAX_PATH];
        DWORD   cbClass = ARRAYSIZE(szClass);

        // I can't just call RegEnumKey with an ever-increasing index, because
        // I'm deleting the subkeys as I go, which alters the indices of the
        // remaining subkeys in an implementation-dependent way.  In order to
        // be safe, I have to count backwards while deleting the subkeys.

        // Find out how many subkeys there are
        dwRet = RegQueryInfoKeyA(hkSubKey,
                                 szClass,
                                 &cbClass,
                                 NULL,
                                 &dwIndex, // The # of subkeys -- all we need
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL);

        if (NO_ERROR == dwRet)
        {
            // dwIndex is now the count of subkeys, but it needs to be
            // zero-based for RegEnumKey, so I'll pre-decrement, rather
            // than post-decrement.
            while (ERROR_SUCCESS == RegEnumKeyA(hkSubKey, --dwIndex, szSubKeyName, cchSubKeyName))
            {
                _DeleteKeyRecursively(hkSubKey, szSubKeyName);
            }
        }

        RegCloseKey(hkSubKey);

        dwRet = RegDeleteKeyA(hkey, pszSubKey);
    }

    return dwRet;
}

// ****************************************************************************************************
//  C   S   T   R   I   N   G   L   I   S   T       C   L   A   S   S
//
//  A really basic string list class.  Actually, its a string array class, but you don't need to know
//  that.  It could do so much more, but for now, it only maintains an array of C strings.
//


CStringList::CStringList()
{
    m_count = 0;
    m_ptrCount = 0;
    m_strings = NULL;
}

/*
    CStringList::~CStringList

    Clean up any memory that was allocated in the CStringList object
*/
CStringList::~CStringList()
{
    if (m_strings)
    {
        for (int i = 0; i < m_count; i++)
        {
            if (m_strings[i])
            {
                MemFree(m_strings[i]);
                m_strings[i] = NULL;
            }
        }
        MemFree(m_strings);
        m_strings = NULL;
        m_count = 0;
    }
}


/*
    CStringList::AddString

    Add a string to the end of the string list.
*/
void    CStringList::AddString(TCHAR* lpszInString)
{
    // make more room for pointers, if necessary
    if (m_ptrCount == m_count)
    {
        m_ptrCount += 5;
        if (!MemRealloc((void **)&m_strings, sizeof(TCHAR *) * m_ptrCount))
        {
            m_ptrCount -= 5;
            Assert(false);
            return;
        }

        // initialize the new strings to nil
        for (int i = m_count; i < m_ptrCount; i++)
            m_strings[i] = NULL;

    }
    
    //now put the string in the next location
    int iNewIndex = m_count++;

    if(MemAlloc((void **)&m_strings[iNewIndex], sizeof(TCHAR) * lstrlen(lpszInString)+1))
    {
        lstrcpy(m_strings[iNewIndex], lpszInString);
    }
    else
    {
        // couldn't allocate space for the string.  Don't count that spot as filled
        m_count--;
    }
}

/*
    CStringList::RemoveString
    
    Remove a string at zero based index iIndex 
*/

void    CStringList::RemoveString(int   iIndex)
{
    int     iCopySize;

    iCopySize = ((m_count - iIndex) - 1) * 4;

    // free the memory for the string
    if (m_strings[iIndex])
    {
        MemFree(m_strings[iIndex]);
        m_strings[iIndex] = NULL;
    }

    // move the other strings down
    if (iCopySize)
    {
        memmove(&(m_strings[iIndex]), &(m_strings[iIndex+1]), iCopySize);
    }

    // null out the last item in the list and decrement the counter.
    m_strings[--m_count] = NULL;
}

/*
    CStringList::GetString
    
    Return the pointer to the string at zero based index iIndex.

    Return the string at the given index.  Note that the TCHAR pointer
    is still owned by the string list and should not be deleted.
*/

TCHAR    *CStringList::GetString(int iIndex)
{
    if (iIndex < m_count && iIndex >= 0)
        return m_strings[iIndex];
    else
        return NULL;
}


int __cdecl _CSL_Compare(const void *p1, const void *p2)
{
    TCHAR *psz1, *psz2;

    psz1 = *((TCHAR **)p1);
    psz2 = *((TCHAR **)p2);

    return lstrcmpi(psz1, psz2);
}


/*
    CStringList::Sort
    
    Sort the strings in the list
*/

void    CStringList::Sort()
{
    qsort(m_strings, m_count, sizeof(TCHAR *), _CSL_Compare);
}


/*
    MU_Init

    Initialize the memory allocator and make sure that there is
    at least one user in the registry.
*/
static BOOL g_inited = FALSE;
EXTERN_C void    MU_Init()
{
    CStringList* pList;

    if (!g_inited)
    {
        pList = MU_GetUsernameList();

        if (!pList || pList->GetLength() == 0)
        {
            _MakeDefaultFirstUser();
        }
        if (pList)
            delete pList;
        g_inited = TRUE;
    }
}


/*
    MU_GetUsernameList
    
    Build a CStringList with all of the names of the users 
    stored in HKLM
*/
#define MAXKEYNAME          256

CStringList*    MU_GetUsernameList(void)
{
    CStringList*    vList = NULL;
    HKEY    hSourceSubKey;
    DWORD   dwEnumIndex = 0, dwStatus, dwSize, dwType;
    int     cb;
    TCHAR    szKeyNameBuffer[MAXKEYNAME];
    DWORD dwIdentityOrdinal = 1;

    vList = new CStringList;
    Assert(vList);
    
    if (!vList)
        goto exit;
        
    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hSourceSubKey) != ERROR_SUCCESS)
    {
        AssertSz(FALSE, "Couldn't open user profiles root Key");
        goto exit;
    }

    dwSize = sizeof(dwIdentityOrdinal);
    RegQueryValueEx(hSourceSubKey, c_szIdentityOrdinal, NULL, &dwType, (LPBYTE)&dwIdentityOrdinal, &dwSize);

    while (TRUE) 
    {
        DWORD   dwOrdinal;
        HKEY    hkUserKey;
        if (RegEnumKey(hSourceSubKey, dwEnumIndex++, szKeyNameBuffer,MAXKEYNAME)!=  ERROR_SUCCESS)
            break;

        cb = lstrlen(szKeyNameBuffer);
            
        if (RegOpenKey(hSourceSubKey, szKeyNameBuffer, &hkUserKey) == ERROR_SUCCESS)
        {
            dwSize = sizeof(szKeyNameBuffer);
            dwStatus = RegQueryValueEx(hkUserKey, c_szUsername, NULL, &dwType, (LPBYTE)&szKeyNameBuffer, &dwSize);
                
            Assert(ERROR_SUCCESS == dwStatus);
            Assert(*szKeyNameBuffer != 0);
            //filter names that begin with _ to hide things like "_Outlook News"
            if (ERROR_SUCCESS == dwStatus && *szKeyNameBuffer != '_')
                vList->AddString(szKeyNameBuffer);

            dwSize = sizeof(dwOrdinal);
            dwStatus = RegQueryValueEx(hkUserKey, c_szIdentityOrdinal, NULL, &dwType, (LPBYTE)&dwOrdinal, &dwSize);
            if (dwStatus==ERROR_SUCCESS)
            {
                if (dwOrdinal>=dwIdentityOrdinal)
                {
                    dwIdentityOrdinal = dwOrdinal+1;
                    AssertSz(FALSE, "MaxOrdinal is smaller than this identity. Why?");
                }
            }
            else
            {
                dwStatus = RegSetValueEx(hkUserKey, c_szIdentityOrdinal, NULL, REG_DWORD, (LPBYTE)&dwIdentityOrdinal, dwSize);
                dwIdentityOrdinal++;
            }
            Assert(ERROR_SUCCESS == dwStatus);
            
            RegCloseKey(hkUserKey); 
        }
        else
            AssertSz(FALSE, "Couldn't open user's Key");
    }

    dwSize = sizeof(dwIdentityOrdinal);
    if (RegSetValueEx(hSourceSubKey, c_szIdentityOrdinal, 0, REG_DWORD, (LPBYTE)&dwIdentityOrdinal, dwSize)!=ERROR_SUCCESS)
    {
        AssertSz(FALSE, "Couldn't set the identity ordinal");
    }

    RegCloseKey(hSourceSubKey);

exit:
    return vList;
}


/*
    MU_UsernameToUserId

    Given a username, find its user id and return it.  Returns E_FAIL if it can't 
    find the given username.
*/

HRESULT   MU_UsernameToUserId(TCHAR *lpszUsername, GUID *puidID)
{
    HKEY    hSourceSubKey;
    ULONG   ulEnumIndex = 0;
    DWORD   dwStatus, dwSize, dwType;
    TCHAR    szKeyNameBuffer[MAXKEYNAME];
    BOOL    fFound = FALSE;
    TCHAR    szUid[255];

    ZeroMemory(puidID, sizeof(GUID));

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hSourceSubKey) == ERROR_SUCCESS)
    {
        while (!fFound) 
        {
            HKEY    hkUserKey;

            if (RegEnumKey(hSourceSubKey, ulEnumIndex++, szKeyNameBuffer,MAXKEYNAME)
                !=  ERROR_SUCCESS)
                break;
            
            if (RegOpenKey(hSourceSubKey, szKeyNameBuffer, &hkUserKey) == ERROR_SUCCESS)
            {
                dwSize = sizeof(szKeyNameBuffer);
                dwStatus = RegQueryValueEx(hkUserKey, c_szUsername, NULL, &dwType, (LPBYTE)&szKeyNameBuffer, &dwSize);
                
                if (ERROR_SUCCESS == dwStatus && lstrcmpi(lpszUsername, szKeyNameBuffer) == 0)
                {
                    dwSize = sizeof(szUid);
                    dwStatus = RegQueryValueEx(hkUserKey, c_szUserID, NULL, &dwType, (LPBYTE)&szUid, &dwSize);
                    fFound = (dwStatus == ERROR_SUCCESS);

                    if (fFound)
                        fFound = SUCCEEDED(GUIDFromAString(szUid, puidID));
                }
                RegCloseKey(hkUserKey); 
            }
        }
        RegCloseKey(hSourceSubKey);
    }
    

    return (fFound ? S_OK : E_FAIL);
}

/*
    MU_GetPasswordForUsername

    Get the password for the provided user and return it in szOutPassword.  Return in 
    pfUsePassword if password is enabled and false if it is disabled.

    Function returns true if the password data could be found, false otherwise
*/

BOOL  MU_GetPasswordForUsername(TCHAR *lpszInUsername, TCHAR *szOutPassword, BOOL *pfUsePassword)
{
#ifdef IDENTITY_PASSWORDS
    TCHAR           szPath[MAX_PATH];
    TCHAR           szPassword[255] = "";
    HKEY            hDestinationSubKey;
    DWORD           dwSize, dwStatus, dwType;
    DWORD           dwPWEnabled = 0;
    GUID            uidUserID;
    HRESULT         hr;
    PASSWORD_STORE  pwStore;

    hr = MU_UsernameToUserId(lpszInUsername, &uidUserID);
    Assert(SUCCEEDED(hr));
    
    if (uidUserID == GUID_NULL)
    {
        *pfUsePassword = FALSE;
        return TRUE;
    }
    
    if (SUCCEEDED(hr = ReadIdentityPassword(&uidUserID, &pwStore)))
    {
        lstrcpy(szOutPassword, pwStore.szPassword);
        *pfUsePassword = pwStore.fUsePassword;
        return TRUE;
    }
    else
    {
        BOOL fFoundPassword = FALSE;
        
        //build the user level key. 
        MU_GetRegRootForUserID(&uidUserID, szPath);
    
        if (RegCreateKey(HKEY_CURRENT_USER, szPath, &hDestinationSubKey) == ERROR_SUCCESS)
        {
            dwSize = sizeof(dwPWEnabled);
            dwStatus = RegQueryValueEx(hDestinationSubKey, c_szUsePassword, NULL, &dwType, (LPBYTE)&dwPWEnabled, &dwSize);
        
            if (ERROR_SUCCESS == dwStatus && 0 != dwPWEnabled)
            {
                dwSize = sizeof(szPassword);
                dwStatus = RegQueryValueEx(hDestinationSubKey, c_szPassword, NULL, &dwType, (LPBYTE)&szPassword, &dwSize);
        
                if (ERROR_SUCCESS == dwStatus)
                {
                    ULONG   cbSize;

                    fFoundPassword = TRUE;
                    cbSize = dwSize;
                    if (cbSize > 1)
                    {
                        DecodeUserPassword(szPassword, &cbSize);
                        strcpy(szOutPassword, szPassword);  
                    }
                    else
                    {
                        *szOutPassword = 0;
                    }
                }
            }
        
            RegCloseKey(hDestinationSubKey);
        }

        // Herein lies the pull.  We can't count on being able to access any
        // given pstore from any given profile on Win9x.  If you log on with
        // a blank password, or hit escape (not much difference to a user)
        // you will have a different pstore.  If we store our passwords in the
        // registry, they can be whacked pretty simply.  If we can't find the 
        // password, we will disable it for now and say there is none.  It 
        // seems that most people don't put passwords on identities now 
        // anyway, though this will change. 
        if (!fFoundPassword)
        {
            fFoundPassword = TRUE;
            dwPWEnabled = 0;
        }
        // Here ends the pull
        
        *pfUsePassword = (dwPWEnabled != 0);
        return fFoundPassword;
    }
#else
    *pfUsePassword = FALSE;
    return TRUE;
#endif //IDENTITY_PASSWORDS

}

/*
    _FillListBoxWithUsernames

    Fill a listbox with the names of the users,  Adds (Default) 
    to the default user.
*/
BOOL _FillListBoxWithUsernames(HWND hListbox)
{
    CStringList *lpCStringList;
    GUID        uidDefault;
    GUID        uidUser;

    lpCStringList = MU_GetUsernameList();

    if (lpCStringList)
    {    
        MU_GetDefaultUserID(&uidDefault);

        SendMessage(hListbox, LB_RESETCONTENT, 0, 0);
        lpCStringList->Sort();

        if (lpCStringList)
        {
            for(int i = 0; i < lpCStringList->GetLength(); i++)
            {
                if (lpCStringList->GetString(i))
                {
                    SendMessage(hListbox, LB_ADDSTRING, 0, (LPARAM)lpCStringList->GetString(i));
                }
            }
            delete lpCStringList;
            return true;
        }
    }
    return false;
}

BOOL _FillComboBoxWithUsernames(HWND hCombobox, HWND hListbox)
{
    TCHAR szRes[128];
    DWORD_PTR cIndex, dwCount = SendMessage(hListbox, LB_GETCOUNT, 0, 0);

    SendMessage(hCombobox, CB_RESETCONTENT, 0, 0);

    for (cIndex = 0; cIndex < dwCount; cIndex++)
    {
        SendMessage(hListbox, LB_GETTEXT, cIndex, (LPARAM)szRes);
        SendMessage(hCombobox, CB_ADDSTRING, 0, (LPARAM)szRes);
    }
    return true;
}

/*
    MU_UsernameExists
    
    Does the given name already exist as a username?
*/

BOOL        MU_UsernameExists(TCHAR*    lpszUsername)
{
    GUID uidID;
    
    return SUCCEEDED(MU_UsernameToUserId(lpszUsername, &uidID));

}

/*
    MU_GetUserInfo
    
    Fill in the user info structure with current values
*/

BOOL    MU_GetUserInfo(GUID *puidUserID, LPUSERINFO lpUserInfo)
{
    TCHAR           szPWBuffer[255];
    TCHAR           szRegPath[MAX_PATH];
    HKEY            hKey;
    BOOL            bResult = false;
    LONG            lValue;
    DWORD           dwStatus, dwType, dwSize;
    GUID            uidUser;
    TCHAR           szUid[255];
    HRESULT         hr;
    PASSWORD_STORE  pwStore;

    lpUserInfo->fPasswordValid = FALSE;
    
    if( puidUserID == NULL)
    {
        MU_GetCurrentUserID(&uidUser);
        if (uidUser == GUID_NULL)
            return FALSE;
    }
    else
        uidUser = *puidUserID;

    MU_GetRegRootForUserID(&uidUser, szRegPath);
    
    if (RegOpenKey(HKEY_CURRENT_USER, szRegPath, &hKey) == ERROR_SUCCESS)
    {
        *lpUserInfo->szPassword = 0;
        lpUserInfo->fUsePassword = false;
        ZeroMemory(&lpUserInfo->uidUserID, sizeof(GUID));

        dwSize = sizeof(lpUserInfo->szUsername);
        if ((dwStatus = RegQueryValueEx(hKey, c_szUsername, NULL, &dwType, (LPBYTE)lpUserInfo->szUsername, &dwSize)) == ERROR_SUCCESS &&
                (0 != *lpUserInfo->szUsername))
        {
            //we have the username, that is the only required part.  The others are optional.
            bResult = true;
            
#ifdef IDENTITY_PASSWORDS
            lpUserInfo->fPasswordValid = FALSE;
            if (SUCCEEDED(hr = ReadIdentityPassword(&uidUser, &pwStore)))
            {
                lstrcpy(lpUserInfo->szPassword, pwStore.szPassword);
                lpUserInfo->fUsePassword = pwStore.fUsePassword;
                lpUserInfo->fPasswordValid = TRUE;
            }
            else
            {
                dwSize = sizeof(lValue);
                if ((dwStatus = RegQueryValueEx(hKey, c_szUsePassword, NULL, &dwType, (LPBYTE)&lValue, &dwSize)) == ERROR_SUCCESS)
                {
                    lpUserInfo->fUsePassword = (lValue != 0);
                }

                dwSize = sizeof(szPWBuffer);
                dwStatus = RegQueryValueEx(hKey, c_szPassword, NULL, &dwType, (LPBYTE)szPWBuffer, &dwSize);

                ULONG   cbSize;

                lpUserInfo->fPasswordValid = (ERROR_SUCCESS == dwStatus);

                // Herein lies the pull (Volume 2).  We can't count on being able to access any
                // given pstore from any given profile on Win9x.  If you log on with
                // a blank password, or hit escape (not much difference to a user)
                // you will have a different pstore.  If we store our passwords in the
                // registry, they can be whacked pretty simply.  If we can't find the 
                // password, we will disable it for now and say there is none.  It 
                // seems that most people don't put passwords on identities now 
                // anyway, though this will change.  
                if (!lpUserInfo->fPasswordValid)
                {
                    lpUserInfo->fPasswordValid = TRUE;
                    lpUserInfo->fUsePassword = FALSE;
                }
                // Here ends the pull

                cbSize = dwSize;
                if (ERROR_SUCCESS == dwStatus && cbSize > 1)
                {
                    DecodeUserPassword(szPWBuffer, &cbSize);
                    strcpy(lpUserInfo->szPassword, szPWBuffer);
                }
                else
                    *lpUserInfo->szPassword = 0;
            }
#endif 
            dwSize = sizeof(szUid);
            if ((dwStatus = RegQueryValueEx(hKey, c_szUserID, NULL, &dwType, (LPBYTE)&szUid, &dwSize)) == ERROR_SUCCESS)
            {
                hr = GUIDFromAString(szUid, &lpUserInfo->uidUserID);
                Assert(hr);
            }

        }
        RegCloseKey(hKey);
    }
        
    return bResult;
}


/*
    MU_SetUserInfo
    
    Save the user info structure with the user values
*/
BOOL        MU_SetUserInfo(LPUSERINFO lpUserInfo)
{
    DWORD           dwType, dwSize, dwValue, dwStatus;
    HKEY            hkCurrUser;
    TCHAR           szPath[MAX_PATH];
    WCHAR           szwPath[MAX_PATH];
    TCHAR           szUid[255];
    BOOL            fNewIdentity = FALSE;
    PASSWORD_STORE  pwStore;
    HRESULT         hr;

    MU_GetRegRootForUserID(&lpUserInfo->uidUserID, szPath);
    
    Assert(pszRegPath && *pszRegPath);
    Assert(lpUserInfo->uidUserID != GUID_NULL);
    
    if ((dwStatus = RegCreateKey(HKEY_CURRENT_USER, szPath, &hkCurrUser)) == ERROR_SUCCESS)
    {
        ULONG   cbSize;
        TCHAR   szBuffer[255];

        // write out the correct values
        dwType = REG_SZ;
        dwSize = lstrlen(lpUserInfo->szUsername) + 1;
        RegSetValueEx(hkCurrUser, c_szUsername, 0, dwType, (LPBYTE)lpUserInfo->szUsername, dwSize);

        dwSize = sizeof(DWORD);
        if ((dwStatus = RegQueryValueEx(hkCurrUser, c_szDirName, NULL, &dwType, (LPBYTE)&dwValue, &dwSize)) != ERROR_SUCCESS)
        {
            dwValue = MU_GenerateDirectoryNameForIdentity(&lpUserInfo->uidUserID);
        
            dwType = REG_DWORD;
            dwSize = sizeof(dwValue);
            RegSetValueEx(hkCurrUser, c_szDirName, 0, dwType, (LPBYTE)&dwValue, dwSize);
            fNewIdentity = TRUE;
        }

#ifdef IDENTITY_PASSWORDS
        lstrcpy(pwStore.szPassword, lpUserInfo->szPassword);
        pwStore.fUsePassword = lpUserInfo->fUsePassword;

        if (FAILED(hr = WriteIdentityPassword(&lpUserInfo->uidUserID, &pwStore)))
        {
            dwType = REG_BINARY ;
            cbSize = strlen(lpUserInfo->szPassword) + 1;
            lstrcpy(szBuffer, lpUserInfo->szPassword);
            EncodeUserPassword(szBuffer, &cbSize);
            dwSize = cbSize;
            RegSetValueEx(hkCurrUser, c_szPassword, 0, dwType, (LPBYTE)szBuffer, dwSize);
        
            dwType = REG_DWORD;
            dwValue = (lpUserInfo->fUsePassword ? 1 : 0);
            dwSize = sizeof(dwValue);
            RegSetValueEx(hkCurrUser, c_szUsePassword, 0, dwType, (LPBYTE)&dwValue, dwSize);
        }
        else
        {
            //don't keep the registry values if we could save it to the pstore.
            RegDeleteValue(hkCurrUser, c_szPassword);
            RegDeleteValue(hkCurrUser, c_szUsePassword);
        }
#endif //IDENTITY_PASSWORDS

        Assert(lpUserInfo->uidUserID != GUID_NULL);
        AStringFromGUID(&lpUserInfo->uidUserID,  szUid, ARRAYSIZE(szUid));

        dwType = REG_SZ;
        dwSize = lstrlen(szUid) + 1;
        RegSetValueEx(hkCurrUser, c_szUserID, 0, dwType, (LPBYTE)&szUid, dwSize);

        RegCloseKey(hkCurrUser);

        if (fNewIdentity)
        {
            if (SUCCEEDED(MU_GetUserDirectoryRoot(&lpUserInfo->uidUserID, GIF_ROAMING_FOLDER, szwPath, MAX_PATH)))
            {
                if (!CreateDirectoryWrapW(szwPath,NULL))
                {
                    _CreateIdentitiesFolder();
                    CreateDirectoryWrapW(szwPath,NULL);
                }
            }
            
            if (SUCCEEDED(MU_GetUserDirectoryRoot(&lpUserInfo->uidUserID, GIF_NON_ROAMING_FOLDER, szwPath, MAX_PATH)))
            {
                if (!CreateDirectoryWrapW(szwPath,NULL))
                {
                    _CreateIdentitiesFolder();
                    CreateDirectoryWrapW(szwPath,NULL);
                }
            }
        }
        return TRUE;
    }
    return FALSE;
}

/*
    MU_SwitchToUser

    Currently, this just saves the last user's info.
*/
HRESULT  MU_SwitchToUser(TCHAR *lpszUsername)
{
    GUID    uidUserID;
    TCHAR    szUid[255];
    HRESULT hr;

    Assert(lpszUsername);
    
    if (*lpszUsername == 0) //  null string means null guid
    {
        uidUserID = GUID_NULL;
    }
    else
    {
        hr = MU_UsernameToUserId(lpszUsername, &uidUserID);
        if (FAILED(hr))
            return hr;
    }


    AStringFromGUID(&uidUserID,  szUid, ARRAYSIZE(szUid));
    Assert(uidUserID != GUID_NULL || (*lpszUsername == 0));

    wsprintf(g_szRegRoot, "%.100s\\%.40s", c_szRegRoot, szUid);
    
    // remember who we last switched to
    HKEY    hkey;
    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        DWORD   dwType, dwSize;

        dwType = REG_SZ;
        dwSize = lstrlen(lpszUsername) + 1;
        RegSetValueEx(hkey, c_szLastUserName, 0, dwType, (LPBYTE)lpszUsername, dwSize);

        dwType = REG_SZ;
        dwSize = lstrlen(szUid) + 1;
        RegSetValueEx(hkey, c_szLastUserID, 0, dwType, (LPBYTE)szUid, dwSize);

        RegCloseKey(hkey);
    }

    return S_OK;
}

/*
    MU_SwitchToLastUser

    Makes the last user current, if there is no
    last user, it switches to the first user it can
    find. If there are no users, it creates a 
    user called "Main User"
*/
void MU_SwitchToLastUser()
{
    HKEY    hkey;
    TCHAR   szUserUid[255];
    TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH + 1];
    BOOL    fSwitched = FALSE;
    GUID    uidUserId;

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        DWORD   dwType, dwStatus, dwSize;
        dwSize = sizeof(szUserUid);
        dwStatus = RegQueryValueEx(hkey, c_szLastUserID, NULL, &dwType, (LPBYTE)szUserUid, &dwSize);
        
        RegCloseKey(hkey);

        if (ERROR_SUCCESS == dwStatus && SUCCEEDED(GUIDFromAString(szUserUid, &uidUserId)) && 
                    SUCCEEDED(MU_UserIdToUsername(&uidUserId, szUsername, CCH_USERNAME_MAX_LENGTH)))
        {
            MU_SwitchToUser(szUsername);
            fSwitched = true;
        }
    }

    if (!fSwitched)
    {
        LPSTR   pszName;

        CStringList*    pList = MU_GetUsernameList();
        
        if (pList)
        {
            DWORD   dwIndex, dwLen = pList->GetLength();

            // find the first non hidden user and switch to them
            for (dwIndex = 0; dwIndex < dwLen; dwIndex++)
            {
                pszName = pList->GetString(dwIndex);
                
                if (pszName && *pszName  && *pszName != '_')
                {
                    MU_SwitchToUser(pszName);
                    fSwitched = TRUE;
                    break;
                }
            }
            delete pList;
        }
    }

    if (!fSwitched)
    {
        _MakeDefaultFirstUser();
        CStringList*    pList = MU_GetUsernameList();
        
        if (pList && pList->GetLength() > 0)
            MU_SwitchToUser(pList->GetString(0));
        
        if (pList)
            delete pList; 
    }
}

/*
    _CreateIdentitiesFolder

    Create the parent folder of all of the identities folders.
*/

static void _CreateIdentitiesFolder()
{
    HRESULT     hr;
    TCHAR       szAppDir[MAX_PATH], szSubDir[MAX_PATH], *psz;
    DWORD       dw, type;

    hr = E_FAIL;

    dw = MAX_PATH;


    if (ERROR_SUCCESS == _SHGetValueA(HKEY_CURRENT_USER, c_szRegFolders, c_szValueAppData, &type, (LPBYTE)szAppDir, &dw))
    {
        lstrcpy(szSubDir, c_szIdentitiesFolderName);
        psz = _PathAddBackslash(szSubDir);
        if (psz)
        {
            psz = _PathAddBackslash(szAppDir);
            if (psz)
            {
                lstrcpy(psz, szSubDir);

                psz = _PathAddBackslash(szAppDir);
        
                CreateDirectory(szAppDir, NULL);
            }
        }
    }
}


/*
    MU_GetCurrentUserDirectoryRoot

    Return the path to the top of the current user's root directory.
    This is the directory where the mail store should be located.
    It is in a subfolder the App Data folder.

    lpszUserRoot is a pointer to a character buffer that is cch chars
    in size.
*/
HRESULT MU_GetUserDirectoryRoot(GUID *uidUserID, DWORD dwFlags, WCHAR   *lpszwUserRoot, int cch)
{
    HRESULT         hr;
    WCHAR           szwSubDir[MAX_PATH], *pszw, szwUid[255]; 
    int             cb;
    DWORD           type, dwDirId;
    LPITEMIDLIST    pidl = NULL;
    IShellFolder   *psf = NULL;
    STRRET          str;
    IMalloc         *pMalloc = NULL;
    BOOL            fNeedHelp = FALSE;

    Assert(lpszUserRoot != NULL);
    Assert(uidUserID);
    Assert(cch >= MAX_PATH);
    Assert((dwFlags & (GIF_NON_ROAMING_FOLDER | GIF_ROAMING_FOLDER)));

    hr = MU_GetDirectoryIdForIdentity(uidUserID, &dwDirId);
    StringFromGUID2(*uidUserID, szwUid, ARRAYSIZE(szwUid));

    if (FAILED(hr))
        return hr;

    hr = SHGetMalloc(&pMalloc);
    Assert(pMalloc);
    if (!pMalloc)
        return E_OUTOFMEMORY;

    hr = E_FAIL;

    if (!!(dwFlags & GIF_NON_ROAMING_FOLDER))
    {
        hr = SHGetSpecialFolderLocation(GetDesktopWindow(), CSIDL_LOCAL_APPDATA, &pidl);
        
        if (FAILED(hr) || pidl == 0)
            hr = SHGetSpecialFolderLocation(GetDesktopWindow(), CSIDL_APPDATA, &pidl);

        if (FAILED(hr))
            fNeedHelp = TRUE;

    }
    else if (!!(dwFlags & GIF_ROAMING_FOLDER))
    {
        hr = SHGetSpecialFolderLocation(GetDesktopWindow(), CSIDL_APPDATA, &pidl);

        if (FAILED(hr))
            fNeedHelp = TRUE;
    }
    else
        hr = E_INVALIDARG;

    *lpszwUserRoot = 0;
    if (SUCCEEDED(hr) && pidl)
    {
        if (FAILED(hr = SHGetDesktopFolder(&psf)))
            goto exit;

        if (FAILED(hr = psf->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str)))
            goto exit;

        switch(str.uType)
        {
            case STRRET_WSTR:
                lstrcpyW(lpszwUserRoot, str.pOleStr);
                pMalloc->Free(str.pOleStr);
                break;

            case STRRET_OFFSET:
                MultiByteToWideChar(CP_ACP, 0, (LPSTR)pidl+str.uOffset, -1, lpszwUserRoot, cch-11);
                break;

            case STRRET_CSTR:
                MultiByteToWideChar(CP_ACP, 0, (LPSTR)str.cStr, -1, lpszwUserRoot, cch-11);
                break;

            default:
                Assert(FALSE);
                goto exit;
        }

        pszw = PathAddBackslashW(lpszwUserRoot);

        if (lstrlenW(lpszwUserRoot) < cch - 10)
        {
            StrCatW(pszw, L"Identities\\");
            StrCatW(pszw, szwUid);
            StrCatW(pszw, L"\\");
        }
        else
        {
            hr = E_OUTOFMEMORY;
            *lpszwUserRoot = 0;
        }
    }
    else if (fNeedHelp)
    {
        // $$$Review: NEIL QFE
        // SHGetSpecialFolderLocation(GetDesktopWindow(), CSIDL_APPDATA, &pidl) fails on non-SI OSR2.
        HKEY hkeySrc;
        DWORD cb;

        if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
                                          0, KEY_QUERY_VALUE, &hkeySrc))
        {
            // -1 for the backslash we may add
            cb = cch - 1;
            if (ERROR_SUCCESS == RegQueryValueExWrapW(hkeySrc, L"AppData", 0, NULL, (LPBYTE)lpszwUserRoot, &cb))
            {
                pszw = PathAddBackslashW(lpszwUserRoot);

                if (lstrlenW(lpszwUserRoot) < cch - 10)
                {
                    StrCatW(pszw, L"Identities\\");
                    StrCatW(pszw, szwUid);
                    StrCatW(pszw, L"\\");
                    hr = S_OK;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                    *lpszwUserRoot = 0;
                }
            }

            RegCloseKey(hkeySrc);
        }
    } 
exit:
    Assert(lstrlenW(lpszwUserRoot) > 0);
    SafeRelease(psf);
    pMalloc->Free(pidl);
    SafeRelease(pMalloc);

    return hr;
}


/*
    _ClaimNextUserId

    Get the next available user id.  Currently this means starting 
    with the CURRENT_USER GUID and changing the first DWORD of it
    until it is unique.  
*/
HRESULT   _ClaimNextUserId(GUID *puidId)
{
    ULONG   ulValue = 1;
    DWORD   dwType, dwSize, dwStatus;
    HKEY    hkeyProfiles;
    TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH+1];
    GUID    uid;
    FILETIME    ft;

    if (FAILED(CoCreateGuid(&uid)))
    {
        uid = UID_GIBC_CURRENT_USER;
        GetSystemTimeAsFileTime(&ft);
        uid.Data1 = ft.dwLowDateTime;

        //make sure it hasn't been used
        while (MU_UserIdToUsername(&uid, szUsername, CCH_USERNAME_MAX_LENGTH))
            uid.Data1 ++;
    }
    
    *puidId = uid;

    return S_OK;
}



BOOL MU_GetCurrentUserID(GUID *puidUserID)
{
    BOOL    fFound = FALSE;
    HKEY    hkey;
    GUID    uidUserId;
    TCHAR   szUid[255];

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        DWORD   dwSize;

        dwSize = 255;
        fFound = (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szLastUserID, 0, NULL, (LPBYTE)szUid, &dwSize));

        if (fFound)
            fFound = SUCCEEDED(GUIDFromAString(szUid, puidUserID));

        if (fFound && *puidUserID == GUID_NULL)
            fFound = false;

        RegCloseKey(hkey);
    }

#ifdef DEBUG
    TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH+1];

    Assert(MU_UserIdToUsername(puidUserID, szUsername, CCH_USERNAME_MAX_LENGTH));
#endif

    return fFound;
}

/*
    MU_UserIdToUsername

    Return the user name for the user whose user id is passed in.  Returns
    whether or not the user was found.
*/
BOOL MU_UserIdToUsername(GUID *puidUserID, TCHAR *lpszUsername, ULONG cch)
{
    HKEY    hkey;
    TCHAR   szPath[MAX_PATH];
    BOOL    fFound = FALSE;

    Assert(lpszUsername);
    lpszUsername[0] = 0;

    MU_GetRegRootForUserID(puidUserID, szPath);    
    Assert(*szPath);

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, szPath, 0, KEY_QUERY_VALUE, &hkey))
    {
        fFound = (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szUsername, 0, NULL, (LPBYTE)lpszUsername, &cch));
        RegCloseKey(hkey);
    }

    return fFound;
}

/*
    MU_CountUsers

    Returns the number of users currently configured.
*/
ULONG  MU_CountUsers(void)
{
    CStringList *psList;
    ULONG       ulCount = 0;

    psList = MU_GetUsernameList();

    if (psList)
    {
        ulCount = psList->GetLength();
        delete psList;
    }

    return ulCount;
}

/*
    MU_GetRegRootForUserid

    Get the reg root path for a given user id.
*/
HRESULT     MU_GetRegRootForUserID(GUID *puidUserID, LPSTR pszPath)
{
    TCHAR szUid[255];

    Assert(pszPath);
    Assert(puidUserID);

    AStringFromGUID(puidUserID,  szUid, ARRAYSIZE(szUid));
    wsprintf(pszPath, "%.100s\\%.40s", c_szRegRoot, szUid);

    return S_OK;
}

/*
    MU_GetDefaultUserID

    Get the user id for the user who is currently marked as the default user.
    Returns true if the proper user was found, false if not.
*/
BOOL MU_GetDefaultUserID(GUID *puidUserID)
{
    BOOL    fFound = FALSE;
    HKEY    hkey;
    TCHAR    szUid[255];

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        DWORD   dwSize;

        dwSize = sizeof(szUid);
        fFound = (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szDefaultUserID, 0, NULL, (LPBYTE)szUid, &dwSize));

        if (fFound)
            fFound = SUCCEEDED(GUIDFromAString(szUid, puidUserID));

        RegCloseKey(hkey);
    }

#ifdef DEBUG
    TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH+1];

    Assert(MU_UserIdToUsername(ulUserID, szUsername, CCH_USERNAME_MAX_LENGTH));
#endif

    return fFound;
}

/*
    MU_MakeDefaultUser

    Set the user referenced by id ulUserID to be the default user.
    The default user is referenced by certain applications which
    can only deal with one user. MS Phone is a good example.

*/
HRESULT MU_MakeDefaultUser(GUID *puidUserID)
{
    HRESULT hr = E_FAIL;
    TCHAR   szUid[255];
    // make sure the user exists and get their name to put in the 
    // Default Username reg key
    
    if (*puidUserID==GUID_NULL)
    {
        // We don't have a current user. So we'll have to figure out a new default user.
        LPSTR   pszName;
        CStringList*    pList = MU_GetUsernameList();
        
        if (pList)
        {
            DWORD   dwIndex, dwLen = pList->GetLength();

            // find the first non hidden user and switch to them
            for (dwIndex = 0; dwIndex < dwLen; dwIndex++)
            {
                pszName = pList->GetString(dwIndex);
                
                if (pszName && *pszName  && *pszName != '_')
                {
                    break;
                }
            }
            if (dwIndex==dwLen)
            {
                // Or, just create one
                delete pList;
                _MakeDefaultFirstUser();
                return S_OK;
            }
            MU_SwitchToUser(pszName);
            GUID    guid;    
            hr = MU_UsernameToUserId(pszName, &guid);
            if (SUCCEEDED(hr))
            {
                AStringFromGUID(&guid,  szUid, ARRAYSIZE(szUid));
            }
            delete pList;
        }
    }
    else
    {
        TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH+1];
        AStringFromGUID(puidUserID,  szUid, ARRAYSIZE(szUid));
        if (MU_UserIdToUsername(puidUserID, szUsername, CCH_USERNAME_MAX_LENGTH))
            hr = S_OK;
    }

    if (SUCCEEDED(hr))
    {        
        HKEY    hkey;
        if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
        {
            DWORD   dwType, dwSize;
            LONG    lError;

            dwType = REG_SZ;
            dwSize = lstrlen(szUid) + 1;
            lError = RegSetValueEx(hkey, c_szDefaultUserID, 0, dwType, (LPBYTE)szUid, dwSize);

            if (lError)
            {
                hr = E_FAIL;
                goto error;
            }

            hr = S_OK;

error:
            RegCloseKey(hkey);
            
        }
    }

    return hr;
}

/*
    MU_DeleteUser

    Remove a user from the registry.  This does not delete
    anything in the user's folder, but it does blow away
    their reg settings.
*/
HRESULT MU_DeleteUser(GUID *puidUserID)
{
    GUID   uidDefault, uidCurrent;
    TCHAR   szPath[MAX_PATH];

    MU_GetCurrentUserID(&uidCurrent);
    MU_GetDefaultUserID(&uidDefault);

    // Can't delete the current user
    if (*puidUserID == uidCurrent)
        return E_FAIL;
    
    // Delete the registry settings
    MU_GetRegRootForUserID(puidUserID, szPath);
    _DeleteKeyRecursively(HKEY_CURRENT_USER, szPath);

    // If we had a default user, we'll have to find a new one now
    if (*puidUserID == uidDefault)
        MU_MakeDefaultUser(&uidCurrent);

    // don't delete the directory since the user may need 
    // data out of it.
    PostMessage(HWND_BROADCAST, WM_IDENTITY_INFO_CHANGED, 0, IIC_IDENTITY_DELETED);

    return S_OK;
}


/*
    MU_CreateUser

    Create a user with the user info passed in.  This includes 
    creating their spot in the registry and their directory in the
    identities folder.
*/

HRESULT MU_CreateUser(LPUSERINFO   lpUserInfo)
{
    TCHAR           szPath[MAX_PATH], szBuffer[MAX_PATH], szUid[255];
    WCHAR           szwPath[MAX_PATH];
    HKEY            hkey;
    HRESULT         hr = S_OK;
    DWORD           dwType, dwSize, cbSize, dwValue;
    PASSWORD_STORE  pwStore;

    MU_GetRegRootForUserID(&lpUserInfo->uidUserID, szPath);
    
    Assert(*szPath && *szAcctPath);
 
    AStringFromGUID(&lpUserInfo->uidUserID,  szUid, ARRAYSIZE(szUid));
    Assert(lpUserInfo->uidUserID != GUID_NULL);

    if (RegCreateKey(HKEY_CURRENT_USER, szPath, &hkey) == ERROR_SUCCESS)
    {
        // write out the correct values
        dwType = REG_SZ;
        dwSize = lstrlen(lpUserInfo->szUsername) + 1;
        RegSetValueEx(hkey, c_szUsername, 0, dwType, (LPBYTE)lpUserInfo->szUsername, dwSize);

#ifdef IDENTITY_PASSWORDS
        lstrcpy(pwStore.szPassword, lpUserInfo->szPassword);
        pwStore.fUsePassword = lpUserInfo->fUsePassword;
        if (FAILED(hr = WriteIdentityPassword(&lpUserInfo->uidUserID, &pwStore)))
        {
            dwType = REG_BINARY ;
            cbSize = strlen(lpUserInfo->szPassword) + 1;
            lstrcpy(szBuffer, lpUserInfo->szPassword);
            EncodeUserPassword(szBuffer, &cbSize);
            dwSize = cbSize;
            RegSetValueEx(hkey, c_szPassword, 0, dwType, (LPBYTE)szBuffer, dwSize);
        
            dwType = REG_DWORD;
            dwValue = (lpUserInfo->fUsePassword ? 1 : 0);
            dwSize = sizeof(dwValue);
            RegSetValueEx(hkey, c_szUsePassword, 0, dwType, (LPBYTE)&dwValue, dwSize);
        }
#endif //IDENTITY_PASSWORDS
        dwType = REG_SZ;
        dwSize = lstrlen(szUid) + 1;
        RegSetValueEx(hkey, c_szUserID, 0, dwType, (LPBYTE)&szUid, dwSize);
    
        RegCloseKey(hkey);

        if (SUCCEEDED(MU_GetUserDirectoryRoot(&lpUserInfo->uidUserID, GIF_ROAMING_FOLDER, szwPath, MAX_PATH)))
            if (!CreateDirectoryWrapW(szwPath,NULL))
            {
                _CreateIdentitiesFolder();
                CreateDirectoryWrapW(szwPath,NULL);
            }

        if (SUCCEEDED(MU_GetUserDirectoryRoot(&lpUserInfo->uidUserID, GIF_NON_ROAMING_FOLDER, szwPath, MAX_PATH)))
            if (!CreateDirectoryWrapW(szwPath,NULL))
            {
                _CreateIdentitiesFolder();
                CreateDirectoryWrapW(szwPath,NULL);
            }
    }
    else
        hr = E_FAIL;

    return hr;
}

/*
    MU_GetRegRoot

    Returns a pointer to a string containing the location 
    in HKEY_CURRENT_USER for the current user.
*/
LPCTSTR     MU_GetRegRoot()
{
    if (*g_szRegRoot)
        return g_szRegRoot;
    else
    {
        TCHAR   szUsername[CCH_USERNAME_MAX_LENGTH + 1];

        if (MU_Login(NULL, 0, szUsername))
        {
            GUID uidUserId;
            TCHAR szUid[255];
            
            MU_UsernameToUserId(szUsername, &uidUserId);

            AStringFromGUID(&uidUserId,  szUid, ARRAYSIZE(szUid));
            wsprintf(g_szRegRoot, "%.100s\\%.40s", c_szRegRoot, szUid);

            return g_szRegRoot;
        }
        else
        {
            Assert(FALSE);
        }
    }
    return NULL;
}


void _MakeDefaultFirstUser()
{
    USERINFO    nuInfo;
    TCHAR        szUid[255];

    MLLoadStringA(idsMainUser, nuInfo.szUsername, CCH_USERNAME_MAX_LENGTH);
    if (nuInfo.szUsername[0] == 0)
    {
        lstrcpy(nuInfo.szUsername, TEXT("Main Identity"));
    }
    *nuInfo.szPassword = 0;
    nuInfo.fUsePassword = false;
    nuInfo.fPasswordValid = true;
    _ClaimNextUserId(&nuInfo.uidUserID);

    MU_CreateUser(&nuInfo);
    MU_MakeDefaultUser(&nuInfo.uidUserID);
    MU_SwitchToUser(nuInfo.szUsername);

    AStringFromGUID(&nuInfo.uidUserID,  szUid, ARRAYSIZE(szUid));
    wsprintf(g_szRegRoot, "%.100s\\%.40s", c_szRegRoot, szUid);
}

void FixMissingIdentityNames()
{
    HKEY    hSourceSubKey;
    ULONG   ulEnumIndex = 0;
    DWORD   dwStatus, dwSize, dwType, dwValue;
    BOOL    fFound = FALSE;
    TCHAR   szKeyNameBuffer[MAX_PATH];
	TCHAR	szUsername[CCH_USERNAME_MAX_LENGTH];

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hSourceSubKey) == ERROR_SUCCESS)
    {
        while (!fFound) 
        {
            HKEY    hkUserKey;

            if (RegEnumKey(hSourceSubKey, ulEnumIndex++, szKeyNameBuffer,MAXKEYNAME)
                !=  ERROR_SUCCESS)
                break;
            
            if (RegOpenKey(hSourceSubKey, szKeyNameBuffer, &hkUserKey) == ERROR_SUCCESS)
            {
                dwSize = sizeof(szUsername);
                dwStatus = RegQueryValueEx(hkUserKey, c_szUsername, NULL, &dwType, (LPBYTE)szUsername, &dwSize);
                
                if (ERROR_SUCCESS != dwStatus || 0 == szUsername[0])
                {
					lstrcpy(szUsername, "Main Identity");
					dwStatus = RegSetValueEx(hkUserKey, c_szUsername, 0, REG_SZ, (LPBYTE)szUsername, lstrlen(szUsername)+1);
                }
                RegCloseKey(hkUserKey); 
            }
        }
        RegCloseKey(hSourceSubKey);
    }
}

typedef DWORD (STDAPICALLTYPE *PNetWkstaUserGetInfo)
    (LPWSTR reserved, DWORD level, LPBYTE *bufptr);


#if 0
/*
    _DomainControllerPresent

    Identities are disabled when the machine they are running on is part of a domain, unless 
    there is a policy to explicitly allow them.  This function checks to see if the machine
    is joined to a domain.
*/
BOOL _DomainControllerPresent()
{
    static BOOL fInDomain = FALSE;
    static BOOL fValid = FALSE;
    HINSTANCE  hInst;
    PNetWkstaUserGetInfo pNetWkstaUserGetInfo;
    _WKSTA_USER_INFO_1  *pwui1;

    if (!fValid)
    {
        fValid = TRUE;
        hInst = LoadLibrary(TEXT("NETAPI32.DLL"));

        if (hInst)
        {
            pNetWkstaUserGetInfo = (PNetWkstaUserGetInfo)GetProcAddress(hInst, TEXT("NetWkstaUserGetInfo"));

            if (pNetWkstaUserGetInfo && (pNetWkstaUserGetInfo(NULL, 1, (LPBYTE*)&pwui1) == NOERROR))
            {
                if (pwui1->wkui1_logon_domain && pwui1->wkui1_logon_server && lstrcmpW(pwui1->wkui1_logon_server, pwui1->wkui1_logon_domain) != 0)
                {
                    fInDomain = TRUE;
                }
            }
            FreeLibrary(hInst);
        }
    }
    return fInDomain;
}
#endif

/*
    MU_IdentitiesDisabled

    Returns if identities is disabled due to a policy 
    or whatever.
*/
BOOL MU_IdentitiesDisabled()
{
#ifndef _WIN64
    TCHAR   szPolicyPath[] = "Software\\Policies\\Microsoft\\Windows\\CurrentVersion\\Identities";
    HKEY    hkey;
    DWORD   dwValue, dwSize;
    BOOL    fLockedDown = FALSE;

    if (RegOpenKey(HKEY_LOCAL_MACHINE, szPolicyPath, &hkey) == ERROR_SUCCESS)
    { 
        dwSize = sizeof(DWORD);
        if (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szPolicyKey, 0, NULL, (LPBYTE)&dwValue, &dwSize) && 1 == dwValue)
            fLockedDown = TRUE;

        RegCloseKey(hkey);
    }

    if (!fLockedDown && RegOpenKey(HKEY_CURRENT_USER, szPolicyPath, &hkey) == ERROR_SUCCESS)
    {
        dwSize = sizeof(DWORD);
        if (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szPolicyKey, 0, NULL, (LPBYTE)&dwValue, &dwSize) && 1 == dwValue)
            fLockedDown = TRUE;

        RegCloseKey(hkey);
    }

#ifdef DISABIDENT
    if (!fLockedDown && RegOpenKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        dwSize = sizeof(DWORD);
        if (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szPolicyKey, 0, NULL, (LPBYTE)&dwValue, &dwSize) && 1 == dwValue)
            fLockedDown = TRUE;

        RegCloseKey(hkey);
    }
#endif //DISABIDENT
#if 0
    // turned off for now, pending determination of whether we even want to
    // have this policy
    if (!fLockedDown && _DomainControllerPresent())
    {
        fLockedDown = TRUE;

        if (RegOpenKey(HKEY_LOCAL_MACHINE, szPolicyPath, &hkey) == ERROR_SUCCESS)
        {
            dwSize = sizeof(DWORD);
            if (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szEnableDCPolicyKey, 0, NULL, (LPBYTE)&dwValue, &dwSize) && 1 == dwValue)
                fLockedDown = FALSE;

            RegCloseKey(hkey);
        }

        if (fLockedDown && RegOpenKey(HKEY_CURRENT_USER, szPolicyPath, &hkey) == ERROR_SUCCESS)
        {
            dwSize = sizeof(DWORD);
            if (ERROR_SUCCESS == RegQueryValueEx(hkey, c_szEnableDCPolicyKey, 0, NULL, (LPBYTE)&dwValue, &dwSize) && 1 == dwValue)
                fLockedDown = FALSE;

            RegCloseKey(hkey);
        }
    }
#endif

    return fLockedDown;
#else // _WIN64
	return(TRUE);
#endif // _WIN64
}

static GUID    g_uidLoginOption;
static BOOLEAN g_uidLoginOptionSet;

void  _ResetRememberedLoginOption(void)
{
    g_uidLoginOption = GUID_NULL;
    g_uidLoginOptionSet = FALSE;
}

void  _RememberLoginOption(HWND hwndCombo)
{
    LRESULT dFoundItem;
    TCHAR   szUsername[CCH_IDENTITY_NAME_MAX_LENGTH * 2];
    GUID    uidUser;

    *szUsername = 0;

    g_uidLoginOptionSet = TRUE;

    dFoundItem = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);

    SendMessage(hwndCombo, CB_GETLBTEXT, dFoundItem, (LPARAM)szUsername);
    
    if (FAILED(MU_UsernameToUserId(szUsername, &uidUser)))
        g_uidLoginOption = GUID_NULL;
    else
        g_uidLoginOption = uidUser;
}

DWORD MU_GetDefaultOptionIndex(HWND hwndCombo)
{
    GUID        uidStart, uidDefault;
    USERINFO    uiDefault;
    DWORD       dwResult = 0;

    if (MU_GetDefaultUserID(&uidDefault))
    {
        MU_GetUserInfo(&uidDefault, &uiDefault);

        if (uiDefault.szUsername[0])
        {
            dwResult = (DWORD)SendMessage(hwndCombo, CB_FINDSTRING, 0, (LPARAM)uiDefault.szUsername);
        }
    }
    return dwResult;
}

DWORD MU_GetLoginOptionIndex(HWND hwndCombo)
{
    GUID        uidStart, uidDefault;
    USERINFO    uiLogin;
    DWORD       dwResult = ASK_BEFORE_LOGIN;

    if (GUID_NULL == g_uidLoginOption)
    {
        if (g_uidLoginOptionSet)
            goto exit;

        MU_GetLoginOption(&uidStart);
    }
    else
        uidStart = g_uidLoginOption;
    
    if (uidStart == GUID_NULL)
        goto exit;

    if(!MU_GetUserInfo(&uidStart, &uiLogin))
        goto exit;

    dwResult = (DWORD)SendMessage(hwndCombo, CB_FINDSTRING, 0, (LPARAM)uiLogin.szUsername);
exit:
    return dwResult;
}
/*
    MU_GetLoginOption

    return the user's choice for what should happen when there is no current 
    user
*/

void MU_GetLoginOption(GUID *puidStartAs)
{
    HKEY    hkey;
    DWORD   dwSize;
    TCHAR   szUid[255];
    GUID    uidUser;

    ZeroMemory(puidStartAs, sizeof(GUID));
    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        dwSize = sizeof(szUid);
        if (ERROR_SUCCESS != RegQueryValueEx(hkey, c_szLoginAs, 0, NULL, (LPBYTE)szUid, &dwSize))
            MU_GetDefaultUserID(puidStartAs);
        else
            GUIDFromAString(szUid, puidStartAs);

        RegCloseKey(hkey);
    }
}

/*
    MU_SetLoginOption

    return the user's choice for what should happen when there is no current 
    user
*/

BOOL MU_SetLoginOption(HWND hwndCombo,  LRESULT dOption)
{
    HKEY    hkey;
    BOOL    fResult = FALSE;
    TCHAR   szUsername[CCH_IDENTITY_NAME_MAX_LENGTH * 2];
    TCHAR   szUid[255];
    GUID    uidUser;


    SendMessage(hwndCombo, CB_GETLBTEXT, dOption, (LPARAM)szUsername);
    
    if (dOption == (LRESULT)ASK_BEFORE_LOGIN || FAILED(MU_UsernameToUserId(szUsername, &uidUser)))
    {
        ZeroMemory(&uidUser, sizeof(uidUser));
    }
    AStringFromGUID(&uidUser,  szUid, sizeof(szUid));
    
    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hkey) == ERROR_SUCCESS)
    {
        fResult = (ERROR_SUCCESS == RegSetValueEx(hkey, c_szLoginAs, 0, REG_SZ, (LPBYTE)szUid, lstrlen(szUid)+1));

        RegCloseKey(hkey);
    }

    return TRUE;
}


/*
    MU_CanEditIdentity

    Is the current identity allowed to edit the indicated identity's settings?
*/
BOOL MU_CanEditIdentity(HWND hwndParent, GUID *puidIdentityId)
{
#ifndef IDENTITY_PASSWORDS
    return TRUE;
#else
    USERINFO        uiCurrent, uiQuery;
    TCHAR           szBuffer[255];    // really ought to be big enough
    TCHAR           szString[255+CCH_USERNAME_MAX_LENGTH];
    BOOL            fResult = FALSE;
    PASSWORD_STORE  pwStore;

    ZeroMemory(&uiQuery, sizeof(USERINFO));

    if (MU_GetUserInfo(puidIdentityId, &uiQuery))
    {
        if (!uiQuery.fPasswordValid)
        {
            MU_ShowErrorMessage(hwndParent, idsPwdNotFound, idsPwdError);
            return FALSE;
        }

        if (uiQuery.szPassword[0] == 0)
        {
            return TRUE;    
        }
        
        if (MU_GetUserInfo(NULL, &uiCurrent))
        {
            if (uiCurrent.uidUserID == uiQuery.uidUserID)
                return TRUE;
        }
    }
    else
        return FALSE;

    MLLoadStringA(idsConfirmEdit, szBuffer, sizeof(szBuffer));

    wsprintf(szString, szBuffer, uiQuery.szUsername);

    fResult = MU_ConfirmUserPassword(hwndParent, szString, uiQuery.szPassword);

    return fResult;
#endif //IDENTITY_PASSWORDS
}

static BOOL _DirectoryIdInUse(DWORD dwId)
{
    HKEY    hSourceSubKey;
    ULONG   ulEnumIndex = 0;
    DWORD   dwStatus, dwSize, dwType, dwValue;
    BOOL    fFound = FALSE;
    TCHAR   szKeyNameBuffer[MAX_PATH];

    if (RegCreateKey(HKEY_CURRENT_USER, c_szRegRoot, &hSourceSubKey) == ERROR_SUCCESS)
    {
        while (!fFound) 
        {
            HKEY    hkUserKey;

            if (RegEnumKey(hSourceSubKey, ulEnumIndex++, szKeyNameBuffer,MAXKEYNAME)
                !=  ERROR_SUCCESS)
                break;
            
            if (RegOpenKey(hSourceSubKey, szKeyNameBuffer, &hkUserKey) == ERROR_SUCCESS)
            {
                dwSize = sizeof(dwValue);
                dwStatus = RegQueryValueEx(hkUserKey, c_szDirName, NULL, &dwType, (LPBYTE)&dwValue, &dwSize);
                
                if (ERROR_SUCCESS == dwStatus && dwValue == dwId)
                {
                    fFound = TRUE;
                    RegCloseKey(hkUserKey); 
                    break;
                }
                RegCloseKey(hkUserKey); 
            }
        }
        RegCloseKey(hSourceSubKey);
    }
    

    return fFound;
}


DWORD   MU_GenerateDirectoryNameForIdentity(GUID *puidIdentityId)
{   
    DWORD dwId, dwRegValue;

    dwId = puidIdentityId->Data1;

    while (_DirectoryIdInUse(dwId))
        dwId++;

    return dwId;
}

HRESULT MU_GetDirectoryIdForIdentity(GUID *puidIdentityId, DWORD *pdwDirId)
{
    TCHAR   szRegPath[MAX_PATH];
    HKEY    hkey;
    HRESULT hr = E_FAIL;
    DWORD   dwSize, dwStatus, dwValue, dwType;

    MU_GetRegRootForUserID(puidIdentityId, szRegPath);

    if (RegOpenKey(HKEY_CURRENT_USER, szRegPath, &hkey) == ERROR_SUCCESS)
    {
        dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hkey, c_szDirName, NULL, &dwType, (LPBYTE)&dwValue, &dwSize);
    
        if (ERROR_SUCCESS == dwStatus)
        {
            *pdwDirId = dwValue;
            hr = S_OK;
        }
        else
        {
            // try to generate one
            dwValue = MU_GenerateDirectoryNameForIdentity(puidIdentityId);
        
            dwType = REG_DWORD;
            dwSize = sizeof(dwValue);
            dwStatus = RegSetValueEx(hkey, c_szDirName, 0, dwType, (LPBYTE)&dwValue, dwSize);

            if (ERROR_SUCCESS == dwStatus)
            {
                *pdwDirId = dwValue;
                hr = S_OK;
            }
        }
        RegCloseKey(hkey);
    }

    return hr;
}


void _MigratePasswords()
{
    CStringList *psList;
    int   i, iCount = 0;
	USERINFO uiUser;
	DWORD dwStatus, dwValue, dwType, dwSize;

	dwType = REG_DWORD;
	dwSize = sizeof(DWORD);
	dwStatus = SHGetValue(HKEY_CURRENT_USER, c_szRegRoot, c_szMigrated5, &dwType, &dwValue, &dwSize);	

	if (dwStatus == ERROR_SUCCESS && dwValue == 1)
		return;
		
    psList = MU_GetUsernameList();

    if (psList)
    {
        iCount = psList->GetLength();

		for (i = 0; i < iCount; i++)
		{
			GUID	uidUser;
			if (SUCCEEDED(MU_UsernameToUserId(psList->GetString(i), &uidUser)) 
				&& MU_GetUserInfo(&uidUser, &uiUser))
			{
				if (!uiUser.fPasswordValid)
				{
					uiUser.fUsePassword = false;
					*uiUser.szPassword = 0;
					MU_SetUserInfo(&uiUser);
				}
			}
		}
        delete psList;
    }

	dwValue = 1;
	SHSetValue(HKEY_CURRENT_USER, c_szRegRoot, c_szMigrated5, REG_DWORD, &dwValue, sizeof(DWORD));	
}

